Skip to content

Guia de Spiders (MercoDocs)

Cada spider coleta documentos publicos do Mercosul e exporta itens em JSONL, com foco em:

  • navegacao de listagens dinamicas (iframe + paginacao)
  • coleta de links de arquivos (pdf/doc/xls/zip etc.)
  • enriquecimento por pagina de detalhe
  • execucao incremental (evitar retrabalho)

Arquivos principais em scraper/spiders/:

  • base.py: utilitarios compartilhados entre spiders
  • mercosur_actas_y_anexos.py
  • mercosur_comunicados_presidenciales.py
  • mercosur_declaraciones_presidenciales.py
  • mercosur_normativa.py

A classe base centraliza comportamentos repetidos:

  • load_existing_data(): carrega URLs de detalhe ja processadas no output_file
  • log_failed_item(): registra erros no failed_items_file em JSONL
  • _append_arquivo(): adiciona arquivos sem duplicar por URL
  • _append_detail(): adiciona detalhes e resolve conflito de valores

Cada spider define ao menos:

  • name
  • start_urls
  • output_file
  • failed_items_file
  1. start_requests() cria requests com Playwright quando necessario.
  2. parse_list() abre iframe/lista, coleta linhas e resolve paginacao.
  3. Para cada linha:
    • monta item base
    • identifica links de arquivo diretos
    • identifica URL de detalhe
  4. parse_details() enriquece o item com mais metadados e arquivos.
  5. Item final eh exportado em JSONL pelo proprio Scrapy.

Campos esperados para manter consistencia:

  • source_list_url: URL da listagem
  • detail_url (quando existir)
  • arquivos: lista de objetos com descricao, url, texto_link
  • detalhes_completos (quando houver enriquecimento)

Observacao: cada spider pode incluir campos extras da tabela (ex.: Año, Título, Numero).

Da raiz do projeto:

Terminal window
uv sync
./scripts/crawl_spider.sh mercosur_declaraciones_presidenciales

Ou direto com Scrapy:

Terminal window
uv run --project . --directory scripts scrapy crawl mercosur_declaraciones_presidenciales
  • Frame ... nao encontrado
    • Verifique se a pagina mudou estrutura (iframe, dominio, seletor).
  • Listagem vazia
    • Revalide seletores de tabela e filtros padrao da pagina.
  • Paginacao estagnada
    • Verifique botao next e condicao de troca de primeira linha.
  • Saida sem arquivos
    • Reavalie regras de deteccao de URL de documento.

Caso real: Ver Acta e Ver Anexo em Declaracoes e Actas y Anexos

Section titled “Caso real: Ver Acta e Ver Anexo em Declaracoes e Actas y Anexos”

Contexto: nas spiders mercosur_declaraciones_presidenciales e mercosur_actas_y_anexos, o item da listagem captura a URL de Ver Acta (ex.: /public/reuniones/doc/...). O objetivo e capturar tambem os links internos da pagina de acta, incluindo Ver Anexo e metadados detalhados da reuniao.

Para a spider mercosur_actas_y_anexos, a estrutura foi organizada para refletir a interface web, focando na extração profunda de atas e anexos:

  • ver_detalles: Objeto principal de enriquecimento.
    • ver_actas: Lista de objetos capturados ao navegar no link “Ver Acta” dentro da página de detalhes.
      • dados_basicos: Metadados da ata. Suporta múltiplos campos por linha (ex: Versión e Fecha Versión).
      • reuniao: Metadados da reunião (Modalidad, Cidade/País). Inclui a lista de Asistentes capturada integralmente (mesmo com múltiplas linhas e tags aninhadas).
      • arquivos_ata: Links diretos para PDF/DOC da ata.
      • anexos: Lista de anexos, onde cada item contém a extração do modal (clicando em “Ver anexo”).
        • ver_anexo: Dados do modal, incluindo a tabela de documentos_relacionados com links estruturados (Tipo, Número, Título, URL).

