Python Scrapy come salvare i dati in file diversi

Sep 05 2020

Voglio salvare ogni citazione da http://quotes.toscrape.com/salvato in un file csv (2 campi: autore, citazione). Un'altra necessità è salvare queste citazioni in file diversi separati dalla pagina in cui risiedono. ad esempio: (pagina1.csv, pagina2.csv ...). Ho provato a raggiungere questo obiettivo dichiarando le esportazioni di feed custom_settingsnell'attributo nel mio spider come mostrato di seguito. Questo, tuttavia, non produce nemmeno un file chiamato page-1.csv. Sono un principiante assoluto che usa scrapy, per favore prova a spiegare supponendo che io sappia poco o niente.

import scrapy
import urllib

class spidey(scrapy.Spider):
    name = "idk"
    start_urls = [
        "http://quotes.toscrape.com/"
    ]

    custom_settings = {
        'FEEDS' : {
            'file://page-1.csv' : { #edit: uri needs to be absolute path
                'format' : 'csv',
                'store_empty' : True
            }
        },
        'FEED_EXPORT_ENCODING' : 'utf-8',
        'FEED_EXPORT_FIELDS' : ['author', 'quote']
    }
    

    def parse(self, response):
        for qts in response.xpath("//*[@class=\"quote\"]"):
            author = qts.xpath("./span[2]/small/text()").get()
            quote = qts.xpath("./*[@class=\"text\"]/text()").get()
            yield {
                'author' : author,
                'quote' : quote
                }

        next_pg = response.xpath('//li[@class="next"]/a/@href').get()      
        if next_pg is not None:
            next_pg = urllib.parse.urljoin(self.start_urls[0], next_pg)
            yield scrapy.Request(next_pg, self.parse)

Come ho eseguito il crawler: scrapy crawl idkcome domanda aggiuntiva, ho bisogno che i miei file vengano sovrascritti anziché aggiunti come quando si specifica il -oflag. È possibile farlo senza dover controllare / eliminare manualmente i file preesistenti da spider?

Risposte

1 PatrickKlein Nov 25 2020 at 17:39

Il salvataggio dei tuoi articoli in un file chiamato dopo la pagina in cui li hai trovati non è (afaik) supportato nelle impostazioni. Se vuoi ottenere questo risultato, puoi creare la tua funzionalità per quello con la openfunzione di python e csv.writernel tuo parsemetodo. Un'opzione alternativa sarebbe scrivere una pipeline di articoli che gestisca diversi esportatori di articoli per file diversi.

Quello che puoi fare con le impostazioni, tuttavia, è limitare il numero di elementi in un file con l' FEED_EXPORT_BATCH_ITEM_COUNTimpostazione, che è supportata dalla versione 2.3 di Scrapy.
A partire da Scrapy 2.4, è anche possibile sovrascrivere invece di aggiungere a un file. In FEEDSpuoi impostare overwritesu True come dimostrato a breve.

Se dovessi sostituire il tuo custom_settingscon il seguente, produrrebbe file con 10 elementi ciascuno denominato page-seguito da batch_id, che inizia con uno. Quindi i tuoi primi 3 file saranno denominati page-1.csv, page-2.csv e page-3.csv.

    custom_settings = {
        'FEED_EXPORT_BATCH_ITEM_COUNT': 10,
        'FEEDS' : {
            'page-%(batch_id)d.csv' : {
                'format' : 'csv',
                'store_empty' : True,
                'overwrite': True
            }
        }
    }

Implementando come pipeline

Se si desidera implementare ciò utilizzando una pipeline di elementi, è possibile salvare il numero di pagina in cui ci si trova nel dizionario restituito, che viene quindi elaborato e rimosso dalla pipeline di elementi.

La pipeline nel tuo pipelines.py(in base a questo esempio ) potrebbe quindi essere simile a questa:

from scrapy.exporters import CsvItemExporter


class PerFilenameExportPipeline:
    """Distribute items across multiple CSV files according to their 'page' field"""

    def open_spider(self, spider):
        self.filename_to_exporter = {}

    def close_spider(self, spider):
        for exporter in self.filename_to_exporter.values():
            exporter.finish_exporting()

    def _exporter_for_item(self, item):
        filename = 'page-' + str(item['page_no'])
        del item['page_no']
        if filename not in self.filename_to_exporter:
            f = open(f'{filename}.csv', 'wb')
            exporter = CsvItemExporter(f)
            exporter.start_exporting()
            self.filename_to_exporter[filename] = exporter
        return self.filename_to_exporter[filename]

    def process_item(self, item, spider):
        exporter = self._exporter_for_item(item)
        exporter.export_item(item)
        return item

Al tuo ragno dovresti quindi aggiungere una routine per ottenere la pagina in cui ti trovi e impostare la pipeline nel tuo custom_settings, cosa che potresti fare come segue:

import scrapy
from ..pipelines import PerFilenameExportPipeline


class spidey(scrapy.Spider):
    name = "idk"
    custom_settings = {
        'ITEM_PIPELINES': {
            PerFilenameExportPipeline: 100
        }
    }
    
    def start_requests(self):
        yield scrapy.Request("http://quotes.toscrape.com/", cb_kwargs={'page_no': 1})

    def parse(self, response, page_no):
        for qts in response.xpath("//*[@class=\"quote\"]"):
            yield {
                'page_no': page_no,
                'author' : qts.xpath("./span[2]/small/text()").get(),
                'quote' : qts.xpath("./*[@class=\"text\"]/text()").get()
            }

        next_pg = response.xpath('//li[@class="next"]/a/@href').get()      
        if next_pg is not None:
            yield response.follow(next_pg, cb_kwargs={'page_no': page_no + 1})

Tuttavia, c'è un problema con questo. L'ultimo file (pagina-10.csv) rimane vuoto per ragioni che esulano dalla mia comprensione. Ho chiesto perché potrebbe essere qui .