- Added scripts/extract_article_contents.py for batch scraping with stealth Foxcape and triple extraction (Trafilatura, Newspaper4k, Readability) - Created unit, integration, and E2E test suite in tests/test_extract_article_contents.py (90/90 passing) - Updated specs/003-article-content-extractor and README.md with usage documentation and CLI contracts - Passed ruff linting/formatting and mypy type checking cleanly
115 lines
10 KiB
Markdown
115 lines
10 KiB
Markdown
# Feature Specification: Article Content Multi-Engine Extractor
|
|
|
|
**Feature Branch**: `003-article-content-extractor`
|
|
**Created**: 2026-08-20
|
|
**Status**: Completed
|
|
**Input**: User description: "Extrator e Parser de Artigos Multimotor a partir de listagens JSON usando Foxcape headless e tripla extração com Trafilatura, Newspaper4k e Readability (conforme docs/prd_extrator_artigos_nlp.md)"
|
|
|
|
---
|
|
|
|
## Clarifications
|
|
|
|
### Session 2026-08-20
|
|
- Q: Como o script deve tratar o armazenamento do HTML bruto (*raw HTML*) baixado pelo Foxcape no arquivo JSON de saída? → A: Não incluir o HTML bruto no JSON final (descartar após extrações e persistir apenas os dados estruturados e limpos dos 3 motores para manter o arquivo leve e performático).
|
|
- Q: Como o idioma para o processamento de NLP do Newspaper4k deve ser definido durante a extração? → A: Automático via JSON de entrada (herda o campo `"language"` do cabeçalho da busca com fallback para `"en"`), permitindo sobrescrita opcional via flag CLI (`--language` / `-l`).
|
|
- Q: Qual deve ser o padrão de nomenclatura e localização do arquivo JSON gerado quando o operador não fornecer a flag `--output`? → A: Salvar no mesmo diretório adicionando o sufixo `_extracted.json` ao nome base do arquivo de entrada (ex.: `out/river_plate.json` → `out/river_plate_extracted.json`).
|
|
|
|
---
|
|
|
|
## User Scenarios & Testing *(mandatory)*
|
|
|
|
### User Story 1 - Extração Completa e Consolidada de Artigos em Lote (Priority: P1) 🌟 MVP
|
|
|
|
Como analista ou operador de dados, quero fornecer um arquivo JSON de listagem de notícias e obter como resultado um novo arquivo JSON enriquecido contendo o texto completo limpo, metadados e sumários estruturados de cada notícia processada por múltiplos motores de extração, sem que bloqueios anti-bot impeçam a coleta.
|
|
|
|
**Why this priority**: É o objetivo central do produto. Transforma referências e manchetes superficiais em conteúdo aprofundado, higienizado e categorizado para análise ou ingestão posterior.
|
|
|
|
**Independent Test**: Executar a extração apontando para um arquivo JSON com notícias válidas (ex.: `out/river_plate.json`) e verificar a geração de um arquivo de saída estruturado em `out/` contendo para cada artigo os blocos preenchidos de extração textual e metadados.
|
|
|
|
**Acceptance Scenarios**:
|
|
1. **Given** um arquivo JSON de entrada contendo artigos com URLs válidas, **When** o processo de extração for disparado, **Then** o sistema acessa furtivamente cada URL em modo headless, aguarda o carregamento do DOM, obtém o HTML renderizado e processa simultaneamente a extração por três motores distintos, consolidando os resultados em um único arquivo JSON sem armazenar o HTML bruto.
|
|
2. **Given** um arquivo de entrada vazio ou sem itens válidos, **When** o processo for executado, **Then** o sistema gera um arquivo de saída indicando 0 artigos processados e finaliza com status de sucesso.
|
|
|
|
---
|
|
|
|
### User Story 2 - Resiliência e Isolamento de Falhas por Artigo e Motor (Priority: P2)
|
|
|
|
Como engenheiro de dados executando rotinas em lote, quero que falhas em URLs individuais (como páginas inexistentes, timeouts ou instabilidade temporária do servidor) ou inconsistências em um dos motores de extração não interrompam o processamento das demais notícias do lote.
|
|
|
|
**Why this priority**: Garante que execuções longas com dezenas de notícias não sejam perdidas por falha pontual de um único portal externo.
|
|
|
|
**Independent Test**: Executar a extração contra um arquivo contendo uma URL inválida misturada com URLs válidas, confirmando que as válidas foram processadas com sucesso e a inválida foi registrada com status de erro sem abortar o pipeline.
|
|
|
|
**Acceptance Scenarios**:
|
|
1. **Given** uma notícia com URL inacessível (ex.: erro 404 ou timeout de conexão), **When** a rotina processa a lista, **Then** o sistema registra o item com status de falha e mensagem explicativa, continuando o processamento do próximo item.
|
|
2. **Given** um HTML que cause erro em um dos três motores de extração, **When** a etapa de análise é executada, **Then** os outros dois motores continuam sua extração normalmente e o erro do motor específico é encapsulado no registro daquele motor.
|
|
|
|
---
|
|
|
|
### User Story 3 - Controle de Execução via Linha de Comando e Feedback Visual (Priority: P3)
|
|
|
|
Como operador de terminal, quero parametrizar a execução via CLI (definindo arquivo de entrada, caminho de saída opcional com padrão `_extracted.json`, limite de itens, idioma e nível de verbosidade) e acompanhar o progresso visualmente no terminal em tempo real sem comprometer a saída padrão de dados.
|
|
|
|
**Why this priority**: Oferece usabilidade, capacidade de testes parciais rápidos (amostragem) e compatibilidade com pipes e automações.
|
|
|
|
**Independent Test**: Executar o comando passando a flag `--limit 2` e verificar que apenas 2 notícias foram processadas, com mensagens de progresso emitidas no canal de diagnóstico (`stderr`).
|
|
|
|
**Acceptance Scenarios**:
|
|
1. **Given** a execução via linha de comando com parâmetros `--input out/river_plate.json` (sem `--output`) e `--limit 2`, **When** o processo inicia, **Then** o terminal exibe logs com status e percentual de avanço no canal de erro/diagnóstico, processa estritamente 2 itens e grava o arquivo automaticamente como `out/river_plate_extracted.json`.
|
|
2. **Given** o uso da flag `--silent`, **When** o script é executado, **Then** nenhuma mensagem de log é impressa no terminal.
|
|
|
|
---
|
|
|
|
### Edge Cases
|
|
|
|
- **Página com paywall severo ou bloqueio de bot**: O sistema deve capturar o HTML retornado, registrar eventuais limitações na extração e prosseguir sem quebrar a execução.
|
|
- **Páginas com renderização pesada via JavaScript (SPA)**: O sistema deve aguardar o evento de carregamento do DOM antes de coletar o HTML para garantir que o conteúdo dinâmico esteja presente.
|
|
- **Ausência de texto no corpo da notícia (apenas vídeo/galeria de fotos)**: Os extratores devem retornar campos de texto vazios de forma graciosa sem gerar exceções não tratadas.
|
|
- **Caracteres especiais e encodings variados (UTF-8, Latin-1, etc.)**: Os textos extraídos devem ser normalizados para UTF-8 válido no JSON final.
|
|
|
|
---
|
|
|
|
## Requirements *(mandatory)*
|
|
|
|
### Functional Requirements
|
|
|
|
- **FR-001**: O sistema DEVE receber como entrada um arquivo JSON contendo uma lista estruturada de notícias e validar a presença das URLs a serem processadas.
|
|
- **FR-002**: O sistema DEVE navegar até cada URL utilizando navegação furtiva automatizada em modo headless, aguardando o carregamento completo do DOM.
|
|
- **FR-003**: O sistema DEVE manter uma única sessão de navegador ativa reutilizada ao longo do lote para otimizar velocidade e consumo de memória.
|
|
- **FR-004**: O sistema DEVE processar o HTML renderizado através do motor Trafilatura, extraindo texto limpo, título, autor, data, categorias/tags, descrição e metadados estruturados.
|
|
- **FR-005**: O sistema DEVE processar o HTML renderizado através do motor Newspaper4k, utilizando o idioma herdado do JSON de entrada (com fallback para `"en"` ou sobrescrito por CLI) para extrair corpo do artigo, autores, data de publicação, resumo por NLP, palavras-chave por NLP, imagens e metadados OpenGraph.
|
|
- **FR-006**: O sistema DEVE processar o HTML renderizado através do motor Readability, extraindo o conteúdo limpo principal (HTML sanitizado e texto puro) e título.
|
|
- **FR-007**: O sistema DEVE consolidar os resultados dos três motores em um documento JSON único por execução, gravando-o por padrão como `<input_stem>_extracted.json` no mesmo diretório (ou no caminho fornecido via `--output`), sem persistir o HTML bruto baixado.
|
|
- **FR-008**: O sistema DEVE registrar o status de extração (`success` ou `failed`) e mensagens de erro individuais para cada notícia processada.
|
|
- **FR-009**: O sistema DEVE fornecer interface de linha de comando (CLI) com suporte a flags de arquivo de entrada (`-i, --input`), saída (`-o, --output`), limite de itens (`--limit`), idioma opcional (`-l, --language`), timeout (`-t, --timeout`) e modo silencioso (`-s, --silent`).
|
|
- **FR-010**: O sistema DEVE enviar logs de progresso e status em tempo real exclusivamente para o fluxo de erro padrão (`stderr`), preservando o fluxo de saída padrão (`stdout`).
|
|
|
|
---
|
|
|
|
### Key Entities
|
|
|
|
- **InputArticle**: Representa a notícia recebida no arquivo de entrada, contendo título original, URL resolvida, subtítulo, data de publicação da listagem e número da página.
|
|
- **ExtractedArticleResult**: Representa o resultado consolidado da extração de um artigo, agregando os metadados de entrada, status de coleta, URL final navegada, código de resposta HTTP e os payloads detalhados de cada um dos três extratores (`trafilatura`, `newspaper4k`, `readability`), omitindo o HTML bruto.
|
|
- **BatchExtractionReport**: Representa o relatório global do lote, contendo metadados de auditoria (arquivo de origem, data/hora de processamento, total de itens, sucessos e falhas) e a lista de `ExtractedArticleResult`.
|
|
|
|
---
|
|
|
|
## Success Criteria *(mandatory)*
|
|
|
|
### Measurable Outcomes
|
|
|
|
- **SC-001**: O sistema processa com sucesso pelo menos 90% das notícias válidas fornecidas em lote sem intervenção manual.
|
|
- **SC-002**: Para páginas padrão de notícias com acesso público, todos os três motores de extração preenchem seus respectivos campos de texto limpo e título.
|
|
- **SC-003**: A falha no carregamento ou na extração de 1 artigo isolado tem taxa de propagação de erro de 0% sobre os demais itens da fila.
|
|
- **SC-004**: Operadores conseguem executar amostragens parciais de testes em menos de 30 segundos utilizando a flag de limite de itens.
|
|
- **SC-005**: O arquivo JSON final gerado é 100% compatível com validadores JSON padrão (UTF-8 formatado).
|
|
|
|
---
|
|
|
|
## Assumptions
|
|
|
|
- Os arquivos JSON de entrada seguirão a estrutura produzida pelo extrator de notícias do Google News deste repositório (com chave `items` e propriedade `url` em cada item).
|
|
- O ambiente de execução possui conectividade à internet para acessar os portais de notícias.
|
|
- Recursos de hardware suficientes para execução de um processo de navegador headless (Firefox/Camoufox) em segundo plano.
|
|
- As dependências de NLP e extração (`foxcape`, `trafilatura`, `newspaper4k`, `readability-lxml`) estarão devidamente instaladas no ambiente Python.
|