Übergeben von Argumenten innen Scrapy Spinne durch Lambda-Rückrufe
Frage
HALLO,
Ich bin haben diese kurze Spinne Code:
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))
, und ich würde die Ausgabe wie folgt erwarten:
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
jedoch die tatsächliche Ausgabe ist dies:
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
so, die arguemnt ich in callback=lambda r:self.parse2(r, i)
Pass ist irgendwie falsch.
Was mit dem Code falsch?
Lösung
Die Lambdas zugreifen i
, die in Schließ gehalten wird, so dass sie alle den gleichen Wert (den Wert von i
in youre parse
Funktion, wenn der Lambda-Ausdrücke genannt werden) referenzieren. Eine einfachere Rekonstruktion des Phänomens ist:
>>> def do(x):
... for i in range(x):
... yield lambda: i
...
>>> delayed = list(do(3))
>>> for d in delayed:
... print d()
...
2
2
2
siehe Sie können, dass die i
die in den lambdas alle auf den Wert von i
in der Funktion do
gebunden sind. Sie werden zurückkehren, was Wert, den es zur Zeit hat und Python halten, dass Umfang am Leben, solange eine der Lambda-Ausdrücke sind am Leben, den Wert für sich zu erhalten. Dies ist, was als Verschluss bezeichnet ist zu.
Eine einfache, aber hässliche Arbeit um ist
>>> 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
Das funktioniert, weil in der Schleife, die Strom Wert von i
ist mit dem Paramater i
des Lambda gebunden. Alternativ (und vielleicht biss ein wenig klarer) lambda r, x=i: (r, x)
. Der wichtige Teil ist, dass durch eine Zuordnung zu machen außerhalb des Körpers des Lambda (die erst später ausgeführt wird) Sie verbindlich sind, eine Variable zu dem Strom Wert von i
statt der Wert, den er am Ende der Schleife erfolgt. Das macht es so, dass die Lambda-Ausdrücke sind nicht i
geschlossen über und können jeweils ihren eigenen Wert.
Also alles, was Sie tun müssen, ist die Zeile
yield Request(link, callback=lambda r:self.parse2(r, i))
yield Request(link, callback=lambda r, i=i:self.parse2(r, i))
und Sie sind Kirsche.
Andere Tipps
Nach der Scrapy Dokumentation Lambda verwendet, wird die Bibliotheken Jobs Funktionalität von der Arbeit ( verhindern http://doc.scrapy.org/en/latest/topics/jobs.html ).
Der Request () und FormRequest () sowohl ein Wörterbuch namens Meta enthalten, die verwendet werden können, Argumente zu übergeben.
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
lambda r:self.parse2(r, i)
bindet die Variablennamen i
, nicht den Wert von i
. Später, wenn das Lambda den aktuellen Wert von i
im Verschluß das heißt, die ausgewertet wird letzte Wert von i
verwendet wird. Dies kann leicht nachgewiesen werden.
>>> def make_funcs():
funcs = []
for x in range(5):
funcs.append(lambda: x)
return funcs
>>> f = make_funcs()
>>> f[0]()
4
>>> f[1]()
4
>>>
Hier make_funcs
ist eine Funktion, die gibt eine Liste der Funktionen, die jeweils an x
gebunden. Sie würden die Funktionen erwarten, wenn auf Druckwerte 0 bis 4 jeweils bezeichnet. Und doch sind sie alle zurück 4
statt.
Es ist nicht alles jedoch verloren. Es gibt eine Lösung (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
>>>
Ich verwende eine explizite, benannte Funktion hier statt lambda
. In diesem Fall wird die Variable Wert wird nicht der Name gebunden. Folglich werden die einzelnen Funktionen wie erwartet verhalten.
Ich sehe, dass @Aaron hat Sie beantworten für Ihre lambda
ändern. Stick mit, dass und Sie werden gut zu gehen:)
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()