Files
claudeskill/skills/excel-report-converter/SKILL.md

290 lines
10 KiB
Markdown
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.
---
name: excel-report-converter
description: 当用户需要创建Python脚本将报表形式的Excel数据转换为结构化数据表时使用此技能。当用户提到将Excel报表转换为数据表、解析Excel报表结构、创建Excel数据转换脚本、从打印模板格式提取数据、或者有类似"请购单维护"、"订单打印模板"等报表格式需要转换为标准表格时,即使没有明确说"创建脚本",也应该触发此技能。这个技能专门处理那些面向打印/展示的报表布局(数据分散在多行多列、有表头/明细/页脚区块)到标准关系型数据表的转换。
---
# Excel报表数据转换技能
这个技能帮助你创建Python脚本将各种报表形式的Excel数据转换为标准的数据表格式。
## 适用场景
当你遇到以下情况时,使用此技能:
- **报表式布局**:数据不是标准的行列表格,而是分散在多个区域
- **有表头/明细/页脚结构**:一个文档包含主信息、明细列表、汇总信息
- **打印模板格式**为打印设计的Excel文件需要提取其中的数据
- **多区块重复**同一个Excel文件包含多个相同格式的报表区块
- **字段映射复杂**:目标字段与源单元格位置有复杂的对应关系
- **大数据量提取(性能敏感)**需要快速提取数千行以上、包含大量单元格的复杂Excel报表
---
## 🚀 性能优化核心技术 (提速法则)
在处理中大型Excel报表时传统的 `openpyxl` 逐个单元格读取方法会导致极严重的性能瓶颈。本技能强制采用以下高阶优化方案:
1. **空间换时间(内存二维数组)**:禁止在循环中频繁调用 `sheet.cell(row, col).value`。必须使用 `sheet.iter_rows(values_only=True)` 将整表数据一次性读入 Python 的 `List[List]` 中。后续所有的查找全部基于内存列表索引进行,速度可提升数十倍。
2. **预先构建映射字典**:将 `column_index_from_string` (字母转数字索引) 等固定操作移出循环,在脚本初始化时预先计算好映射字典。
3. **Pandas 向量化降维打击**:在保存 Excel 自动调整列宽时,放弃 `openpyxl` 的逐单元格遍历,直接利用 Pandas 的底层 C 语言级别操作 `df[col].astype(str).map(len).max()` 秒算列宽。
4. **避开 `read_only` 的“公式陷阱”**:绝不能为了加载速度盲目开启 `read_only=True`,这会导致由 Excel 隐式公式(如自动递增序号 `=A1+1`)生成的值返回 `None`。必须坚持使用默认加载模式配合 `data_only=True`,然后依靠“内存二维数组”来解决速度问题。
---
## 工作流程
### 第一步分析源Excel文件结构并构建内存视图
使用openpyxl读取Excel文件并立即将其转换为内存二维数组以提升后续处理速度
```python
import openpyxl
from typing import List, Any
# 1. 加载工作簿 (保留 data_only=True, 弃用 read_only=True 保证公式值完整)
wb = openpyxl.load_workbook(file_path, data_only=True)
sheet = wb.active
# 2. 【核心提速机制】将整表数据一次性抽取为 Python 二维数组
# 填充一行 [None],并为每一行填充一列 [None],使后续列表索引(1-based)与Excel坐标严格对齐
excel_data = [[None]]
for row in sheet.iter_rows(values_only=True):
excel_data.append([None] + list(row))
wb.close() # 释放文件句柄
# 3. 辅助读取函数 (替代慢速的 sheet.cell().value)
def get_val(data: List[List[Any]], row: int, col: int) -> Any:
try:
if row < len(data) and col < len(data[row]):
return data[row][col]
except IndexError:
pass
return None
````
### 第二步:识别报表区块
大多数报表式Excel有以下特征按优先级在 `excel_data` 中进行检测
|**特征**|**检测方法**|**示例**|
|---|---|---|
|区块标题|A列包含特定关键词|"请购单维护""订单"|
|表头标签|冒号结尾的标签|"请购单号:""日期:"|
|明细表头|包含列名的行|"行号""物料编码""数量"|
|数据行|标签行下方连续的数据|非空的具体数据值|
|页脚行|包含"制单""审批"|"制单人:""审批人:"|
**识别规则**
1. 区块开始通常在A列包含报表类型名称
2. 表头区域区块开始后3-6包含带冒号的字段标签
3. 明细表头表头区域后A列包含"行号"或类似列名
4. 明细数据明细表头后连续的非空行
5. 页脚区域明细数据后包含签名/审批信息
### 第三步:设计数据结构
根据识别结果设计输出表结构
**扁平化单表**
- 每条明细记录携带完整的表头信息
- 适合数据分析和导出
### 第四步:生成转换脚本
基于分析结果生成包含以下函数的Python脚本
Python
```
# 必需的核心函数
def find_sections(excel_data)
"""识别所有报表区块的起始行"""
def extract_header_data(excel_data, start_row)
"""提取表头信息"""
def extract_line_items(excel_data, header_row, section_end)
"""提取明细行数据"""
def extract_footer_data(excel_data, line_items_end)
"""提取页脚信息"""
def parse_section(excel_data, start_row, next_section)
"""解析单个报表区块"""
def parse_excel_file(file_path)
"""读取Excel文件转换为二维数组并解析所有区块"""
def save_to_excel(data_df, output_path)
"""保存转换结果利用Pandas向量化计算列宽"""
```
## 字段提取模式
### 模式1固定偏移量利用预计算
当表头字段位置固定时使用:
Python
```
# 提取表头信息 (从内存数组快速读取)
def extract_header_data(excel_data, start_row):
header = {}
header['请购单号'] = get_val(excel_data, start_row + 3, 2) # Row+3, B列(2)
return header
```
### 模式2标签查找
当字段位置不固定但标签唯一时使用:
Python
```
def find_field_by_label(excel_data, label, start_row, search_range=10):
"""通过标签查找字段位置"""
max_row = len(excel_data) - 1
for row in range(start_row, min(start_row + search_range, max_row + 1)):
# 假设标签在前10列中
for col in range(1, min(11, len(excel_data[row]))):
cell_value = get_val(excel_data, row, col)
if cell_value and label in str(cell_value):
# 返回值的位置(通常在标签的右侧)
return get_val(excel_data, row, col + 1)
return None
```
## 明细行提取策略
### 策略:预计算列映射字典 (极速匹配)
避免在双重循环中调用 `column_index_from_string`。
Python
```
from openpyxl.utils import column_index_from_string
LINE_ITEM_COLUMNS = {
'A': '行号', 'B': '排产号', 'C': '物料编码'
}
# 全局预计算列索引
LINE_ITEM_COLUMNS_IDX = {
column_index_from_string(col): field
for col, field in LINE_ITEM_COLUMNS.items()
}
def extract_line_items(excel_data, header_row, section_end):
line_items = []
for row in range(header_row + 1, section_end):
# ... 判断跳出逻辑 ...
item = {}
for col_num, field_name in LINE_ITEM_COLUMNS_IDX.items():
item[field_name] = get_val(excel_data, row, col_num)
if any(item.values()):
line_items.append(item)
return line_items
```
## 处理特殊情况
### 1. 空区块处理
当某个区块没有明细数据时,仍需创建一条记录:
Python
```
if not line_items:
# 创建一条空记录,保留表头和页脚信息
record = {**header_data, **footer_data}
for field in LINE_ITEM_COLUMNS.values():
record[field] = None
flat_records.append(record)
```
## 输出格式
### Excel格式结合 Pandas 向量化提速)
Python
```
from openpyxl.utils import get_column_letter
def save_to_excel(data_df, output_path):
"""保存为Excel文件并极速调整列宽"""
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
data_df.to_excel(writer, sheet_name='转换结果', index=False)
worksheet = writer.sheets['转换结果']
# 【提速】利用 Pandas 的向量化操作一次性算出最大列宽
for idx, col in enumerate(data_df.columns):
max_len = max(data_df[col].astype(str).map(len).max() if not data_df.empty else 0, len(str(col)))
adjusted_width = min(max_len + 2, 50)
col_letter = get_column_letter(idx + 1)
worksheet.column_dimensions[col_letter].width = adjusted_width
```
## 依赖库
脚本需要以下依赖,确保在运行前安装:
Bash
```
pip install openpyxl pandas
```
## 验证清单
生成脚本后,验证以下内容:
- [ ] 成功识别所有报表区块
- [ ] 表头字段提取正确
- [ ] 明细行数据完整
- [ ] 页脚信息准确
- [ ] 空区块得到正确处理
- [ ] 输出文件格式正确
- [ ] 数据类型准确(日期、数字等)
- [ ] 没有重复或遗漏的记录
## 调试技巧
当脚本出现问题时:
1. **打印中间结果**在每个函数中添加print语句查看提取的数据
2. **检查单元格值**确认openpyxl读取的值与预期一致
3. **验证索引**:确保行号和列号计算正确
4. **分步测试**:先测试单个区块,确认正确后再处理全部
5. **对比原文件**在Excel中查看原始数据和提取结果的差异
## 常见问题
**Q: 为什么提取时有些序列号或公式计算的值变成了 `None`**
A: 这是因为在 `openpyxl` 中错误开启了 `read_only=True` 模式,导致依靠 Excel 公式生成的值无法正确读取缓存。**解决方案**:去掉 `read_only=True`,只保留 `data_only=True`,并使用二维数组提取法来保障速度。
**Q: 输出的数据需要进一步处理怎么办?**
A: 脚本生成后,可以在将其转换为 Pandas DataFrame 之后,利用 Pandas 强大的生态添加数据清洗、验证、格式转换(如日期格式化)等功能。
## 最佳实践
1. **绝对优先使用内存二维数组**:直接摒弃 `sheet.cell().value` 的传统思维,这是报表转换脚本能商用的性能基石。
2. **预先计算,拒绝重复**:所有能够确定位置或索引关系的映射字典,全部放在全局作用域一次性计算完成。
3. **先分析,后编码**:花时间理解报表结构和分页/分块标识,比直接编码更高效。
4. **异常容错机制**:数据提取行要进行 `if any(item.values())` 判断,过滤纯空行;对于越界索引使用 `try-except` 包裹。