Extract sections and subsections from academic PDFs
Project description
SectionMiner
Biblioteca Python para extrair secoes e subsecoes de PDFs academicos com heuristicas de layout + consolidacao por LLM.
Suporta dois backends de extracao de texto:
pymupdf(local, via spans/layout do PDF)gemini(OCR/extracao via Google Gemini)
Em ambos os casos, a consolidacao final da arvore de secoes ainda e feita por OpenAI no estado atual do projeto.
Visao geral
O fluxo do projeto e:
- Ler spans do PDF com fonte/tamanho (
PyMuPDF). - Detectar titulos provaveis (heading).
- Montar secoes com intervalos de caracteres (
start,end). - Enviar um indice de headings para LLM consolidar a arvore final.
- Buscar texto de uma secao pelo titulo.
Requisitos
- Python 3.10+
- Dependencias em
requirements.txt - SDK Gemini:
google-genai(substituigoogle-generativeai) - Chaves de API:
OPENAI_API_KEY(obrigatoria para consolidacao LLM)GEMINI_API_KEY(obrigatoria quando usarextraction_backend="gemini")
Instalacao (modo biblioteca)
git clone https://github.com/ehodiogo/SectionMiner.git
cd SectionMiner
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .
Depois disso, voce pode importar com from sectionminer import SectionMiner em qualquer script do ambiente.
Tambem instala a CLI sectionminer.
Instalacao direta do PyPI (apos publicacao):
pip install sectionminer
Configuracao das chaves
Os scripts usam python-decouple para ler variaveis de ambiente.
Opcao 1 (rapida, terminal atual):
export OPENAI_API_KEY="sua-chave-aqui"
export GEMINI_API_KEY="sua-chave-aqui"
Opcao 2 (.env na raiz do projeto):
OPENAI_API_KEY=sua-chave-aqui
GEMINI_API_KEY=sua-chave-aqui
Se voce nao for usar Gemini, pode omitir GEMINI_API_KEY.
Backends de extracao
pymupdf(padrao): extrai texto a partir do layout interno do PDF.gemini: envia o PDF para o Gemini e usa o texto retornado para o pipeline.
Exemplo de construtor com Gemini:
miner = SectionMiner(
"files/Artigo_Provatis.pdf",
api_key=openai_api_key,
extraction_backend="gemini",
gemini_api_key=gemini_api_key,
gemini_model="gemini-2.5-flash-lite",
)
Fluxo principal (o que o test.py faz)
Arquivo: test.py (exemplo usando a biblioteca)
- Le a chave com
config("OPENAI_API_KEY"). - Cria
SectionMiner("files/Artigo_Provatis.pdf", api_key). - Executa
extract_structure(return_tokens=True)para obter:- arvore de secoes/subsecoes
- uso de tokens/custo
- Consulta uma secao (ex.:
introducao) por offsets comget_section_start_and_end_chars. - Recupera texto completo com
get_full_text()e fatia por[start:end]. - Reexecuta pipeline manual (
extract_blocks,build_full_text,build_sections) e imprime secoes comget_sections()eget_section_text(). - Fecha o PDF com
close()nofinally.
Executar:
python3 test.py
Exemplo alternativo (arquivo dedicado em examples/):
python3 examples/basic_usage.py
Fluxo com Gemini (o que o test_gemini.py faz)
Arquivo: test_gemini.py
- Le
OPENAI_API_KEYeGEMINI_API_KEY. - Cria
SectionMiner(..., extraction_backend="gemini", gemini_api_key=...). - Executa
extract_structure(return_tokens=True)e imprimeusage. - Consulta secao por offsets com
get_section_start_and_end_chars. - Lista secoes com
get_sections()e imprime comget_section_text().
Executar:
python3 test_gemini.py
CLI inicial
Comando raiz:
sectionminer --help
Observacao importante: os comandos extract e section-text usam backend pymupdf (ou --heuristic-only).
Para usar extraction_backend="gemini" via CLI, use sectionminer runserver --extraction-backend gemini.
Extrair estrutura (com LLM):
sectionminer extract files/Artigo_Provatis.pdf --tokens --pretty
Extrair estrutura e mostrar custo total da chamada:
sectionminer extract files/Artigo_Provatis.pdf --show-cost --pretty
Extrair estrutura heuristica (sem LLM/OpenAI):
sectionminer extract files/Artigo_Provatis.pdf --heuristic-only --pretty
Salvar saida JSON em arquivo:
sectionminer extract files/Artigo_Provatis.pdf --heuristic-only --output out.json --pretty
Buscar texto de secao por titulo:
sectionminer section-text files/Artigo_Provatis.pdf "introducao"
Buscar texto e mostrar custo total da chamada:
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --show-cost
Buscar texto de secao sem LLM (heuristica):
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --heuristic-only
Observacao: --show-cost imprime o resumo de custo no stderr (nao polui JSON de saida).
API web visual (FastAPI)
Agora a CLI tambem sobe uma API com interface visual para upload do PDF, lista de secoes e destaque no documento. O painel mostra, em tempo real: backend utilizado (PyMuPDF ou Gemini), paginas, quantidade de secoes, tokens e custo total. O arquivo enviado e processado em arquivo temporario e removido ao final da extracao (sem acumular PDFs em disco).
Subir servidor local:
sectionminer runserver --host 127.0.0.1 --port 8000 --reload
Com backend Gemini para extracao:
sectionminer runserver --extraction-backend gemini --gemini-model gemini-2.0-flash
Modo sem LLM (somente heuristica local):
sectionminer runserver --heuristic-only
Depois abra no navegador:
http://127.0.0.1:8000
Highlight visual no PDF
Quando voce seleciona uma secao no painel da direita, a area correspondente no PDF e automaticamente destacada com uma caixa verde. O destaque mostra exatamente onde cada secao foi encontrada no documento, com suporte para multiplos trechos em diferentes paginas.
A funcionalidade requer que o PDF tenha bom suporte de spans/layout (PyMuPDF extrai os dados). Para PDFs com layout complexo ou OCR, pode haver algumas imprecisoes.
Para debugar highlights, abra o console do navegador (F12 > Console) enquanto seleciona secoes - vera logs detalhados do calculo das posicoes.
Endpoints da API
GET /: UI visual com PDF a esquerda e secoes extraidas a direita.POST /api/extract: upload de arquivo PDF (multipart/form-data, campofile).GET /api/files/{job_id}: stream do PDF armazenado temporariamente para renderizacao no viewer.
Smoke test rapido (sem depender de chave de API):
python3 examples/api_smoke_test.py
Resposta de POST /api/extract (resumo):
{
"job_id": "uuid",
"filename": "meu_arquivo.pdf",
"pdf_url": "/api/files/uuid",
"extraction_backend": "pymupdf",
"heuristic_only": false,
"pages": 10,
"metrics": {
"pages": 10,
"sections": 24,
"prompt_tokens": 1800,
"completion_tokens": 450,
"total_tokens": 2250,
"cost_usd": 0.00046
},
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
"cost_usd": 0.0
},
"tree": {"title": "Document", "children": []},
"sections": [
{
"title": "1. Introducao",
"level": 1,
"start_char": 0,
"end_char": 1200,
"text": "...",
"locations": [
{"page": 0, "bbox": [72.0, 120.0, 380.0, 138.0], "text": "..."}
]
}
]
}
Exemplos de custo (extracao + obtencao dos textos)
Base de medicao local em 2026-03-21, com modelo gpt-4o-mini, usando os PDFs em files/.
files/Artigo_Provatis.pdf: 0.736 MB, 21 paginas- Extracao da estrutura: 2297 tokens,
US$ 0.00047505 - Obtencao dos textos das secoes no mesmo processo:
US$ 0.00adicional (usa offsets locais)
- Extracao da estrutura: 2297 tokens,
files/Artigo_Mae.pdf: 0.036 MB, 4 paginas- Extracao da estrutura: 356 tokens,
US$ 0.00005970 - Obtencao dos textos das secoes no mesmo processo:
US$ 0.00adicional
- Extracao da estrutura: 356 tokens,
Comando para reproduzir no seu ambiente:
sectionminer extract files/Artigo_Provatis.pdf --show-cost --pretty
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --show-cost
Funcoes principais da API (SectionMiner)
Arquivo: sectionminer/miner.py
-
extract_structure(return_tokens=False)- Pipeline completo (extracao, deteccao, merge com LLM).
- Retorna a arvore final; com
return_tokens=True, retorna(arvore, usage).
-
get_section_start_and_end_chars(title)- Retorna
(start, end)da secao localizada por titulo. - Bom para recortar diretamente em
get_full_text().
- Retorna
-
get_full_text()- Retorna o texto linear completo do PDF processado.
-
get_section_text(title)- Busca no tree consolidado e devolve o texto da secao.
-
get_sections()- Retorna lista de titulos detectados a partir das estruturas internas.
-
extract_blocks(),build_full_text(),build_sections()- Etapas internas do pipeline usadas no
test.pypara depuracao/inspecao.
- Etapas internas do pipeline usadas no
-
close()- Fecha o documento PDF aberto em memoria.
Exemplo minimo (mesma ideia do teste)
import json
from decouple import config
from sectionminer import SectionMiner
api_key = config("OPENAI_API_KEY")
miner = SectionMiner("files/Artigo_Provatis.pdf", api_key)
try:
structure, tokens = miner.extract_structure(return_tokens=True)
print(tokens)
print(json.dumps(structure, indent=2, ensure_ascii=False))
start, end = miner.get_section_start_and_end_chars("introducao")
if start is not None and end is not None:
print(miner.get_full_text()[start:end][:800])
print(miner.get_section_text("conclusao"))
finally:
miner.close()
Exemplo minimo com Gemini:
from decouple import config
from sectionminer import SectionMiner
openai_api_key = config("OPENAI_API_KEY")
gemini_api_key = config("GEMINI_API_KEY")
miner = SectionMiner(
"files/Artigo_Provatis.pdf",
api_key=openai_api_key,
extraction_backend="gemini",
gemini_api_key=gemini_api_key,
)
try:
structure, usage = miner.extract_structure(return_tokens=True)
print(usage)
print(structure.get("title"))
finally:
miner.close()
Estrutura do projeto
SectionMiner/
sectionminer/
__init__.py # API publica da biblioteca
miner.py # classe SectionMiner
client.py # cliente LLM e merge da arvore
prompts.py # prompt de consolidacao
server/ # FastAPI + UI (routes, static, templates)
base.py # compatibilidade com import legado
client.py # compatibilidade com import legado
prompts.py # compatibilidade com import legado
test.py # fluxo de uso principal
test_gemini.py # fluxo com backend Gemini
examples/api_smoke_test.py # teste rapido da API
examples/ # exemplos prontos de execucao
files/ # PDFs de exemplo
Problemas comuns
1) "Falha ao processar PDF: Invalid control character"
- O PDF contém caracteres de controle inválidos que quebram a serialização JSON.
- Solução: A versão atual ja sanitiza automaticamente caracteres de controle.
- Se o erro persistir, tente outro PDF ou valide o arquivo com um leitor de PDF.
2) "As secoes estao vindo quebradas"
- Revise filtros em
_is_noise_headinge_looks_like_headingemsectionminer/miner.py. - Ajuste threshold em
_detect_thresholdpara o padrao do seu PDF. - PDFs com layout irregular (duas colunas, rodape intrusivo, OCR ruim) tendem a piorar a deteccao.
3) Secao nao encontrada por titulo
- Tente variacao sem acento/caixa (a busca normaliza texto).
- Verifique os titulos retornados por
get_sections().
4) Erro de chave OpenAI
- Confirme
OPENAI_API_KEYno mesmo ambiente da execucao. - Se usar
.env, confirme que esta na raiz do projeto.
TODO (coisas a fazer)
- Criar testes automatizados para
detect_headings,build_sectionseget_section_text. - Adicionar modo sem LLM (somente heuristica local) para uso offline.
- Criar CLI inicial:
sectionminer extract arquivo.pdf --output out.json. - Expor parametros de heuristica por configuracao (threshold, filtros de ruido).
- Melhorar merge para manter apenas secoes/subsecoes validas (sem fragmentos quebrados).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sectionminer-0.1.4.tar.gz.
File metadata
- Download URL: sectionminer-0.1.4.tar.gz
- Upload date:
- Size: 26.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1364077754bb080548400aee7fc88da5dbca3d3b140eb9e1a1ffffa97fffb641
|
|
| MD5 |
07edc4fb8c10d07464996d198a9fdb5d
|
|
| BLAKE2b-256 |
4f5da6ed8f9dbf5c81985b7ef9c98c22d8a07ff1dc5b42eda6fc695f2e6a66d7
|
File details
Details for the file sectionminer-0.1.4-py3-none-any.whl.
File metadata
- Download URL: sectionminer-0.1.4-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9a40bd612535926287b3103505d74087f56749e0d5b42283907665d0be31fff
|
|
| MD5 |
d5b4f8f9e7cc3d8b929f1f161cea22e4
|
|
| BLAKE2b-256 |
d358d80b08ea41370906662499859ee915ebf8cda9562c2ba85c14328aefee3d
|