Python递归爬取URL
我有一个方法,当给它一堆链接时,它会获取这些链接的子链接,然后再继续获取下去:
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 个回答
你在每次递归调用的时候都在清空 finalLinks
数组。
你需要的是一个更全局的链接集合,用来记录你已经访问过的链接。每次递归调用都应该把访问的链接加到这个全局列表里,否则如果你的图有循环的话,你肯定会最终访问到同一个网站两次。
更新:可以看看这个链接里提到的不错的模式,使用 Python 生成器在图上进行深度优先搜索。你的 finalList
可以作为一个参数,默认值是 []
。在每次递归调用时把访问的链接加到这个列表里。另外,如果你觉得有用的话,可以考虑用 set
而不是 list
—— 这样会更快。
你需要
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 提出的建议。