Extrair texto de PDF em Python
A extração de texto de PDF é uma das tarefas mais comuns em pipelines de processamento de documentos — desde a construção de índices de busca e alimentação de sistemas RAG até mineração de dados e fluxos de trabalho de conformidade. Este guia aborda tudo que você precisa para extrair texto de PDFs em Python, JavaScript e Rust usando o PDF Oxide, incluindo extração de texto simples, posicionamento em nível de caractere, spans com estilo, OCR para documentos digitalizados, tratamento de arquivos criptografados e ajuste de desempenho para pipelines em lote.
Extraia texto de qualquer PDF em três linhas:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("document.pdf")
text = doc.extract_text(0) # page 0
print(text)
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const bytes = new Uint8Array(buffer);
const doc = new WasmPdfDocument(bytes);
const text = doc.extractText(0); // page 0
console.log(text);
doc.free();
Rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("document.pdf")?;
let text = doc.extract_text(0)?;
println!("{}", text);
Go
package main
import (
"fmt"
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
doc, err := pdfoxide.Open("document.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
text, err := doc.ExtractText(0) // page 0
if err != nil { log.Fatal(err) }
fmt.Println(text)
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("document.pdf");
var text = doc.ExtractText(0); // page 0
Console.WriteLine(text);
Java
import fyi.oxide.pdf.PdfDocument;
import java.nio.file.Path;
try (PdfDocument doc = PdfDocument.open(Path.of("document.pdf"))) {
String text = doc.extractText(0); // page 0
System.out.println(text);
}
Kotlin
import fyi.oxide.pdf.PdfDocument
import java.nio.file.Path
PdfDocument.open(Path.of("document.pdf")).use { doc ->
val text = doc.extractText(0) // page 0
println(text)
}
Scala
import fyi.oxide.pdf.PdfDocument
import scala.util.Using
Using.resource(PdfDocument.open("document.pdf")) { doc =>
val text = doc.extractText(0) // page 0
println(text)
}
Clojure
(require '[pdf-oxide.core :as pdf])
(with-open [doc (pdf/open "document.pdf")]
(println (pdf/extract-text doc 0))) ; page 0
PHP
use PdfOxide\PdfDocument;
$doc = PdfDocument::open('document.pdf');
$text = $doc->extractText(0); // page 0
echo $text;
$doc->close();
Ruby
require 'pdf_oxide'
PdfOxide::PdfDocument.open('document.pdf') do |doc|
text = doc.extract_text(0) # page 0
puts text
end
C++
#include <pdf_oxide/pdf_oxide.hpp>
#include <iostream>
auto doc = pdf_oxide::Document::open("document.pdf");
auto text = doc.extract_text(0); // page 0
std::cout << text << '\n';
Swift
import PdfOxide
let doc = try Document.open("document.pdf")
let text = try doc.extractText(0) // page 0
print(text)
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('document.pdf');
final text = doc.extractText(0); // page 0
print(text);
doc.close();
R
library(pdfoxide)
doc <- pdf_open("document.pdf")
text <- pdf_extract_text(doc, 0) # page 0
cat(text)
Julia
using PdfOxide
doc = open_document("document.pdf")
text = extract_text(doc, 0) # page 0
println(text)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("document.pdf");
const text = try doc.extractText(a, 0); // page 0
std.debug.print("{s}\n", .{text});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"document.pdf" error:&err];
NSString *text = [doc extractText:0 error:&err]; // page 0
NSLog(@"%@", text);
Elixir
{:ok, doc} = PdfOxide.open("document.pdf")
{:ok, text} = PdfOxide.extract_text(doc, 0) # page 0
IO.puts(text)
O PDF Oxide extrai texto com média de 0,8ms por página — 5× mais rápido que o PyMuPDF, 15× mais rápido que o pypdf — com 100% de aprovação em 3.830 PDFs de teste.
Por que a extração de texto de PDF é difícil
PDF é um formato visual, não um formato de texto. Diferentemente do HTML ou Markdown, um arquivo PDF não armazena “parágrafos” ou “frases” — ele armazena caracteres individuais posicionados em coordenadas específicas numa página. Extrair texto legível requer:
- Decodificação de fontes — As fontes PDF mapeiam códigos de caracteres para glifos usando tabelas de codificação (WinAnsi, MacRoman, Unicode CMap, Type 1, TrueType, CIDFont). O código de caractere
0x41pode significar “A” em uma fonte e “α” em outra. - Análise de fluxo de texto — Operadores de texto como
Tj,TJ,',"posicionam caracteres na página. Ajustes de kerning em arraysTJdeslocam caracteres por frações de um ponto. Espaços ausentes precisam ser inferidos a partir das lacunas entre as posições dos caracteres. - Reconstrução de layout — Os caracteres numa página não têm ordem de leitura explícita. Layouts em duas colunas, cabeçalhos, rodapés, tabelas e barras laterais precisam ser analisados espacialmente para produzir um fluxo de texto linear.
- Casos extremos de codificação — Texto CJK (chinês, japonês, coreano) usa codificação CIDFont/CMap com milhares de glifos. Árabe e hebraico exigem reordenação da direita para a esquerda. Ligaduras (fi, fl, ffi) precisam ser decompostas.
- Subconjuntos incorporados — Muitos PDFs incorporam apenas os glifos que utilizam, com vetores de codificação personalizados. Uma fonte pode mapear o índice de glifo 1→“T”, 2→“h”, 3→“e” sem codificação padrão.
É por isso que diferentes bibliotecas PDF produzem saídas de texto diferentes para o mesmo arquivo — e por que algumas falham completamente em documentos complexos. O PDF Oxide lida com todos esses casos com um parser baseado em Rust que foi testado em 3.830 PDFs reais com 100% de aprovação.
Instalação
Python (PyPI):
pip install pdf_oxide
Wheels pré-compilados para Linux (x86_64, aarch64), macOS (Intel e Apple Silicon) e Windows (x86_64). Python 3.8+. Sem dependências do sistema — o núcleo em Rust é compilado no wheel, portanto não é necessário instalar Poppler, MuPDF ou quaisquer bibliotecas C.
JavaScript (npm):
npm install pdf-oxide-wasm
Funciona no Node.js 18+ e navegadores modernos. O binário WASM está incluído no pacote.
Rust (Cargo):
cargo add pdf_oxide
Requer Rust 1.70+. Sem dependências do sistema além de um toolchain Rust padrão.
Extrair todas as páginas
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
full_text = []
for i in range(doc.page_count()):
text = doc.extract_text(i)
full_text.append(text)
print("\n".join(full_text))
WASM
const doc = new WasmPdfDocument(bytes);
const fullText = doc.extractAllText();
console.log(fullText);
doc.free();
Rust
let mut doc = PdfDocument::open("report.pdf")?;
let mut full_text = Vec::new();
for i in 0..doc.page_count()? {
full_text.push(doc.extract_text(i)?);
}
println!("{}", full_text.join("\n"));
Go
doc, err := pdfoxide.Open("report.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
full, err := doc.ExtractAllText()
if err != nil { log.Fatal(err) }
fmt.Println(full)
C#
using var doc = PdfDocument.Open("report.pdf");
var parts = new List<string>();
for (int i = 0; i < doc.PageCount; i++)
parts.Add(doc.ExtractText(i));
Console.WriteLine(string.Join("\n", parts));
Java
try (PdfDocument doc = PdfDocument.open(Path.of("report.pdf"))) {
StringBuilder all = new StringBuilder();
for (int i = 0; i < doc.pageCount(); i++)
all.append(doc.extractText(i));
System.out.println(all);
}
Kotlin
PdfDocument.open(Path.of("report.pdf")).use { doc ->
val all = (0 until doc.pageCount()).joinToString("") { doc.extractText(it) }
println(all)
}
Scala
Using.resource(PdfDocument.open("report.pdf")) { doc =>
val all = (0 until doc.pageCount()).map(doc.extractText).mkString
println(all)
}
Clojure
(with-open [doc (pdf/open "report.pdf")]
(println (apply str (map #(pdf/extract-text doc %)
(range (pdf/page-count doc))))))
PHP
$doc = PdfDocument::open('report.pdf');
$all = '';
for ($i = 0; $i < $doc->pageCount(); $i++) { $all .= $doc->extractText($i); }
echo $all;
$doc->close();
Ruby
PdfOxide::PdfDocument.open('report.pdf') do |doc|
all = (0...doc.page_count).map { |i| doc.extract_text(i) }.join
puts all
end
C++
auto doc = pdf_oxide::Document::open("report.pdf");
auto all = doc.extract_all_text();
std::cout << all << '\n';
Swift
let doc = try Document.open("report.pdf")
let all = try doc.extractAllText()
print(all)
Dart
final doc = PdfDocument.open('report.pdf');
final all = doc.extractAllText();
print(all);
doc.close();
R
doc <- pdf_open("report.pdf")
all <- pdf_extract_all_text(doc)
cat(all)
Julia
doc = open_document("report.pdf")
all = extract_all_text(doc)
println(all)
Zig
var doc = try pdf_oxide.Document.open("report.pdf");
const all = try doc.extractAllText(a);
std.debug.print("{s}\n", .{all});
Objective-C
POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
NSString *all = [doc extractAllTextWithError:&err];
NSLog(@"%@", all);
Elixir
{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, n} = PdfOxide.page_count(doc)
all = 0..(n - 1)
|> Enum.map(fn i -> {:ok, t} = PdfOxide.extract_text(doc, i); t end)
|> Enum.join()
IO.puts(all)
Extrair texto com posições de caractere
Obtenha coordenadas exatas, nomes de fonte e tamanhos para cada caractere:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
chars = doc.extract_chars(0)
for ch in chars[:20]:
print(f"'{ch.char}' at ({ch.x:.1f}, {ch.y:.1f}) "
f"font={ch.font_name} size={ch.font_size:.1f}")
WASM
const doc = new WasmPdfDocument(bytes);
const chars = doc.extractChars(0);
for (const ch of chars.slice(0, 20)) {
console.log(`'${ch.char}' at (${ch.x.toFixed(1)}, ${ch.y.toFixed(1)}) font=${ch.fontName} size=${ch.fontSize.toFixed(1)}`);
}
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let chars = doc.extract_chars(0)?;
for ch in chars.iter().take(20) {
println!("'{}' at ({:.1}, {:.1}) font={} size={:.1}",
ch.char, ch.x, ch.y, ch.font_name, ch.font_size);
}
Go
doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
chars, _ := doc.ExtractChars(0)
for _, ch := range chars[:20] {
fmt.Printf("%q at (%.1f, %.1f) font=%s size=%.1f\n",
ch.Char, ch.X, ch.Y, ch.FontName, ch.FontSize)
}
C#
using var doc = PdfDocument.Open("paper.pdf");
var chars = doc.ExtractChars(0);
foreach (var ch in chars.Take(20))
Console.WriteLine($"'{ch.Char}' at ({ch.X:F1}, {ch.Y:F1}) font={ch.FontName} size={ch.FontSize:F1}");
Java
import fyi.oxide.pdf.text.TextChar;
try (PdfDocument doc = PdfDocument.open(Path.of("paper.pdf"))) {
for (TextChar ch : doc.page(0).chars().subList(0, 20)) {
System.out.printf("'%s' at (%.1f, %.1f)%n",
ch.asString(), ch.bbox().x0(), ch.bbox().y0());
}
}
Kotlin
PdfDocument.open(Path.of("paper.pdf")).use { doc ->
doc.page(0).chars().take(20).forEach { ch ->
println("'${ch.asString()}' at (${ch.bbox().x0()}, ${ch.bbox().y0()})")
}
}
Scala
import fyi.oxide.pdf.charsSeq
Using.resource(PdfDocument.open("paper.pdf")) { doc =>
doc.page(0).charsSeq.take(20).foreach { ch =>
println(f"'${ch.asString}' at (${ch.bbox.x0}%.1f, ${ch.bbox.y0}%.1f)")
}
}
Clojure
(with-open [doc (pdf/open "paper.pdf")]
(doseq [ch (take 20 (pdf/chars (pdf/page doc 0)))]
(let [b (.bbox ch)]
(println (format "'%s' at (%.1f, %.1f)"
(.asString ch) (.x0 b) (.y0 b))))))
C++
auto doc = pdf_oxide::Document::open("paper.pdf");
auto chars = doc.extract_chars(0);
int shown = 0;
for (const auto& ch : chars) {
if (shown++ >= 20) break;
std::printf("U+%04X at (%.1f, %.1f) font=%s size=%.1f\n",
ch.character, ch.bbox.x, ch.bbox.y,
ch.font_name.c_str(), ch.font_size);
}
Swift
let doc = try Document.open("paper.pdf")
let chars = try doc.extractChars(0)
for ch in chars.prefix(20) {
let s = String(UnicodeScalar(ch.character) ?? " ")
print("'\(s)' at (\(ch.bbox.x), \(ch.bbox.y)) font=\(ch.fontName) size=\(ch.fontSize)")
}
Dart
final doc = PdfDocument.open('paper.pdf');
final chars = doc.extractChars(0);
for (final ch in chars.take(20)) {
final s = String.fromCharCode(ch.character);
print("'$s' at (${ch.bbox.x}, ${ch.bbox.y}) "
"font=${ch.fontName} size=${ch.fontSize}");
}
doc.close();
R
doc <- pdf_open("paper.pdf")
chars <- pdf_extract_chars(doc, 0)
for (ch in head(chars, 20)) {
cat(sprintf("'%s' at (%.1f, %.1f) font=%s size=%.1f\n",
intToUtf8(ch$character), ch$bbox$x, ch$bbox$y,
ch$font_name, ch$font_size))
}
Julia
doc = open_document("paper.pdf")
chars = extract_chars(doc, 0)
for ch in chars[1:min(20, end)]
println("'$(Char(ch.character))' at ($(ch.bbox.x), $(ch.bbox.y)) ",
"font=$(ch.font_name) size=$(ch.font_size)")
end
Zig
var doc = try pdf_oxide.Document.open("paper.pdf");
const chars = try doc.extractChars(a, 0);
defer pdf_oxide.Document.freeChars(a, chars);
for (chars[0..@min(20, chars.len)]) |ch| {
std.debug.print("U+{X:0>4} at ({d:.1}, {d:.1}) font={s} size={d:.1}\n",
.{ ch.character, ch.bbox.x, ch.bbox.y, ch.fontName, ch.fontSize });
}
Objective-C
POXDocument *doc = [POXDocument openPath:@"paper.pdf" error:&err];
NSArray<POXChar*> *chars = [doc extractChars:0 error:&err];
for (POXChar *ch in [chars subarrayWithRange:NSMakeRange(0, MIN(20, chars.count))]) {
NSLog(@"U+%04X at (%.1f, %.1f) font=%@ size=%.1f",
ch.character, ch.bbox.x, ch.bbox.y, ch.fontName, ch.fontSize);
}
Elixir
{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, chars} = PdfOxide.extract_chars(doc, 0)
chars
|> Enum.take(20)
|> Enum.each(fn ch ->
IO.puts("'#{<<ch.character::utf8>>}' at (#{ch.bbox.x}, #{ch.bbox.y}) " <>
"font=#{ch.font_name} size=#{ch.font_size}")
end)
Cada caractere inclui:
| Campo | Tipo | Descrição |
|---|---|---|
char |
str |
O caractere Unicode |
x, y |
float |
Posição em pontos |
font_size |
float |
Tamanho da fonte em pontos |
font_name |
str |
Nome da fonte PostScript |
bbox |
tuple |
Caixa delimitadora (x0, y0, x1, y1) |
A extração em nível de caractere é útil para reconstruir tabelas, detectar títulos por tamanho de fonte ou construir caixas delimitadoras ao redor de regiões de texto. Por exemplo, você pode agrupar caracteres em linhas pela coordenada y e detectar limites de colunas pelas lacunas nas posições x.
Extrair spans de texto com estilo
Agrupe caracteres consecutivos por fonte e tamanho:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
spans = doc.extract_spans(0)
for span in spans:
print(f"'{span.text}' font={span.font_name} size={span.font_size:.1f}")
WASM
const doc = new WasmPdfDocument(bytes);
const spans = doc.extractSpans(0);
for (const span of spans) {
console.log(`'${span.text}' font=${span.fontName} size=${span.fontSize.toFixed(1)}`);
}
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;
for span in &spans {
println!("'{}' font={} size={:.1}", span.text, span.font_name, span.font_size);
}
Útil para detectar títulos, texto em negrito ou construir saída estruturada.
Processamento em lote
Processe centenas ou milhares de PDFs:
from pdf_oxide import PdfDocument, PdfError
from pathlib import Path
pdf_dir = Path("documents/")
for pdf_path in pdf_dir.glob("*.pdf"):
try:
doc = PdfDocument(str(pdf_path))
for i in range(doc.page_count()):
text = doc.extract_text(i)
# Process text...
except PdfError as e:
print(f"Skipped {pdf_path.name}: {e}")
A 0,8ms por página, processar 3.830 PDFs leva cerca de 3,1 segundos. Para pipelines de produção, consulte o guia de Processamento em Lote para padrões de processamento paralelo usando multiprocessing e async I/O.
Tratando PDFs digitalizados (OCR)
Se um PDF contém imagens digitalizadas em vez de texto, extract_text() retorna saída vazia ou mínima. Use o OCR integrado do PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
text = doc.extract_text(0)
if not text.strip():
# Page is likely scanned — use OCR
text = doc.extract_text_ocr(0)
print(text)
O PDF Oxide usa PaddleOCR via ONNX Runtime — nenhuma instalação do Tesseract é necessária. Consulte o guia de OCR para seleção de modelos e configuração.
Tratando PDFs criptografados
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("protected.pdf", password="secret")
text = doc.extract_text(0)
print(text)
WASM
const doc = new WasmPdfDocument(bytes);
doc.authenticate("secret");
const text = doc.extractText(0);
console.log(text);
doc.free();
Rust
let mut doc = PdfDocument::open_with_password("protected.pdf", "secret")?;
let text = doc.extract_text(0)?;
println!("{}", text);
Go
doc, _ := pdfoxide.Open("protected.pdf")
defer doc.Close()
if _, err := doc.Authenticate("secret"); err != nil { log.Fatal(err) }
text, _ := doc.ExtractText(0)
fmt.Println(text)
C#
using var doc = PdfDocument.OpenWithPassword("protected.pdf", "secret");
Console.WriteLine(doc.ExtractText(0));
Java
try (PdfDocument doc = PdfDocument.open("protected.pdf", "secret")) {
System.out.println(doc.extractText(0));
}
Kotlin
PdfDocument.open("protected.pdf", "secret").use { doc ->
println(doc.extractText(0))
}
Scala
Using.resource(PdfDocument.open("protected.pdf", "secret")) { doc =>
println(doc.extractText(0))
}
Clojure
(with-open [doc (pdf/open "protected.pdf" "secret")]
(println (pdf/extract-text doc 0)))
Ruby
PdfOxide::PdfDocument.open('protected.pdf', password: 'secret') do |doc|
puts doc.extract_text(0)
end
C++
auto doc = pdf_oxide::Document::open_with_password("protected.pdf", "secret");
std::cout << doc.extract_text(0) << '\n';
Swift
let doc = try Document.openWithPassword("protected.pdf", password: "secret")
print(try doc.extractText(0))
Dart
final doc = PdfDocument.openWithPassword('protected.pdf', 'secret');
print(doc.extractText(0));
doc.close();
R
doc <- pdf_open_with_password("protected.pdf", "secret")
cat(pdf_extract_text(doc, 0))
Julia
doc = open_with_password("protected.pdf", "secret")
println(extract_text(doc, 0))
Zig
var doc = try pdf_oxide.Document.openWithPassword("protected.pdf", "secret");
const text = try doc.extractText(a, 0);
std.debug.print("{s}\n", .{text});
Objective-C
POXDocument *doc = [POXDocument openWithPassword:@"protected.pdf"
password:@"secret" error:&err];
NSLog(@"%@", [doc extractText:0 error:&err]);
Elixir
{:ok, doc} = PdfOxide.open_with_password("protected.pdf", "secret")
{:ok, text} = PdfOxide.extract_text(doc, 0)
IO.puts(text)
Suporta PDFs criptografados com AES-256, AES-128 e RC4. Ao contrário do pdfplumber (que não consegue abrir arquivos criptografados) e do pdfminer (que falha com AES-256), o PDF Oxide lida com todos os métodos padrão de criptografia de PDF de forma transparente.
Saída como Markdown
Para saída estruturada com títulos e formatação:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
WASM
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
console.log(md);
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let md = doc.to_markdown(0, true)?;
println!("{}", md);
Go
doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
md, _ := doc.ToMarkdown(0)
fmt.Println(md)
C#
using var doc = PdfDocument.Open("paper.pdf");
Console.WriteLine(doc.ToMarkdown(0));
Java
try (PdfDocument doc = PdfDocument.open(Path.of("paper.pdf"))) {
System.out.println(doc.toMarkdown(0));
}
Kotlin
PdfDocument.open(Path.of("paper.pdf")).use { doc ->
println(doc.toMarkdown(0))
}
Scala
Using.resource(PdfDocument.open("paper.pdf")) { doc =>
println(doc.toMarkdown(0))
}
Clojure
(with-open [doc (pdf/open "paper.pdf")]
(println (pdf/to-markdown doc 0)))
PHP
$doc = PdfDocument::open('paper.pdf');
echo $doc->toMarkdown(0);
$doc->close();
Ruby
PdfOxide::PdfDocument.open('paper.pdf') do |doc|
puts doc.to_markdown(0)
end
C++
auto doc = pdf_oxide::Document::open("paper.pdf");
std::cout << doc.to_markdown(0) << '\n';
Swift
let doc = try Document.open("paper.pdf")
print(try doc.toMarkdown(0))
Dart
final doc = PdfDocument.open('paper.pdf');
print(doc.toMarkdown(0));
doc.close();
R
doc <- pdf_open("paper.pdf")
cat(pdf_to_markdown(doc, 0))
Julia
doc = open_document("paper.pdf")
println(to_markdown(doc, 0))
Zig
var doc = try pdf_oxide.Document.open("paper.pdf");
const md = try doc.toMarkdown(a, 0);
std.debug.print("{s}\n", .{md});
Objective-C
POXDocument *doc = [POXDocument openPath:@"paper.pdf" error:&err];
NSLog(@"%@", [doc toMarkdown:0 error:&err]);
Elixir
{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, md} = PdfOxide.to_markdown(doc, 0)
IO.puts(md)
Consulte o guia de PDF para Markdown para padrões de integração com RAG e LLM.
Pesquisar em PDFs
Encontre texto em todas as páginas com dados de posição:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("manual.pdf")
results = doc.search("configuration")
for r in results:
print(f"Page {r.page}: '{r.text}' at ({r.x:.0f}, {r.y:.0f})")
WASM
const doc = new WasmPdfDocument(bytes);
const results = doc.search("configuration", false);
for (const r of results) {
console.log(`Page ${r.page}: '${r.text}' at (${r.x.toFixed(0)}, ${r.y.toFixed(0)})`);
}
doc.free();
Rust
let mut pdf = Pdf::open("manual.pdf")?;
let results = pdf.search("configuration")?;
for r in &results {
println!("Page {}: '{}' at ({:.0}, {:.0})", r.page, r.text, r.bbox.x, r.bbox.y);
}
Go
doc, _ := pdfoxide.Open("manual.pdf")
defer doc.Close()
results, _ := doc.SearchAll("configuration", false)
for _, r := range results {
fmt.Printf("Page %d: %q at (%.0f, %.0f)\n", r.PageIndex, r.Text, r.X, r.Y)
}
C#
using var doc = PdfDocument.Open("manual.pdf");
foreach (var r in doc.SearchAll("configuration", caseSensitive: false))
Console.WriteLine($"Page {r.PageIndex}: '{r.Text}' at ({r.X:F0}, {r.Y:F0})");
Java
import fyi.oxide.pdf.search.SearchMatch;
try (PdfDocument doc = PdfDocument.open(Path.of("manual.pdf"))) {
for (SearchMatch m : doc.search("configuration")) {
System.out.printf("Page %d: '%s' at (%.0f, %.0f)%n",
m.pageIndex(), m.text(), m.bbox().x0(), m.bbox().y0());
}
}
Kotlin
PdfDocument.open(Path.of("manual.pdf")).use { doc ->
for (m in doc.search("configuration")) {
println("Page ${m.pageIndex()}: '${m.text()}' at (${m.bbox().x0()}, ${m.bbox().y0()})")
}
}
Scala
import fyi.oxide.pdf.searchSeq
Using.resource(PdfDocument.open("manual.pdf")) { doc =>
for (m <- doc.searchSeq("configuration"))
println(f"Page ${m.pageIndex}: '${m.text}' at (${m.bbox.x0}%.0f, ${m.bbox.y0}%.0f)")
}
Clojure
(with-open [doc (pdf/open "manual.pdf")]
(doseq [m (pdf/search doc "configuration")]
(let [b (.bbox m)]
(println (format "Page %d: '%s' at (%.0f, %.0f)"
(.pageIndex m) (.text m) (.x0 b) (.y0 b))))))
Ruby
PdfOxide::PdfDocument.open('manual.pdf') do |doc|
doc.search('configuration').each do |r|
puts "Page #{r[:page]}: '#{r[:text]}' at (#{r[:bbox][:x].round}, #{r[:bbox][:y].round})"
end
end
C++
auto doc = pdf_oxide::Document::open("manual.pdf");
for (const auto& r : doc.search_all("configuration", /*case_sensitive=*/false)) {
std::printf("Page %d: '%s' at (%.0f, %.0f)\n",
r.page, r.text.c_str(), r.bbox.x, r.bbox.y);
}
Swift
let doc = try Document.open("manual.pdf")
for r in try doc.searchAll("configuration", false) {
print("Page \(r.page): '\(r.text)' at (\(r.bbox.x), \(r.bbox.y))")
}
Dart
final doc = PdfDocument.open('manual.pdf');
for (final r in doc.searchAll('configuration', false)) {
print("Page ${r.page}: '${r.text}' at (${r.bbox.x}, ${r.bbox.y})");
}
doc.close();
R
doc <- pdf_open("manual.pdf")
for (r in pdf_search_all(doc, "configuration", case_sensitive = FALSE)) {
cat(sprintf("Page %d: '%s' at (%.0f, %.0f)\n",
r$page, r$text, r$bbox$x, r$bbox$y))
}
Julia
doc = open_document("manual.pdf")
for r in search_all(doc, "configuration", false)
println("Page $(r.page): '$(r.text)' at ($(r.bbox.x), $(r.bbox.y))")
end
Zig
var doc = try pdf_oxide.Document.open("manual.pdf");
const hits = try doc.searchAll(a, "configuration", false);
defer pdf_oxide.Document.freeSearchResults(a, hits);
for (hits) |r| {
std.debug.print("Page {d}: '{s}' at ({d:.0}, {d:.0})\n",
.{ r.page, r.text, r.bbox.x, r.bbox.y });
}
Objective-C
POXDocument *doc = [POXDocument openPath:@"manual.pdf" error:&err];
for (POXSearchResult *r in [doc searchAll:@"configuration" caseSensitive:NO error:&err]) {
NSLog(@"Page %ld: '%@' at (%.0f, %.0f)",
(long)r.page, r.text, r.bbox.x, r.bbox.y);
}
Elixir
{:ok, doc} = PdfOxide.open("manual.pdf")
{:ok, hits} = PdfOxide.search_all(doc, "configuration", false)
Enum.each(hits, fn r ->
IO.puts("Page #{r.page}: '#{r.text}' at (#{r.bbox.x}, #{r.bbox.y})")
end)
Comparação com outras bibliotecas Python para PDF
Existem várias bibliotecas Python para extração de texto de PDF. Veja como elas se comparam:
- pypdf — Python puro, sem dependências C. Fácil de instalar, mas lento (12ms por página) e falha em 1,6% dos PDFs devido ao suporte limitado a fontes e codificações. Sem dados de posição de caractere. Bom para PDFs simples onde a velocidade não importa.
- pdfplumber — Construído sobre o pdfminer, fornece extração detalhada de caracteres e tabelas. Muito lento (23ms por página) e não consegue abrir PDFs criptografados. Melhor para extração de tabelas quando você precisa de dados em nível de célula e não precisa de desempenho.
- PyMuPDF (fitz) — Bindings Python para a biblioteca C MuPDF. Rápido (4,6ms por página) e confiável (99,3% de aprovação). Requer instalação de uma biblioteca C e tem licença AGPL. Uma boa escolha se a licença funcionar para o seu projeto.
- pypdfium2 — Bindings Python para o motor PDFium do Google. Rápido (4,1ms por página), mas a latência p99 é alta (42ms) em documentos complexos. Superfície de API limitada comparada ao PyMuPDF.
- pdfminer.six — Python puro com análise de layout detalhada. Muito lento e sem manutenção. Falha em PDFs criptografados com AES-256. Em grande parte substituído pelo pdfplumber.
- PDF Oxide — Baseado em Rust com bindings Python via PyO3. Opção mais rápida (0,8ms por página), 100% de aprovação, lida com todos os métodos de criptografia, inclui OCR integrado. Licença MIT sem dependências do sistema.
O PDF Oxide foi construído especificamente para resolver as lacunas nas bibliotecas existentes: as limitações de velocidade dos parsers Python puros, as restrições de licenciamento do MuPDF e os problemas de confiabilidade que fazem as bibliotecas falharem em PDFs reais com fontes incomuns, tabelas de referência cruzada quebradas ou codificações não padrão.
Desempenho: quão rápido é o PDF Oxide?
Testado em 3.830 PDFs de três conjuntos de testes públicos independentes:
| Biblioteca | Média | p99 | Taxa de aprovação |
|---|---|---|---|
| PDF Oxide | 0,8ms | 9ms | 100% |
| PyMuPDF | 4,6ms | 28ms | 99,3% |
| pypdfium2 | 4,1ms | 42ms | 99,2% |
| pypdf | 12,1ms | 97ms | 98,4% |
| pdfplumber | 23,2ms | 189ms | 98,8% |
Para um pipeline processando 10.000 PDFs:
- PDF Oxide: 8 segundos
- PyMuPDF: 46 segundos
- pypdf: 2 minutos
- pdfplumber: 3,9 minutos
Consulte os benchmarks completos para metodologia e etapas de reprodução.
Problemas comuns e solução de problemas
Saída de texto vazia
Se extract_text() retornar uma string vazia, a página provavelmente contém imagens digitalizadas em vez de texto. Use extract_text_ocr(). Consulte OCR de PDFs Digitalizados para instruções de configuração.
Caracteres distorcidos ou incorretos
Isso geralmente indica uma fonte com vetor de codificação não padrão ou um ToUnicode CMap ausente. O PDF Oxide lida com a maioria dos casos extremos de codificação, mas alguns PDFs intencionalmente ofuscados (conteúdo protegido por DRM) podem produzir saída incorreta.
Espaços ausentes ou palavras fundidas
Os operadores de texto PDF posicionam caracteres individualmente. A inferência de espaços depende da lacuna entre as posições dos caracteres em relação à largura do espaço da fonte. Se as palavras aparecerem fundidas, tente extract_chars() e aplique lógica de espaçamento personalizada com base nas posições dos caracteres.
Saída diferente de outras bibliotecas
Diferentes bibliotecas usam heurísticas diferentes para inferência de espaços, quebra de linha e ordem de leitura. O PDF Oxide atinge 99,5% de paridade de texto com o PyMuPDF em 3.830 PDFs. A diferença de 0,5% está na normalização de espaços em branco e no tratamento de ligaduras.
Casos de uso reais
Indexação de busca — Extraia texto de cada página de cada PDF em um repositório de documentos e alimente o texto no Elasticsearch, Typesense ou um banco de dados vetorial para busca de texto completo. A velocidade do PDF Oxide torna prático reindexar milhares de documentos sob demanda.
Pipelines RAG (geração aumentada por recuperação) — Extraia e divida o texto de PDFs para embedding com OpenAI, Cohere ou modelos open-source. Use extract_spans() para preservar a estrutura de títulos para que os chunks se alinhem com as seções do documento. Consulte o guia de PDF para Markdown para saída otimizada para LLM.
Conformidade e auditoria — Escaneie contratos, faturas e documentos regulatórios em busca de cláusulas ou palavras-chave específicas. Use doc.search() para localizar termos em todas as páginas com posições exatas, ou extraia o texto completo para detecção de cláusulas baseada em NLP.
Extração de dados — Extraia dados estruturados de faturas, recibos, extratos bancários e formulários. Combine extract_chars() para dados posicionais com regras específicas do domínio para localizar campos como “Valor Total” ou “Data da Fatura” e extrair valores adjacentes.
Pesquisa acadêmica — Processe milhares de artigos de pesquisa para revisão de literatura, extração de citações ou meta-análise. O PDF Oxide lida com toda a gama de produtores de PDF (LaTeX, Word, InDesign, Quark) e codificações de fonte encontradas em publicações acadêmicas.
Páginas relacionadas
- API de Extração de Texto — referência completa da API
- PDF para Markdown — conversão estruturada
- Processamento em Lote — padrões de processamento paralelo
- OCR de PDFs Digitalizados — configuração e uso do OCR
- Benchmarks de Desempenho — metodologia e resultados do benchmark