tabula-py вместо camelot и libreoffice, v1.30
This commit is contained in:
+1
-1
@@ -5,6 +5,6 @@ requests
|
||||
psycopg2-binary
|
||||
python-dotenv
|
||||
pdfplumber
|
||||
camelot-py[cv]
|
||||
tabula-py
|
||||
httpx
|
||||
h2
|
||||
|
||||
+24
-50
@@ -124,16 +124,24 @@ def _parse_doc(file_bytes: bytes) -> dict:
|
||||
def _parse_pdf(file_bytes: bytes) -> dict:
|
||||
result = {"elements": [], "errors": []}
|
||||
|
||||
# Попробовать libreoffice → docx (точные таблицы)
|
||||
# Попробовать tabula (Java) — точнее таблицы
|
||||
tables_from_tabula = False
|
||||
try:
|
||||
docx_result = _pdf_via_libreoffice(file_bytes)
|
||||
if docx_result["elements"]:
|
||||
return docx_result
|
||||
result["errors"].extend(docx_result.get("errors", []))
|
||||
import tabula
|
||||
tables = tabula.read_pdf(io.BytesIO(file_bytes), pages="all", multiple_tables=True)
|
||||
for tbl in tables:
|
||||
rows = [list(tbl.columns)] + tbl.values.tolist()
|
||||
result["elements"].append({
|
||||
"type": "table",
|
||||
"rows": rows,
|
||||
})
|
||||
tables_from_tabula = len(tables) > 0
|
||||
except ImportError:
|
||||
result["errors"].append("tabula not installed, using pdfplumber")
|
||||
except Exception as e:
|
||||
result["errors"].append(f"libreoffice pdf: {e}")
|
||||
result["errors"].append(f"tabula error: {e}")
|
||||
|
||||
# Fallback: pdfplumber
|
||||
# pdfplumber: текст + таблицы (если tabula не сработал)
|
||||
try:
|
||||
import pdfplumber
|
||||
with pdfplumber.open(io.BytesIO(file_bytes)) as pdf:
|
||||
@@ -150,55 +158,21 @@ def _parse_pdf(file_bytes: bytes) -> dict:
|
||||
"text": line,
|
||||
"page": pn,
|
||||
})
|
||||
tables = page.extract_tables()
|
||||
for tbl in tables:
|
||||
if tbl:
|
||||
result["elements"].append({
|
||||
"type": "table",
|
||||
"rows": tbl,
|
||||
"page": pn,
|
||||
})
|
||||
if not tables_from_tabula:
|
||||
tables = page.extract_tables()
|
||||
for tbl in tables:
|
||||
if tbl:
|
||||
result["elements"].append({
|
||||
"type": "table",
|
||||
"rows": tbl,
|
||||
"page": pn,
|
||||
})
|
||||
except Exception as e:
|
||||
result["errors"].append(f"pdfplumber: {e}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _pdf_via_libreoffice(file_bytes: bytes) -> dict:
|
||||
"""Конвертировать PDF → DOCX через libreoffice, затем python-docx."""
|
||||
result = {"elements": [], "errors": []}
|
||||
tmp_pdf = None
|
||||
tmp_dir = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
|
||||
tmp.write(file_bytes)
|
||||
tmp_pdf = tmp.name
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
subprocess.run(
|
||||
["libreoffice", "--headless", "--convert-to", "docx", "--outdir", tmp_dir, tmp_pdf],
|
||||
timeout=60, capture_output=True,
|
||||
)
|
||||
docx_files = [f for f in os.listdir(tmp_dir) if f.endswith(".docx")]
|
||||
if docx_files:
|
||||
with open(os.path.join(tmp_dir, docx_files[0]), "rb") as f:
|
||||
result = _parse_docx(f.read())
|
||||
else:
|
||||
result["errors"].append("libreoffice PDF→DOCX: no output")
|
||||
except FileNotFoundError:
|
||||
result["errors"].append("libreoffice not installed")
|
||||
except Exception as e:
|
||||
result["errors"].append(f"libreoffice PDF→DOCX: {e}")
|
||||
finally:
|
||||
if tmp_pdf and os.path.exists(tmp_pdf):
|
||||
os.unlink(tmp_pdf)
|
||||
if tmp_dir and os.path.exists(tmp_dir):
|
||||
for f in os.listdir(tmp_dir):
|
||||
os.unlink(os.path.join(tmp_dir, f))
|
||||
os.rmdir(tmp_dir)
|
||||
return result
|
||||
|
||||
|
||||
# ── ZIP ──
|
||||
|
||||
def _parse_zip(file_bytes: bytes) -> dict:
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.29</span></span>
|
||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.30</span></span>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
Reference in New Issue
Block a user