offer-catcher-agent-v2 / src /file_parser.py
hungryb's picture
Deploy Offer Catcher Agent demo
084a966 verified
Raw
History Blame Contribute Delete
5.22 kB
"""
File parser for Offer Catcher.
Supports: .txt, .md, .pdf, .docx
Pure-local parsing — no file saved to disk, no API calls.
"""
from io import BytesIO
from typing import Tuple
def extract_text_from_upload(uploaded_file) -> Tuple[str, str]:
"""
Read an uploaded file (Streamlit UploadedFile) and extract text.
Returns:
(text, message)
- text: extracted resume text, or "" on failure
- message: UI-friendly status message (success / error / warning)
"""
if uploaded_file is None:
return "", "未上传文件。"
filename = getattr(uploaded_file, "name", "unknown")
suffix = ""
if "." in filename:
suffix = "." + filename.rsplit(".", 1)[-1].lower()
# --- .txt / .md ---
if suffix in (".txt", ".md"):
return _read_text_file(uploaded_file, filename)
# --- .pdf ---
if suffix == ".pdf":
return _read_pdf(uploaded_file, filename)
# --- .docx ---
if suffix == ".docx":
return _read_docx(uploaded_file, filename)
# --- .doc (not supported) ---
if suffix == ".doc":
return (
"",
f"❌ 不支持 .doc 格式({filename})。\n"
f"请先将文件另存为 .docx 或 PDF,再重新上传。",
)
return "", f"❌ 不支持的文件格式:{suffix}{filename})"
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
def _read_text_file(uploaded_file, filename: str) -> Tuple[str, str]:
"""Read .txt / .md as text, trying multiple encodings."""
raw = uploaded_file.read()
# Reset pointer so downstream calls can re-read if needed
if hasattr(uploaded_file, "seek"):
try:
uploaded_file.seek(0)
except Exception:
pass
if not raw:
return "", f"❌ 文件为空({filename})。"
encodings = ["utf-8", "utf-8-sig", "gbk", "gb18030", "latin-1"]
text = None
for enc in encodings:
try:
text = raw.decode(enc)
break
except UnicodeDecodeError:
continue
if text is None:
return "", f"❌ 无法识别文件编码({filename})。请另存为 UTF-8 文本后重试。"
text = text.strip()
if len(text) < 30:
return "", f"⚠️ 文件内容过少(仅 {len(text)} 字符,{filename}),请检查文件是否正确。"
return text, f"✅ 已解析 {filename}{len(text)} 字符,文本格式)"
def _read_pdf(uploaded_file, filename: str) -> Tuple[str, str]:
"""Extract text from PDF using pypdf."""
try:
import pypdf
except ImportError:
return (
"",
f"❌ 服务器未安装 pypdf,无法解析 PDF({filename})。\n"
f"请在 requirements.txt 中添加 pypdf>=4.0.0 并重新部署。",
)
try:
uploaded_file.seek(0)
reader = pypdf.PdfReader(uploaded_file)
parts = []
for page in reader.pages:
try:
page_text = page.extract_text()
if page_text:
parts.append(page_text)
except Exception:
continue
text = "\n".join(parts).strip()
if len(text) < 30:
return (
"",
f"⚠️ PDF 解析结果过少(仅 {len(text)} 字符,{filename})。\n"
f"可能是扫描版 PDF,请尝试复制文本后粘贴,或上传 DOCX/PDF 文字版。",
)
return text, f"✅ 已解析 {filename}{len(text)} 字符,PDF {len(reader.pages)} 页)"
except Exception as e:
return "", f"❌ PDF 解析失败({filename}):{e}"
def _read_docx(uploaded_file, filename: str) -> Tuple[str, str]:
"""Extract text from DOCX using python-docx."""
try:
import docx
except ImportError:
return (
"",
f"❌ 服务器未安装 python-docx,无法解析 DOCX({filename})。\n"
f"请在 requirements.txt 中添加 python-docx>=1.1.0 并重新部署。",
)
try:
uploaded_file.seek(0)
doc = docx.Document(uploaded_file)
parts = []
# Paragraphs
for para in doc.paragraphs:
if para.text.strip():
parts.append(para.text.strip())
# Tables
for table in doc.tables:
for row in table.rows:
row_texts = []
for cell in row.cells:
t = cell.text.strip()
if t:
row_texts.append(t)
if row_texts:
parts.append(" | ".join(row_texts))
text = "\n".join(parts).strip()
if len(text) < 30:
return (
"",
f"⚠️ DOCX 解析结果过少(仅 {len(text)} 字符,{filename}),请检查文件是否正确。",
)
return text, f"✅ 已解析 {filename}{len(text)} 字符,DOCX)"
except Exception as e:
return "", f"❌ DOCX 解析失败({filename}):{e}"