Подтвердить что ты не робот

Передача аргументов внутри Scrapy spider через лямбда-обратные вызовы

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), как-то не так.

Что не так с кодом?

4b9b3361

Ответ 1

Лямбды получают доступ к i, который удерживается в закрытии, поэтому все они ссылаются на одно и то же значение (значение i в функции youre 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 в lambdas привязаны к значению i в функции do. Они возвратят любое значение, которое оно имеет в настоящее время, и python сохранит эту область действия до тех пор, пока любой из lambdas жив, чтобы сохранить ценность для нее. Это то, что называется закрытием.

Простая, но уродливая работа -

>>> 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 привязано к параметру i лямбда. Альтернативно (и, возможно, немного яснее) lambda r, x=i: (r, x). Важная часть состоит в том, что, создавая назначение вне тела лямбды (которое выполняется только позже), вы привязываете переменную к текущему значению 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

В соответствии с документацией Scrapy, использующей лямбда, будет запрещена работа рабочих функций библиотек (http://doc.scrapy.org/en/latest/topics/jobs.html).

В запросе() и 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

Ответ 3

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

Ответ 4

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()