辅助处理excel表格,按列拆分大表为多个文件
这种技能遵循[excel-seafe-workflow]的四步法. .是一项面向实际任务的技能,主要用于Grouping使用熊猫, fan-out 使用 lxml terperse 单色-scan 多步输出.;本技能遵循 [[excel-sefefect-workflow]] 四步法;
从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。
执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。
This skill follows [[excel-safe-workflow]] four-step method. Grouping uses pandas, fan-out uses lxml iterparse single-scan multi-output. 本技能遵循 [[excel-safe-workflow]] 四步法。分组用 pandas,分流用 lxml iterparse 一次扫描多路输出。
把一张大表按某列的值拆成 N 个独立文件。
总表 (33万行) │ │ 按"申请人"拆分 Top 10 │ ├── 上海诺基亚贝尔.xlsx (1859行) ├── 上海泰康网络.xlsx (1301行) ├── ... (8个) └── 其他.xlsx (283145行)
| 要素 | 用户说 | 默认值 |
|---|---|---|
| 拆分列 | "按申请人拆""按年份分" | 必须明确 |
| Top N | "前10个""最多的20个" | 20 |
| 输出目录 | "放到 split 文件夹" | {原文件名}_split_{列名}/ |
import pandas as pd
FILE = '目标文件.xlsx'
SPLIT_COL = '列名'
df = pd.read_excel(FILE)
counts = df[SPLIT_COL].value_counts()
print(f'总行数: {len(df)}, 唯一值: {len(counts)}')
print(f'Top 10:')
for k, v in counts.head(10).items():
print(f' {k}: {v} 行')
{原文件名}_split/{拆分值}.xlsx(自动清理非法字符)核心思路:一次 iterparse 流式解析 XML,按行分流到各输出缓冲区,避免重复解析。
import pandas as pd, zipfile, os, shutil, re, time
from lxml import etree
from collections import defaultdict
S_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
FILE = '目标文件.xlsx'
SPLIT_COL = '列名'
TOP_N = 20
OUTPUT_DIR = FILE.replace('.xlsx', f'_split_{SPLIT_COL}')
# ====== 3.1 pandas 分组 ======
print(f'[1/4] pandas 分组...')
df = pd.read_excel(FILE).dropna(how='all') # 去掉空行(如有间隙)
total = len(df)
counts = df[SPLIT_COL].value_counts()
top_keys = set(counts.head(TOP_N).index.tolist()) if len(counts) > TOP_N else set(counts.index)
row_to_file = {}
file_sizes = defaultdict(int)
for key in top_keys:
safe = str(key).replace('/', '_').replace('\', '_').replace(':', '_')[:80]
indices = df.index[df[SPLIT_COL] == key].tolist()
for i in indices:
row_to_file[i + 2] = f'{safe}.xlsx'
file_sizes[f'{safe}.xlsx'] = len(indices)
other = df.index[~df[SPLIT_COL].isin(top_keys)].tolist()
if other:
for i in other:
row_to_file[i + 2] = '其他.xlsx'
file_sizes['其他.xlsx'] = len(other)
print(f' 将生成 {len(file_sizes)} 个文件')
# ====== 3.2 解压 ======
print(f'[2/4] 解压...')
TMP = FILE.replace('.xlsx', '_split_tmp')
if os.path.exists(TMP): shutil.rmtree(TMP)
os.makedirs(TMP)
with zipfile.ZipFile(FILE, 'r') as z:
z.extractall(TMP)
worksheets_dir = os.path.join(TMP, 'xl', 'worksheets')
orig_sheet = None
for sf in sorted(os.listdir(worksheets_dir)):
if sf.endswith('.xml') and sf.startswith('sheet'):
orig_sheet = os.path.join(worksheets_dir, sf)
break
# ====== 3.3 iterparse 流式分流 ======
print(f'[3/4] 流式分流...')
row_xml = defaultdict(list)
header_xml = []
tag = f'{{{S_NS}}}row'
for event, elem in etree.iterparse(orig_sheet, tag=tag):
r = int(elem.get('r'))
row_str = etree.tostring(elem, encoding='unicode')
if r == 1: # 表头行
header_xml.append(row_str)
elif r in row_to_file:
row_xml[row_to_file[r]].append(row_str)
elem.clear()
while elem.getprevious() is not None:
del elem.getparent()[0]
# ====== 3.4 生成输出文件 ======
print(f'[4/4] 生成输出文件...')
os.makedirs(OUTPUT_DIR, exist_ok=True)
# 构建 sheet XML 模板( 前后的结构)
tree_orig = etree.parse(orig_sheet, etree.XMLParser(huge_tree=True))
full_xml = etree.tostring(tree_orig.getroot(), encoding='unicode')
sd_start = full_xml.find('')
prefix = full_xml[:sd_start]
suffix = full_xml[sd_end + len(' '):]
for idx, (fname, rows) in enumerate(sorted(row_xml.items(), key=lambda x: -len(x[1]))):
fpath = os.path.join(OUTPUT_DIR, fname)
all_rows = ''.join(header_xml) + ''.join(rows)
new_xml = f'{prefix}{all_rows} {suffix}'
with open(orig_sheet, 'w', encoding='utf-8') as f:
f.write(new_xml)
with zipfile.ZipFile(fpath, 'w', zipfile.ZIP_DEFLATED) as zout:
for dirpath, _, filenames in os.walk(TMP):
for fn in filenames:
full = os.path.join(dirpath, fn)
zout.write(full, os.path.relpath(full, TMP).replace('\', '/'))
shutil.rmtree(TMP)
print(f'完成,输出: {OUTPUT_DIR}/')
import pandas as pd, os
total_out = 0
for f in os.listdir(OUTPUT_DIR):
if not f.endswith('.xlsx'): continue
fp = os.path.join(OUTPUT_DIR, f)
df = pd.read_excel(fp).dropna(how='all')
total_out += len(df)
if f != '其他.xlsx':
key = f.replace('.xlsx', '')
bad = df[df[SPLIT_COL] != key].shape[0]
if bad: print(f' ❌ {f}: {bad} 行错配')
print(f'输出总行: {total_out} (期望 {total})')
| 文件 | 行数 | 输出文件数 | pandas扫描 | iterparse分流 | 生成打包 | 总耗时 |
|---|---|---|---|---|---|---|
| 测试文件 | 914 | 6 | 1s | 0s | 0s | 1s |
| 主文件 | 29万 | 11 | 82s | 51s | 258s | ~6.5min |
生成打包阶段耗时较长是因为每个输出文件都包含完整的 sharedStrings.xml(273MB),11 个文件约 3GB 压缩量。迭代次数越多,此阶段越慢。
相关专题
热门下载
相关下载
精品课程
共162课时 | 45.4万人学习
共15课时 | 1.8万人学习
共28课时 | 3.6万人学习
最新文章