# 🧠 TextNLPClassifierApp [![Python Version](https://img.shields.io/badge/Python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) [![Code Style](https://img.shields.io/badge/Code%20Style-Ruff-black.svg)](https://github.com/astral-sh/ruff) [![Type Checked](https://img.shields.io/badge/Type%20Check-Mypy-blue.svg)](https://mypy-lang.org/) [![Tests](https://img.shields.io/badge/Tests-Pytest%20(100%25%20Passing)-brightgreen.svg)](tests/) > Plataforma modular em Python para **Classificação Multilíngue de Inerência de Entidades (NLP/LLM/ECP)**, **Extração Inteligente de Manchetes (Google News RSS & Foxcape)**, **Extração Multimotor de Artigos (Trafilatura, Newspaper4k, Readability)** e **Seleção Determinística de Conteúdo por Consenso Textual ($F_1$ Shingles)**. --- ## 📑 Tabela de Conteúdos - [Visão Geral](#-visão-geral) - [Instalação e Setup](#-instalação-e-setup) - [1. Classificador de Conteúdo e Inerência (NLP / LLM / ECP)](#1--classificador-de-conteúdo-e-inerência-nlp--llm--ecp) - [O que é e Como Funciona](#o-que-é-e-como-funciona) - [Arquitetura de Classificação em 3 Tiers](#arquitetura-de-classificação-em-3-tiers) - [Categorias de Decisão](#categorias-de-decisão) - [Formato do ECP Snapshot e Markdown](#formato-do-ecp-snapshot-e-markdown) - [Exemplos de Uso CLI](#exemplos-de-uso-cli) - [2. Extrator de Manchetes do Google News](#2--extrator-de-manchetes-do-google-news) - [O que é e Como Funciona](#o-que-é-e-como-funciona-1) - [Diferenciais Técnicos](#diferenciais-técnicos) - [Argumentos e Flags de Linha de Comando](#argumentos-e-flags-de-linha-de-comando) - [Exemplos Práticos de Uso](#exemplos-práticos-de-uso) - [3. Extrator e Parser Multimotor de Artigos](#3--extrator-e-parser-multimotor-de-artigos) - [Visão Geral e Tríplice Extração](#visão-geral-e-tríplice-extração) - [Argumentos e Flags CLI](#argumentos-e-flags-cli) - [Exemplos de Uso](#exemplos-de-uso) - [4. Seletor Determinístico de Conteúdo de Artigos](#4--seletor-determinístico-de-conteúdo-de-artigos) - [Visão Geral e Algoritmo de Consenso ($F_1$)](#visão-geral-e-algoritmo-de-consenso-f_1) - [Pipeline de Normalização e Shingles](#pipeline-de-normalização-e-shingles) - [Critérios de Desempate Técnico e Resiliência](#critérios-de-desempate-técnico-e-resiliência) - [Argumentos e Flags CLI](#argumentos-e-flags-cli-1) - [Exemplos Práticos de Uso](#exemplos-práticos-de-uso-1) - [Estrutura do Projeto](#-estrutura-do-projeto) - [Testes e Qualidade de Código](#-testes-e-qualidade-de-código) - [Licença](#-licença) --- ## 🌟 Visão Geral O **TextNLPClassifierApp** reúne um ecossistema completo de ferramentas de engenharia de dados e processamento de linguagem natural: 1. **`classify.py`**: Motor de classificação semântica e contextual que determina o grau de aderência e inerência de um documento Markdown em relação a uma entidade alvo descrita em um **ECP Snapshot (Entity Context Profile)**. 2. **`scripts/extract_google_news.py`**: Extrator de notícias por palavra-chave, idioma e região geográfica utilizando navegação stealth **Foxcape** (headless), decodificação paralela de URLs para links reais e feedback em tempo real. 3. **`scripts/extract_article_contents.py`**: Extrator e parser de artigos multimotor com navegação stealth Foxcape headless e extração simultânea via **Trafilatura**, **Newspaper4k** (NLP) e **Readability**, consolidando texto higienizado, autores, datas, imagens e resumos. 4. **`scripts/select_article_extractor.py`**: Motor determinístico de seleção de extratores que avalia as saídas dos três motores, aplica normalização em memória, calcula métricas de consenso de shingles (5-tokens) com pontuação $F_1$, desempata tecnicamente ($\le 0.03$) favorecendo menor concisão/ruído e enriquece os dados de forma não-destrutiva e atômica. --- ## ⚙️ Instalação e Setup ### 1. Clonar o Repositório e Criar Ambiente Virtual ```bash git clone cd TextNLPClassifierApp python -m venv .venv # Windows (PowerShell) .venv\Scripts\Activate.ps1 # Linux/macOS source .venv/bin/activate ``` ### 2. Instalar Dependências ```bash pip install -r requirements.txt ``` ### 3. Baixar Binários do Navegador Stealth (Camoufox) O motor **Foxcape** utiliza o navegador customizado Camoufox para evasão avançada de fingerprinting: ```bash python -m camoufox fetch ``` --- ## 1. 🧠 Classificador de Conteúdo e Inerência (NLP / LLM / ECP) ### O que é e Como Funciona O classificador avalia se um texto em Markdown trata centralmente, contextualmente ou apenas de forma superficial de uma determinada entidade alvo (ex: empresa, figura pública, clube, conceito). A entidade é descrita através de um **ECP Snapshot (Entity Context Profile)** contendo nomes canônicos, apelidos (*aliases*), âncoras temáticas, âncoras negativas e entidades de contexto relacional. O sistema suporta nativamente **6 idiomas**: Português (`pt`), Inglês (`en`), Espanhol (`es`), Francês (`fr`), Alemão (`de`) e Italiano (`it`). ### Arquitetura de Classificação em 3 Tiers ```mermaid flowchart TD MD[Markdown Content] --> Pre[Pré-processamento & Detecção de Idioma] ECP[ECP Snapshot JSON] --> Pre Pre --> T1[Tier 1: Determinístico & Regras NLP] T1 -- Alta Confiança --> Decision[Decisão Final JSON] T1 -- Score Intermediário / Ambíguo --> T2{Tier 2 Habilitado?} T2 -- Sim --> Emb[Tier 2: Similaridade Semântica / Embeddings] T2 -- Não --> Decision Emb -- Inconclusivo --> T3{Tier 3 Habilitado?} T3 -- Sim --> LLM[Tier 3: LLM Inherence Adapter] T3 -- Não --> Decision LLM --> Decision ``` * **Tier 1 (Determinístico / NLP Leve)**: Análise de frequência de termos, detecção de âncoras temáticas no primeiro terço do documento, contagem de aliases e penalização por âncoras negativas. * **Tier 2 (Vetorial / Embeddings)** *(Opcional: `--enable-embeddings`)*: Projeção vetorial e cálculo de cosseno entre o perfil da entidade e os parágrafos do documento. * **Tier 3 (LLM Fallback)** *(Opcional: `--enable-llm`)*: Consulta a modelo de linguagem para desambiguação de casos limiares e sutilezas semânticas. ### Categorias de Decisão | Categoria | Descrição | | :--- | :--- | | `DIRECT_INHERENT` | O conteúdo é centrado e focado diretamente na entidade alvo. | | `CONTEXTUAL_INHERENT` | A entidade é relevante no contexto da discussão, mesmo dividindo foco com outros temas. | | `TANGENTIAL` | A entidade é mencionada apenas de passagem ou em listas ilustrativas. | | `NOT_RELATED` | O conteúdo não possui relação substantiva com a entidade alvo. | ### Formato do ECP Snapshot e Markdown #### Exemplo de ECP Snapshot (`ecp_sample.json`): ```json { "target_entity_id": "ent_river_plate", "target_name": "River Plate", "aliases": ["Club Atlético River Plate", "Millonario", "El Más Grande"], "domain": "sports/football", "anchors": ["Monumental", "Copa Libertadores", "Marcelo Gallardo", "Copa Sudamericana"], "negative_anchors": ["Boca Juniors vitória", "Flamengo campeão"], "graph_version": "1.0.0", "related_entities": [ { "entity_id": "ent_gallardo", "name": "Marcelo Gallardo", "relation_type": "manager", "weight": 0.85, "aliases": ["Muñeco"] } ] } ``` ### Exemplos de Uso CLI ```bash # Classificação Determinística padrão (Tier 1) python classify.py --ecp tests/fixtures/sample_ecp.json --content tests/fixtures/sample_article.md # Salvar resultado em arquivo JSON formatado python classify.py --ecp ecp.json --content artigo.md --output out/resultado_classificacao.json # Habilitar camadas adicionais (Embeddings e LLM) python classify.py --ecp ecp.json --content artigo.md --enable-embeddings --enable-llm ``` --- ## 2. 📰 Extrator de Manchetes do Google News ### O que é e Como Funciona O script [`scripts/extract_google_news.py`](scripts/extract_google_news.py) é um extrator CLI autônomo projetado para consultar o feed RSS do Google News com máxima velocidade, resiliência e integridade de dados. ### Diferenciais Técnicos * 🛡️ **Foxcape Headless Anti-Bot**: Utiliza o motor `foxcape` com `FoxcapeConfig(headless=True)` e Camoufox para evitar bloqueios 429/403 e captchas em segundo plano sem abrir navegadores visuais. * 🔗 **Resolução Automática de URLs Reais**: Decodifica em paralelo (`ThreadPoolExecutor`) as URLs intermediárias do Google News (`news.google.com/rss/articles/...`) entregando diretamente o link final do portal de notícia (*Olé, TyC Sports, ge, ESPN, BBC, etc.*). * 🧹 **Higienização de HTML**: Sanitiza resumos e descrições, eliminando tags HTML residuais (`
    `, `
  1. `, ``, ``). * 📊 **Logging Informativo no Terminal**: Emite o status passo a passo no canal `stderr` sem poluir a saída JSON em `stdout` (100% compatível com pipes e `jq`). * 🌎 **Mapeamento de Idioma & Região**: Mapeia automaticamente códigos regionais (`pt` → `BR:pt-BR`, `es` → `AR:es-419`, `en` → `US:en-US`) permitindo customização com `--locale`. ### Argumentos e Flags de Linha de Comando | Flag | Tipo | Obrigatório | Padrão | Descrição | | :--- | :--- | :--- | :--- | :--- | | `-q`, `--query`, `--keyword` | `str` | **Sim** | — | Termo de pesquisa (ex: `"River Plate"`). | | `-l`, `--lang`, `--language` | `str` | Não | `"pt"` | Código do idioma (`pt`, `es`, `en`, `de`, `fr`, `it`). | | `--locale`, `--country` | `str` | Não | `None` | País/região editorial (`BR`, `AR`, `MX`, `US`, `GB`, `ES`). | | `-p`, `--max-pages` | `int` | Não | `1` | Quantidade de páginas (1 a 10; cada página traz 10 itens). | | `-o`, `--output` | `str` | Não | `None` | Caminho para salvar o arquivo JSON. | | `--pretty` | `flag` | Não | `False` | Formata o JSON no terminal com indentação de 2 espaços. | | `--no-resolve-urls` | `flag` | Não | `False` | Desativa a resolução e mantém as URLs brutas do Google News. | | `-s`, `--silent`, `--quiet` | `flag` | Não | `False` | Suprime os logs de progresso no `stderr`. | ### Exemplos Práticos de Uso ```bash # River Plate (Argentina / Espanhol / 2 Páginas / Salvar em Arquivo) python scripts/extract_google_news.py -q "River Plate" -l es --locale AR -p 2 -o out/river_plate.json # Cruzeiro (Brasil / Português / Formatado no Terminal) python scripts/extract_google_news.py --query "Cruzeiro" --lang pt --locale BR --pretty # Filtragem com jq em modo silencioso python scripts/extract_google_news.py -q "inteligência artificial" -s | jq '.items[].url' ``` --- ## 3. 📄 Extrator e Parser Multimotor de Artigos ### Visão Geral e Tríplice Extração O script [`scripts/extract_article_contents.py`](scripts/extract_article_contents.py) lê os arquivos JSON gerados pelo extrator do Google News (ou qualquer lista contendo `items` com `url`), acessa cada página via **Foxcape** em modo stealth headless (reutilizando uma única sessão de navegador ativa com espera do evento `domcontentloaded`), e executa simultaneamente 3 motores especializados de extração: 1. **Trafilatura**: Texto principal higienizado, autores, data de publicação, categorias, tags, URL canônica e payload estruturado nativo. 2. **Newspaper4k**: Artigo completo, autores, imagens (`top_image` e galeria), resumo automático e palavras-chave (*keywords*) extraídas por NLP nativo. 3. **Readability (`readability-lxml`)**: Miolo limpo em HTML sem anúncios ou scripts supérfluos, títulos e texto puro formatado. O JSON final consolidado é salvo em `out/` com descarte de strings HTML brutas para manter o arquivo leve e veloz. ### Argumentos e Flags CLI | Parâmetro | Tipo | Padrão | Descrição | |---|---|---|---| | `-i, --input` | Caminho (obrigatório) | — | Arquivo JSON de busca de notícias (ex: `out/river_plate.json`). | | `-o, --output` | Caminho (opcional) | `_extracted.json` | Arquivo JSON de destino consolidado. | | `-l, --limit` | Inteiro (opcional) | Todos | Limita a quantidade máxima de notícias processadas. | | `--lang, --language` | String (opcional) | Do JSON / `en` | Sobrescreve o código de idioma para o NLP do Newspaper4k (ex: `pt`, `es`, `en`). | | `-t, --timeout` | Inteiro (opcional) | `30` | Timeout em segundos por página no Foxcape. | | `-s, --silent` | Flag booleana | `False` | Suprime logs informativos de progresso no `stderr`. | ### Exemplos de Uso ```bash # Extração Completa Automática (gera out/river_plate_extracted.json) python scripts/extract_article_contents.py -i out/river_plate.json # Amostragem Rápida (Limit 2 Notícias) python scripts/extract_article_contents.py -i out/river_plate.json --limit 2 # Destino Customizado e Timeout Ajustado python scripts/extract_article_contents.py -i out/petrobras_result.json -o out/petrobras_full.json --timeout 45 ``` --- ## 4. 🎯 Seletor Determinístico de Conteúdo de Artigos ### Visão Geral e Algoritmo de Consenso ($F_1$) O script [`scripts/select_article_extractor.py`](scripts/select_article_extractor.py) é uma ferramenta autônoma, leve e 100% determinística (baseada exclusivamente na biblioteca padrão do Python) que resolve o problema de divergência entre múltiplos extratores de texto. O motor analisa as saídas dos candidatos ativos (`trafilatura`, `newspaper4k` e `readability`), compara a sobreposição de conteúdo através de **shingles de 5 tokens** e calcula o índice de concordância mútua via pontuação $F_1$: $$\text{Coverage} = \frac{|\text{Shingles do Candidato} \cap \text{Consenso}|}{|\text{Consenso}|}$$ $$\text{Support} = \frac{|\text{Shingles do Candidato} \cap \text{Consenso}|}{|\text{Shingles do Candidato}|}$$ $$F_1 = \frac{2 \times \text{Coverage} \times \text{Support}}{\text{Coverage} + \text{Support}}$$ ```mermaid flowchart TD Art[Artigo com Trafilatura, Newspaper4k e Readability] --> Class[Classificação de Viabilidade: Usable, Degraded, Unavailable] Class --> Set[Formação do Conjunto Ativo] Set --> Norm[Normalização NFKC & Shingles de 5 Tokens] Norm --> Metric[Cálculo de Consenso & Métricas F1] Metric --> Check{Existe Consenso >= 2?} Check -- Sim --> TopScore[Avaliação do Top Score] TopScore --> TiePool{Empate Técnico <= 0.03?} TiePool -- Sim --> SmallestShingle[Menor Quantidade de Shingles / Menos Ruído] SmallestShingle --> Winner[Extrator Selecionado] TiePool -- Não --> HighestScore[Maior Pontuação F1] HighestScore --> Winner Check -- Não --> ZeroCons[Desempate Sem Consenso: Mediana / Max / Prioridade] ZeroCons --> Winner ``` ### Pipeline de Normalização e Shingles A normalização ocorre em memória exclusivamente para fins comparativos: 1. **Decodificação de entidades HTML** (`html.unescape`). 2. **Descarte de imagens Markdown** (`![alt](url)`) para evitar que descrições de imagens criem falsos consensos com legendas. 3. **Preservação de links Markdown** (`[texto](url)` $\to$ `texto`). 4. **Remoção de tags HTML** preservando espaçamento entre palavras adjacentes. 5. **Normalização Unicode NFKC** e conversão para minúsculas. 6. **Colapso de espaços em branco**. 7. **Tokenização Unicode alfanumérica** com descarte de pontuações. 8. **Geração de Shingles**: Janela deslizante de 5 tokens (ou tupla única para textos curtos de 1 a 4 tokens). ### Critérios de Desempate Técnico e Resiliência * **Empate Técnico ($\le 0.03$)**: Quando dois ou mais extratores atingem pontuações com diferença $\le 0.03$, o algoritmo seleciona aquele com **menor quantidade de shingles** (penalizando *boilerplate*, cabeçalhos ou menus excedentes). * **Desempate Hierárquico Estrito**: Em caso de empate absoluto de pontuação e tamanho, aplica-se a hierarquia fixa: $$\text{newspaper4k} > \text{readability} > \text{trafilatura}$$ * **Cenários de 0 Consenso**: * 3 candidatos ativos $\to$ seleciona o de **tamanho mediano de shingles**. * 2 candidatos ativos $\to$ seleciona o de **maior tamanho de shingles**. * 1 candidato ativo $\to$ seleciona o único utilizável. * Todos indisponíveis $\to$ *fallback* obrigatório em `newspaper4k`. * **Gravação Atômica e Não-Destrutiva**: Criação de arquivo temporário com substituição atômica (`os.replace`), preservando 100% dos dados pré-existentes, ordem de artigos e propriedades originais. ### Argumentos e Flags CLI | Parâmetro | Tipo | Padrão | Descrição | |---|---|---|---| | `input_file` | Posicional (obrigatório) | — | Caminho para o arquivo JSON contendo a coleção `articles`. | | `-o, --output` | Caminho (opcional) | `_selected.json` | Caminho do arquivo JSON de destino. | | `--indent` | Inteiro (opcional) | `2` | Espaços de indentação do JSON (`0` para compacto). | | `-v, --verbose` | Flag booleana | `False` | Emite no `stderr` os detalhes de pontuação, ativos e regra de escolha por artigo. | ### Exemplos Práticos de Uso #### 1. Execução Padrão Automática ```bash python scripts/select_article_extractor.py out/river_plate_extracted.json # Gera automaticamente out/river_plate_extracted_selected.json ``` #### 2. Execução com Modo Verboso ```bash python scripts/select_article_extractor.py out/river_plate_extracted.json --verbose ``` * **Saída no Terminal**: ```text [Artigo #001] Extrator: newspaper4k | Motivo: technical_tie_smallest_shingles | Ativos: 3 | Consenso: 1048 [Artigo #002] Extrator: newspaper4k | Motivo: highest_score | Ativos: 3 | Consenso: 333 [Artigo #003] Extrator: readability | Motivo: highest_score | Ativos: 3 | Consenso: 778 ... { "status": "success", "input_file": "out/river_plate_extracted.json", "output_file": "out/river_plate_extracted_selected.json", "total_articles": 20, "processed_count": 20, "distribution": { "newspaper4k": 9, "readability": 9, "trafilatura": 2 } } ``` #### 3. Uso Programático como Módulo Python ```python from scripts.select_article_extractor import select_article_extractor article_data = { "trafilatura": {"text": "River Plate venceu ontem por 3-0.", "error": None}, "newspaper4k": {"text": "River Plate venceu ontem por 3-0 no Monumental.", "error": None}, "readability": {"cleaned_text": "River Plate venceu ontem por 3-0.", "error": None}, } result = select_article_extractor(article_data) print("Extrator Selecionado:", result.selected_extractor.value) print("Motivo:", result.selection_reason) ``` --- ## 📁 Estrutura do Projeto ```text TextNLPClassifierApp/ ├── classify.py # CLI principal do Classificador de Inerência ├── scripts/ │ ├── __init__.py # Pacote utilitário de scripts │ ├── extract_google_news.py # CLI de Extração de Manchetes do Google News │ ├── extract_article_contents.py # CLI de Extração e Parsing Multimotor de Artigos │ └── select_article_extractor.py # CLI de Seleção Determinística de Extrator ├── src/ # Módulos centrais do classificador │ ├── classifier.py # Orquestrador de classificação (Tier 1, 2, 3) │ ├── models.py # Modelos de dados e esquemas (ECPSnapshot, Decision) │ ├── preprocessor.py # Normalização de texto e detecção de idioma │ └── adapters/ # Adaptadores opcionais de Embeddings e LLM ├── specs/ # Especificações e planos arquiteturais (Speckit) │ ├── 001-multilingual-entity-classifier/ │ ├── 002-google-news-extractor/ │ ├── 003-article-content-extractor/ │ └── 004-deterministic-content-selection/ # Specs da seleção determinística ├── tests/ # Suíte de testes automatizados │ ├── test_classifier.py │ ├── test_extract_google_news.py │ ├── test_extract_article_contents.py │ └── test_select_article_extractor.py # Testes do seletor determinístico ├── requirements.txt # Dependências do projeto ├── pyproject.toml # Configurações de ferramentas (pytest, ruff, mypy) └── README.md # Documentação principal ``` --- ## 🧪 Testes e Qualidade de Código O repositório possui **120 testes automatizados** com 100% de aprovação cobrindo testes unitários, de regressão, de integração e testes End-to-End (E2E) via CLI subprocess: ```bash # Executar toda a suíte de testes do projeto (120 testes) pytest -v # Executar especificamente os testes do Seletor Determinístico pytest tests/test_select_article_extractor.py -v # Executar os testes do Extrator de Conteúdo Multimotor pytest tests/test_extract_article_contents.py -v # Executar os testes do Extrator do Google News pytest tests/test_extract_google_news.py -v # Validação e correção automática de formatação com Ruff ruff check --fix . ruff format . # Verificação estática de tipos com Mypy mypy scripts/ src/ ``` --- ## 📄 Licença Este projeto está licenciado sob os termos da licença **MIT**. Consulte o arquivo `LICENSE` para mais detalhes.