feat(extractor): add Google News headlines extractor with Foxcape headless and URL resolution

- Add standalone CLI script scripts/extract_google_news.py for Google News RSS scraping
- Integrate foxcape in headless mode as primary stealth anti-bot engine
- Implement parallel article URL resolution using googlenewsdecoder and ThreadPoolExecutor
- Support language and regional locale mapping (-l, --lang, --locale)
- Implement real-time progress logging in stderr and --silent flag
- Add unit, integration, and live E2E tests in tests/test_extract_google_news.py
- Add full SpecKit documentation (specs/002-google-news-extractor/)
- Create comprehensive README.md covering both NLP Classifier and Google News Extractor
This commit is contained in:
2026-08-20 11:50:16 -03:00
parent 67cc40f91a
commit 6e3d57619b
59 changed files with 16118 additions and 2160 deletions
+98
View File
@@ -0,0 +1,98 @@
# Feature Specification: Google News Headlines Extractor
**Feature Branch**: `002-google-news-extractor`
**Created**: 2026-08-20
**Status**: Implemented & Validated
**Input**: User description: "Extrator de manchetes do Google News de acordo com assunto, idioma, locale, com Foxcape headless, resolução de URLs reais e logging"
---
## Clarifications
### Session 2026-08-20
- Q: Como o extrator de notícias do Google News deve ser disponibilizado e consumido dentro do projeto? → A: Apenas Script CLI autônomo para execução direta via linha de comando no terminal (`scripts/extract_google_news.py`).
- Q: Qual biblioteca/mecanismo de requisição e raspagem deve ser utilizado no script CLI? → A: Biblioteca `foxcape` (https://pypi.org/project/foxcape/) em modo `headless=True` com proteção anti-bot e fingerprinting stealth.
- Q: Como lidar com as URLs intermediárias do Google News? → A: Resolução e decodificação automática das URLs intermediárias (`https://news.google.com/rss/articles/...`) para as URLs reais e finais dos portais de notícias via `googlenewsdecoder`.
- Q: Como acompanhar o progresso de extração no terminal? → A: Logs informativos em tempo real enviados exclusivamente para `sys.stderr`, mantendo `sys.stdout` limpo para pipes JSON e suportando a flag `-s / --silent`.
---
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Extração Básica de Notícias com URLs Finais Resolvidas (Priority: P1) 🌟 MVP
Como analista ou operador no terminal, quero fornecer um termo de busca e um idioma de interesse via linha de comando para obter rapidamente uma lista estruturada de manchetes recentes com as URLs finais reais dos veículos de imprensa (ex: *Olé, TyC Sports, ge, ESPN*).
**Why this priority**: É o valor fundamental do produto. Sem a capacidade de buscar, obter manchetes e fornecer os links diretos dos veículos, o extrator não cumpre sua função de pesquisa e ingestão.
**Independent Test**: Executar `python scripts/extract_google_news.py -q "River Plate" -l es --locale AR -p 1` e verificar se o JSON retornado contém notícias com URLs apontando para os domínios finais (`tycsports.com`, `ole.com.ar`, etc.).
**Acceptance Scenarios**:
1. **Given** um termo de busca válido e idioma, **When** o script for executado com o motor `foxcape` em modo headless, **Then** o sistema extrai o feed RSS e decodifica as URLs de cada artigo para os sites de origem.
2. **Given** um termo de busca sem notícias correspondentes, **When** o script for executado, **Then** o sistema retorna uma coleção vazia com código de saída 0.
---
### User Story 2 - Filtragem Regional e Edição Geográfica (Priority: P2)
Como usuário que monitora notícias em mercados específicos, quero definir a região/país geográfica (*locale*) além do idioma (por exemplo, espanhol da Argentina `--locale AR` vs. México `--locale MX`, ou inglês do Reino Unido `--locale GB` vs. Estados Unidos `--locale US`) para receber manchetes contextualmente relevantes àquele território.
**Why this priority**: Garante relevância e precisão geográfica para análises de mídia multinacionais e segmentadas.
**Independent Test**: Executar o script com idioma "es" e locale "AR" e verificar que portais e manchetes são da Argentina.
**Acceptance Scenarios**:
1. **Given** um termo de busca, idioma "es" e país/região "AR", **When** o script CLI for executado, **Then** as manchetes retornadas priorizam a edição e veículos argentinos.
2. **Given** um idioma informado sem país explícito (ex: "pt"), **When** o script for solicitado, **Then** o sistema aplica o mapeamento padrão correspondente (ex: Brasil / pt-BR).
---
### User Story 3 - Paginação, Exportação em Arquivo e Feedback Visual (Priority: P3)
Como operador de automação de dados, quero parametrizar o número de páginas de resultados (`--max-pages 1..10`), exportar direto para arquivo (`--output`) e acompanhar o progresso no terminal com mensagens descritivas de log.
**Why this priority**: Permite flexibilidade de uso em rotinas batch, pipelines de ETL e depuração interativa.
**Independent Test**: Executar com `-p 2 -o out/resultado.json` e verificar criação do arquivo com diretórios pais automáticos e logs em `stderr`.
**Acceptance Scenarios**:
1. **Given** uma execução CLI com `-p 2 -o out/teste.json`, **When** o processo é executado, **Then** o terminal exibe logs em `stderr` (`[INFO] 🔍 Consultando...`, `[INFO] 🔗 Decodificando...`, `[INFO] 💾 Arquivo salvo...`) e grava o JSON final com 20 itens.
2. **Given** o uso da flag `-s` ou `--silent`, **When** o script é executado, **Then** os logs em `stderr` são suprimidos.
---
## Edge Cases
- **Termo de busca vazio ou composto apenas por espaços**: O script rejeita a solicitação com mensagem de erro em `stderr` e código de saída 1.
- **Falha de conectividade ou bloqueio**: O Foxcape executa com evasões anti-bot em modo headless, com captura de exceções e emissão de erro em `stderr` com código de saída 2.
- **Resumo da notícia idêntico ao título**: Subtítulo redundante vira `None`.
- **Caminho de saída em pasta inexistente**: O script cria os diretórios pais automaticamente antes de salvar.
- **Falha na decodificação de URL específica**: Mantém a URL original como fallback gracioso sem abortar a execução dos demais itens.
---
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: O sistema DEVE ser disponibilizado como script CLI autônomo em `scripts/extract_google_news.py`.
- **FR-002**: O script DEVE utilizar a biblioteca `foxcape` com `FoxcapeConfig(headless=True)` como motor primário de requisição com proteção anti-bot.
- **FR-003**: O CLI DEVE aceitar argumento obrigatório para o termo de busca (`-q, --query, --keyword`).
- **FR-004**: O CLI DEVE suportar argumentos opcionais para código de idioma (`-l, --lang, --language`, padrão `pt`) e região/país (`--locale, --country`).
- **FR-005**: O sistema DEVE aplicar mapeamento padrão de região quando apenas o idioma for informado (`pt` → BR, `es` → AR, `en` → US, `de` → DE, `fr` → FR, `it` → IT).
- **FR-006**: O CLI DEVE permitir configurar a paginação lógica (`-p, --max-pages`, de 1 a 10 páginas / 10 a 100 itens).
- **FR-007**: O sistema DEVE extrair: título, URL, data de publicação, subtítulo higienizado de HTML e número da página.
- **FR-008**: O sistema DEVE resolver e decodificar automaticamente as URLs intermediárias do Google News para as URLs finais dos portais de notícias em paralelo.
- **FR-009**: O CLI DEVE emitir logs informativos de progresso em `sys.stderr` e suportar a flag `-s, --silent` para supressão.
- **FR-010**: O CLI DEVE suportar a flag `--no-resolve-urls` para obter as URLs brutas do feed RSS quando desejado.
- **FR-011**: O CLI DEVE suportar gravação em arquivo via `-o, --output` com criação de diretórios pais e formatação legível com `--pretty`.
---
## Success Criteria *(mandatory)*
- **SC-001**: Extração executada com sucesso utilizando Foxcape headless e alta taxa de entrega.
- **SC-002**: 100% das URLs de notícias decodificadas para os portais reais dos veículos quando a resolução de URLs estiver ativa.
- **SC-003**: 100% dos resumos/subtítulos livres de marcações HTML.
- **SC-004**: Formato JSON no `stdout` 100% compatível com utilitários como `jq` e pipelines shell.
- **SC-005**: 100% dos testes unitários e E2E aprovados no pytest.