Видобути текст із PDF у Python
Видобування тексту з PDF — одне з найчастіших завдань у конвеєрах обробки документів: від побудови пошукових індексів і живлення RAG-систем до data mining та процесів комплаєнсу. У цьому посібнику зібрано все, що потрібно, щоб видобувати текст із PDF у Python, JavaScript і Rust за допомогою PDF Oxide: звичайний текст, покоординатне видобування символів, стилізовані спани, OCR для сканів, робота із зашифрованими файлами та тонке налаштування продуктивності для пакетних конвеєрів.
Видобудьте текст із будь-якого PDF у три рядки:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("document.pdf")
text = doc.extract_text(0) # сторінка 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); // сторінка 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) // сторінка 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); // сторінка 0
Console.WriteLine(text);
PDF Oxide видобуває текст у середньому за 0,8 мс на сторінку — це у 5 разів швидше за PyMuPDF і у 15 разів швидше за pypdf, із 100 % успіхом на 3830 тестових PDF.
Чому видобування тексту з PDF — складна задача
PDF — це візуальний, а не текстовий формат. На відміну від HTML чи Markdown, PDF-файл не містить ані «абзаців», ані «речень»: усередині лежать окремі символи, розставлені в конкретних координатах сторінки. Щоб зробити з них читабельний текст, потрібно:
- Декодувати шрифти — шрифти PDF зіставляють коди символів гліфам через таблиці кодувань (WinAnsi, MacRoman, Unicode CMap, Type 1, TrueType, CIDFont). Той самий код
0x41у одному шрифті означає «A», а в іншому — «α». - Розбирати текстові потоки — оператори
Tj,TJ,',"розставляють символи на сторінці. Поправки кернінгу в масивахTJзміщують символи на частки пункту, а пропущені пробіли доводиться виводити з відстаней між позиціями символів. - Відтворювати верстку — символи на сторінці не мають явного порядку читання. Дві колонки, колонтитули, таблиці й бічні панелі доводиться розбирати просторово, щоб отримати лінійний текстовий потік.
- Закривати крайні випадки кодувань — CJK-текст (китайська, японська, корейська) використовує кодування CIDFont/CMap з тисячами гліфів. Для арабської та івриту потрібне переставляння справа наліво, а лігатури (fi, fl, ffi) слід розкладати на частини.
- Враховувати вбудовані підмножини — багато PDF вбудовують лише потрібні гліфи з власними векторами кодувань. Шрифт може відображати гліф з індексом 1→«T», 2→«h», 3→«e», не спираючись на жодну стандартну кодувальну схему.
Саме тому різні бібліотеки видають для того самого файлу різний текст, а деякі взагалі не справляються зі складними документами. PDF Oxide покриває всі ці випадки Rust-парсером, що пройшов перевірку на 3830 справжніх PDF зі 100 % успіхом.
Встановлення
Python (PyPI):
pip install pdf_oxide
Готові колеса для Linux (x86_64, aarch64), macOS (Intel та Apple Silicon) і Windows (x86_64). Python 3.8 і новіше. Жодних системних залежностей: Rust-ядро компілюється прямо в колесо, тож встановлювати Poppler, MuPDF чи інші C-бібліотеки не потрібно.
JavaScript (npm):
npm install pdf-oxide-wasm
Працює в Node.js 18 і новіше, а також у сучасних браузерах. WASM-бінарник уже в пакеті.
Rust (Cargo):
cargo add pdf_oxide
Потрібен Rust 1.70 або новіше. Нічого, окрім стандартного інструментарію Rust, встановлювати не доведеться.
Видобування всіх сторінок
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));
Видобування тексту з позиціями символів
Отримайте точні координати, імена шрифтів і розміри для кожного символа:
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}");
Для кожного символа повертаються такі поля:
| Поле | Тип | Опис |
|---|---|---|
char |
str |
Символ Unicode |
x, y |
float |
Позиція в пунктах |
font_size |
float |
Розмір шрифту в пунктах |
font_name |
str |
PostScript-ім’я шрифту |
bbox |
tuple |
Обмежувальний прямокутник (x0, y0, x1, y1) |
Покоординатне видобування знадобиться, щоб відновлювати таблиці, виявляти заголовки за розміром шрифту або будувати обмежувальні прямокутники навколо ділянок тексту. Наприклад, символи можна згрупувати в рядки за координатою y, а межі колонок визначити за розривами в x.
Видобування стилізованих спанів
Згрупуйте послідовні символи зі спільним шрифтом і розміром:
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);
}
Придасться для розпізнавання заголовків, жирного тексту та побудови структурованого виводу.
Пакетна обробка
Обробляйте сотні й тисячі PDF:
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)
# Обробляємо текст...
except PdfError as e:
print(f"Пропущено {pdf_path.name}: {e}")
На швидкості 0,8 мс на сторінку обробка 3830 PDF триває близько 3,1 секунди. Для продакшн-конвеєрів див. посібник із пакетної обробки з патернами паралельної обробки через multiprocessing і асинхронний ввід/вивід.
Робота зі сканованими PDF (OCR)
Якщо PDF містить скановані зображення замість тексту, extract_text() поверне порожній або майже порожній рядок. У такому разі використовуйте вбудований у PDF Oxide OCR:
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
text = doc.extract_text(0)
if not text.strip():
# Схоже, сторінка сканована — використовуємо OCR
text = doc.extract_text_ocr(0)
print(text)
PDF Oxide запускає PaddleOCR через ONNX Runtime — встановлювати Tesseract не потрібно. Про вибір моделі та налаштування читайте в посібнику з OCR.
Робота із зашифрованими PDF
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));
Підтримуються PDF із шифруванням AES-256, AES-128 і RC4. На відміну від pdfplumber (який узагалі не відкриває зашифровані файли) та pdfminer (який падає на AES-256), PDF Oxide прозоро обробляє всі стандартні способи шифрування PDF.
Вивід у Markdown
Якщо потрібен структурований вивід із заголовками й форматуванням:
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));
Сценарії інтеграції з RAG і LLM описано в посібнику PDF → Markdown.
Пошук у PDF
Знайдіть текст на всіх сторінках разом із координатами:
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})");
Порівняння з іншими Python-бібліотеками для PDF
Для видобування тексту з PDF у Python існує кілька бібліотек. Коротке порівняння:
- pypdf — чистий Python, без C-залежностей. Легко встановлюється, але повільна (12 мс на сторінку) і падає приблизно на 1,6 % PDF через обмежену підтримку шрифтів і кодувань. Не дає координат символів. Годиться для простих PDF, де швидкість не критична.
- pdfplumber — побудована на pdfminer, детально розбирає символи й таблиці. Дуже повільна (23 мс на сторінку) і не відкриває зашифровані PDF. Корисна для роботи з таблицями, коли потрібні дані по клітинках, а продуктивність не на першому місці.
- PyMuPDF (fitz) — Python-біндинги до C-бібліотеки MuPDF. Швидка (4,6 мс на сторінку) та стабільна (99,3 % успіху). Потрібна встановлена C-бібліотека, ліцензія AGPL. Надійний варіант, якщо ліцензія вам підходить.
- pypdfium2 — Python-біндинги до рушія PDFium від Google. Швидка (4,1 мс на сторінку), але p99 на складних документах сягає 42 мс. API помітно вужче за PyMuPDF.
- pdfminer.six — чистий Python із детальним аналізом верстки. Дуже повільна й практично не підтримується. Падає на AES-256. У більшості сценаріїв її витіснила pdfplumber.
- PDF Oxide — Rust-ядро з Python-біндингами через PyO3. Найшвидша (0,8 мс на сторінку), 100 % успіху, підтримує всі способи шифрування й має вбудований OCR. Ліцензія MIT, без системних залежностей.
PDF Oxide задумана саме так, щоб закрити слабкі місця наявних бібліотек: обмеження швидкості чистих Python-парсерів, ліцензійні вимоги MuPDF і проблеми стабільності, які змушують інші інструменти падати на реальних PDF з нестандартними шрифтами, зламаними перехресними таблицями чи незвичними кодуваннями.
Продуктивність: наскільки швидкий PDF Oxide
Виміри проведено на 3830 PDF із трьох незалежних публічних тестових наборів:
| Бібліотека | Середнє | p99 | Успіх |
|---|---|---|---|
| PDF Oxide | 0,8 мс | 9 мс | 100 % |
| PyMuPDF | 4,6 мс | 28 мс | 99,3 % |
| pypdfium2 | 4,1 мс | 42 мс | 99,2 % |
| pypdf | 12,1 мс | 97 мс | 98,4 % |
| pdfplumber | 23,2 мс | 189 мс | 98,8 % |
Для конвеєра з 10 000 PDF:
- PDF Oxide: 8 секунд
- PyMuPDF: 46 секунд
- pypdf: 2 хвилини
- pdfplumber: 3,9 хвилини
Методологію й кроки відтворення шукайте в повних бенчмарках.
Типові проблеми та як їх виправити
Порожній текстовий вивід
Якщо extract_text() повертає порожній рядок, сторінка, найпевніше, містить скановані зображення, а не текст. Використайте extract_text_ocr(). Налаштування описано в OCR для сканованих PDF.
Перекручені або неправильні символи
Зазвичай це шрифт із нестандартним вектором кодування або без CMap ToUnicode. PDF Oxide закриває більшість таких випадків, але для свідомо обфускованих PDF (контент під DRM) результат усе одно може бути неправильним.
Зникають пробіли або склеюються слова
Оператори тексту в PDF розставляють символи по одному. Виведення пробілів залежить від проміжку між позиціями символів відносно ширини пробілу шрифту. Якщо слова злипаються, спробуйте extract_chars() і застосуйте власну логіку розставляння пробілів за координатами.
Інший результат, ніж в інших бібліотеках
Кожна бібліотека використовує власні евристики для пробілів, переносів рядків і порядку читання. PDF Oxide показує 99,5 % збігу тексту з PyMuPDF на 3830 PDF. Решта 0,5 % припадає на нормалізацію пробілів та обробку лігатур.
Реальні сценарії використання
Індексація для пошуку — Видобудьте текст кожної сторінки кожного PDF у сховищі документів і віддайте його Elasticsearch, Typesense чи векторній базі для повнотекстового пошуку. Швидкість PDF Oxide робить реальним переіндексування тисяч документів на вимогу.
RAG-конвеєри (retrieval-augmented generation) — Видобувайте та ріжте текст PDF на чанки для ембедингів через OpenAI, Cohere або відкриті моделі. За допомогою extract_spans() ви зберігаєте структуру заголовків, тож чанки природно збігаються з розділами документа. Оптимізований під LLM вивід описано в посібнику PDF → Markdown.
Комплаєнс та аудит — Перевіряйте договори, рахунки та регуляторну звітність на конкретні положення або ключові слова. doc.search() знаходить терміни на всіх сторінках із точними координатами, а повний текст можна прогнати через NLP для пошуку положень.
Видобування даних — Діставайте структуровані дані з рахунків-фактур, чеків, банківських виписок і анкет. Поєднуйте extract_chars() із правилами вашої предметної області, щоб знаходити поля на кшталт «Всього» чи «Дата рахунка» та видобувати значення поруч.
Академічні дослідження — Обробляйте тисячі статей для оглядів літератури, видобування цитат або метааналізу. PDF Oxide справляється з усім спектром виробників PDF (LaTeX, Word, InDesign, Quark) і кодувань шрифтів, які трапляються в наукових публікаціях.
Пов’язані сторінки
- API видобування тексту — повний довідник API
- PDF → Markdown — структуроване перетворення
- Пакетна обробка — патерни паралельної обробки
- OCR для сканованих PDF — налаштування і використання OCR
- Бенчмарки продуктивності — методологія та результати