12

こんにちは、

私はこの短いスパイダーコードを持っています:

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

4 に答える 4

38

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
于 2013-05-19T07:07:36.167 に答える
11

ラムダは、クロージャーに保持されているものにアクセス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の値にバインドされていることがわかります。それらは現在持っている値を返し、python はその値を保持するためにラムダのいずれかが生きている限り、そのスコープを存続させます。これが閉鎖と呼ばれるものです。ido

シンプルだが醜い回避策は

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

そしてあなたはチェリーです。

于 2010-10-08T05:55:46.993 に答える
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()
于 2012-06-13T20:43:00.863 に答える
2

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代わりに全員が戻ってきます。

ただし、すべてが失われるわけではありません。解決策があります。

>>> 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. それに固執すると、準備が整います:)

于 2010-10-08T06:00:15.970 に答える