Skip to main content

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:

  1. Ler spans do PDF com fonte/tamanho (PyMuPDF).
  2. Detectar titulos provaveis (heading).
  3. Montar secoes com intervalos de caracteres (start, end).
  4. Enviar um indice de headings para LLM consolidar a arvore final.
  5. Buscar texto de uma secao pelo titulo.

Requisitos

  • Python 3.10+
  • Dependencias em requirements.txt
  • Chaves de API:
    • OPENAI_API_KEY (obrigatoria para consolidacao LLM)
    • GEMINI_API_KEY (obrigatoria quando usar extraction_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)

  1. Le a chave com config("OPENAI_API_KEY").
  2. Cria SectionMiner("files/Artigo_Provatis.pdf", api_key).
  3. Executa extract_structure(return_tokens=True) para obter:
    • arvore de secoes/subsecoes
    • uso de tokens/custo
  4. Consulta uma secao (ex.: introducao) por offsets com get_section_start_and_end_chars.
  5. Recupera texto completo com get_full_text() e fatia por [start:end].
  6. Reexecuta pipeline manual (extract_blocks, build_full_text, build_sections) e imprime secoes com get_sections() e get_section_text().
  7. Fecha o PDF com close() no finally.

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

  1. Le OPENAI_API_KEY e GEMINI_API_KEY.
  2. Cria SectionMiner(..., extraction_backend="gemini", gemini_api_key=...).
  3. Executa extract_structure(return_tokens=True) e imprime usage.
  4. Consulta secao por offsets com get_section_start_and_end_chars.
  5. Lista secoes com get_sections() e imprime com get_section_text().

Executar:

python3 test_gemini.py

CLI inicial

Comando raiz:

sectionminer --help

Observacao importante: a CLI atual usa backend pymupdf (ou --heuristic-only) e nao expoe flag para extraction_backend="gemini" ainda. Para Gemini, use a API Python (test_gemini.py como referencia).

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).

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.00 adicional (usa offsets locais)
  • 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.00 adicional

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().
  • 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.py para depuracao/inspecao.
  • 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
  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/      # exemplos prontos de execucao
  files/         # PDFs de exemplo

Problemas comuns

1) "As secoes estao vindo quebradas"

  • Revise filtros em _is_noise_heading e _looks_like_heading em sectionminer/miner.py.
  • Ajuste threshold em _detect_threshold para o padrao do seu PDF.
  • PDFs com layout irregular (duas colunas, rodape intrusivo, OCR ruim) tendem a piorar a deteccao.

2) Secao nao encontrada por titulo

  • Tente variacao sem acento/caixa (a busca normaliza texto).
  • Verifique os titulos retornados por get_sections().

3) Erro de chave OpenAI

  • Confirme OPENAI_API_KEY no 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_sections e get_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).

Publicacao (preparo inicial)

O projeto ja possui pyproject.toml, LICENSE e entrypoint de CLI.

Gerar artefatos:

python3 -m pip install --upgrade build twine
python3 -m build
python3 -m twine check dist/*

Teste local do wheel:

python3 -m pip install dist/*.whl
sectionminer --help

Publicar (TestPyPI primeiro, recomendado):

python3 -m twine upload --repository testpypi dist/*

Publicar no PyPI oficial:

python3 -m twine upload dist/*

Licenca

MIT (arquivo LICENSE).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sectionminer-0.1.1.tar.gz (16.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sectionminer-0.1.1-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file sectionminer-0.1.1.tar.gz.

File metadata

  • Download URL: sectionminer-0.1.1.tar.gz
  • Upload date:
  • Size: 16.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for sectionminer-0.1.1.tar.gz
Algorithm Hash digest
SHA256 f6c8665dd7a2d7291447a3611b3cba48bcb52f664b20d7e17ede2feba120e0ba
MD5 f26778a9046e80fc0a8a741b6cbc073f
BLAKE2b-256 8b14cd9a429f798f60f6123b129bd7b6c74a102f33491ced768010dcd21eb698

See more details on using hashes here.

File details

Details for the file sectionminer-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: sectionminer-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for sectionminer-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b64e83e1c5f46f5d7c35e6daff7795b322db0166d33b2585dd06572007a22ab6
MD5 6f052bcfb33d21a49a468f6db01ba3af
BLAKE2b-256 32f6fc841cdbc9aa1e363b5b2f97861d6d1793f69de417ad929ac3181cbd8c79

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page