Armazenamento de dados
Ao coletar dados em web scraping, um dos passos mais importantes do processo é decidir como esses dados serão armazenados. O armazenamento eficiente é crucial, pois permite que você acesse, analise e utilize os dados extraídos de maneira prática e organizada.
Scrapy oferece diversas opções de armazenamento, incluindo arquivos CSV e bancos de dados, que atendem a diferentes necessidades e cenários.
Armazenamento em arquivo
Para exportar os dados coletados por uma spider Scrapy para um arquivo CSV, você pode usar um comando simples ao executar sua spider no terminal. O Scrapy possui um recurso de exportação que permite que você salve os itens diretamente em formatos como CSV, JSON ou XML.
Uau! O Scrapy tem várias baterias incluídas e facilitou nossa vida! Mais um requisito do cliente foi atendido e já podemos disponibilizar os dados em CSV.
Ops! Nosso cliente gostou da entrega e solicitou mais um requisito envolvendo armazenamento de dados que não estava previsto inicialmente. O time comercial precisa receber os dados em formato de planilha em Excel.
Vamos ver o que acontece se tentamos pedir ao Scrapy para exportar em formato XLSx.
Usage
=====
scrapy crawl [options] <spider>
crawl: error: Unrecognized output format 'xlsx'. Set a supported one (('json', 'jsonlines', 'jsonl', 'jl', 'csv', 'xml', 'marshal', 'pickle')) after a colon at the end of the output URI (i.e. -o/-O <URI>:<FORMAT>) or as a file extension.
Ah, o Scrapy não tem uma solução nativa que exporte para planilhas Excel. Que tal criarmos uma pipeline para isso? Agora é sua vez de se desafiar =D
Armazenamento em banco de dados
Uma das opções mais populares e eficientes para esse propósito é o uso do banco de dados SQLite. Iremos criar uma pipeline para inserir os itens coletados diretamente no banco de dados.
import sqlite3
class SqlitePipeline:
def __init__(self): # (1)!
self.conn = sqlite3.connect('db.sqlite3') # (2)!
self.cursor = self.conn.cursor() # (3)!
# (4)!
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS product(
id INTEGER PRIMARY KEY,
sku TEXT,
category TEXT,
name TEXT,
image TEXT,
price TEXT,
url TEXT,
description TEXT,
size TEXT,
color TEXT
)
""")
def process_item(self, item, spider): #(5) !
self.cursor.execute( # (6)!
"""
INSERT INTO product (sku, category, name, image, price, url, description, size, color)
VALUES (:sku, :category, :name, :image, :price, :url, :description, :size, :color)
""",
item
)
self.conn.commit() # (7)!
return item # (8)!
def close_spider(self, spider):
self.cursor.close() # (9)!
self.conn.close() # (10)!
- Será executado quando o pipeline for ativado pelo spider
- Cria a conexão do banco de dados
- Cria o cursor que será usado para executar os comandos
- Cria a tabela product se não existir
- Será ativado toda vez que um item for coletado pela spider
- Definição dos dados que serão inseridos no banco de dados
- Execução do insert no banco de dados
- Retorno do item processado
- Fecha o cursor que executa os comandos
- Fecha a conexão com o banco de dados
E não podemos esquecer de habilitar a SqlitePipeline no arquivo settings.py. Outra configuração importante é ordem de execução das pipelines. Já temos uma que converte o preço para real e faz sentido para o contexto do projeto que ela seja executada antes dos dados serem armazenados no banco de dados.
ITEM_PIPELINES = {
"tutorial.pipelines.PriceConversionToBRLPipeline": 100,
"tutorial.pipelines.SqlitePipeline": 300,
}
Vamos executar a spider e analisar o log de execução:
No diretório do projeto foi criado o arquivo do banco de dados! E os dados estão lá =D
Vamos fazer mais um teste: Execute novamente a spider. O que acontece com na tabela de produtos?
Os dados que já foram coletados estão sendo salvos novamente a cada execução da spider.
Precisamos melhorar nosso código para evitar isso. Vamos refatorar o método process_item() dessa pipeline para verificar se já existe o produto na base antes de fazer a inserção no banco de dados.
def process_item(self, item, spider):
self.cursor.execute( # (1)!
"""
SELECT * FROM product WHERE sku = ?
""",
(item['sku'],)
)
result = self.cursor.fetchone()
if result:
('Item already in database: %s - %s\n\n' %(item['sku'], item['name']))
else:
self.cursor.execute(
"""
INSERT INTO product (sku, category, name, image, price, url, description, size, color)
VALUES (:sku, :category, :name, :image, :price, :url, :description, :size, :color)
""",
item
)
self.conn.commit()
return item
- Verifica se já existe o produto na tabela de produtos
Vamos executar a spider novamente e verificar os registros na tabela de produtos no banco de dados.