Files

3.9 KiB

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:

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)

{
  "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)

{
  "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)