首页 > 解决方案 > 使用 for 循环或 str 格式绕过 Scrapy 爬网列表

问题描述

我正在寻找一种解决方案,即我的代码只抓取每个项目一次。自从我添加了最后一个循环以来,我收到了每个项目三次。我怎样才能只执行一次我的最后一个循环,或者是否可以确定所有的双爬?

import scrapy
from ..items import TopartItem

class LinkSpider(scrapy.Spider):
    name = "link"
    allow_domains = ['topart-online.com']
    start_urls = ['https://www.topart-online.com/de/Blattzweige-Blatt-und-Bluetenzweige/l-KAT282?seg=1']
    custom_settings = {'FEED_EXPORT_FIELDS': ['title','links','ItemSKU','ItemEAN','Delivery_Status', 'Attribute', 'Values'] } 
    def parse(self, response):
        card = response.xpath('//a[@class="clearfix productlink"]')
        
        for a in card:
            items = TopartItem()
            link = a.xpath('@href')
            items['title'] = a.xpath('.//div[@class="sn_p01_desc h4 col-12 pl-0 pl-sm-3 pull-left"]/text()').get().strip()
            items['links'] = link.get()
            items['ItemSKU'] = a.xpath('.//span[@class="sn_p01_pno"]/text()').get().strip()
            items['Delivery_Status'] = a.xpath('.//div[@class="availabilitydeliverytime"]/text()').get().strip().replace('/','')
            yield response.follow(url=link.get(),callback=self.parse_item, meta={'items':items})

        last_pagination_link = response.xpath('//a[@class="page-link"]/@href')[-1].get()
        last_page_number = int(last_pagination_link.split('=')[-1])
        for i in range(2,last_page_number+1):
            url = f'https://www.topart-online.com/de/Blattzweige-Blatt-und-Bluetenzweige/l-KAT282?seg={i}'
            yield response.follow(url=url, callback=self.parse)
    

    def parse_item(self,response):
        table = response.xpath('//div[@class="productcustomattrdesc word-break col-6"]')

        for a in table:
            items = TopartItem()
            items = response.meta['items']
            items['ItemEAN'] = response.xpath('//div[@class="productean"]/text()').get().strip()
            items['Attribute'] = response.xpath('//div[@class="productcustomattrdesc word-break col-6"]/text()').getall()
            items['Values'] = response.xpath('//div[@class="col-6"]/text()').getall()
            yield items

我只期待 51 个元素,但我收到了 153 个。

标签: pythonpython-3.xscrapyweb-crawler

解决方案


你得到每个项目 3 个的原因是你在桌子周围做一个 for 循环,我认为这不是必要的。如果数据没有意义,尽管很高兴出错。

添加

顶部代码的一个小补充。我之所以把它放进去是因为在创建 CSV 文件时指定列的显示方式。通常使用 item 时,您不会以您想要的方式获得列的顺序。在这里,我们通过使 scrapy 包含这些设置来指定它们。当您创建 CSV 文档时,我们必须将其添加attribute到该列表中以包含它。value

custom_settings = {'FEED_EXPORT_FIELDS': ['title','links','ItemSKU','ItemEAN','Delivery_Status','Attribute','Values'] } 

代码更正

def parse_item(self,response):
    items = response.meta['items']
    items['ItemEAN'] = response.xpath('//div[@class="productean"]/text()').get().strip()
    items['Attribute'] = response.xpath('//div[@class="productcustomattrdesc word-break col-6"]/text()').getall()
    items['Values'] = response.xpath('//div[@class="col-6"]/text()').getall()
    yield items

解释

  1. 无需实例化 TopArtItem() ,parse_item因为它已经在parse函数中实例化了。
  2. 无需使用 for 循环,只需使用响应获取详细信息。

尖端

  1. 如果您确实需要围绕表格或任何为您提供列表的 XPATH 选择器执行 for 循环,请记住您的 xpath 选择器应该是a.xpath('.//div etc....)NOT response('//)。这是因为您想使用a而不是responseor table,并且您必须根据需要使用.//相对 XPATH NOT//来搜索整个文档。

相对路径我的意思是你想告诉scrapy,你假设xpath选择器table是XPATH选择器中给定的,.//并且使用.//XPATH_SELECTOR,你告诉scrapy将该表XPATH选择器添加到XPATH选择器中的任何内容.//。这是一种不必使用非常大的字符串 XPATH 选择器的简洁方法。但是,如果您正在围绕已创建选择器列表的 XPATH 选择器执行 for 循环,则必须使用它。

例如

不是要包含的代码,而是当表 XPATH 选择器为您提供列表时如何使用 for 循环的示例。

table = response.xpath('//div[@class="productcustomattrdesc word-break col-6"]')

for a in table:
    items = response.meta['items']
    items['ItemEAN'] = a.xpath('.//div[@class="productean"]/text()').get().strip()
    items['Attribute'] = a.xpath('.//div[@class="productcustomattrdesc word-break col-6"]/text()').getall()
    items['Values'] = a.xpath('.//div[@class="col-6"]/text()').getall()
    yield items

我们使用a了代替tableorresponse并且我们专门使用了.//NOT//

更新每条评论

因此,对于下一个问题,它需要一些字符串和列表操作。

更改为代码

为了使代码在下面工作,您需要更改custom_settings

custom_settings = {'FEED_EXPORT_FIELDS': ['title','links','ItemSKU','ItemEAN','Delivery_Status','Values'] }

您还需要在 items.py 中删除

Attributes = scrapy.Field()

更新了 parse_items 代码

def parse_item(self,response):
        items = response.meta['items']
        attribute = response.xpath('//div[@class="productcustomattrdesc word-break col-6"]/text()').getall()
        values = response.xpath('//div[@class="col-6"]/text()').getall()
        combined = []
        for i,j in zip(attribute,values):
            combined.append(i.strip().replace('.','').replace(':',': ') + j.strip().replace('\'',''))                 
        items['ItemEAN'] = response.xpath('//div[@class="productean"]/text()').get().strip()            
        items['values'] = ', '.join(combined)
        yield items

解释

我们定义变量attributesvalues。我们不会将这些添加到 items 字典中,因为我们想先进行一些操作。

组合变量很长,但很容易理解。

我们有两个列表,attributes并且values,我们希望将两个列表中的每个项目组合在一起。属性中的第一项与值中的第一项。这可以通过 zip 函数来完成。

举一个抽象的例子来理解 zip 在做什么。

如果我们有一个名为num = ['1','2','3']and的列表letter = [a,b,c]zip(num,letter) 将创建[('1',a),('2',b),('3',c)]. Zip 创建每个相应列表项的元组并将它们放入列表中。

现在我们想把这个列表的所有项目组合成一个字符串作为目标。

zip(num,letter)我们可以像这样循环每个列表项

combined = []
for i,j zip(num,letter): 
   combined.append(i + j)

这将创建combined = ['1 + a','2 + b','3 + c']

然后我们使用''.join(combined),这是将列表转换为字符串以将所有这些组合成字符串的标准方法。

所以我们用这段代码来做这件事,除了我使用 strip() 方法并为每个 i 或 j 替换一些字母只是为了整理它。


推荐阅读