【问题标题】:Troubleshooting item loaders between callbacks对回调之间的项目加载器进行故障排除
【发布时间】:2016-10-17 16:20:52
【问题描述】:

为了理解http://oliverguenther.de/2014/08/almost-asynchronous-requests-for-single-item-processing-in-scrapy/中的“Naive approach”例子

我正在尝试复制该代码。这个想法是填充一个项目,其中每个字段都来自不同的网站。

我试图了解为什么在运行以下代码时会出现以下行为,然后我使用scrapy crawl compSpider -o prices.csv 将结果导出到 csv 文件中。

代码实际上用相关价格填充了 nic_price,但它对 tester_price 不做同样的事情。

我相信它应该这样做,因为项目加载器对象在请求元字段中从第一个回调 [firstRequest](创建项目加载器对象)到第二个回调 [parseDescription1](项目加载器在其中对象最终被加载到一个项目中。

我已经测试过 css 选择器可以工作。有人可以帮我理解为什么我会出现这种行为吗?

项目声明

import scrapy
class ProductItem(scrapy.Item):       
    nic_price = scrapy.Field()
    tester_price = scrapy.Field()

蜘蛛代码

import scrapy
from scrapy.http import Request
from scrapy.loader import ItemLoader
from comparator.items import ProductItem


class Compspider(scrapy.Spider):
    name = "compSpider"


    #start_urls = (  'https://www.shop.niceic.com/',   )

    def start_requests(self):  

        yield Request(
            'https://www.shop.niceic.com/6559-megger-mft1711-multifunction-tester-1008-121',  callback=self.firstRequest)


    def firstRequest(self, response):
        l = ItemLoader(item=ProductItem(), response=response)
        l.add_css('nic_price', 'div.product-info p.product-price span[itemprop="price"]::text')

        yield Request('https://www.tester.co.uk/test-safe-pen-co-meter', meta={'loader' : l}, callback= self.parseDescription1)

    def parseDescription1(self, response):

        # Recover item(loader)
        l = response.meta['loader']

        # Use just as before
        l.add_css('tester_price', 'div.price-breakdown div.price-excluding-tax span.price::text')

        yield l.load_item()

【问题讨论】:

    标签: python csv scrapy web-crawler


    【解决方案1】:

    最近,我遇到了同样的问题。两种方法都试过了,但每一种都有很大的缺点:

    1. 传递项目加载器并使用add_value() 引入了双重处理值的问题(通过输入和输出处理器),这使得设计这些处理器变得更加困难,从而使代码更难阅读和维护。

    2. 传递项目而不是项目加载器只会扼杀项目加载器的整个概念,并且还会在整个代码中引入不一致。

    通过scrapy 的scrapy.loader.ItemLoader 源代码(顺便说一句,代码非常小且简单),我发现它是parent 关键字参数,它允许您传递父ItemLoader 并将其所有值复制到新的ItemLoader。

    这里是修改后的parseDescription1

    def parseDescription1(self, response):
    
        # Recover item(loader)
        loader = response.meta['loader']
    
        if response.body:
            loader = ItemLoader(item=ProductItem(), response=response, parent=loader)
    
            # Use just as before
            tester_loader.add_css('tester_price', 'div.price-breakdown div.price-excluding-tax span.price::text')
    
        yield loader.load_item()
    

    scrapy crawl 结果:

    2016-11-01 10:48:50 [scrapy] DEBUG: Crawled (200) <GET https://www.shop.niceic.com/robots.txt> (referer: None)
    2016-11-01 10:48:50 [scrapy] DEBUG: Crawled (200) <GET https://www.shop.niceic.com/6559-megger-mft1711-multifunction-tester-1008-121> (referer: None)
    2016-11-01 10:48:50 [scrapy] DEBUG: Crawled (200) <GET https://www.tester.co.uk/robots.txt> (referer: None)
    2016-11-01 10:48:51 [scrapy] DEBUG: Crawled (200) <GET https://www.tester.co.uk/test-safe-pen-co-meter> (referer: https://www.shop.niceic.com/6559-megger-mft1711-multifunction-tester-1008-121)
    2016-11-01 10:48:51 [scrapy] DEBUG: Scraped from <200 https://www.tester.co.uk/test-safe-pen-co-meter>
    {'nic_price': [u'\xa3509.00'],
     'tester_price': [u'\n\t\t            \xa349.00            \n\t\t        ']}
    

    【讨论】:

    • 刚发现最好用新的覆盖加载器变量。也只有在响应有body 成员的情况下才这样做。稍微修改一下代码。
    • 我收到一个错误:KeyError: 'loader' on loader = response.meta['loader'] using Scrapy 1.4.0.
    【解决方案2】:

    css 选择器确实有效。问题出在您保存在元字典中的 itemloader 变量 l 中,它指向来自 firstRequest 回调的响应,而不是指向 parseDescription1 这个 CSS 选择器可以在其中工作的回调

    'div.price-breakdown div.price- exclude-tax span.price::text'

    要解决这个问题,只需创建一个新的 itemloader parseDescription1 或更好地加载项目本身而不是元​​字典中的加载器,如下所示...

        def firstRequest(self, response):
    
            l = ItemLoader(item=ProductItem(), response=response)
    
            l.add_css('nic_price', 'div.product-info p.product-price span[itemprop="price"]::text')
    
            yield Request('https://www.tester.co.uk/test-safe-pen-co-meter',
                      meta={'item': l.load_item()},
                      callback=self.parseDescription1)
    
        def parseDescription1(self, response):
    
            # Recover item
            item = response.meta['item']
    
            l = ItemLoader(item=ProductItem(), response=response)
    
            # Use just as before
            l.add_css('tester_price', 'div.price-breakdown div.price-excluding-tax span.price::text')
    
            l.add_value('nic_price', item['nic_price'])
    
            yield l.load_item()
    

    【讨论】:

    • 我的程序的目标是通过从不同站点获取每个属性来生成一个项目。我已尝试实现您的代码,但随后我分别生成了“l”和“l2”项目加载器,这导致了 2 个项目。
    • 我正在研究一种解决方法,非常欢迎任何帮助。
    • 有很多方法可以解决这个问题,一种简单的方法是在元meta={'item': l.load_item()}, 中加载项目本身而不是加载器,然后在 parseDescription1 中获取项目元字典并添加 nic_price 作为值 item = response.meta['item'] l.add_value('nic_price', item['nic_price'])
    • 我最终传递了项目而不是项目加载器。在每个链式解析函数中,我添加了该项的属性。
    猜你喜欢
    • 2013-11-20
    • 2020-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-09
    • 2012-03-16
    • 1970-01-01
    • 2011-06-07
    相关资源
    最近更新 更多