Python Scrapy come salvare i dati in file diversi
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
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 .