feat: add deterministic content extractor selector engine with F1 consensus
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
[](https://mypy-lang.org/)
|
||||
[-brightgreen.svg)](tests/)
|
||||
|
||||
> Plataforma modular em Python para **Classificação Multilíngue de Inerência de Entidades (NLP/LLM/ECP)** e **Extração Inteligente de Manchetes de Notícias com Evasão Anti-Bot (Google News RSS & Foxcape)**.
|
||||
> 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)**.
|
||||
|
||||
---
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
- [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)
|
||||
@@ -37,11 +43,12 @@
|
||||
|
||||
## 🌟 Visão Geral
|
||||
|
||||
O **TextNLPClassifierApp** reúne ferramentas de engenharia de dados e processamento de linguagem natural:
|
||||
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 definida 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 que utiliza o motor stealth **Foxcape** (em modo headless), decodificação paralela de URLs para os links reais dos portais de notícias 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 combinada via **Trafilatura**, **Newspaper4k** (NLP) e **Readability**, consolidando texto higienizado, autores, datas, imagens e resumos em JSON estruturado.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -157,7 +164,7 @@ python classify.py --ecp ecp.json --content artigo.md --enable-embeddings --enab
|
||||
|
||||
### O que é e Como Funciona
|
||||
|
||||
O script [`scripts/extract_google_news.py`](file:///c:/Users/aferr/Projects/AFTech/DunaMedia/TextNLPClassifierApp/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.
|
||||
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
|
||||
|
||||
@@ -182,42 +189,24 @@ O script [`scripts/extract_google_news.py`](file:///c:/Users/aferr/Projects/AFTe
|
||||
|
||||
### Exemplos Práticos de Uso
|
||||
|
||||
#### 1. River Plate (Argentina / Espanhol / 2 Páginas / Salvar em Arquivo)
|
||||
```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
|
||||
```
|
||||
* **Saída no Terminal**:
|
||||
```text
|
||||
[INFO] 🔍 Consultando Google News: 'River Plate' (idioma: es, locale: AR, max_pages: 2)...
|
||||
[INFO] 📥 Feed RSS recebido (162117 bytes).
|
||||
[INFO] 📰 20 artigos extraídos do feed XML.
|
||||
[INFO] 🔗 Decodificando 20 URLs do Google News para os portais reais...
|
||||
[INFO] ✅ 20/20 URLs resolvidas com sucesso para os domínios de origem.
|
||||
[INFO] 💾 Arquivo salvo com sucesso: 'out/river_plate.json' (20 notícias).
|
||||
```
|
||||
|
||||
#### 2. Cruzeiro (Brasil / Português / Formatado no Terminal)
|
||||
```bash
|
||||
# Cruzeiro (Brasil / Português / Formatado no Terminal)
|
||||
python scripts/extract_google_news.py --query "Cruzeiro" --lang pt --locale BR --pretty
|
||||
```
|
||||
|
||||
#### 3. Fórmula 1 (Inglaterra / Inglês)
|
||||
```bash
|
||||
python scripts/extract_google_news.py --query "Formula 1" --lang en --locale GB --pretty
|
||||
```
|
||||
|
||||
#### 4. Filtragem com `jq` em Modo Silencioso
|
||||
```bash
|
||||
# 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
|
||||
## 3. 📄 Extrator e Parser Multimotor de Artigos
|
||||
|
||||
### Visão Geral e Tríplice Extração
|
||||
|
||||
O script `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:
|
||||
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.
|
||||
@@ -238,20 +227,128 @@ O JSON final consolidado é salvo em `out/` com descarte de strings HTML brutas
|
||||
|
||||
### Exemplos de Uso
|
||||
|
||||
#### 1. Extração Completa Automática
|
||||
```bash
|
||||
# Extração Completa Automática (gera out/river_plate_extracted.json)
|
||||
python scripts/extract_article_contents.py -i out/river_plate.json
|
||||
# Gera automaticamente out/river_plate_extracted.json
|
||||
```
|
||||
|
||||
#### 2. Amostragem Rápida (Limit 2 Notícias)
|
||||
```bash
|
||||
# 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
|
||||
```
|
||||
|
||||
#### 3. Destino Customizado e Timeout Ajustado
|
||||
---
|
||||
|
||||
## 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** (``) 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) | `<input_stem>_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/extract_article_contents.py -i out/petrobras_result.json -o out/petrobras_full.json --timeout 45
|
||||
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)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -264,7 +361,8 @@ TextNLPClassifierApp/
|
||||
├── 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
|
||||
│ ├── 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)
|
||||
@@ -273,11 +371,13 @@ TextNLPClassifierApp/
|
||||
├── specs/ # Especificações e planos arquiteturais (Speckit)
|
||||
│ ├── 001-multilingual-entity-classifier/
|
||||
│ ├── 002-google-news-extractor/
|
||||
│ └── 003-article-content-extractor/ # Specs da feature de extração multimotor
|
||||
│ ├── 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 # Testes do extrator de conteúdo
|
||||
│ ├── 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
|
||||
@@ -287,13 +387,19 @@ TextNLPClassifierApp/
|
||||
|
||||
## 🧪 Testes e Qualidade de Código
|
||||
|
||||
O repositório possui cobertura com testes unitários, testes de integração e testes End-to-End (E2E) com requisição de rede ao vivo:
|
||||
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 todos os testes do projeto
|
||||
# Executar toda a suíte de testes do projeto (120 testes)
|
||||
pytest -v
|
||||
|
||||
# Executar especificamente os testes do Extrator de Notícias
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user