12

你好,

我有这个简短的蜘蛛代码:

class TestSpider(CrawlSpider):
    name = "test"
    allowed_domains = ["google.com", "yahoo.com"]
    start_urls = [
        "http://google.com"
    ]

    def parse2(self, response, i):
        print "page2, i: ", i
        # traceback.print_stack()


    def parse(self, response):
        for i in range(5):
            print "page1 i : ", i
            link = "http://www.google.com/search?q=" + str(i)
            yield Request(link, callback=lambda r:self.parse2(r, i))

我希望输出是这样的:

page1 i :  0
page1 i :  1
page1 i :  2
page1 i :  3
page1 i :  4

page2 i :  0
page2 i :  1
page2 i :  2
page2 i :  3
page2 i :  4

,但是,实际输出是这样的:

page1 i :  0
page1 i :  1
page1 i :  2
page1 i :  3
page1 i :  4

page2 i :  4
page2 i :  4
page2 i :  4
page2 i :  4
page2 i :  4

所以,我传入的争论在callback=lambda r:self.parse2(r, i)某种程度上是错误的。

代码有什么问题?

4

4 回答 4

38

根据使用 lambda 的 Scrapy 文档将阻止库作业功能工作(http://doc.scrapy.org/en/latest/topics/jobs.html)。

Request() 和 FormRequest() 都包含一个名为 meta 的字典,可用于传递参数。

def some_callback(self, response):
    somearg = 'test'
    yield Request('http://www.example.com', 
                   meta={'somearg': somearg}, 
                   callback=self.other_callback)

def other_callback(self, response):
    somearg = response.meta['somearg']
    print "the argument passed is:", somearg
于 2013-05-19T07:07:36.167 回答
11

lambdas 正在访问i被关闭的对象,因此它们都引用相同的值(调用ilambdasparse时函数中的值)。对该现象的更简单的重构是:

>>> def do(x):
...     for i in range(x):
...         yield lambda: i
... 
>>> delayed = list(do(3))
>>> for d in delayed:
...     print d()
... 
2
2
2

您可以看到ilambda 中的 's 都绑定到i函数中的值do。它们将返回它当前具有的任何值,并且只要任何 lambdas 活着以保留它的值,python 就会保持该范围活着。这就是所谓的闭包。

一个简单但丑陋的解决方法是

>>> def do(x):
...     for i in range(x):
...         yield lambda i=i: i
... 
>>> delayed = list(do(3))
>>> for d in delayed:
...     print d()
... 
0
1
2

这是有效的,因为在循环中,当前i绑定到ilambda 的参数。或者(也许更清楚一点)lambda r, x=i: (r, x)重要的部分是,通过在 lambda 主体之外进行赋值(仅稍后执行),您将变量绑定到当前值,i而不是在循环结束时获取的值。这使得 lambdas 不会被关闭i并且每个都有自己的值。

所以你需要做的就是改变线路

yield Request(link, callback=lambda r:self.parse2(r, i))

yield Request(link, callback=lambda r, i=i:self.parse2(r, i))

你是樱桃。

于 2010-10-08T05:55:46.993 回答
2

lambda r:self.parse2(r, i)绑定变量名i,而不是 的值i。稍后当评估 lambda 时i,使用闭包中的当前值,即使用最后一个i。这很容易证明。

>>> def make_funcs():
    funcs = []
    for x in range(5):
        funcs.append(lambda: x)
    return funcs

>>> f = make_funcs()
>>> f[0]()
4
>>> f[1]()
4
>>> 

make_funcs是一个返回函数列表的函数,每个函数都绑定到x. 您希望这些函数在调用时分别打印值 0 到 4。然而他们都回来4了。

然而,一切都没有丢失。有一个解决方案(s?)。

>>> def make_f(value):
    def _func():
        return value
    return _func

>>> def make_funcs():
    funcs = []
    for x in range(5):
        funcs.append(make_f(x))
    return funcs

>>> f = make_funcs()
>>> f[0]()
0
>>> f[1]()
1
>>> f[4]()
4
>>> 

我在这里使用了一个显式的命名函数,而不是lambda. 在这种情况下,变量的被绑定而不是名称。因此,各个功能按预期运行。

我看到@Aaron 给了你一个答案来改变你的lambda. 坚持下去,你会很高兴的:)

于 2010-10-08T06:00:15.970 回答
2
class TestSpider(CrawlSpider):
    name = "test"
    allowed_domains = ["google.com", "yahoo.com"]
    start_urls = [
        "http://google.com"
    ]

    def parse(self, response):
        for i in range(5):
            print "page1 i : %s" % i
            yield Request("http://www.google.com/search?q=%s" % i, callback=self.next, meta={'i': i})

    def next(self, response):
        print "page1 i : %s" % response.meta['i']
        # traceback.print_stack()
于 2012-06-13T20:43:00.863 回答