Видобування таблиць із PDF у Python
Видобування таблиць із PDF-документів — одне з найпоширеніших завдань у конвеєрах обробки документів. Чи то потрібно витягти фінансові показники з річного звіту, чи зібрати каталог товарів, чи подати структуровані дані в LLM — без надійного розпізнавання таблиць не обійтися. У цьому посібнику зібрано все, що знадобиться у Python: від однорядкових викликів до готових до продакшену сценаріїв для багатосторінкових таблиць.
Рушій розпізнавання
PDF Oxide використовує універсальний конвеєр розпізнавання таблиць ребра → snap/merge → перетини → комірки → групи — той самий підхід, що й Tabula, pdfplumber та PyMuPDF, але реалізований чистим Rust.
Можливості розпізнавання:
- На основі перетинів — знаходить перетини горизонтальних і вертикальних ліній, складає комірки з чотирикутних прямокутників і об’єднує їх у таблиці через union-find.
- Розширена сітка — коли горизонтальні й вертикальні лінії лежать у різних ділянках сторінки, будується віртуальна сітка з декартового добутку всіх координат.
- Розпізнавання тексту з урахуванням колонок — двоколонкові розкладки ділить за гістограмою X-проєкції, після чого в кожній колонці запускається розпізнавання таблиць лише за текстом.
- Текстові таблиці, обмежені горизонтальними лінійками — знаходить таблиці, які облямовані лише горизонтальними лініями без вертикальних (типово для наукових статей).
- Гібридне розпізнавання рядків — виводить межі рядків за Y-координатами тексту, коли є лише вертикальні межі (рядки рахунків-фактур).
- Відновлення пунктирних і штрихових ліній — короткі відрізки зшиваються у суцільні ребра.
- Розбиття за розділювачами секцій — багатосекційні форми ділить на повну ширину сторінки за горизонтальними розділювачами.
- Фільтрація за покриттям ребрами — відкидає одинокі ребра, які не беруть участі в жодній можливій сітці.
Налаштування
TableDetectionConfig виставляє такі параметри для тонкого налаштування:
| Поле | Типово | Опис |
|---|---|---|
horizontal_strategy |
"lines_strict" |
"lines_strict", "lines", "text" або "explicit" |
vertical_strategy |
"lines_strict" |
Той самий набір значень |
v_split_gap |
20.0 pt |
Розрив між вертикальними лініями, за якого таблиці розбиваються на окремі (до v0.3.20 значення було жорстко задане як 4 pt) |
snap_tolerance |
3.0 pt |
Допуск при об’єднанні сусідніх ребер |
text_tolerance |
3.0 pt |
Допуск при об’єднанні текстових рядків |
Зміна поведінки
Починаючи з v0.3.20, типова стратегія Python-методу extract_tables() — Both (розпізнавання і за лініями, і за текстом). Сторінкам, що покладалися на колишній режим лише за текстом, потрібно явно передати horizontal_strategy="text" та vertical_strategy="text".
Python-прив’язка тепер коректно читає vertical_strategy зі словника table_settings — раніше значення мовчки ігнорувалось.
Рендер
Видобуті таблиці виводяться з вирівнюванням колонок пробілами (замість ASCII-псевдографіки зі старіших версій). Колонки з валютою й числами автоматично вирівнюються праворуч. Префікси нумерації форм ("1 Apr 11" → "Apr 11") та декоративні комірки з тире чи підкреслень ("------") під час рендеру видаляються.
Витягуйте дані таблиць із PDF через конвертацію в Markdown:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
# Вивід містить таблиці у форматі GFM:
# | Item | Qty | Price |
# |------|-----|-------|
# | Widget | 10 | $9.99 |
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
console.log(md);
// Вивід містить таблиці у форматі GFM:
// | Item | Qty | Price |
// |------|-----|-------|
// | Widget | 10 | $9.99 |
doc.free();
Rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("invoice.pdf")?;
let md = doc.to_markdown(0, true)?;
println!("{}", md);
Go
package main
import (
"fmt"
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
doc, err := pdfoxide.Open("invoice.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
md, err := doc.ToMarkdown(0)
if err != nil { log.Fatal(err) }
fmt.Println(md)
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("invoice.pdf");
Console.WriteLine(doc.ToMarkdown(0));
PDF Oxide розпізнає табличні розкладки через просторовий аналіз вирівняних текстових блоків і віддає таблиці у форматі GitHub Flavored Markdown.
Чому видобування таблиць із PDF — складна задача
Якщо колись намагалися скопіювати таблицю з PDF у таблицю електронних таблиць, ви знаєте: зазвичай виходить каша. Це не баг перегляду — це наслідок фундаментальних обмежень самого формату PDF.
У PDF немає поняття «таблиця». На відміну від HTML, що описує табличну структуру тегами <table>, <tr> і <td>, PDF-файл зберігає лише інструкції малювання: помісти цей гліф у координатах (x, y), проведи лінію з точки A в точку B. Немає семантичного шару, який би казав, що «ці символи належать комірці рядка 3, колонки 2». Будь-яка бібліотека видобування змушена реконструювати цю структуру, аналізуючи просторове розміщення тексту й ліній на сторінці.
Ця реконструкція складна з кількох причин:
-
Таблиці з рамкою та без. Коли видно лінії сітки, інструмент може брати їх за межі комірок. У таблицях без рамок — звичних у фінансовій звітності, державних звітах і наукових статтях — ліній взагалі немає. Бібліотеці доводиться виводити межі колонок лише з прогалин між текстовими блоками, і це легко зірвати змінною шириною колонок чи числами, вирівняними праворуч.
-
Об’єднані комірки та заголовки, що перекривають кілька колонок. Заголовкова комірка, яка перекриває три колонки, виглядає як один широкий текстовий блок. Без ліній сітки парсер не може надійно визначити, які саме колонки охоплює заголовок. Деякі бібліотеки добре з цим справляються, але багато мовчки видає спотворений результат.
-
Багаторядковий вміст комірки. Якщо в комірці — абзац із перенесеннями, наївний порядковий парсер сприймає кожен перенесений рядок як окремий рядок таблиці. Щоб знову зібрати все в одну комірку, потрібно розуміти вертикальні межі кожного рядка.
-
Багатосторінкові таблиці. Великі таблиці часто розтягнуті на дві або більше сторінок. Заголовковий рядок може повторюватись на кожній сторінці або ж ні, а між рядками трапляються колонтитули, водяні знаки та номери сторінок. Щоб зшити фрагменти в цілісну таблицю, потрібна логіка з урахуванням розбиття на сторінки.
-
Повернутий текст і нестандартні розкладки. Деякі PDF використовують повернутий текст для заголовків колонок або розміщують таблиці в багатоколонкових макетах сторінки. Такі граничні випадки ламають типові припущення парсерів про читання зліва направо й зверху вниз.
Розуміння цих труднощів допомагає добирати інструмент під конкретні документи. Для охайно вирівняних таблиць — більшість рахунків, підтверджень замовлень і простих звітів — швидкого просторового аналізу, як у PDF Oxide, цілком достатньо. Для документів зі складними об’єднаннями, таблицями без рамок чи нетиповим оформленням варто мати бібліотеку з розвиненішими евристиками.
Видобування таблиць: PDF Oxide проти інших бібліотек
Вибір бібліотеки для видобування таблиць із PDF у Python залежить від ваших документів, вимог до швидкості й потрібного формату виводу. Ось як виглядають основні варіанти:
| Бібліотека | Розпізнавання таблиць | Таблиці з рамкою | Таблиці без рамки | Формат виводу | Швидкість |
|---|---|---|---|---|---|
| PDF Oxide | Вбудовано | Так | Базово | Markdown/HTML | 0,8 мс |
| pdfplumber | Вбудовано | Так | Розширено | Списки Python | 23,2 мс |
| Camelot | Вбудовано | Так | Так (lattice/stream) | DataFrame | ~50 мс+ |
| PyMuPDF | Базово (v1.23+) | Так | Обмежено | DataFrame | 4,6 мс |
| pypdf | Ні | Ні | Ні | — | — |
| tabula-py | Вбудовано | Так | Так | DataFrame | ~100 мс+ (Java) |
PDF Oxide — з відривом найшвидший варіант. Розпізнає таблиці через просторовий аналіз вирівняних текстових блоків і відразу віддає охайні таблиці GitHub Flavored Markdown. Середній час видобування 0,8 мс — це у 29× швидше за pdfplumber і більш як у 100× швидше за tabula-py. Добре працює з таблицями в рамках і простими вирівняними таблицями без рамок. Для LLM-пайплайнів, де Markdown і так потрібен, вибір очевидний.
pdfplumber має найзріліше розпізнавання таблиць без рамок. Метод find_tables() пропонує налаштовувані стратегії для виявлення рядків і колонок за вирівнюванням тексту і помітно краще опрацьовує об’єднані комірки та багаторядковий вміст, ніж більшість альтернатив. Ціна — швидкість: 23,2 мс на сторінку помітно сповільнюють пакетну обробку.
Camelot має два режими — lattice (для таблиць з рамкою) та stream (для таблиць без рамки). Він одразу повертає pandas DataFrame, що зручно в сценаріях аналізу даних. Проте залежить від Ghostscript і OpenCV, тому встановлення тяжке, а швидкість найнижча серед варіантів чистого Python.
PyMuPDF (fitz) додав базове видобування таблиць у версії 1.23. Він швидкий (4,6 мс) і непогано справляється з простими таблицями в рамках, але підтримка таблиць без рамок поступається pdfplumber і Camelot.
pypdf взагалі не має засобів розпізнавання таблиць. Він видає сирий текст, тож структуру доведеться реконструювати власним парсером.
tabula-py — це Python-обгортка над Java-бібліотекою Tabula. Розпізнавання і для таблиць з рамкою, і без неї гідне, але потрібен рантайм Java, а старт JVM робить цей варіант найповільнішим. Він краще пасує одноразовим задачам, аніж високонавантаженим конвеєрам.
Для більшості продакшен-випадків радять використовувати PDF Oxide як основний екстрактор заради швидкості й простоти, а pdfplumber залишати як запасний варіант для підмножини документів зі складними таблицями, де потрібні розвинені евристики.
Встановлення
pip install pdf_oxide
Базове видобування таблиць
Як Markdown-таблиці
Найпростіший шлях — перевести сторінку в Markdown, де таблиці одразу записані в синтаксисі GFM:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
for i in range(doc.page_count()):
md = doc.to_markdown(i, detect_headings=True)
if "|" in md: # На сторінці є таблиця
print(f"--- Сторінка {i + 1} ---")
print(md)
WASM
const doc = new WasmPdfDocument(bytes);
for (let i = 0; i < doc.pageCount(); i++) {
const md = doc.toMarkdown(i);
if (md.includes("|")) { // На сторінці є таблиця
console.log(`--- Сторінка ${i + 1} ---`);
console.log(md);
}
}
doc.free();
Rust
let mut doc = PdfDocument::open("report.pdf")?;
for i in 0..doc.page_count()? {
let md = doc.to_markdown(i, true)?;
if md.contains("|") {
println!("--- Сторінка {} ---", i + 1);
println!("{}", md);
}
}
Go
doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
n, _ := doc.PageCount()
for i := 0; i < n; i++ {
md, _ := doc.ToMarkdown(i)
if strings.Contains(md, "|") {
fmt.Printf("--- Сторінка %d ---\n%s\n", i+1, md)
}
}
C#
using var doc = PdfDocument.Open("report.pdf");
for (int i = 0; i < doc.PageCount; i++)
{
var md = doc.ToMarkdown(i);
if (md.Contains("|"))
Console.WriteLine($"--- Сторінка {i + 1} ---\n{md}");
}
Структуроване видобування таблиць (v0.3.34)
Щоб мати типізований доступ до рядків і обмежувальних прямокутників без парсингу Markdown, викликайте ExtractTables(pageIndex) (Go, C#) або extract_tables(page) (Python, Rust). Кожна таблиця виставляє структуровані комірки, тож результати можна передавати прямо в базу даних або DataFrame — без регулярних виразів.
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
for table in doc.extract_tables(0):
for row in table.rows:
print(row)
Rust
let mut doc = PdfDocument::open("invoice.pdf")?;
for table in doc.extract_tables(0)? {
for row in &table.rows {
println!("{:?}", row);
}
}
Go
doc, _ := pdfoxide.Open("invoice.pdf")
defer doc.Close()
tables, _ := doc.ExtractTables(0)
for _, t := range tables {
for _, row := range t.Rows {
fmt.Println(row)
}
}
C#
using var doc = PdfDocument.Open("invoice.pdf");
foreach (var table in doc.ExtractTables(0))
foreach (var row in table.Rows)
Console.WriteLine(string.Join(" | ", row));
Парсинг Markdown-таблиць у рядки
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0)
# Витягнути рядки таблиці з Markdown
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
header = rows[0] if rows else []
data = rows[1:] if len(rows) > 1 else []
print(f"Колонки: {header}")
for row in data:
print(row)
WASM
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
const rows = [];
for (const line of md.split("\n")) {
const trimmed = line.trim();
if (trimmed.startsWith("|") && !trimmed.startsWith("|--")) {
const cells = trimmed.split("|").slice(1, -1).map(c => c.trim());
rows.push(cells);
}
}
const header = rows[0] || [];
const data = rows.slice(1);
console.log("Колонки:", header);
data.forEach(row => console.log(row));
doc.free();
Rust
let mut doc = PdfDocument::open("invoice.pdf")?;
let md = doc.to_markdown(0, false)?;
let rows: Vec<Vec<String>> = md.lines()
.map(|l| l.trim())
.filter(|l| l.starts_with('|') && !l.starts_with("|--"))
.map(|l| l.split('|').skip(1).map(|c| c.trim().to_string())
.take_while(|c| !c.is_empty()).collect())
.collect();
if let Some(header) = rows.first() {
println!("Колонки: {:?}", header);
for row in &rows[1..] {
println!("{:?}", row);
}
}
Експорт у CSV
import csv
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
with open("table.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
Експорт у DataFrame pandas
import pandas as pd
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
md = doc.to_markdown(0)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
if rows:
df = pd.DataFrame(rows[1:], columns=rows[0])
print(df)
Позиції символів для власного парсингу таблиць
Коли потрібен повний контроль, комбінуйте посимвольне видобування з просторовим аналізом:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("financial.pdf")
chars = doc.extract_chars(0)
# Згрупувати символи за Y-координатою (рядки)
rows = {}
for ch in chars:
row_key = round(ch.y / 2) * 2 # Прив'язка до сітки 2 pt
rows.setdefault(row_key, []).append(ch)
# Рядки — згори вниз, символи — зліва направо
for y in sorted(rows.keys(), reverse=True):
line_chars = sorted(rows[y], key=lambda c: c.x)
text = "".join(c.char for c in line_chars)
print(text)
WASM
const doc = new WasmPdfDocument(bytes);
const chars = doc.extractChars(0);
// Згрупувати символи за Y-координатою (рядки)
const rows = new Map();
for (const ch of chars) {
const rowKey = Math.round(ch.y / 2) * 2; // Прив'язка до сітки 2 pt
if (!rows.has(rowKey)) rows.set(rowKey, []);
rows.get(rowKey).push(ch);
}
// Рядки — згори вниз, символи — зліва направо
const sortedKeys = [...rows.keys()].sort((a, b) => b - a);
for (const y of sortedKeys) {
const lineChars = rows.get(y).sort((a, b) => a.x - b.x);
const text = lineChars.map(c => c.char).join("");
console.log(text);
}
doc.free();
Rust
use std::collections::BTreeMap;
let mut doc = PdfDocument::open("financial.pdf")?;
let chars = doc.extract_chars(0)?;
let mut rows: BTreeMap<i32, Vec<_>> = BTreeMap::new();
for ch in &chars {
let row_key = ((ch.y / 2.0).round() * 2.0) as i32;
rows.entry(row_key).or_default().push(ch);
}
for (_, line_chars) in rows.iter().rev() {
let mut sorted = line_chars.clone();
sorted.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
let text: String = sorted.iter().map(|c| c.char).collect();
println!("{}", text);
}
Go
doc, _ := pdfoxide.Open("financial.pdf")
defer doc.Close()
chars, _ := doc.ExtractChars(0)
rows := map[int][]pdfoxide.Char{}
for _, ch := range chars {
key := int(math.Round(float64(ch.Y)/2) * 2)
rows[key] = append(rows[key], ch)
}
keys := make([]int, 0, len(rows))
for k := range rows { keys = append(keys, k) }
sort.Sort(sort.Reverse(sort.IntSlice(keys)))
for _, y := range keys {
line := rows[y]
sort.Slice(line, func(i, j int) bool { return line[i].X < line[j].X })
var b strings.Builder
for _, c := range line { b.WriteString(c.Char) }
fmt.Println(b.String())
}
C#
using var doc = PdfDocument.Open("financial.pdf");
var chars = doc.ExtractChars(0);
var rows = chars
.GroupBy(c => (int)(Math.Round(c.Y / 2) * 2))
.OrderByDescending(g => g.Key);
foreach (var row in rows)
{
var line = string.Concat(row.OrderBy(c => c.X).Select(c => c.Char));
Console.WriteLine(line);
}
Експорт таблиць у Markdown
Коли вміст PDF ідe у велику мовну модель, будується RAG-пайплайн, а дані треба зберегти у форматі, зрозумілому і людині, і машині, Markdown — ідеальний варіант. PDF Oxide нативно видає таблиці у GitHub Flavored Markdown (GFM), тож додаткова конвертація не потрібна.
from pdf_oxide import PdfDocument
doc = PdfDocument("quarterly-report.pdf")
# Зібрати всі таблиці з усіх сторінок як Markdown
all_tables = []
for i in range(doc.page_count()):
md = doc.to_markdown(i, detect_headings=True)
# Розбити markdown на шматки й виділити блоки таблиць
in_table = False
current_table = []
for line in md.split("\n"):
if line.strip().startswith("|"):
in_table = True
current_table.append(line)
else:
if in_table and current_table:
all_tables.append("\n".join(current_table))
current_table = []
in_table = False
if current_table:
all_tables.append("\n".join(current_table))
print(f"Знайдено таблиць: {len(all_tables)}")
for idx, table in enumerate(all_tables):
print(f"\n--- Таблиця {idx + 1} ---")
print(table)
Вивід GFM можна прямо підставляти в промпти для LLM. Передайте його як є до виклику API OpenAI або Anthropic — модель зрозуміє табличну структуру без додаткового форматування:
# Передати видобуту таблицю LLM для аналізу
prompt = f"""Проаналізуй фінансову таблицю нижче та підсумуй ключові тенденції:
{all_tables[0]}
"""
Такий підхід значно швидший, ніж видобувати таблиці pdfplumber-ом і потім вручну перетворювати на Markdown.
Багатосторінкові таблиці
Таблиці на кілька сторінок — класичний виклик у роботі з PDF. Фінансові звіти, інвентарні відомості та регуляторні подання часто містять таблиці на дві, п’ять чи навіть десятки сторінок. Ключова ідея — видобувати таблицю з кожної сторінки окремо, а потім зшивати рядки, ретельно обходячи повторювані заголовки та службові елементи сторінок.
from pdf_oxide import PdfDocument
doc = PdfDocument("long-report.pdf")
def extract_table_rows(md_text):
"""Витягнути рядки таблиці з markdown, повертаючи заголовок і дані окремо."""
header = None
data_rows = []
for line in md_text.split("\n"):
line = line.strip()
if not line.startswith("|") or line.startswith("|--"):
continue
cells = [cell.strip() for cell in line.split("|")[1:-1]]
if header is None:
header = cells
else:
data_rows.append(cells)
return header, data_rows
# Зібрати рядки з усіх сторінок
combined_header = None
combined_rows = []
for i in range(doc.page_count()):
md = doc.to_markdown(i)
header, rows = extract_table_rows(md)
if header is None:
continue # На цій сторінці таблиці немає
if combined_header is None:
combined_header = header
elif header == combined_header:
pass # Пропустити повторюваний заголовок на наступних сторінках
else:
# Інша таблиця — зберегти поточну та почати нову
print(f"Знайдено таблицю на {len(combined_rows)} рядків")
combined_header = header
combined_rows = []
combined_rows.extend(rows)
if combined_header and combined_rows:
print(f"Колонки: {combined_header}")
print(f"Усього рядків: {len(combined_rows)}")
for row in combined_rows[:5]:
print(row)
if len(combined_rows) > 5:
print(f"... і ще {len(combined_rows) - 5} рядків")
Цей шаблон надійний для таблиць, у яких заголовок повторюється на кожній сторінці (найчастіший випадок). Якщо заголовок трапляється лише на першій сторінці, логіку можна спростити: зняти заголовок лише з першої сторінки з таблицею, а решту рядків трактувати як дані.
Експорт таблиць у CSV або DataFrame
Коли таблиця видобута, далі зазвичай потрібна структурована форма для аналізу. Приклади нижче показують, як за кілька рядків дістатися від PDF до DataFrame pandas або CSV-файлу.
Пакетний експорт: кожну таблицю — в окремий CSV
import csv
from pdf_oxide import PdfDocument
doc = PdfDocument("catalog.pdf")
table_count = 0
for i in range(doc.page_count()):
md = doc.to_markdown(i)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
if len(rows) > 1: # Принаймні заголовок і один рядок даних
table_count += 1
filename = f"table_page{i + 1}_{table_count}.csv"
with open(filename, "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
print(f"Збережено {filename} (рядків даних: {len(rows) - 1})")
print(f"Усього експортовано таблиць: {table_count}")
Багатосторінкова таблиця у DataFrame
Для таблиць, що простягаються через кілька сторінок, поєднайте шаблон зшивання з pandas:
import pandas as pd
from pdf_oxide import PdfDocument
doc = PdfDocument("financial-statement.pdf")
header = None
all_rows = []
for i in range(doc.page_count()):
md = doc.to_markdown(i)
for line in md.split("\n"):
line = line.strip()
if not line.startswith("|") or line.startswith("|--"):
continue
cells = [cell.strip() for cell in line.split("|")[1:-1]]
if header is None:
header = cells
elif cells == header:
continue # Пропустити повторюваний заголовок
else:
all_rows.append(cells)
if header and all_rows:
df = pd.DataFrame(all_rows, columns=header)
# Впорядкувати числові колонки
for col in df.columns:
# Спробувати конвертувати колонки, що виглядають числовими
cleaned = df[col].str.replace(r"[$,%]", "", regex=True).str.strip()
try:
df[col] = pd.to_numeric(cleaned)
except (ValueError, TypeError):
pass # Залишити рядком
print(df.dtypes)
print(df.head(10))
df.to_csv("financial_data.csv", index=False)
Унаслідок виходить охайний DataFrame з правильними числовими типами, готовий до аналізу в pandas, побудови графіків у matplotlib чи завантаження в базу даних.
Складні таблиці: коли підключати pdfplumber
Розпізнавання таблиць у PDF Oxide добре справляється зі стандартними вирівняними таблицями. У складних випадках — об’єднані комірки, заголовки на кілька колонок, таблиці без рамок, багаторядковий вміст — спеціалізовані алгоритми pdfplumber надійніші:
import pdfplumber
with pdfplumber.open("complex-report.pdf") as pdf:
page = pdf.pages[0]
tables = page.extract_tables()
for table in tables:
for row in table:
print(row)
Коли що використовувати
| Сценарій | Рекомендовано |
|---|---|
| Прості вирівняні таблиці | PDF Oxide (у 29× швидше) |
| Таблиці в Markdown усієї сторінки | PDF Oxide |
| Складні об’єднані комірки / заголовки на кілька колонок | pdfplumber |
| Таблиці без рамок | pdfplumber |
| Пакетна обробка з наголосом на швидкість | PDF Oxide |
Поєднати обидва інструменти
Швидке видобування тексту — PDF Oxide, складні таблиці — pdfplumber:
from pdf_oxide import PdfDocument
import pdfplumber
# Швидке видобування повного тексту
doc = PdfDocument("report.pdf")
text = doc.extract_text(0)
# Прицільне видобування таблиць на складних сторінках
with pdfplumber.open("report.pdf") as pdf:
tables = pdf.pages[0].extract_tables()
Пов’язані сторінки
- Конвертація в Markdown — повна довідка з Markdown API
- Видобування тексту — звичайний текст і посимвольне видобування
- PDF Oxide vs pdfplumber — детальне порівняння
- PDF у Markdown — посібник із конвертації в Markdown