我已经构建了一个必须在大约5M页面上运行的爬虫(通过增加url ID),然后解析包含“我需要”信息的页面。
使用在网址(200K)上运行的算法并保存了好的和坏的结果后,我发现我浪费了很多时间。我可以看到有一些返回的减数我可以用来检查下一个有效的URL。
你可以非常快地看到减数(少数首先是“好身份证”) -
510000011 # +8
510000029 # +18
510000037 # +8
510000045 # +8
510000052 # +7
510000060 # +8
510000078 # +18
510000086 # +8
510000094 # +8
510000102 # +8
510000110 # etc'
510000128
510000136
510000144
510000151
510000169
510000177
510000185
510000193
510000201
爬行大约200K网址后,我只有14K的好结果,我知道我浪费时间并需要优化它,所以我运行一些统计数据并构建了一个功能,可以检查网址,同时增加id为8 \ 18 \ 17 \ 8(最高返回减数)等。
这是函数 -
def checkNextID(ID):
global numOfRuns, curRes, lastResult
while ID < lastResult:
try:
numOfRuns += 1
if numOfRuns % 10 == 0:
time.sleep(3) # sleep every 10 iterations
if isValid(ID + 8):
parseHTML(curRes)
checkNextID(ID + 8)
return 0
if isValid(ID + 18):
parseHTML(curRes)
checkNextID(ID + 18)
return 0
if isValid(ID + 7):
parseHTML(curRes)
checkNextID(ID + 7)
return 0
if isValid(ID + 17):
parseHTML(curRes)
checkNextID(ID + 17)
return 0
if isValid(ID+6):
parseHTML(curRes)
checkNextID(ID + 6)
return 0
if isValid(ID + 16):
parseHTML(curRes)
checkNextID(ID + 16)
return 0
else:
checkNextID(ID + 1)
return 0
except Exception, e:
print "somethin went wrong: " + str(e)
基本上做的是-checkNextID(ID)获取我知道的第一个包含数据减去8的id,因此第一次迭代将匹配第一个“if isValid”子句(isValid(ID + 8)将返回True)
lastResult 是一个保存最后一个已知网址ID的变量,因此我们将运行直到numOfRuns为
isValid()是一个获取ID +其中一个副交易的函数,如果网址包含我需要的内容,则返回True,并将网址的汤对象保存到名为“ - ”的全局变量中。 curRes ',如果网址不包含我需要的数据,则返回False。
parseHTML 是一个获取汤对象(curRes)的函数,解析我需要的数据,然后将数据保存到csv,然后返回True。
如果isValid()返回True,我们将调用parseHTML()然后尝试检查下一个ID +副减数(通过调用checkNextID(ID + subtrahends),如果它们都不会返回我正在寻找的内容我会用1增加它并再次检查,直到我找到下一个有效的URL。
您可以看到代码的其余部分here
运行代码后,我得到了大约950个好结果,突然出现异常 -
“有些错误:调用a时超出了最大递归深度 Python对象“
我可以在WireShark上看到scipt卡在id上 - 510009541(我用510000003启动了我的脚本),脚本尝试使用该ID获取几次url,然后才注意到错误并停止了它。
我真的很高兴看到我得到了相同的结果,但比我的旧脚本快了25x-40倍,HTTP请求更少,非常精确,我只错过了1个结果,获得了1000个好结果,这是由我,不可能朗读5M次,我的旧脚本运行了30个小时,当我的新脚本在5-10分钟内给我960~结果时得到了14-15K的结果。
我读过有关堆栈限制的内容,但是我必须有一个解决方案,我想用Python实现的算法(我不能回到我原来的“算法”,它永远不会端)。
谢谢!
答案 0 :(得分:33)
Python没有很好的支持递归,因为它缺少TRE(Tail Recursion Elimination)。
这意味着每次调用递归函数都会创建一个函数调用堆栈,因为堆栈深度限制(默认为1000),您可以通过sys.getrecursionlimit
签出(当然您可以更改)它使用sys.setrecursionlimit,但不推荐)你的程序最终会在达到此限制时崩溃。
由于其他答案已经为你提供了一个更好的方法来解决这个问题(通过简单的循环替换递归),如果你仍然想要使用递归,那么还有另一个解决方案是使用其中一个许多在python中实现TRE的方法,如one。
NB:我的回答是为了让您更深入地了解导致错误的原因,而且我并不建议您使用TRE,因为在您的情况下,循环会更好,更容易阅读。
答案 1 :(得分:15)
您可以通过以下方式增加堆栈容量:
import sys
sys.setrecursionlimit(10000)
答案 2 :(得分:13)
这会将递归转换为循环:
def checkNextID(ID):
global numOfRuns, curRes, lastResult
while ID < lastResult:
try:
numOfRuns += 1
if numOfRuns % 10 == 0:
time.sleep(3) # sleep every 10 iterations
if isValid(ID + 8):
parseHTML(curRes)
ID = ID + 8
elif isValid(ID + 18):
parseHTML(curRes)
ID = ID + 18
elif isValid(ID + 7):
parseHTML(curRes)
ID = ID + 7
elif isValid(ID + 17):
parseHTML(curRes)
ID = ID + 17
elif isValid(ID+6):
parseHTML(curRes)
ID = ID + 6
elif isValid(ID + 16):
parseHTML(curRes)
ID = ID + 16
else:
ID = ID + 1
except Exception, e:
print "somethin went wrong: " + str(e)
答案 3 :(得分:2)
而不是进行递归,代码checkNextID(ID + 18)
和类似代码的部分可以替换为ID+=18
,然后如果删除return 0
的所有实例,那么它应该执行同样的事情,但作为一个简单的循环然后,您应该在最后添加return 0
并使您的变量非全局。
答案 4 :(得分:2)
您可以增加递归深度和线程堆栈大小。
import sys, threading
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27) # new thread will get stack of such size