Scrapy 自定义管道输出文件是预期大小的一半

Scrapy custom pipeline outputting files half the size expected

我正在尝试为 Scrapy 项目创建自定义管道,将收集的项目输出到 CSV 文件。为了减小每个文件的大小,我想设置每个文件可以包含的最大行数。一旦达到当前文件中的行限制,就会创建一个新文件以继续输出项目。

幸运的是,我找到 a question where someone was looking to do the same thing. And there's an answer 那个显示示例实现的问题。

我实现了示例实现,但调整了访问 stats 的方式以与 Scrapy 的当前版本保持一致。

我当前的代码

from scrapy.exporters import CsvItemExporter
import datetime

class PartitionedCsvPipeline(object):

    def __init__(self, stats):
        self.stats = stats
        self.stats.set_value('item_scraped_count', 0)
        self.base_filename = "site_{}.csv"
        self.next_split = self.split_limit = 100
        self.create_exporter()  

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.stats)

    def create_exporter(self):
        now = datetime.datetime.now()
        datetime_stamp = now.strftime("%Y%m%d%H%M")
        self.file = open(self.base_filename.format(datetime_stamp),'w+b')
        self.exporter = CsvItemExporter(self.file)
        self.exporter.start_exporting()       

    def process_item(self, item, spider):
        if self.stats.get_value('item_scraped_count') >= self.next_split:
            self.next_split += self.split_limit
            self.exporter.finish_exporting()
            self.file.close()
            self.create_exporter()
        self.exporter.export_item(item)
        self.stats.inc_value('item_scraped_count')
        return item

问题

管道确实会导致输出多个文件,但这些文件都只有 50 个项目,而不是预期的 100 个。

问题

我做错了什么导致文件大小只有预期的一半?

process_item()时我添加

 print('>>> stat count:', self.stats.get_value('item_scraped_count'))

并删除

 self.stats.inc_value('item_scraped_count')

然后我看到它仍然增加了这个变量。

这意味着其他代码已经计算了抓取的值,因此您不应该增加它。

如果我保留 inc_value(),那么我会看到它对所有元素计数两次。


我不确定它是否只计算您添加到 CSV 中的元素,因此您可以使用分隔变量来计算它

class PartitionedCsvPipeline(object):

    def __init__(self, stats):
        self.count = 0

        # ... code ...
 
def process_item(self, item, spider):
    print('>>> count:', self.count)

    if self.count >= self.next_split:
        # ... code ...

    # ... code ...

    self.count += 1

    return item

编辑:

管道需要此方法来关闭最后一个文件并保存此文件中的所有数据。

def close_spider(self, spider):
    self.file.close()

最简单的工作示例。

我都放在一个文件里,不用创建项目也可以运行python script.py。这样大家就可以轻松测试了。

因为我在每个文件中抓取了 10 个项目,所以它创建新文件的速度如此之快,以至于我不得不在文件名中添加微秒 (%f) 以创建唯一的名称。

import scrapy
from scrapy.exporters import CsvItemExporter
import datetime

class MySpider(scrapy.Spider):

    name = 'myspider'

    # see page created for scraping: http://toscrape.com/
    start_urls = ['http://books.toscrape.com/'] #'http://quotes.toscrape.com']

    def parse(self, response):
        print('url:', response.url)

        # download images and convert to JPG (even if it is already JPG)
        for url in response.css('img::attr(src)').extract():
            url = response.urljoin(url)
            yield {'image_urls': [url], 'session_path': 'hello_world'}


class PartitionedCsvPipeline(object):

    def __init__(self, stats):
        self.filename = "site_{}.csv"
        self.split_limit = 10
        
        self.count = 0
        self.create_exporter()  

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.stats)

    def create_exporter(self):
        now = datetime.datetime.now()
        datetime_stamp = now.strftime("%Y.%m.%d-%H.%M.%S.%f")  # %f for microseconds because sometimes it can create next file in less then 1 second and create the same name.
        
        self.file = open(self.filename.format(datetime_stamp), 'w+b')
        
        self.exporter = CsvItemExporter(self.file)
        self.exporter.start_exporting()       

    def finish_exporter(self):
        self.exporter.finish_exporting()
        self.file.close()
    
    def process_item(self, item, spider):

        if self.count >= self.split_limit:
            self.finish_exporter()
            self.count = 0
            self.create_exporter()

        self.exporter.export_item(item)
        self.count += 1
        print('self.count:', self.count)

        return item
        
    def close_spider(self, spider):
        self.finish_exporter()
        
# --- run without project and save in `output.csv` ---

from scrapy.crawler import CrawlerProcess

c = CrawlerProcess({
    'USER_AGENT': 'Mozilla/5.0',

    'ITEM_PIPELINES': {'__main__.PartitionedCsvPipeline': 1},   # used Pipeline create in current file (needs __main___)
})

c.crawl(MySpider)
c.start()