v3.1.2: фильтр пустых таблиц + text strategy для pdfplumber
This commit is contained in:
+48
-7
@@ -122,12 +122,21 @@ def _parse_doc(file_bytes: bytes) -> dict:
|
|||||||
# ── PDF ──
|
# ── PDF ──
|
||||||
|
|
||||||
def _parse_pdf(file_bytes: bytes) -> dict:
|
def _parse_pdf(file_bytes: bytes) -> dict:
|
||||||
|
"""
|
||||||
|
Извлечь текст и таблицы из PDF через pdfplumber.
|
||||||
|
|
||||||
|
Таблицы: пробуем два режима — с рамками (lines) и без (text).
|
||||||
|
Отбрасываем полностью пустые таблицы.
|
||||||
|
Текст: извлекаем построчно, сохраняем номера страниц.
|
||||||
|
"""
|
||||||
result = {"elements": [], "errors": []}
|
result = {"elements": [], "errors": []}
|
||||||
try:
|
try:
|
||||||
import pdfplumber
|
import pdfplumber
|
||||||
with pdfplumber.open(io.BytesIO(file_bytes)) as pdf:
|
with pdfplumber.open(io.BytesIO(file_bytes)) as pdf:
|
||||||
for page in pdf.pages:
|
for page in pdf.pages:
|
||||||
pn = page.page_number
|
pn = page.page_number
|
||||||
|
|
||||||
|
# ── Текст: каждая непустая строка → paragraph ──
|
||||||
text = page.extract_text()
|
text = page.extract_text()
|
||||||
if text:
|
if text:
|
||||||
for line in text.split("\n"):
|
for line in text.split("\n"):
|
||||||
@@ -139,19 +148,51 @@ def _parse_pdf(file_bytes: bytes) -> dict:
|
|||||||
"text": line,
|
"text": line,
|
||||||
"page": pn,
|
"page": pn,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# ── Таблицы: с рамками (lines) ──
|
||||||
tables = page.extract_tables()
|
tables = page.extract_tables()
|
||||||
for tbl in tables:
|
if tables:
|
||||||
if tbl:
|
for tbl in tables:
|
||||||
result["elements"].append({
|
if tbl and _table_has_content(tbl):
|
||||||
"type": "table",
|
result["elements"].append({
|
||||||
"rows": tbl,
|
"type": "table",
|
||||||
"page": pn,
|
"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({
|
||||||
|
"type": "table",
|
||||||
|
"rows": tbl,
|
||||||
|
"page": pn,
|
||||||
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result["errors"].append(f"pdfplumber: {e}")
|
result["errors"].append(f"pdfplumber: {e}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _table_has_content(tbl: list) -> bool:
|
||||||
|
"""
|
||||||
|
Проверить что в таблице есть хоть одна непустая ячейка.
|
||||||
|
Игнорирует None и пустые строки.
|
||||||
|
"""
|
||||||
|
for row in tbl:
|
||||||
|
if row:
|
||||||
|
for cell in row:
|
||||||
|
if cell and str(cell).strip():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ── ZIP ──
|
# ── ZIP ──
|
||||||
|
|
||||||
def _parse_zip(file_bytes: bytes) -> dict:
|
def _parse_zip(file_bytes: bytes) -> dict:
|
||||||
|
|||||||
Reference in New Issue
Block a user