56

我已经构建了一个爬虫,它必须在大约 5M 页面上运行(通过增加 url ID),然后解析包含我需要的信息的页面。

在使用在 url (200K) 上运行的算法并保存好结果和坏结果后,我发现我浪费了很多时间。我可以看到有一些返回的减数可以用来检查下一个有效的 url。

你可以很快地看到减数(少数几个第一个“好 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

在爬取了大约 200K 的 url 之后,我只得到了 14K 的好结果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 的数据,因此第一次迭代将匹配第一个“if isValid”子句(isValid(ID + 8) 将返回 True)。

lastResult是一个保存最后一个已知 url id 的变量,所以我们将运行直到 numOfRuns

isValid()是一个函数,它获取一个 ID + 一个被减数,如果 url 包含我需要的内容,则返回 True,并将 url 的汤对象保存到名为 - ' curRes ' 的全局变量中,如果 url 不包含,则返回 False '不包含我需要的数据。

parseHTML是一个函数,它获取汤对象 (curRes),解析我需要的数据,然后将数据保存到 csv,然后返回 True。

如果 isValid() 返回 True,我们将调用 parseHTML() 然后尝试检查下一个 ID+被减数(通过调用 checkNextID(ID + subtrahends),如果它们都不会返回我正在寻找的我会将其增加 1 并再次检查,直到找到下一个有效 url。

你可以在这里看到其余的代码

运行代码后,我得到了大约 950~ 的好结果,突然引发了一个异常 -

“出了点问题:调用 Python 对象时超出了最大递归深度”

我可以在 WireShark 上看到 scipt 卡在 id - 510009541 上(我以 510000003 开始​​我的脚本),脚本尝试使用该 ID 获取 url 几次,然后我注意到错误并停止它。

看到我得到了相同的结果,但比我的旧脚本快 25 到 40 倍,HTTP 请求更少,这非常精确,我只错过了 1 个结果来获得 1000 个好的结果,这是我发现的,我真的很兴奋不可能朗姆酒 5M 次,当我的新脚本在 5-10 分钟内给我 960~ 结果时,我让我的旧脚本运行了 30 个小时并获得了 14-15K 的结果。

我阅读了有关堆栈限制的信息,但是对于我试图在 Python 中实现的算法必须有一个解决方案(我不能回到我的旧“算法”,它永远不会结束)。

谢谢!

4

6 回答 6

53

Python 对递归没有很好的支持,因为它缺少 TRE(尾递归消除)。

这意味着对递归函数的每次调用都会创建一个函数调用堆栈,并且因为堆栈深度有一个限制(默认为 1000),您可以通过它来检查sys.getrecursionlimit(当然您可以使用sys.setrecursionlimit更改它,但它不是推荐)您的程序在达到此限制时最终会崩溃。

由于其他答案已经为您提供了一种更好的方法来解决您的情况(即通过简单循环替换递归),如果您仍想使用递归,则还有另一种解决方案,即使用许多食谱之一像这样在 python实现 TRE 。

注意:我的回答旨在让您更深入地了解为什么会出现错误,我不建议您使用我已经解释过的 TRE,因为在您的情况下,循环会更好且易于阅读。

于 2011-07-24T20:42:34.883 回答
41

您可以通过以下方式增加堆栈的容量:

import sys
sys.setrecursionlimit(10000)
于 2013-03-06T07:28:40.423 回答
18

这将递归转换为循环:

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)
于 2011-07-24T20:22:45.107 回答
4

您可以增加递归深度和线程堆栈大小。

import sys, threading
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27)  # new thread will get stack of such size
于 2021-04-11T11:42:23.677 回答
2

而不是进行递归,代码中与checkNextID(ID + 18)和相似的部分可以替换为ID+=18,然后如果你删除所有实例return 0,那么它应该做同样的事情,但作为一个简单的循环。然后,您应该将 areturn 0放在最后并使您的变量成为非全局变量。

于 2011-07-24T20:23:06.530 回答
-1

使用 try 和 except 但不要在 except 中打印您的错误,只需在 except 语句中再次运行您的函数

于 2022-02-02T14:52:05.510 回答