تمرير الحجج داخل Scrapy Spider من خلال عمليات الاسترجاعات Lambda

StackOverflow https://stackoverflow.com/questions/3887968

  •  28-09-2019
  •  | 
  •  

سؤال

مرحبا،

لدي رمز العنكبوت القصير هذا:

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

لذا ، فإن Arguemnt مرر callback=lambda r:self.parse2(r, i) مخطئ بطريقة ما.

ما الخطأ في الكود؟

هل كانت مفيدة؟

المحلول

تصل Lambdas i الذي يتم الاحتفاظ به في الإغلاق بحيث يشيرون جميعًا إلى نفس القيمة (قيمة i فيك parse وظيفة عندما تسمى lambdas). إعادة بناء أبسط للظاهرة هي:

>>> 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. سيقومون بإرجاع أي قيمة لها حاليًا وسيبقي بيثون هذا النطاق على قيد الحياة طالما أن أي من 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 بدلاً من القيمة التي تتطلبها في نهاية الحلقة. هذا يجعله حتى لا يتم إغلاق lambdas i ويمكن أن يكون لكل منها قيمته الخاصة.

لذلك كل ما عليك فعله هو تغيير الخط

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

إلى

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

وأنت الكرز.

نصائح أخرى

وفقًا لوثائق Scrapy باستخدام Lambda ، ستمنع وظائف وظائف المكتبات من العمل (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

lambda r:self.parse2(r, i) يربط الاسم المتغير i, وليس قيمة i. في وقت لاحق عندما يتم تقييم Lambda القيمة الحالية 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. التزم بذلك وستكون على ما يرام :)

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()
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top