【问题标题】:Can't see infinite loop看不到无限循环
【发布时间】:2016-12-14 22:09:15
【问题描述】:

我正在尝试编写一个网络爬虫,但我被卡住了,因为我在代码中的某处看不到无限循环。

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)

它永远运行。我应该如何确保消除重复链接?

【问题讨论】:

  • “看不到无限循环”是什么意思?
  • @uoɥʇʎPʎzɐɹC 他不明白为什么他的代码会陷入无限循环。
  • 与您的问题无关,但有一个建议:在 __init__ 中创建 PoolManager 并始终使用它以从中获得最大收益。
  • 可能没有无限循环,程序只是运行了很长时间。粗略计算:如果每个爬取的页面包含 20 个链接(对于大多数网站来说是一个较低的估计值),则您必须加载 20^4=160K 个页面。以每页 10 毫秒计算,这将花费 4 多个小时。每页有 40 个链接,大约需要 70 个小时。等等。指数增长。当您将默认深度设置为 1 而不是 5 时会发生什么?

标签: python beautifulsoup web-crawler urllib3


【解决方案1】:

在你的爬虫中添加一个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...

说实话,我也看不到无限循环。如果您发布一些输出会有所帮助。

编辑:请注意,在上面的答案中,我写道使用 defaultdict 是错误的解决方案。我的意思是说使用 list 是错误的解决方案!

编辑 2:@Jona Christopher Sahnwald 提出的观点比我的更有效(请参阅他在 OP 问题下的评论)。在您的类中添加max_visitcurrent_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

【讨论】:

  • @uoɥʇʎPʎzɐɹC 当然不完整!它只是为了指导 OP。
  • 看我的回答,列表更好
  • 也不符合 PEP 8
【解决方案2】:

改编自 Giogian 的代码:

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 中断您的程序。这将打印一个 Traceback,显示程序中断时正在执行的命令。这样做几次,您应该对它发生的位置有一个很好的了解。或者,使用调试器并在它处于无限循环时暂停,并使用“step”功能运行到下一行执行,以便您可以跟踪程序的执行。 PyCharm 是一个很棒的编辑器,包含一个调试器。它具有良好的自动补全功能,并且非常全面。它是免费的,请查看。

【讨论】:

  • defaultdict 是错误的解决方案!检查一个值是否在列表中需要 O(n) 时间,而使用 defaultdict 只需要 O(1) 时间!
  • @uoɥʇʎPʎzɐɹC 如果你想使用 in 成语,请使用集合。
  • @uoɥʇʎPʎzɐɹC 至少,您能否从我的答案中删除反对票,因为您的代码基本上是复制粘贴我的答案?唯一的区别是您使用了set(),无论如何这是我的建议!
  • @GiorgianBorca-Tasciuc 我自己想出了它。没看到你的评论。删除了反对票。好吗?
  • @GiorgianBorca-Tasciuc 和我有调试器建议。
猜你喜欢
  • 1970-01-01
  • 2013-02-20
  • 2013-12-24
  • 1970-01-01
  • 1970-01-01
  • 2016-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多