A Modalidad (Presencial/Virtual) é capturada de forma redundante:

  1. Na Listagem: Extração via atributo title ou aria-label de ícones.
  2. Nos Detalhes/Ata: Sincronização automática para a raiz do item principal caso descoberta durante a navegação profunda.

Iteração por Órgãos (mercosur_actas_y_anexos)

Section titled “Iteração por Órgãos (mercosur_actas_y_anexos)”

A spider de Atas e Anexos agora automatiza a troca de órgãos no portal:

  1. Identifica todas as opções do menu #organo.
  2. Seleciona cada órgão sequencialmente.
  3. Aguarda o recarregamento dinâmico da tabela.
  4. Processa todas as páginas de resultados daquele órgão antes de prosseguir.

O campo Órgano é incluído em cada item para identificar a origem (ex: CMC, GMC, CCM).

Coleta Incremental vs. Completa (full_crawl)

Section titled “Coleta Incremental vs. Completa (full_crawl)”

Por padrão, as spiders operam em modo incremental (atualização), pulando URLs de detalhes que já existem no arquivo de saída (output_file).

  • Modo Atualização (Padrão):
    • Carrega processed_urls do arquivo JSONL.
    • Se a URL de detalhe já existe, o item é ignorado.
  • Modo Coleta Completa:
    • Força a re-coleta de todos os itens, ignorando o histórico.
    • Uso: uv run scrapy crawl mercosur_actas_y_anexos -a full_crawl=True

Melhorias na Extração de Dados (Base Spider)

Section titled “Melhorias na Extração de Dados (Base Spider)”

A classe base agora utiliza uma lógica de extração sequencial e exaustiva para o conteúdo dos boxes (_extract_box_kv):

  • Campos em Linha: Detecta padrões Chave: Valor dentro do mesmo elemento HTML (como em datas ou versões).
  • Suporte a Listas: Títulos seguidos de listas (como Asistentes) são consolidados automaticamente, preservando o texto interno de tags como <b>.
  • Hierarquia de Anexos: Modais de anexo agora identificam tabelas internas e capturam links de documentos vinculados (como Diretivas ou Informes) de forma estruturada, incluindo seus respectivos metadados.

Abordagem A (testada e falhando): request direto da URL de acta

Section titled “Abordagem A (testada e falhando): request direto da URL de acta”

Implementacao testada:

  • detectar links /public/reuniones/doc/... no parse_details
  • abrir cada URL com novo scrapy.Request (com e sem playwright_include_page)
  • parsear os a[href] da resposta para buscar anexos/documentos

Sintoma observado:

  • em varios casos, a pagina retornada e de desafio anti-bot (Just a moment...), sem os links reais de documento/anexo
  • resultado: o crawler chega na URL de acta, mas nao encontra Ver Anexo nem novos arquivos

Como reconhecer no debug:

  • titulo/HTML da pagina contem Just a moment
  • baixa contagem de links uteis na pagina de acta
  • smoke test nao adiciona URLs novas em relacao ao baseline

Abordagem B (recomendada): navegacao browser-driven no mesmo contexto

Section titled “Abordagem B (recomendada): navegacao browser-driven no mesmo contexto”

Ideia:

  • em vez de abrir a acta por request separada, navegar/clicar no link Ver Acta dentro da mesma sessao/contexto de browser da pagina de detalhe
  • esperar seletor de conteudo real da acta (nao apenas networkidle)
  • extrair Ver Anexo e documentos finais dessa tela

Vantagem:

  • tende a reaproveitar cookies, estado JS e checks ja resolvidos no contexto corrente
  • reduz chance de cair no desafio anti-bot ao trocar de pagina

Risco/Trade-off:

  • implementacao fica mais acoplada ao Playwright (menos “HTTP puro”)
  • exige mais cuidado com fechamento de paginas e controle de fluxo assincrono

Abordagem C (fallback operacional): sessao assistida + estado persistido

Section titled “Abordagem C (fallback operacional): sessao assistida + estado persistido”

