Arquitetura e Implementação de Web Scraping com Scrapy

Fluxo de Execução do Framework

O Scrapy opera através de um mecanismo assíncrono coordenado por cinco componentes principais:

  1. O componente Spider gera requisições iniciais (objetos Request) baseadas nas URLs definidas em start_urls
  2. O Scheduler enfileira essas requisições e as disponibiliza para o Engine conforme a capacidade de processamento
  3. O Dwonloader executa as requisições HTTP e encapsula as respostas em objetos Response
  4. O Spider recebe o Response e extrai dados estruturados ou novas URLs para rastreamento
  5. O Item Pipeline processa os dados extraídos — validação, limpeza e persistência em storage
  6. URLs descobertas durante a análise retornam ao Scheduler, reiniciando o ciclo

Componentes da Arquitetura

Componente Responsabilidade Principal
Engine Orchestração de eventos e sinais entre todos os módulos
Scheduler Fila de prioridade para requisições pendentes
Downloader Camada de rede: execução HTTP/HTTPS com gerenciamento de conexões
Spider Lógica de extração: parsing de HTML/JSON e geração de itens
Item Pipeline Pós-processamento: deduplicação, transformação e exportação
Middlewares Interceptação de requisições/respostas para customização

Configuração do Ambiente

# Instalação via pip
pip install scrapy

# Geração da estrutura de projeto
scrapy startproject coletor

Estrutura de diretórios gerada:

coletor/
├── coletor/
│   ├── __init__.py
│   ├── items.py          # Definição de schemas de dados
│   ├── middlewares.py    # Hooks de processamento
│   ├── pipelines.py      # Fluxos de exportação
│   ├── settings.py       # Configurações globais
│   └── spiders/          # Módulos de extração
│       └── __init__.py
└── scrapy.cfg            # Configuração de deployment

Criação de Spider Básico

scrapy genspider noticias example.com

Implementação mínima em spiders/noticias.py:

import scrapy

class NoticiasSpider(scrapy.Spider):
    identificador = "noticias"  # nome único do spider
    dominios_permitidos = ["example.com"]
    urls_iniciais = ["https://example.com/noticias"]
    
    def analisar(self, resposta):
        # resposta.text: conteúdo HTML bruto
        # resposta.css(): seletores CSS
        # resposta.xpath(): expressões XPath
        
        for artigo in resposta.css('article.noticia'):
            yield {
                'titulo': artigo.css('h2::text').get(),
                'link': artigo.css('a::attr(href)').get()
            }

Execução:

scrapy crawl noticias

Extração de Conteúdo

Métodos disponíveis no objeto Response:

  • resposta.css('seletor'): Retorna objeto SelectorList
  • .get() / .extract_first(): Primeiro elemento ou None
  • .getall() / .extract(): Lista com todos os elementos
  • resposta.xpath('//tag[@classe="valor"]'): Navegação via XPath
def analisar(self, resposta):
    registros = resposta.xpath('//div[@class="lista"]/div')
    
    for reg in registros:
        yield {
            'nome': reg.xpath('.//h3/text()').get(default=''),
            'preco': reg.xpath('.//span[@class="valor"]/text()').get(),
            'disponivel': bool(reg.xpath('.//button[not(@disabled)]'))
        }

Modelagem de Dados com Items

Definição em items.py:

import scrapy

class ProdutoItem(scrapy.Item):
    referencia = scrapy.Field()   # SKU ou identificador
    denominacao = scrapy.Field()  # nome do produto
    valor = scrapy.Field()        # preço numérico
    estoque = scrapy.Field()      # quantidade disponível

Uso no spider:

from coletor.items import ProdutoItem

class LojaSpider(scrapy.Spider):
    # ... configurações ...
    
    def analisar(self, resposta):
        for card in resposta.css('.produto-card'):
            item = ProdutoItem()
            item['referencia'] = card.css('::attr(data-sku)').get()
            item['denominacao'] = card.css('.titulo::text').get().strip()
            item['valor'] = self._extrair_preco(card.css('.preco::text').get())
            item['estoque'] = card.css('.estoque::text').get()
            yield item
    
    def _extrair_preco(self, texto):
        return float(texto.replace('R$', '').replace('.', '').replace(',', '.'))

Procesamento e Persistência

Pipeline para exportação JSON Lines:

import json
from itemadapter import ItemAdapter

class ExportacaoJsonPipeline:
    def __init__(self):
        self.arquivo = None
    
    def open_spider(self, spider):
        self.arquivo = open(f'{spider.name}_dados.jsonl', 'w', encoding='utf-8')
    
    def close_spider(self, spider):
        self.arquivo.close()
    
    def process_item(self, item, spider):
        linha = json.dumps(ItemAdapter(item).asdict(), ensure_ascii=False)
        self.arquivo.write(linha + '\n')
        return item  # obrigatório: propagação para próximo pipeline

Ativação em settings.py:

ITEM_PIPELINES = {
    'coletor.pipelines.ExportacaoJsonPipeline': 100,
    'coletor.pipelines.ValidacaoPipeline': 200,
}

Valores menores indicam prioridade de execução mais alta.

Rastreamento Automático com CrawlSpider

Para sites com estrutura hierárquica previsível:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class RastreadorSpider(CrawlSpider):
    name = 'rastreador'
    start_urls = ['https://example.com/categorias']
    
    regras = (
        # Segue paginação
        Rule(
            LinkExtractor(allow=r'/categorias\?pagina=\d+'),
            follow=True
        ),
        # Extrai detalhes de produtos
        Rule(
            LinkExtractor(allow=r'/produto/\d+'),
            callback='extrair_detalhes',
            follow=False
        ),
    )
    
    def extrair_detalhes(self, resposta):
        yield {
            'sku': resposta.css('span.sku::text').get(),
            'descricao': resposta.css('meta[name="description"]::attr(content)').get()
        }

Restrição importante: não sobrescrever o método parse() em CrawlSpider, pois é utilizado internamente para processamento de regras.

Middlewares para Customização

Tipos e aplicações típicas:

  • Downloader Middleware: rotação de proxies, manipulação de headers, retry automático, delay adaptativo
  • Spider Middleware: tratamento de exceções em parsing, modificação de itens antes do pipeline

Exemplo de rotação de User-Agent:

from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
import random

class RotacaoUserAgentMiddleware(UserAgentMiddleware):
    agentes = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.0',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)',
    ]
    
    def process_request(self, request, spider):
        request.headers['User-Agent'] = random.choice(self.agentes)

Registro em settings.py:

DOWNLOADER_MIDDLEWARES = {
    'coletor.middlewares.RotacaoUserAgentMiddleware': 400,
}

Tags: Scrapy web-scraping Python crawlspider xpath

Publicado em 9-27 23:34