Не вижу бесконечного цикла


Я пытаюсь написать webcrawler, но я застрял, потому что я не могу видеть бесконечный цикл где-то в моем коде.

class Crawler(object):
    def __init__(self, url, query, dir = os.path.dirname(__file__)):
        self.start_url = url
        self.start_parsed = urllib3.util.parse_url(url)
        self.query = re.compile(query, re.IGNORECASE)
        self.dir = dir
        self.__horizon = set()
        self.log = []

        self.__horizon.add(url)
        self.log.append(url)
        print("initializing crawler....")
        print(locals())

    def start(self, depth= 5, url = '/'):
        print(url, depth)
        self.log.append(url)
        if depth > 0:
            pool = urllib3.PoolManager()
            data = pool.request("GET", self.start_url if url == '/' else url).data.decode('utf-8')

            valid_list = []
            self.add_horizon(parser_soup.get_links(data), valid_list)

            if re.search(self.query, parser_soup.get_text(data)):
                self.output(data)

            for u in valid_list:
                self.start(depth = (depth-1), url = u)

    def output(self, data):
        with open(os.path.join(self.dir, get_top_domain(self.start_parsed.host) + '.' + str(time.time()) + '.html'), 'w+') as f:
            f.write(data)

    def add_horizon(self, url_list, valid_list = []):
        for url in url_list:
            if get_top_domain(url) == get_top_domain(self.start_parsed.host)  
                    and (not str(url) in self.log or not str(url) in self.__horizon):
                valid_list.append(str(url))

        self.__horizon.update(valid_list)

Он работает вечно. Как я должен гарантировать, что устраню дублирующие ссылки?

2 2

2 ответа:

Адаптировано из кода Джоджиана:

class Crawler(object):
    def __init__(self, url, query, dir=os.path.dirname(__file__)):
        self.visited = set()
        # Rest of code...

    def start(self, depth=5, url='/'):
        if url in self.visited:
            return True
        self.visited.add(url)

defaultdict это словарь, который имеет значение по умолчанию, которое используется, если индекс не существует. Однако это неправильное решение. Набор будет более эффективным и элегантным, как показано в моем коде.

Множество использует время O (1) - так же быстро, как и ответ @Giorgian.

Используйте Ctrl-C, чтобы прервать вашу программу, когда она находится в бесконечном цикле. При этом будет выведена обратная трассировка, показывающая команду, которая выполнялась, когда программа была прервана. Сделайте это несколько раз, и вы должны получить хорошее представление о том, где это происходит. Кроме того, используйте отладчик и приостановите его, когда он находится в бесконечном цикле, и используйте функцию "шаг", чтобы перейти к следующей строке выполнения, чтобы вы могли следить за выполнением программы. PyCharm-это отличный редактор, который включает в себя отладчик. Он имеет хорошее автозавершение и просто хорошее многоборье. Это бесплатно, проверьте это.

Добавьте свойство visited в ваш искатель.

from collections import defaultdict
class Crawler:
    def __init__(self, url, query, dir = os.path.dirname(__file__)):
        self.visited = defaultdict(bool)
        # Rest of code...

    def start(self, depth= 5, url = '/'):
        if self.visited[url]:
            return True
        self.visited[url] = True
        # Rest of code...

, Чтобы быть честным, я не могу увидеть бесконечный цикл либо. Это поможет, если вы разместите некоторые выходные данные.

EDIT: обратите внимание, что в приведенном выше ответе я написал, что использование defaultdict является неправильным решением. Я хотел сказать, что использование списка - неправильное решение!

[13]}EDIT 2: @Jona Christopher Sahnwald сделал замечание более обоснованным, чем мое (см. Его комментарий под вопросом ОП). Возможно, было бы более продуктивно добавить a max_visit и current_visit свойство в вашем классе (установлено как 1000 или около того). Начните с current_visit в 0, и каждый раз, когда вы посещаете сайт, увеличивайте current_visit. Если current_visit больше, чем max_visit, прервите обход. Обратите внимание, что вместо использования рекурсии для рекурсии по посещенным веб-сайтам, возможно, лучше реализовать какой-то стек, чтобы вы могли приостановить/возобновить обход, а не прерывать его. Вот так:
from collections import defaultdict

class Crawler:
    def __init__(self, url, query, dir = os.path.dirname(__file__)):
        self.visited = defaultdict(bool)
        self.current_visit = 0
        self.max_visit = 1000
        self.to_visit = []
        # Rest of code...

    def start(self, depth=5, url = '/'):
        self.to_visit.append((url, 1))
        while len(self.to_visit) > 0:
            url, current_depth = self.to_visit.pop()
            if current_depth > depth:
                continue
            elif visited[url]:
                continue
            elif self.current_visited > self.max_visited:
                break

            self.current_visited += 1
            visited[url] = True

            # Code that does something for each page (like download it, etc)

            # Code that finds links on page...

            for link in links_on_page:
                self.to_visit.append((link, current_depth + 1))

Таким образом, вы можете приостановить обход, как только current_visit превысит max_visit, что позволит вам выполнять обход в пакетах max_visit.