Quando usar:

  • quando as abordagens A e B continuam caindo em challenge anti-bot
  • quando voce precisa destravar rapidamente uma coleta critica

Ideia:

  • abrir execucao em modo visual (headful)
  • resolver manualmente challenge/CAPTCHA uma vez
  • salvar cookies/storage state do contexto
  • reutilizar esse estado em execucoes seguintes da spider

Fluxo sugerido:

  1. Rodar a spider em ambiente local com navegador visivel.
  2. Completar challenge manualmente na primeira pagina bloqueada.
  3. Exportar o estado de sessao (cookies/localStorage) para arquivo.
  4. Configurar a spider para iniciar Playwright com esse estado.
  5. Monitorar expiracao do estado (normalmente expira e precisa renovar).

Vantagens:

  • aumenta chance de acessar paginas internas de acta/anexo
  • reduz retrabalho em coletas com bloqueio intermitente

Riscos/Trade-offs:

  • menos automatizavel em CI puro
  • estado pode expirar ou invalidar por IP/ambiente
  • exige cuidado de seguranca com o arquivo de estado

Boas praticas para o estado de sessao:

  • nao commitar arquivo de estado no repositório
  • adicionar caminho do estado no .gitignore
  • tratar como credencial operacional
  • manter runbook de renovacao para contribuidores
  1. Capturar estado de sessao manualmente:
Terminal window
uv run python scripts/capture_playwright_state.py --output debug/.state/mercodocs_playwright_state.json
  1. Rodar spider reutilizando o estado salvo:
Terminal window
PLAYWRIGHT_STORAGE_STATE_PATH=debug/.state/mercodocs_playwright_state.json \
uv run --project . --directory scripts scrapy crawl mercosur_declaraciones_presidenciales -O test_declaraciones_stateful.jsonl
  1. Se voltar a bloquear, renovar estado:
  • repetir passo 1 para gerar novo arquivo de estado
  • rodar novamente o passo 2
  1. Teste dirigido para confirmar Ver Acta/Ver Anexo:
Terminal window
uv run python debug/debug_acta_links.py \
--input data/test_declaraciones_stateful.jsonl \
--output debug/debug_acta_report.json \
--limit 5 \
--storage-state debug/.state/mercodocs_playwright_state.json

Interpretacao rapida do relatorio:

  • has_challenge: true => pagina caiu em anti-bot, sem DOM util para extracao
  • anexo_count > 0 => pagina exibe links de anexo no DOM
  • doc_count > 0 => pagina exibe links de documento no DOM
  • para pelo menos 1 item com Ver Acta, o JSONL final deve conter:
    • URL da acta (manter)
    • ao menos 1 link interno da acta (quando existir)
    • links de Ver Anexo (quando existir)
  • sem duplicacao de URL em arquivos
  • Prefira metodos pequenos e nomes diretos.
  • Sempre use _append_arquivo() para evitar duplicacao.
  • Sempre use log_failed_item() com stage claro.
  • Evite hardcode desnecessario de timeout sem justificativa.
  • Mantenha compatibilidade do formato de saida JSONL.
  • Rodou a spider localmente
  • Validou se arquivos nao contem duplicatas
  • Validou pelo menos 1 pagina de detalhe
  • Confirmou que failed_items.json nao explodiu sem motivo
  • Atualizou este guia se mudou padrao arquitetural
import scrapy
from scraper.spiders.base import BaseMercosurSpider
class MinhaSpider(BaseMercosurSpider):
name = "minha_spider"
start_urls = ["https://exemplo"]
output_file = "minha_spider.jsonl"
failed_items_file = "failed_items.json"
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(
url,
meta={"playwright": True, "playwright_include_page": True},
callback=self.parse_list,
)
async def parse_list(self, response):
# montar itens base e enviar requests de detalhe
...
async def parse_details(self, response, item):
# enriquecer item, usar _append_arquivo/_append_detail
yield item