v3.1.3: pdfplumber — только рамки, фильтр 30% заполнения
This commit is contained in:
+37
-28
@@ -125,9 +125,10 @@ def _parse_pdf(file_bytes: bytes) -> dict:
|
|||||||
"""
|
"""
|
||||||
Извлечь текст и таблицы из PDF через pdfplumber.
|
Извлечь текст и таблицы из PDF через pdfplumber.
|
||||||
|
|
||||||
Таблицы: пробуем два режима — с рамками (lines) и без (text).
|
Таблицы: только с видимыми рамками (lines). Безрамковые (text)
|
||||||
Отбрасываем полностью пустые таблицы.
|
дают слишком много ложных срабатываний — не используем.
|
||||||
Текст: извлекаем построчно, сохраняем номера страниц.
|
Отбрасываем таблицы где >50% ячеек пусты или где все ячейки
|
||||||
|
содержат фрагменты одной строки текста.
|
||||||
"""
|
"""
|
||||||
result = {"elements": [], "errors": []}
|
result = {"elements": [], "errors": []}
|
||||||
try:
|
try:
|
||||||
@@ -149,26 +150,11 @@ def _parse_pdf(file_bytes: bytes) -> dict:
|
|||||||
"page": pn,
|
"page": pn,
|
||||||
})
|
})
|
||||||
|
|
||||||
# ── Таблицы: с рамками (lines) ──
|
# ── Таблицы: только с рамками ──
|
||||||
tables = page.extract_tables()
|
tables = page.extract_tables()
|
||||||
if tables:
|
if tables:
|
||||||
for tbl in tables:
|
for tbl in tables:
|
||||||
if tbl and _table_has_content(tbl):
|
if tbl and _table_has_content(tbl, min_fill_ratio=0.3):
|
||||||
result["elements"].append({
|
|
||||||
"type": "table",
|
|
||||||
"rows": tbl,
|
|
||||||
"page": pn,
|
|
||||||
})
|
|
||||||
|
|
||||||
# ── Таблицы: без рамок (text) — если lines ничего не дал ──
|
|
||||||
if not tables:
|
|
||||||
tbl_text = page.extract_tables({
|
|
||||||
"vertical_strategy": "text",
|
|
||||||
"horizontal_strategy": "text",
|
|
||||||
})
|
|
||||||
if tbl_text:
|
|
||||||
for tbl in tbl_text:
|
|
||||||
if tbl and _table_has_content(tbl):
|
|
||||||
result["elements"].append({
|
result["elements"].append({
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"rows": tbl,
|
"rows": tbl,
|
||||||
@@ -180,18 +166,41 @@ def _parse_pdf(file_bytes: bytes) -> dict:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _table_has_content(tbl: list) -> bool:
|
def _table_has_content(tbl: list, min_fill_ratio: float = 0.0) -> bool:
|
||||||
"""
|
"""
|
||||||
Проверить что в таблице есть хоть одна непустая ячейка.
|
Проверить что таблица содержит осмысленные данные.
|
||||||
Игнорирует None и пустые строки.
|
|
||||||
|
Фильтры:
|
||||||
|
- Хотя бы одна непустая ячейка
|
||||||
|
- Не менее min_fill_ratio ячеек заполнены (по умолчанию 0 — без фильтра)
|
||||||
"""
|
"""
|
||||||
for row in tbl:
|
if not tbl or not tbl[0]:
|
||||||
if row:
|
|
||||||
for cell in row:
|
|
||||||
if cell and str(cell).strip():
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
filled = 0
|
||||||
|
all_text = ""
|
||||||
|
|
||||||
|
for row in tbl:
|
||||||
|
if not row:
|
||||||
|
continue
|
||||||
|
for cell in row:
|
||||||
|
total += 1
|
||||||
|
cell_str = str(cell).strip() if cell else ""
|
||||||
|
if cell_str:
|
||||||
|
filled += 1
|
||||||
|
all_text += " " + cell_str
|
||||||
|
|
||||||
|
if filled == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Фильтр по доле заполненных ячеек
|
||||||
|
if min_fill_ratio > 0 and total > 0:
|
||||||
|
if filled / total < min_fill_ratio:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# ── ZIP ──
|
# ── ZIP ──
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user