Передача аргументов в Scrapy Spider через лямбда-колбэки - PullRequest
12 голосов
/ 08 октября 2010

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 ]

38 голосов
/ 19 мая 2013

В соответствии с документацией 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
11 голосов
/ 08 октября 2010

Лямбды обращаются к i, который удерживается в закрытом состоянии, поэтому все они ссылаются на одно и то же значение (значение 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

Вы можете видеть, что все i в лямбдах связаны со значением i в функции do.Они будут возвращать любое значение, которое оно имеет в настоящее время, и 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

Это работает, потому что в цикле значение current i связан с параметром i лямбды.В качестве альтернативы (и, возможно, немного яснее) lambda r, x=i: (r, x).Важная часть заключается в том, что, присваивая вне тела лямбды (которое выполняется только позже), вы привязываете переменную к current значению i вместозначение, которое он принимает в конце цикла.Это делает так, чтобы лямбды не были закрыты в течение i и каждый мог иметь свое значение.

Так что все, что вам нужно сделать, это изменить строку

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

на

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

и вы черри.

2 голосов
/ 14 июня 2012
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()
2 голосов
/ 08 октября 2010

lambda r:self.parse2(r, i) связывает имя переменной i, а не значение i. Позже, когда лямбда оценивается, текущее значение i в замыкании, т. Е. Используется last значение 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.

Однако еще не все потеряно. Есть решение (ы?).

>>> 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. Придерживайтесь этого, и вам будет хорошо идти :) 1027 *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...