258 lines
7.0 KiB
Python
258 lines
7.0 KiB
Python
"""
|
||
Docx文档提取服务
|
||
|
||
使用Mammoth库提取Word文档文本
|
||
支持.docx格式(不支持老版.doc)
|
||
"""
|
||
|
||
import mammoth
|
||
from pathlib import Path
|
||
from typing import Dict, Any
|
||
from loguru import logger
|
||
|
||
|
||
def extract_docx_mammoth(file_path: str) -> Dict[str, Any]:
|
||
"""
|
||
使用Mammoth提取Docx文本
|
||
|
||
Mammoth特点:
|
||
- 转换为纯文本或HTML
|
||
- 保留基本格式信息
|
||
- 处理表格、列表等结构
|
||
|
||
Args:
|
||
file_path: Docx文件路径
|
||
|
||
Returns:
|
||
{
|
||
"success": True,
|
||
"text": "提取的文本内容",
|
||
"format": "plain_text",
|
||
"metadata": {
|
||
"char_count": 字符数,
|
||
"has_tables": 是否包含表格,
|
||
"file_size": 文件大小
|
||
}
|
||
}
|
||
"""
|
||
try:
|
||
file_path_obj = Path(file_path)
|
||
|
||
# 验证文件存在
|
||
if not file_path_obj.exists():
|
||
return {
|
||
"success": False,
|
||
"error": f"文件不存在: {file_path}",
|
||
"text": "",
|
||
"metadata": {}
|
||
}
|
||
|
||
# 验证文件格式
|
||
if file_path_obj.suffix.lower() != '.docx':
|
||
return {
|
||
"success": False,
|
||
"error": f"不支持的文件格式: {file_path_obj.suffix},仅支持.docx",
|
||
"text": "",
|
||
"metadata": {}
|
||
}
|
||
|
||
logger.info(f"开始提取Docx文件: {file_path_obj.name}")
|
||
|
||
# 使用Mammoth提取纯文本
|
||
with open(file_path, "rb") as docx_file:
|
||
result = mammoth.extract_raw_text(docx_file)
|
||
text = result.value # 提取的文本
|
||
messages = result.messages # 警告/错误信息
|
||
|
||
# 检查是否有警告
|
||
if messages:
|
||
logger.warning(f"Mammoth提取警告: {len(messages)}个")
|
||
for msg in messages:
|
||
logger.debug(f" - {msg.type}: {msg.message}")
|
||
|
||
# 简单的质量检查
|
||
char_count = len(text)
|
||
if char_count == 0:
|
||
logger.warning("提取的文本为空")
|
||
return {
|
||
"success": False,
|
||
"error": "文档内容为空或无法提取",
|
||
"text": "",
|
||
"metadata": {
|
||
"char_count": 0,
|
||
"file_size": file_path_obj.stat().st_size
|
||
}
|
||
}
|
||
|
||
# 简单判断是否包含表格(通过制表符或特殊结构)
|
||
has_tables = '\t' in text or '|' in text
|
||
|
||
logger.info(f"Docx提取成功: {char_count}个字符")
|
||
|
||
return {
|
||
"success": True,
|
||
"text": text,
|
||
"format": "plain_text",
|
||
"metadata": {
|
||
"char_count": char_count,
|
||
"has_tables": has_tables,
|
||
"file_size": file_path_obj.stat().st_size,
|
||
"warnings": len(messages)
|
||
}
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"Docx提取失败: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"error": str(e),
|
||
"text": "",
|
||
"metadata": {}
|
||
}
|
||
|
||
|
||
def extract_docx_html(file_path: str) -> Dict[str, Any]:
|
||
"""
|
||
使用Mammoth提取Docx为HTML格式(保留更多格式)
|
||
|
||
Args:
|
||
file_path: Docx文件路径
|
||
|
||
Returns:
|
||
{
|
||
"success": True,
|
||
"html": "HTML格式的文本",
|
||
"format": "html",
|
||
"metadata": {...}
|
||
}
|
||
"""
|
||
try:
|
||
file_path_obj = Path(file_path)
|
||
|
||
if not file_path_obj.exists():
|
||
return {
|
||
"success": False,
|
||
"error": f"文件不存在: {file_path}",
|
||
"html": "",
|
||
"metadata": {}
|
||
}
|
||
|
||
logger.info(f"开始提取Docx为HTML: {file_path_obj.name}")
|
||
|
||
# 提取为HTML
|
||
with open(file_path, "rb") as docx_file:
|
||
result = mammoth.convert_to_html(docx_file)
|
||
html = result.value
|
||
messages = result.messages
|
||
|
||
if messages:
|
||
logger.warning(f"HTML转换警告: {len(messages)}个")
|
||
|
||
logger.info(f"HTML提取成功: {len(html)}个字符")
|
||
|
||
return {
|
||
"success": True,
|
||
"html": html,
|
||
"format": "html",
|
||
"metadata": {
|
||
"html_length": len(html),
|
||
"file_size": file_path_obj.stat().st_size,
|
||
"warnings": len(messages)
|
||
}
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"HTML提取失败: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"error": str(e),
|
||
"html": "",
|
||
"metadata": {}
|
||
}
|
||
|
||
|
||
def validate_docx_file(file_path: str) -> Dict[str, Any]:
|
||
"""
|
||
验证Docx文件的有效性
|
||
|
||
Args:
|
||
file_path: 文件路径
|
||
|
||
Returns:
|
||
{
|
||
"valid": True/False,
|
||
"reason": "原因",
|
||
"file_info": {文件信息}
|
||
}
|
||
"""
|
||
try:
|
||
file_path_obj = Path(file_path)
|
||
|
||
# 检查文件存在
|
||
if not file_path_obj.exists():
|
||
return {
|
||
"valid": False,
|
||
"reason": "文件不存在"
|
||
}
|
||
|
||
# 检查后缀
|
||
if file_path_obj.suffix.lower() != '.docx':
|
||
return {
|
||
"valid": False,
|
||
"reason": f"不支持的格式: {file_path_obj.suffix}(仅支持.docx)"
|
||
}
|
||
|
||
# 检查文件大小(限制50MB)
|
||
file_size = file_path_obj.stat().st_size
|
||
max_size = 50 * 1024 * 1024 # 50MB
|
||
|
||
if file_size > max_size:
|
||
return {
|
||
"valid": False,
|
||
"reason": f"文件过大: {file_size / 1024 / 1024:.2f}MB(限制50MB)"
|
||
}
|
||
|
||
if file_size == 0:
|
||
return {
|
||
"valid": False,
|
||
"reason": "文件为空"
|
||
}
|
||
|
||
# 尝试打开文件(基本有效性检查)
|
||
try:
|
||
with open(file_path, "rb") as f:
|
||
# 读取前4个字节检查ZIP签名(docx本质是ZIP文件)
|
||
signature = f.read(4)
|
||
if signature != b'PK\x03\x04':
|
||
return {
|
||
"valid": False,
|
||
"reason": "不是有效的Docx文件(ZIP签名错误)"
|
||
}
|
||
except Exception as e:
|
||
return {
|
||
"valid": False,
|
||
"reason": f"无法读取文件: {str(e)}"
|
||
}
|
||
|
||
return {
|
||
"valid": True,
|
||
"reason": "文件有效",
|
||
"file_info": {
|
||
"filename": file_path_obj.name,
|
||
"size": file_size,
|
||
"size_mb": round(file_size / 1024 / 1024, 2)
|
||
}
|
||
}
|
||
|
||
except Exception as e:
|
||
return {
|
||
"valid": False,
|
||
"reason": f"验证失败: {str(e)}"
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|