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

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.2.tar.gz (15.6 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.2-py3-none-any.whl (13.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: sectionminer-0.1.2.tar.gz
  • Upload date:
  • Size: 15.6 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.2.tar.gz
Algorithm Hash digest
SHA256 71b5a328c344a7879b37dd76d438567203052158f97b646ff28b892410df007f
MD5 cb410cc5cde73fea60260c3de1886d96
BLAKE2b-256 0020417c44a74c45090aadb62a97eafd47a9941b4a6b316e5407146a5d08f759

See more details on using hashes here.

File details

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

File metadata

  • Download URL: sectionminer-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 13.9 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9e5fb6bdabb0346774b379cc3a6ac4f7402714ceeaa6c349694ba6fda6c42678
MD5 b39a8acf50c697b8f5c6bc1f5e935205
BLAKE2b-256 050555600e5301f6ba973c5a7bd31f586772b8b612930d2b200bf9c64733689c

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