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?

War es hilfreich?

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()
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top