Python递归爬取URL

1 投票
2 回答
2380 浏览
提问于 2025-04-16 21:58

我有一个方法,当给它一堆链接时,它会获取这些链接的子链接,然后再继续获取下去:

def crawlSite(self, linksList):
    finalList = []
    for link in list(linksList):
        if link not in finalList:
            print link            
            finalList.append(link)
            childLinks = self.getAllUniqueLinks(link)
            length = len(childLinks)
            print 'Total links for this page: ' + str(length)

        self.crawlSite(childLinks)
    return finalList

但是最终它会重复处理同一组链接,我搞不清楚为什么。当我把 self.crawlSite(childLinks) 放到条件语句里面时,我发现列表中的第一个链接被重复处理了很多次。

关于 self.getAllUniqueLinks(link) 这个方法,它是从给定的页面获取链接列表,并过滤出在特定域名下所有可以点击的链接。简单来说,我想从一个网站上获取所有可以点击的链接。如果这样做不是最佳方法,能不能推荐一个更好的方法来实现同样的功能?请考虑到我对Python还比较陌生,可能不太理解更复杂的做法。所以请你解释一下你的思路,如果你不介意的话 :)

2 个回答

2

你在每次递归调用的时候都在清空 finalLinks 数组。

你需要的是一个更全局的链接集合,用来记录你已经访问过的链接。每次递归调用都应该把访问的链接加到这个全局列表里,否则如果你的图有循环的话,你肯定会最终访问到同一个网站两次。

更新:可以看看这个链接里提到的不错的模式,使用 Python 生成器在图上进行深度优先搜索。你的 finalList 可以作为一个参数,默认值是 []。在每次递归调用时把访问的链接加到这个列表里。另外,如果你觉得有用的话,可以考虑用 set 而不是 list —— 这样会更快。

3

你需要

finalList.extend(self.crawlSite(childLinks))

不仅仅是

self.crawlSite(childLinks)

你需要把内层的 crawlSite() 返回的列表和外层的 crawlSite() 中已经存在的列表合并起来。虽然它们都叫 finalList,但在不同的作用域中其实是不同的列表。

更好的解决办法是把 finalList 设为一个实例变量(或者某种类型的非局部变量),这样它就可以在所有的 crawlSite() 的作用域中共享:

def __init__(self, *args, **kwargs):
    self.finalList = set()

def crawlSite(self, linksList):
    for link in linksList:
        if link not in self.finalList:
            print link            
            self.finalList.add(link)
            childLinks = self.getAllUniqueLinks(link)
            length = len(childLinks)
            print 'Total links for this page: ' + str(length)
            self.crawlSite(childLinks)

如果你想从头开始使用同一个实例,只需要确保你写 self.finalList = []

编辑:通过把递归调用放在 if 块中修复了代码。使用了集合。同时,linksList 不需要是一个列表,只要是一个可迭代的对象,所以在 for 循环中去掉了 list() 的调用。集合是 @Ray-Toal 提出的建议。

撰写回答