Files
cnfinancialscraper/scripts/document_parser.py
T

398 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
文档解析模块
支持PDF、Word、Excel等金融文档解析
"""
import json
import re
import os
from pathlib import Path
from typing import Dict, Any, List, Optional
# 文档解析依赖检测
PDF_AVAILABLE = False
DOCX_AVAILABLE = False
XLSX_AVAILABLE = False
try:
import pypdf
PDF_AVAILABLE = True
except ImportError:
pass
try:
import PyPDF2
PDF_AVAILABLE = True
except ImportError:
pass
try:
from docx import Document as DocxDocument
DOCX_AVAILABLE = True
except ImportError:
pass
try:
import openpyxl
XLSX_AVAILABLE = True
except ImportError:
pass
SKILL_DATA_DIR = Path(__file__).parent.parent / "data"
def parse_pdf(file_path: str) -> Dict[str, Any]:
"""
解析PDF文档
Args:
file_path: PDF文件路径
Returns:
解析结果
"""
result = {
"file_type": "pdf",
"file_path": file_path,
"pages": 0,
"text_content": "",
"tables": [],
"financial_data": {}
}
if not PDF_AVAILABLE:
result["error"] = "PyPDF2未安装,请运行: pip install PyPDF2"
return result
try:
with open(file_path, 'rb') as f:
reader = PyPDF2.PdfReader(f)
result["pages"] = len(reader.pages)
# 提取文本
full_text = []
for page in reader.pages:
text = page.extract_text()
if text:
full_text.append(text)
result["text_content"] = "\n\n".join(full_text)
# 尝试提取财务数据
result["financial_data"] = extract_financial_numbers(result["text_content"])
except Exception as e:
result["error"] = f"PDF解析失败: {e}"
return result
def parse_docx(file_path: str) -> Dict[str, Any]:
"""
解析Word文档
Args:
file_path: DOCX文件路径
Returns:
解析结果
"""
result = {
"file_type": "docx",
"file_path": file_path,
"paragraphs": [],
"tables": [],
"text_content": "",
"financial_data": {}
}
if not DOCX_AVAILABLE:
result["error"] = "python-docx未安装,请运行: pip install python-docx"
return result
try:
doc = DocxDocument(file_path)
# 提取段落
paragraphs = []
for para in doc.paragraphs:
if para.text.strip():
paragraphs.append(para.text)
result["paragraphs"] = paragraphs
result["text_content"] = "\n\n".join(paragraphs)
# 提取表格
tables = []
for i, table in enumerate(doc.tables):
table_data = []
for row in table.rows:
row_data = [cell.text.strip() for cell in row.cells]
table_data.append(row_data)
tables.append(table_data)
result["tables"] = tables
# 财务数据
result["financial_data"] = extract_financial_numbers(result["text_content"])
except Exception as e:
result["error"] = f"DOCX解析失败: {e}"
return result
def parse_xlsx(file_path: str, sheet_name: Optional[str] = None) -> Dict[str, Any]:
"""
解析Excel文档
Args:
file_path: Excel文件路径
sheet_name: 指定工作表名称(默认第一个)
Returns:
解析结果
"""
result = {
"file_type": "xlsx",
"file_path": file_path,
"sheets": {},
"financial_data": {}
}
if not XLSX_AVAILABLE:
result["error"] = "openpyxl未安装,请运行: pip install openpyxl"
return result
try:
wb = openpyxl.load_workbook(file_path, data_only=True)
result["sheet_names"] = wb.sheetnames
# 读取指定sheet或全部
sheets_to_read = [sheet_name] if sheet_name else wb.sheetnames
for sheet in sheets_to_read:
ws = wb[sheet]
data = []
for row in ws.iter_rows(values_only=True):
row_data = [str(cell) if cell is not None else "" for cell in row]
if any(row_data):
data.append(row_data)
result["sheets"][sheet] = {
"rows": len(data),
"data": data[:100] # 限制前100行
}
# 财务数据
all_text = json.dumps(result["sheets"])
result["financial_data"] = extract_financial_numbers(all_text)
except Exception as e:
result["error"] = f"Excel解析失败: {e}"
return result
def parse_txt(file_path: str) -> Dict[str, Any]:
"""
解析文本文件
Args:
file_path: 文本文件路径
Returns:
解析结果
"""
result = {
"file_type": "txt",
"file_path": file_path,
"text_content": "",
"financial_data": {}
}
try:
with open(file_path, 'r', encoding='utf-8') as f:
result["text_content"] = f.read()
result["financial_data"] = extract_financial_numbers(result["text_content"])
except Exception as e:
try:
# 尝试GBK编码
with open(file_path, 'r', encoding='gbk') as f:
result["text_content"] = f.read()
result["financial_data"] = extract_financial_numbers(result["text_content"])
except:
result["error"] = f"文本解析失败: {e}"
return result
def extract_financial_numbers(text: str) -> Dict[str, Any]:
"""
从文本中提取财务数字
Args:
text: 文本内容
Returns:
财务数据字典
"""
data = {}
# 提取金额(亿/万)
amount_patterns = [
(r'([+-]?\d+\.?\d*)\s*亿元', 'amount_yi'),
(r'([+-]?\d+\.?\d*)\s*万元', 'amount_wan'),
(r'([+-]?\d+\.?\d*)\s*%,', 'percentage'),
]
amounts = []
percentages = []
for pattern, ptype in amount_patterns:
matches = re.findall(pattern, text)
for match in matches:
try:
value = float(match)
if ptype == 'amount_yi':
amounts.append(value)
elif ptype == 'percentage':
percentages.append(value)
except:
pass
if amounts:
data["amounts_yi"] = amounts[:10] # 取前10个
data["total_yi"] = sum(amounts)
if percentages:
data["percentages"] = percentages[:20]
# 提取常见财务指标
indicators = {
'净利润': r'净利润[::]\s*([+-]?\d+\.?\d*)',
'营业收入': r'营业收入[::]\s*([+-]?\d+\.?\d*)',
'总资产': r'总资产[::]\s*([+-]?\d+\.?\d*)',
'净资产': r'净资产[::]\s*([+-]?\d+\.?\d*)',
'每股收益': r'每股收益[((]EPS[))][::]\s*([+-]?\d+\.?\d*)',
'市盈率': r'市盈率[((]PE[))][::]\s*([+-]?\d+\.?\d*)',
'市净率': r'市净率[((]PB[))][::]\s*([+-]?\d+\.?\d*)',
}
for name, pattern in indicators.items():
match = re.search(pattern, text)
if match:
try:
data[name] = float(match.group(1))
except:
pass
return data
def parse_document(file_path: str, sheet_name: Optional[str] = None) -> Dict[str, Any]:
"""
通用文档解析接口
Args:
file_path: 文件路径
sheet_name: Excel指定工作表
Returns:
解析结果
"""
path = Path(file_path)
suffix = path.suffix.lower()
parsers = {
'.pdf': parse_pdf,
'.docx': parse_docx,
'.doc': parse_docx, # 尝试作为docx
'.xlsx': parse_xlsx,
'.xls': parse_xlsx,
'.txt': parse_txt,
'.md': parse_txt,
'.csv': parse_txt,
}
parser = parsers.get(suffix)
if not parser:
return {"error": f"不支持的文件类型: {suffix}"}
if suffix == '.xlsx' or suffix == '.xls':
return parser(file_path, sheet_name)
else:
return parser(file_path)
def extract_key_info(parsed_doc: Dict[str, Any]) -> str:
"""
从解析结果中提取关键信息摘要
Args:
parsed_doc: parse_document的返回结果
Returns:
关键信息摘要文本
"""
if "error" in parsed_doc:
return f"解析错误: {parsed_doc['error']}"
summary = f"【文档解析结果】\n\n"
summary += f"文件类型: {parsed_doc.get('file_type', 'unknown').upper()}\n"
if "pages" in parsed_doc:
summary += f"页数: {parsed_doc['pages']}\n"
if "paragraphs" in parsed_doc:
summary += f"段落数: {len(parsed_doc['paragraphs'])}\n"
if "tables" in parsed_doc:
summary += f"表格数: {len(parsed_doc['tables'])}\n"
if "sheets" in parsed_doc:
summary += f"工作表: {', '.join(parsed_doc.get('sheet_names', []))}\n"
# 财务数据摘要
fin_data = parsed_doc.get("financial_data", {})
if fin_data:
summary += "\n【提取的财务数据】\n"
for key, value in fin_data.items():
if key == "percentages":
summary += f"百分比数据: {value[:5]}...\n" if len(value) > 5 else f"百分比数据: {value}\n"
elif key == "amounts_yi":
summary += f"金额数据(亿元): {value[:5]}...\n" if len(value) > 5 else f"金额数据(亿元): {value}\n"
elif key == "total_yi":
summary += f"总金额: {value:.2f}亿元\n"
else:
summary += f"{key}: {value}\n"
# 文本预览
text = parsed_doc.get("text_content", "")
if text:
preview = text[:500].replace('\n', ' ')
summary += f"\n【内容预览】\n{preview}..."
return summary
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("用法: python document_parser.py <文件路径>")
print("示例: python document_parser.py ./report.pdf")
print("\n支持格式: PDF, DOCX, XLSX, TXT, MD, CSV")
sys.exit(1)
file_path = sys.argv[1]
if not os.path.exists(file_path):
print(f"文件不存在: {file_path}")
sys.exit(1)
print(f"正在解析: {file_path}\n")
result = parse_document(file_path)
print(extract_key_info(result))