# CLI Contract & Routing Specification: `scripts/extract_article_contents.py` ## 1. Interface de Linha de Comando (CLI) O script `scripts/extract_article_contents.py` preserva 100% da interface CLI existente sem novos argumentos: ```bash python scripts/extract_article_contents.py \ -i, --input PATH # (Obrigatório) JSON de busca de notícias de entrada [-o, --output PATH] # (Opcional) Caminho do JSON de saída textual [-l, --limit N] # (Opcional) Limite máximo de artigos a processar [--lang, --language CODE] # (Opcional) Código do idioma para NLP (ex: pt, es, en) [-t, --timeout SEC] # (Opcional) Timeout em segundos para navegação (default: 30) [-s, --silent] # (Opcional) Suprime logs no stderr ``` --- ## 2. Regras de Resolução de Arquivos de Saída ### Caso A: Sem `-o/--output` (Padrão) Para uma entrada: `out/river_plate.json` - **Saída Textual**: `out/river_plate_extracted.json` - **Saída de Mídia**: `out/river_plate_media.json` (gerado obrigatoriamente; contém `{"articles": []}` se nenhuma mídia for identificada) ### Caso B: Com `-o/--output` customizado Para uma entrada: `out/noticias.json` com `-o out/processados/brasil_completo.json` - **Saída Textual**: `out/processados/brasil_completo.json` - **Saída de Mídia**: `out/processados/brasil_completo_media.json` (gerado obrigatoriamente; contém `{"articles": []}` se nenhuma mídia for identificada) Regra: - Diretório: mesmo diretório da saída textual informada. - Nome base (stem): mesmo stem da saída textual informada. - Sufixo: `_media`. - Extensão: `.json`. - Geração: O arquivo `*_media.json` MUST ser gerado em toda execução bem-sucedida do lote, sem omissão condicional. --- ## 3. Estrutura do Arquivo de Saída Textual (`*_extracted.json`) ```json { "source_file": "out/river_plate.json", "processed_at": "2026-08-24T20:30:00.000000+00:00", "total_articles": 8, "successful_articles": 7, "failed_articles": 1, "articles": [ { "input_meta": { "titulo": "Notícia textual completa", "url": "https://example.com/noticia-1", "subtitulo": "Subtítulo", "quando_publicado": "há 2 horas", "pagina": 1 }, "extraction_status": "success", "error_message": null, "crawled_url": "https://example.com/noticia-1", "page_title": "Título no DOM", "http_status": 200, "trafilatura": { "text": "...", "markdown": "...", "title": "..." }, "newspaper4k": { "text": "...", "summary": "...", "keywords": [] }, "readability": { "cleaned_text": "...", "cleaned_html": "..." } }, { "input_meta": { "titulo": "Notícia onde todos provedores falharam", "url": "https://example.com/falha" }, "classification_status": "failed", "error_message": "media classification providers unavailable", "crawled_url": "https://example.com/falha", "page_title": null, "http_status": null, "trafilatura": null, "newspaper4k": null, "readability": null } ] } ``` --- ## 4. Estrutura do Arquivo de Saída de Mídia (`*_media.json`) ```json { "articles": [ { "input_meta": { "titulo": "Vídeo dos melhores momentos do jogo", "url": "https://example.com/video-gols", "subtitulo": "Assista ao lance", "quando_publicado": "há 1 hora", "pagina": 1 }, "crawled_url": "https://example.com/video-gols", "page_title": "Vídeo dos Melhores Momentos", "http_status": 200, "content_type": "media", "media_type": "video" } ] } ``` --- ## 5. Códigos de Saída (Exit Codes) | Exit Code | Significado | |:---|:---| | `0` | Lote processado com sucesso (arquivos gravados) | | `1` | Arquivo de entrada inexistente ou erro de argumentos CLI | | `2` | Erro fatal não tratado | | `130` | Interrupção pelo usuário (`SIGINT` / Ctrl+C) |