Python:调用Python对象时超出最大递归深度
我写了一个爬虫程序,它需要在大约500万页上运行(通过增加网址的ID),然后解析那些包含我需要信息的页面。
在使用一个算法处理了20万条网址后,保存了好的和坏的结果,我发现自己浪费了很多时间。我注意到有几个返回的减数可以用来检查下一个有效的网址。
你可以很快看到这些减数(这里举个例子,前几个“好的ID”) -
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
在爬取了大约20万条网址后,我只得到了1.4万条好的结果,我意识到自己在浪费时间,需要优化这个过程,于是我做了一些统计,写了一个函数来检查网址,同时增加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)获取我知道包含数据的第一个ID减去8,所以第一次运行会匹配第一个“如果有效”的条件(isValid(ID + 8)会返回True)。
lastResult是一个变量,用来保存最后一个已知的网址ID,所以我们会一直运行,直到numOfRuns达到设定的次数。
isValid()是一个函数,它接收一个ID加上其中一个减数,如果网址包含我需要的数据,就返回True,并把这个网址的内容保存到一个名为curRes的全局变量中。如果网址不包含我需要的数据,就返回False。
parseHTML是一个函数,它接收soup对象(curRes),解析我需要的数据,然后把数据保存到一个csv文件中,最后返回True。
如果isValid()返回True,我们就会调用parseHTML(),然后尝试检查下一个ID加上减数(通过调用checkNextID(ID + subtrahends)),如果没有一个返回我想要的结果,我就会把ID加1,再次检查,直到找到下一个有效的网址。
你可以在这里看到其余的代码。
运行代码后,我得到了大约950个好的结果,突然抛出了一个异常 -
“发生了错误:调用Python对象时超出了最大递归深度”
我在WireShark上看到脚本卡在了ID为510009541的地方(我从510000003开始运行脚本),脚本在我注意到错误并停止之前,尝试了几次获取那个ID的网址。
我很兴奋地看到,我得到了相同的结果,但速度比我之前的脚本快了25到40倍,HTTP请求也少了很多,准确性很高,1000个好的结果中只漏掉了1个,这对我来说可以接受,毕竟不可能运行500万次。我之前的脚本运行了30小时,只得到了1.4万到1.5万条结果,而我的新脚本在5到10分钟内就给了我大约960个结果。
我读过关于栈限制的内容,但我相信我在Python中实现的算法一定有解决方案(我不能再回到我之前的“算法”了,那样永远也不会结束)。
谢谢!
6 个回答
你可以通过以下方式来增加栈的容量:
import sys
sys.setrecursionlimit(10000)
Python 对递归的支持不是很好,因为它没有尾递归消除(TRE)的功能。
这意味着每次你调用递归函数时,都会在内存中创建一个函数调用的堆栈,而这个堆栈的深度是有限制的(默认是1000)。你可以通过 sys.getrecursionlimit
来查看这个限制(当然你可以用 sys.setrecursionlimit 来改变它,但不推荐这样做),一旦超过这个限制,你的程序就会崩溃。
虽然其他回答已经给出了更好的解决方案(就是用简单的循环来替代递归),但如果你还是想用递归的话,可以参考一些在 Python 中实现尾递归消除的技巧,比如这个 例子。
注意:我的回答是为了让你更明白为什么会出现这个错误,并不是建议你使用尾递归消除,因为在你的情况下,使用循环会更好,也更容易理解。
这段代码把递归变成了一个循环:
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)