通过lambda回调传递Scrapy蜘蛛内的参数

时间:2010-10-08 05:38:32

标签: python lambda scrapy

HI,

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

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 个答案:

答案 0 :(得分:38)

根据Scrapy文档,使用lambda将阻止库作业功能工作(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

答案 1 :(得分:11)

lambdas正在访问i,它正在闭包中,因此它们都引用相同的值(调用lambda时,i函数中的parse的值)。更简单的重建现象是:

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

您可以看到lambdas中的i都绑定到函数ido的值。它们将返回它当前具有的任何值,并且只要任何lambda都存活,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当前值绑定到lambda的参数i。或者(也许更清楚一点)lambda r, x=i: (r, x)。重要的是通过在lambda 的主体之外进行赋值(稍后才执行),您将变量绑定到{{1>}的当前值而不是它在循环结束时所需的值。这使得lambda不会在i上关闭,并且每个lambda都有自己的值。

所以你需要做的就是改变行

i

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

你真是太棒了。

答案 2 :(得分:2)

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

>>> 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已经通过an answer更改了您的lambda。坚持下去,你会很高兴:)

答案 3 :(得分: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()