上述程序运行后,委托方,仪器编号,功率座型号、功率座编号未能获取信息,excel表中只有地址等其他信息
时间: 2025-09-05 22:52:40 AIGC 浏览: 2
<think>
我们注意到委托方、仪器编号、功率座型号、功率座编号未能提取成功。这可能是由于以下原因:
1. 表格中字段名称可能有变体(如委托方可能写作“委 托 方”或“委托单位”)
2. 功率座信息可能存在于不同的行结构
3. 字段值的位置可能在更复杂的单元格合并中
解决方案:
1. 增强字段匹配的灵活性:使用模糊匹配或正则表达式放宽匹配条件
2. 改进功率座提取:考虑功率座信息可能跨越多行的情况
3. 增加调试输出:帮助识别提取失败的原因
我们将进行以下改进:
- 使用正则表达式匹配字段标题,忽略空格和标点
- 对功率座提取部分,增加跨行搜索深度
- 为每个文件添加详细的日志输出,以便追踪问题
修改后的代码:
</think>
针对提取失败的问题,我将提供一个增强版的解决方案,重点解决委托方、仪器编号、功率座信息的提取问题。以下是经过优化的代码:
```python
import os
import re
from docx import Document
import pandas as pd
from tkinter import Tk, filedialog
from collections import defaultdict
def parse_table_structure(table):
"""解析表格结构,处理合并单元格"""
row_map = defaultdict(list)
for row_idx, row in enumerate(table.rows):
col_idx = 0
for cell in row.cells:
# 获取单元格跨行跨列属性
row_span = cell._tc.get('rowSpan')
col_span = cell._tc.get('gridSpan')
row_span = int(row_span) if row_span else 1
col_span = int(col_span) if col_span else 1
# 构建单元格元数据
cell_data = {
'text': re.sub(r'\s+', ' ', cell.text).strip(), # 规范化空白字符
'row_start': row_idx,
'row_end': row_idx + row_span - 1,
'col_start': col_idx,
'col_end': col_idx + col_span - 1
}
row_map[row_idx].append(cell_data)
col_idx += col_span
return row_map
def extract_target_info(docx_path):
"""提取指定字段信息:委托方、仪器型号、仪器编号、地址、制造商、功率座"""
doc = Document(docx_path)
if not doc.tables:
print(f"警告: {docx_path} 中未找到表格")
return None
table = doc.tables[0]
row_map = parse_table_structure(table)
# 初始化目标字段(包含可能的变体)
results = {
"委托方": "",
"地址": "",
"仪器型号": "",
"仪器编号": "",
"制造商": "",
"功率座_型号": "",
"功率座_编号": "",
"文件名": os.path.basename(docx_path)
}
# 目标字段映射(支持多种可能的字段标题)
FIELD_MAPPING = {
"委托方": ["委托方", "委 托 方", "委托单位", "客户名称"],
"地址": ["地址", "地 址", "联系地址", "单位地址"],
"仪器型号": ["仪器型号", "型 号", "设备型号"],
"仪器编号": ["仪器编号", "编 号", "序列号", "设备编号", "仪器序号"],
"制造商": ["制造商", "生产厂家", "生产商", "制 造 商"],
"功率座": ["功率座", "功率计探头", "功率探头"]
}
# 状态标志
in_power_meter_section = False
power_meter_found = False
# 创建一个单元格位置到文本的映射用于调试
debug_info = []
# 遍历所有行
for row_idx, cells in sorted(row_map.items()):
row_text = " ".join([cell['text'] for cell in cells if cell['text']])
debug_info.append(f"行 {row_idx}: {row_text}")
# 1. 检查是否进入功率座区域
if any(keyword in row_text for keyword in ["所用主要测量标准仪器", "主要测量设备", "标准设备"]):
in_power_meter_section = True
continue
# 2. 检查是否离开功率座区域
if any(keyword in row_text for keyword in ["规程(标准)代号", "校准依据", "校准方法"]):
in_power_meter_section = False
continue
# 3. 处理功率座信息(仅提取功率座)
if in_power_meter_section and any(keyword in row_text for keyword in FIELD_MAPPING["功率座"]):
power_meter_found = True
# 提取功率座详细信息
for col_idx, cell in enumerate(cells):
cell_text = cell['text']
# 型号提取
if any(keyword in cell_text for keyword in ["型 号", "型号", "模 型"]):
# 向右查找值
for next_col in range(col_idx + 1, len(cells)):
if cells[next_col]['text']:
results["功率座_型号"] = cells[next_col]['text']
break
# 向下查找值(最多向下3行)
if not results["功率座_型号"]:
for next_row in range(row_idx+1, min(row_idx+4, len(row_map))):
next_cells = row_map[next_row]
if col_idx < len(next_cells) and next_cells[col_idx]['text']:
results["功率座_型号"] = next_cells[col_idx]['text']
break
# 编号提取
if any(keyword in cell_text for keyword in ["出厂编号", "序列号", "编 号", "编号"]):
# 向右查找值
for next_col in range(col_idx + 1, len(cells)):
if cells[next_col]['text']:
results["功率座_编号"] = cells[next_col]['text']
break
# 向下查找值(最多向下3行)
if not results["功率座_编号"]:
for next_row in range(row_idx+1, min(row_idx+4, len(row_map))):
next_cells = row_map[next_row]
if col_idx < len(next_cells) and next_cells[col_idx]['text']:
results["功率座_编号"] = next_cells[col_idx]['text']
break
# 找到功率座后直接跳出循环
if power_meter_found:
continue
# 4. 处理主信息字段(使用灵活的匹配策略)
for cell in cells:
cell_text = cell['text']
# 委托方提取
if not results["委托方"] and any(keyword in cell_text for keyword in FIELD_MAPPING["委托方"]):
# 尝试提取委托方名称
if ":" in cell_text or ":" in cell_text:
# 从字段标题后的内容提取
match = re.search(r"[::]\s*(.+)", cell_text)
if match:
results["委托方"] = match.group(1).strip()
else:
# 向右搜索
if cell['col_end'] + 1 < len(cells):
next_cell = cells[cell['col_end'] + 1]
if next_cell['text']:
results["委托方"] = next_cell['text']
# 向下搜索
elif not results["委托方"] and row_idx + 1 in row_map:
next_row = row_map[row_idx+1]
if cell['col_start'] < len(next_row):
next_cell = next_row[cell['col_start']]
if next_cell['text']:
results["委托方"] = next_cell['text']
# 仪器编号提取
if not results["仪器编号"] and any(keyword in cell_text for keyword in FIELD_MAPPING["仪器编号"]):
# 尝试从当前单元格提取编号
if ":" in cell_text or ":" in cell_text:
match = re.search(r"[::]\s*(\w+[\w-]*)", cell_text)
if match:
results["仪器编号"] = match.group(1).strip()
else:
# 向右搜索
if cell['col_end'] + 1 < len(cells):
next_cell = cells[cell['col_end'] + 1]
if next_cell['text']:
results["仪器编号"] = next_cell['text']
# 向下搜索
elif not results["仪器编号"] and row_idx + 1 in row_map:
next_row = row_map[row_idx+1]
if cell['col_start'] < len(next_row):
next_cell = next_row[cell['col_start']]
if next_cell['text']:
results["仪器编号"] = next_cell['text']
# 保存调试信息
debug_dir = os.path.join(os.path.dirname(docx_path), "debug")
os.makedirs(debug_dir, exist_ok=True)
debug_file = os.path.join(debug_dir, f"{os.path.basename(docx_path)}.txt")
with open(debug_file, "w", encoding="utf-8") as f:
f.write("\n".join(debug_info))
return results
def batch_process_word_docs():
root = Tk()
root.withdraw()
folder_path = filedialog.askdirectory(title="选择包含Word文档的文件夹")
if not folder_path:
print("未选择文件夹,操作取消")
return
# 获取所有docx文件
docx_files = [
os.path.join(folder_path, f)
for f in os.listdir(folder_path)
if f.lower().endswith(('.docx', '.doc'))
]
if not docx_files:
print("所选文件夹中未找到Word文档")
return
# 提取所有文件的信息
all_info = []
failed_files = []
for docx_file in docx_files:
try:
info = extract_target_info(docx_file)
if info:
all_info.append(info)
print(f"√ 已处理: {os.path.basename(docx_file)}")
else:
failed_files.append(os.path.basename(docx_file))
print(f"× 处理失败: {os.path.basename(docx_file)}")
except Exception as e:
failed_files.append(os.path.basename(docx_file))
print(f"! 处理错误: {os.path.basename(docx_file)} - {str(e)}")
if not all_info:
print("未提取到任何信息")
return
# 创建DataFrame并导出Excel
df = pd.DataFrame(all_info)
# 设置列顺序(仅包含目标字段)
target_columns = [
'文件名', '委托方', '地址', '仪器型号', '仪器编号', '制造商',
'功率座_型号', '功率座_编号'
]
# 确保DataFrame包含所有目标列
for col in target_columns:
if col not in df.columns:
df[col] = ""
df = df[target_columns]
# 保存结果
output_path = os.path.join(folder_path, "提取结果.xlsx")
df.to_excel(output_path, index=False)
# 保存失败文件列表
if failed_files:
with open(os.path.join(folder_path, "失败文件.txt"), "w", encoding="utf-8") as f:
f.write("\n".join(failed_files))
print(f"成功导出 {len(all_info)} 条记录到: {output_path}")
if failed_files:
print(f"失败文件数: {len(failed_files)},详见失败文件.txt")
os.startfile(output_path)
if __name__ == "__main__":
batch_process_word_docs()
```
### 代码优化详解:
#### 1. 增强字段匹配灵活性(解决委托方提取问题)
```python
# 目标字段映射(支持多种可能的字段标题)
FIELD_MAPPING = {
"委托方": ["委托方", "委 托 方", "委托单位", "客户名称"],
"地址": ["地址", "地 址", "联系地址", "单位地址"],
"仪器型号": ["仪器型号", "型 号", "设备型号"],
"仪器编号": ["仪器编号", "编 号", "序列号", "设备编号", "仪器序号"],
"制造商": ["制造商", "生产厂家", "生产商", "制 造 商"],
"功率座": ["功率座", "功率计探头", "功率探头"]
}
# 委托方提取(灵活匹配)
if not results["委托方"] and any(keyword in cell_text for keyword in FIELD_MAPPING["委托方"]):
# 多种提取策略
if ":" in cell_text or ":" in cell_text:
# 从字段标题后的内容提取
match = re.search(r"[::]\s*(.+)", cell_text)
if match:
results["委托方"] = match.group(1).strip()
else:
# 向右或向下搜索相邻单元格
...
```
- 支持字段标题的多种变体(如"委 托 方"、"委托单位")
- 多种提取策略:直接从标题文本提取、向右搜索、向下搜索
- 正则表达式处理字段标题与值在同一单元格的情况
#### 2. 仪器编号提取优化
```python
# 仪器编号提取(支持不同格式)
if not results["仪器编号"] and any(keyword in cell_text for keyword in FIELD_MAPPING["仪器编号"]):
# 尝试从当前单元格提取编号
if ":" in cell_text or ":" in cell_text:
# 匹配字母数字组合的编号(支持连字符)
match = re.search(r"[::]\s*(\w+[\w-]*)", cell_text)
if match:
results["仪器编号"] = match.group(1).strip()
else:
# 相邻单元格搜索
...
```
- 支持多种编号格式(纯数字、字母数字组合、带连字符)
- 优化正则表达式提取编号值
- 增强相邻单元格搜索逻辑
#### 3. 功率座信息提取增强
```python
# 功率座区域检测增强
if any(keyword in row_text for keyword in ["所用主要测量标准仪器", "主要测量设备", "标准设备"]):
in_power_meter_section = True
continue
# 功率座识别增强
if in_power_meter_section and any(keyword in row_text for keyword in FIELD_MAPPING["功率座"]):
power_meter_found = True
# 深度搜索功率座信息
...
# 型号和编号提取(支持多种标题)
if any(keyword in cell_text for keyword in ["型 号", "型号", "模 型"]):
# 深度搜索(最多向下3行)
for next_row in range(row_idx+1, min(row_idx+4, len(row_map))):
...
```
- 增强功率座区域检测(支持多种区域标题)
- 功率座识别支持多种名称变体
- 深度搜索策略(最多向下3行查找相关信息)
- 型号和编号提取支持多种标题格式
#### 4. 调试与错误处理增强
```python
# 创建调试信息
debug_info = []
for row_idx, cells in sorted(row_map.items()):
row_text = " ".join([cell['text'] for cell in cells if cell['text']])
debug_info.append(f"行 {row_idx}: {row_text}")
# 保存调试信息
debug_dir = os.path.join(os.path.dirname(docx_path), "debug")
os.makedirs(debug_dir, exist_ok=True)
debug_file = os.path.join(debug_dir, f"{os.path.basename(docx_path)}.txt")
with open(debug_file, "w", encoding="utf-8") as f:
f.write("\n".join(debug_info))
# 错误处理
failed_files = []
for docx_file in docx_files:
try:
info = extract_target_info(docx_file)
...
except Exception as e:
failed_files.append(os.path.basename(docx_file))
print(f"! 处理错误: {os.path.basename(docx_file)} - {str(e)}")
# 保存失败文件列表
if failed_files:
with open(os.path.join(folder_path, "失败文件.txt"), "w", encoding="utf-8") as f:
f.write("\n".join(failed_files))
```
- 详细的表格结构调试输出
- 每个文档生成单独的调试文件
- 完善的异常处理和错误报告
- 记录失败文件列表便于后续分析
### 解决方案优势:
1. **强大的字段识别能力**:
- 支持字段标题的多种变体(如"委 托 方"、"委托单位")
- 使用灵活的匹配策略而非严格匹配
- 适应文档格式的微小变化
2. **深度内容提取**:
- 功率座信息提取深度达3行
- 支持型号和编号的多种标题格式
- 优化编号正则表达式(支持字母数字组合)
3. **全面的调试支持**:
- 为每个文档生成表格结构调试文件
- 清晰的错误提示和失败文件追踪
- 详细的处理状态反馈(√成功 ×失败 !错误)
4. **健壮的错误处理**:
- 异常捕获防止程序崩溃
- 失败文件单独记录
- 错误信息包含具体原因
### 使用说明:
1. 运行脚本后选择包含Word文档的文件夹
2. 程序会:
- 处理每个文档并提取指定字段
- 在debug文件夹中生成调试信息
- 创建失败文件列表(如有)
3. 结果将保存为"提取结果.xlsx"并自动打开
### 提取失败排查步骤:
1. 检查debug文件夹中的文本文件,查看文档的表格结构
2. 查看"失败文件.txt"中的失败文档列表
3. 在失败文档的调试文件中搜索目标字段标题:
- 委托方相关:搜索"委托"、"客户"
- 仪器编号:搜索"编号"、"序列号"
- 功率座:搜索"功率座"、"功率探头"
4. 根据实际字段标题调整代码中的`FIELD_MAPPING`字典
阅读全文