
Automate Excel
- 9 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
automate-excel is a skill that reads, writes, merges, transforms, and validates Excel and CSV files via bundled scripts.
About
This skill automates reading, writing, merging, transforming, and validating Excel files via bundled Python scripts. It supports merging sheets, CSV conversion, filtering, splitting, deduplication, column-based aggregation, validation, and VLOOKUP-style table joins. A developer uses it for batch spreadsheet processing and report generation from tabular data.
- Reads, writes, merges, transforms, and validates Excel .xlsx/.xls files
- Bundled scripts for merge, filter, split, dedupe, aggregate, and VLOOKUP
- Handles CSV-to-Excel conversion and batch report generation
Automate Excel by the numbers
- 9 all-time installs (skills.sh)
- Ranked #496 of 687 Office & Documents skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
automate-excel capabilities & compatibility
Free and local; runs bundled Python scripts on Excel/CSV files, no API keys.
- Works with
- excel
- Use cases
- data analysis
- Runs
- Runs locally
- Pricing
- Free
What automate-excel says it does
Automates reading, writing, merging, transforming, and validating Excel (.xlsx/.xls) files.
两个表按一列对齐合并(VLOOKUP)
按列分组并求和/计数/平均
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill automate-excelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Automate reading, merging, transforming, and validating Excel/CSV files for batch processing and reporting.
Who is it for?
Batch spreadsheet work: merging, filtering, deduping, aggregating, and validating Excel/CSV data.
Skip if: Live spreadsheet dashboards or non-tabular office docs; it is a batch file-processing toolkit.
When should I use this skill?
the user works with spreadsheets, .xlsx files, CSV-to-Excel conversion, or batch Excel processing.
What you get
- Merged or split Excel files
- CSV/Excel conversions
- Aggregated and validated reports
By the numbers
- 11 bundled task scripts (merge, filter, split, dedupe, aggregate, validate, VLOOKUP, and more)
Files
Excel 自动化处理
在用户需要处理 Excel 文件、表格数据、批量转换或报表生成时应用本 skill。
| 你想做的事 | 调用的脚本 | 典型参数 |
|---|---|---|
| 多个 Excel 或多个 sheet 合成一张表 | merge_sheets.py | --inputs 文件或目录 --output out.xlsx |
| 把某 sheet 导出成 CSV | excel_to_csv.py | --input file.xlsx --output file.csv |
| 把 CSV 转成 Excel | csv_to_excel.py | --input a.csv --output a.xlsx 或 --inputs a.csv b.csv |
| 按条件筛选行(等于/大于/包含) | filter_excel.py | --where "列名=值" 或 "列名>100"、"列名~北京" |
| 按行数拆成多个文件,或按某列取值拆分 | split_excel.py | --by-rows 5000 或 --by-column 地区 |
| 按某列去重 | deduplicate_excel.py | --keys 编号 --keep first/last |
| 按列分组并求和/计数/平均 | aggregate_excel.py | --group-by 地区 --agg "销售额:sum" |
| 检查必须列、重复键、空行 | validate_excel.py | --require-cols 列名 --key-cols 列名 |
| 只保留/重命名部分列 | select_columns.py | --columns 列1,列2 --rename "旧:新" |
| 两个表按一列对齐合并(VLOOKUP) | merge_tables.py | --left a.xlsx --right b.xlsx --on 键列 |
| 主表依次跟多个表做 VLOOKUP | vlookup_multi.py | --main 主.xlsx --lookups "表1.xlsx:键列" "表2.xlsx:键列" |
| 行列转置 | transpose_excel.py | --input in.xlsx --output out.xlsx |
| 用数据表按行填模板里的 {{列名}} | template_fill.py | --template t.xlsx --data d.csv --output out.xlsx |
| 重命名工作表 | rename_sheets.py | --rename "Sheet1:新名" 或 --prefix "2024_" |
| 条件格式(大于/小于/重复值/色阶) | format_conditional.py | --column C --rule gt --value 100 --fill red |
| 某列当文本显示,避免科学计数法 | format_columns_as_text.py | --columns 身份证号,订单号 |
本地直接运行:进入本 skill 所在目录(或把 scripts/ 加入路径),先 pip install -r scripts/requirements.txt,再执行 python scripts/脚本名.py --help 看参数,或按上表传参运行。
技术栈
- 读写 .xlsx:
openpyxl(保留格式、公式、多工作表) - 数据分析/透视:
pandas+openpyxl引擎 - 旧格式 .xls:
xlrd(只读)
优先用 openpyxl 做单元格级操作和格式保留;需要筛选、聚合、合并多表时用 pandas。
依赖
pip install openpyxl pandas xlrd或使用 skill 自带:pip install -r scripts/requirements.txt
通用流程
1. 确认输入:文件路径、工作表名或索引、是否有表头、编码(CSV 时)。 2. 读取:用 openpyxl 或 pandas 按需读取整表/区域。 3. 处理:按用户需求转换、过滤、合并、计算。 4. 写出:指定输出路径与格式(.xlsx/.csv);必要时保留原格式或新建工作簿。 5. 校验:检查行数、关键列、重复值或业务规则。
读取 Excel
整表为 list of dict(保留表头):
import openpyxl
wb = openpyxl.load_workbook("input.xlsx", read_only=True, data_only=True)
ws = wb.active # 或 wb["Sheet1"]
rows = list(ws.iter_rows(min_row=1, values_only=True))
header = rows[0]
data = [dict(zip(header, row)) for row in rows[1:]]
wb.close()用 pandas(适合分析、过滤、合并):
import pandas as pd
df = pd.read_excel("input.xlsx", sheet_name=0, engine="openpyxl")
# sheet_name 可为 0、"Sheet1" 或 [0, 1] 多表指定区域:
# openpyxl
for row in ws["A1:D10"]:
...
# pandas
df = pd.read_excel("input.xlsx", usecols="A:D", header=0, nrows=100)写入 Excel
新建并写入(openpyxl):
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment
wb = Workbook()
ws = wb.active
ws.title = "结果"
ws.append(["列A", "列B", "列C"])
for row in data_rows:
ws.append(row)
ws["A1"].font = Font(bold=True)
wb.save("output.xlsx")用 pandas 写出多表:
with pd.ExcelWriter("output.xlsx", engine="openpyxl") as writer:
df1.to_excel(writer, sheet_name="汇总", index=False)
df2.to_excel(writer, sheet_name="明细", index=False)追加到已有文件(先加载再写):
wb = openpyxl.load_workbook("existing.xlsx")
ws = wb["Sheet1"]
for row in new_rows:
ws.append(row)
wb.save("existing.xlsx")常见任务速查
| 任务 | 做法 |
|---|---|
| 多文件合并 | merge_sheets.py 或 pandas read_excel + pd.concat + to_excel。 |
| CSV ↔ Excel | excel_to_csv.py / csv_to_excel.py 或 pandas 读写。 |
| 按条件筛选 | filter_excel.py 或 df[df["列"] == 值] / df.query()。 |
| 按行/按列拆分 | split_excel.py(按行数或按某列取值分文件)。 |
| 去重 | deduplicate_excel.py 或 df.drop_duplicates(subset=["列"], keep="first")。 |
| 按列聚合 | aggregate_excel.py 或 df.groupby("列").agg({"数值列": "sum"})。 |
| 校验 | validate_excel.py。 |
| 选择/重命名列 | select_columns.py。 |
| 两表按键合并 | merge_tables.py(left/inner/outer)。 |
| 行列转置 | transpose_excel.py。 |
| 模板填充 | template_fill.py({{列名}} 占位符)。 |
| 重命名 sheet | rename_sheets.py。 |
| 多表 VLOOKUP | vlookup_multi.py(主表 + 多个查找表依次左连接)。 |
| 条件格式 | format_conditional.py(大于/小于/介于/重复值/色阶)。 |
| 列格式为文本 | format_columns_as_text.py(避免长数字科学计数法)。 |
| 保留原格式写数据 | 用 openpyxl 加载原文件,只改写目标单元格,再 save。 |
批量处理
当用户需要处理目录下多个 Excel 时:
1. 用 pathlib.Path("目录").glob("*.xlsx") 枚举文件。 2. 对每个文件读取 → 处理 → 可汇总到一个 DataFrame 或分别写出。 3. 输出可为一个合并文件,或 原名_out.xlsx 等约定;在 SKILL 中明确输出命名规则。 4. 出错时记录文件名和异常,继续处理其余文件,最后汇总报错列表。
校验与错误处理
- 读取前用
Path(file).exists()检查文件存在。 - 表为空或缺少预期列时给出明确提示(列名/行数)。
- 写入前若目标文件已存在,按用户要求覆盖或换名;大文件考虑
write_only=True或分块。 - 捕获
openpyxl.utils.exceptions.InvalidFileException、KeyError(工作表名)等并返回可读错误信息。
工具脚本
skill 目录下 scripts/ 提供可执行脚本,优先在用户环境中运行脚本而非临时写长代码。
| 脚本 | 功能 |
|---|---|
| merge_sheets.py | 多 Excel 或同文件多 sheet 合并为一张表 |
| excel_to_csv.py | 指定 sheet 导出为 CSV |
| csv_to_excel.py | CSV 转 Excel(单/多 CSV → 多 sheet) |
| filter_excel.py | 按列条件筛选(=、>、<、~ 包含)输出 Excel/CSV |
| split_excel.py | 按行数分片或按某列取值拆成多个文件 |
| deduplicate_excel.py | 按指定列去重,保留 first/last |
| aggregate_excel.py | 按列分组聚合(sum/count/mean/min/max) |
| validate_excel.py | 校验必须列、重复键、空行 |
| select_columns.py | 选择/重命名/排序列 |
| merge_tables.py | 两表按键列合并(VLOOKUP 式,left/inner/outer) |
| transpose_excel.py | 行列转置 |
| template_fill.py | 用数据表按行填充模板中的 {{列名}} 占位符 |
| rename_sheets.py | 重命名工作表(原名:新名、索引:新名、前缀/后缀) |
| vlookup_multi.py | 多表 VLOOKUP:主表依次与多个查找表左连接 |
| format_conditional.py | 按条件格式化(gt/lt/介于/重复值/色阶) |
| format_columns_as_text.py | 将指定列设为文本格式,避免纯数字显示为科学计数法 |
用法见各脚本 --help 或 reference.md。
更多说明
- 详细 API 与参数见 reference.md。
- 更多场景示例见 examples.md。
{
"ownerId": "kn703552hwsfcpgxn1vsnpmy4n82c74j",
"slug": "automate-excel",
"version": "0.1.3",
"publishedAt": 1772774736664
}{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "automate-excel",
"installedVersion": "0.1.3",
"installedAt": 1776070889696
}
Excel 自动化 — 示例
示例 1:多文件合并为一个 Excel
需求:data/ 下多个 .xlsx,表结构相同,合并为一个表并写到一个新 Excel。
import pandas as pd
from pathlib import Path
files = list(Path("data").glob("*.xlsx"))
dfs = [pd.read_excel(f, engine="openpyxl") for f in files]
merged = pd.concat(dfs, ignore_index=True)
merged.to_excel("data/merged.xlsx", index=False)示例 2:按条件筛选并导出 CSV
需求:从 report.xlsx 的「订单」表里筛选「状态」为「已完成」,导出为 CSV。
import pandas as pd
df = pd.read_excel("report.xlsx", sheet_name="订单", engine="openpyxl")
done = df[df["状态"] == "已完成"]
done.to_csv("orders_done.csv", index=False, encoding="utf-8-sig")示例 3:保留原表格式并写入新数据
需求:在已有模板 template.xlsx 的「汇总」表从第 2 行起写入新数据,不动表头和格式。
import openpyxl
wb = openpyxl.load_workbook("template.xlsx")
ws = wb["汇总"]
for i, row in enumerate(new_data_rows, start=2):
for j, val in enumerate(row, start=1):
ws.cell(row=i, column=j, value=val)
wb.save("output.xlsx")示例 4:使用脚本合并多表
python scripts/merge_sheets.py --inputs file1.xlsx file2.xlsx --output merged.xlsx
python scripts/merge_sheets.py --inputs ./data --output merged.xlsx示例 5:校验并检查重复键
python scripts/validate_excel.py --input data.xlsx --key-cols 编号,日期 --require-cols 名称,金额示例 6:CSV 转 Excel(多 CSV 多 sheet)
python scripts/csv_to_excel.py --inputs a.csv b.csv --output report.xlsx
python scripts/csv_to_excel.py --input data.csv --output data.xlsx --sheet-name 原始数据示例 7:按条件筛选并输出
python scripts/filter_excel.py --input orders.xlsx --where "状态=已完成" --where "金额>100" --output done.xlsx
python scripts/filter_excel.py --input users.xlsx --where "城市~北京" --output beijing.csv示例 8:按行数或按列拆分
python scripts/split_excel.py --input large.xlsx --by-rows 5000 --output-dir ./chunks
python scripts/split_excel.py --input sales.xlsx --by-column 地区 --output-dir ./by_region示例 9:去重与聚合
python scripts/deduplicate_excel.py --input data.xlsx --keys 编号 --keep last --output unique.xlsx
python scripts/aggregate_excel.py --input sales.xlsx --group-by 地区 --agg "销售额:sum,数量:sum" --output summary.xlsx
python scripts/aggregate_excel.py --input orders.xlsx --group-by 部门,月份 --agg "金额:sum" "订单数:count"示例 10:选择列与重命名
python scripts/select_columns.py --input data.xlsx --columns 姓名,部门,金额 --output out.xlsx
python scripts/select_columns.py --input data.xlsx --columns 姓名,部门,金额 --rename "部门:dept,金额:amount" --output out.csv示例 11:两表按键合并(VLOOKUP)
python scripts/merge_tables.py --left 订单.xlsx --right 客户.xlsx --on 客户ID --output 订单带客户.xlsx
python scripts/merge_tables.py --left a.xlsx --right b.xlsx --on 编号 --how inner --output merged.xlsx示例 12:行列转置
python scripts/transpose_excel.py --input data.xlsx --output transposed.xlsx
python scripts/transpose_excel.py --input data.xlsx --output transposed.xlsx --first-col-as-header示例 13:模板填充
模板中某行单元格写 {{姓名}}、{{金额}} 等,数据表有对应列;每行数据生成一行填充结果。
python scripts/template_fill.py --template report_template.xlsx --data items.csv --output report.xlsx
python scripts/template_fill.py --template t.xlsx --data d.xlsx --pattern-row 2 --output out.xlsx示例 14:重命名 sheet
python scripts/rename_sheets.py --input file.xlsx --rename "Sheet1:汇总" "Sheet2:明细" --output out.xlsx
python scripts/rename_sheets.py --input file.xlsx --rename "0:首页" "1:数据"
python scripts/rename_sheets.py --input file.xlsx --prefix "2024_" --output out.xlsx示例 15:多表 VLOOKUP
主表依次与多个查找表按指定键列左连接,每次追加查找表中的列。
python scripts/vlookup_multi.py --main 订单.xlsx --lookups "客户.xlsx:客户ID" "产品.xlsx:产品ID" --output 订单完整.xlsx
python scripts/vlookup_multi.py --main main.xlsx --lookups "a.xlsx:编号" "b.xlsx:ID" --output merged.xlsx示例 16:条件格式
对指定区域设置大于/小于/介于/重复值高亮或双色色阶。
python scripts/format_conditional.py --input data.xlsx --range "C2:C100" --rule gt --value 100 --fill red --output out.xlsx
python scripts/format_conditional.py --input data.xlsx --column D --rule duplicates --fill yellow
python scripts/format_conditional.py --input data.xlsx --range "E2:E50" --rule between --min 0 --max 1 --fill green
python scripts/format_conditional.py --input data.xlsx --range "F2:F100" --color-scale --output out.xlsx示例 17:列设为文本格式(避免科学计数法)
长数字列(身份证号、订单号、条码等)在 Excel 中易被显示为科学计数法,将列设为文本格式并写为字符串即可完整显示。
python scripts/format_columns_as_text.py --input data.xlsx --columns 身份证号,订单号 --output out.xlsx
python scripts/format_columns_as_text.py --input data.xlsx --columns A,B,C --output out.xlsx
python scripts/format_columns_as_text.py --input data.xlsx --columns 编号 --sheet 明细 --output out.xlsxExcel 自动化 — API 与参数参考
openpyxl 常用
openpyxl.load_workbook(path, read_only=False, data_only=False, keep_vba=False)read_only:大文件时 True 节省内存,此时不可写。data_only=True:读单元格时取计算后的值而非公式。wb.sheetnames:工作表名列表。wb.active:当前活动表;wb["Sheet1"]按名取表。ws.iter_rows(min_row, max_row, min_col, max_col, values_only=True):按行迭代。ws["A1"].value、ws.cell(row=1, column=1).value:读单元格。ws.append([a, b, c]):在最后一行后追加一行。- 写出:
wb.save(path);若为 read_only 打开的,需用另一 Workbook 写入。
pandas read_excel
pd.read_excel(io, sheet_name=0, header=0, names=None, usecols=None, index_col=None, engine="openpyxl")sheet_name:0、"Sheet1"、或 [0, 1] 多表(返回 dict)。header:表头行索引。usecols:如 "A:D" 或 [0, 1, 2]。nrows:只读前 n 行。- 引擎:.xlsx 用
openpyxl,.xls 用xlrd。
pandas to_excel / ExcelWriter
df.to_excel(excel_writer, sheet_name="Sheet1", index=True, startrow=0, startcol=0)- 多表:
with pd.ExcelWriter(path, engine="openpyxl") as w: df1.to_excel(w, sheet_name="A"); df2.to_excel(w, sheet_name="B")
scripts 参数速查
merge_sheets.py
--inputs:输入文件或目录(多个用空格)。--output:输出 .xlsx 路径。--sheet:若输入为单文件,可指定要合并的 sheet 名(默认全部)。--header-row:表头行索引,默认 0。
excel_to_csv.py
--input:.xlsx 文件。--output:输出 .csv 路径。--sheet:工作表名或索引,默认第一个。--encoding:如 utf-8-sig。--sep:分隔符,默认,。
validate_excel.py
--input:.xlsx 文件。--sheet:工作表名或索引。--key-cols:用于检查重复的列名,逗号分隔。--require-cols:必须存在的列名,逗号分隔。- 退出码:0 通过,非 0 校验失败。
csv_to_excel.py
--input:单个 CSV(与--inputs二选一)。--inputs:多个 CSV,每个转为一个 sheet。--output:输出 .xlsx。--encoding、--sep:CSV 编码与分隔符。--sheet-name:单文件时的 sheet 名。
filter_excel.py
--input、--output:输入 .xlsx,输出 .xlsx 或 .csv。--sheet:工作表名或索引。--where:条件,可多次(AND)。格式:列名=值、列名>值、列名~子串(包含)等。
split_excel.py
--input、--sheet:输入 .xlsx 与工作表。--by-rows:每 N 行一个文件。--by-column:按该列取值拆分,每个取值一个文件。--output-dir、--prefix:输出目录与行分片时的文件名前缀。
deduplicate_excel.py
--input、--output:输入/输出 .xlsx 或 .csv。--sheet:工作表名或索引。--keys:去重键列名,逗号分隔。--keep:first 或 last。
aggregate_excel.py
--input、--output:输入 .xlsx,输出可选(默认输入名_agg.xlsx)。--sheet:工作表名或索引。--group-by:分组列,逗号分隔。--agg:聚合,如列名:sum、列名:count,可多次或逗号分隔。支持 sum/count/mean/min/max。
select_columns.py
--input、--output:输入 .xlsx,输出 .xlsx 或 .csv。--columns:保留的列名,按此顺序,逗号分隔。--rename:重命名 旧名:新名,逗号分隔。
merge_tables.py
--left、--right:左表/右表 .xlsx。--on:键列名(可逗号分隔多键)。--output:输出 .xlsx 或 .csv。--how:left(默认)/inner/outer。--sheet-left、--sheet-right:左右表 sheet。--suffixes:重名列后缀,默认 _left,_right。
transpose_excel.py
--input、--output:输入/输出 .xlsx 或 .csv。--sheet:工作表名或索引。--first-col-as-header:转置后用第一列作为列名。
template_fill.py
--template、--data:模板 .xlsx、数据 .xlsx 或 .csv。--output:输出 .xlsx。--pattern-row:模板中占位符所在行号(1-based),默认 2。--sheet-template、--sheet-data:模板/数据 sheet。模板单元格内 {{列名}} 会被替换为数据行对应列的值。
rename_sheets.py
--input、--output:输入 .xlsx,输出可选(默认覆盖)。--rename:原名:新名 或 索引:新名(索引从 0 起),可多次。--prefix、--suffix:为所有 sheet 名加前缀/后缀。
vlookup_multi.py
--main:主表 .xlsx。--lookups:查找表 文件:键列名,如 "ref.xlsx:ID",可多个,依次左连接。--output:输出 .xlsx 或 .csv。--sheet-main:主表 sheet。查找表默认每个文件的第一个 sheet。--suffix:与主表重名列在查找表中的后缀,默认 _lookup。
format_conditional.py
--input、--output:输入 .xlsx,输出可选(默认覆盖)。--sheet:工作表名或索引。--range或--column:应用区域(如 A2:A100)或列字母(自动从第 2 行到最大行)。--rule:gt/gte/lt/lte/eq/between/duplicates。--value:gt/lt/eq 等的比较值。--min、--max:between 时的下限、上限。--fill:满足条件时的填充色(red、yellow 或六位十六进制如 FF6B6B)。--color-scale:使用双色色阶(红-绿),忽略 --rule/--value。
format_columns_as_text.py
--input、--output:输入 .xlsx,输出可选(默认覆盖)。--sheet:工作表名或索引。--columns:要设为文本的列,逗号分隔;可为列名(与表头一致,如 身份证号,订单号)或列字母(如 A,B,C)。--header-row:表头行号(1-based),默认 1。脚本会将该列所有单元格设为数字格式 "@" 并将值转为字符串存储,避免长数字显示为科学计数法。
openpyxl>=3.1.0
pandas>=2.0.0
xlrd>=2.0.0
#!/usr/bin/env python3
"""生成测试用 Excel/CSV,供 run_all_tests 使用。"""
import os
from pathlib import Path
import pandas as pd
TEST_DIR = Path(__file__).resolve().parent / "_test_out"
TEST_DIR.mkdir(parents=True, exist_ok=True)
os.chdir(TEST_DIR)
# 主表:编号, 姓名, 地区, 金额, 销售额, 数量, 状态, 日期
df_main = pd.DataFrame({
"编号": ["A001", "A002", "A003", "A004", "A005"],
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"地区": ["北京", "上海", "北京", "广州", "上海"],
"金额": [100, 200, 150, 300, 250],
"销售额": [1000, 2000, 1500, 3000, 2500],
"数量": [10, 20, 15, 30, 25],
"状态": ["已完成", "进行中", "已完成", "已完成", "进行中"],
"日期": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04", "2024-01-05"],
})
df_main.to_excel("sample.xlsx", index=False, sheet_name="Sheet1")
# 第二个 Excel(多 sheet 合并用)
df_extra = pd.DataFrame({
"编号": ["B001", "B002"],
"姓名": ["孙八", "周九"],
"地区": ["深圳", "杭州"],
"金额": [180, 220],
"销售额": [1800, 2200],
"数量": [18, 22],
"状态": ["已完成", "进行中"],
"日期": ["2024-01-06", "2024-01-07"],
})
df_extra.to_excel("sample2.xlsx", index=False, sheet_name="Sheet1")
# 右表(merge_tables / vlookup):编号 + 补充列
df_lookup = pd.DataFrame({
"编号": ["A001", "A002", "A003", "A004", "A005"],
"部门": ["销售", "技术", "销售", "市场", "技术"],
"备注": ["备注1", "备注2", "备注3", "备注4", "备注5"],
})
df_lookup.to_excel("lookup.xlsx", index=False, sheet_name="Sheet1")
# CSV
df_main.to_csv("sample.csv", index=False, encoding="utf-8-sig")
# 模板:第一行表头,第二行占位符 {{姓名}} {{金额}}
wb_tpl = __import__("openpyxl").Workbook()
ws = wb_tpl.active
ws["A1"], ws["B1"], ws["C1"] = "姓名", "金额", "地区"
ws["A2"], ws["B2"], ws["C2"] = "{{姓名}}", "{{金额}}", "{{地区}}"
wb_tpl.save("template.xlsx")
print("Test data written to", TEST_DIR)
#!/usr/bin/env python3
"""
按列分组并聚合(sum/count/mean/min/max),输出到新 Excel。
用法:
python aggregate_excel.py --input data.xlsx --group-by 地区 --agg "销售额:sum,数量:sum"
python aggregate_excel.py --input data.xlsx --group-by 部门,月份 --agg "金额:sum" "人数:count" --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
AGG_FUNCS = {"sum": "sum", "count": "count", "mean": "mean", "min": "min", "max": "max"}
def main():
parser = argparse.ArgumentParser(description="按列分组聚合")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", help="输出 .xlsx,默认覆盖输入并加 _agg 后缀")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--group-by", "-g", required=True, help="分组列,逗号分隔")
parser.add_argument("--agg", "-a", action="append", required=True, help="聚合:列名:sum 或 列名:count 等,可多次或逗号分隔")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
group_cols = [c.strip() for c in args.group_by.split(",") if c.strip()]
missing = [c for c in group_cols if c not in df.columns]
if missing:
print(f"分组列不存在: {missing}", file=sys.stderr)
sys.exit(1)
agg_spec = {}
for item in args.agg:
for part in item.split(","):
part = part.strip()
if ":" not in part:
continue
col, func = part.split(":", 1)
col, func = col.strip(), func.strip().lower()
if col not in df.columns:
print(f"聚合列不存在: {col}", file=sys.stderr)
sys.exit(1)
if func not in AGG_FUNCS:
print(f"不支持的聚合: {func},可选 sum/count/mean/min/max", file=sys.stderr)
sys.exit(1)
agg_spec[col] = AGG_FUNCS[func]
if not agg_spec:
print("请至少指定一个 --agg 列:函数", file=sys.stderr)
sys.exit(1)
result = df.groupby(group_cols, dropna=False).agg(agg_spec).reset_index()
if args.output:
out = Path(args.output)
else:
out = path.parent / f"{path.stem}_agg.xlsx"
out.parent.mkdir(parents=True, exist_ok=True)
result.to_excel(out, index=False, engine="openpyxl")
print(f"聚合完成,{len(result)} 行 -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
将 CSV 文件转为 Excel。支持单文件单 sheet,或多 CSV 合并为一个 Excel 多 sheet。
用法:
python csv_to_excel.py --input data.csv --output data.xlsx
python csv_to_excel.py --inputs a.csv b.csv --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="CSV 转 Excel")
parser.add_argument("--input", "-i", help="单个输入 CSV(与 --inputs 二选一)")
parser.add_argument("--inputs", nargs="+", help="多个 CSV,每个转为一个 sheet")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 路径")
parser.add_argument("--encoding", default="utf-8-sig", help="CSV 编码")
parser.add_argument("--sep", default=",", help="CSV 分隔符")
parser.add_argument("--sheet-name", help="单文件时的 sheet 名,默认用文件名")
args = parser.parse_args()
if args.input and args.inputs:
print("请只使用 --input 或 --inputs 之一", file=sys.stderr)
sys.exit(1)
if not args.input and not args.inputs:
print("请指定 --input 或 --inputs", file=sys.stderr)
sys.exit(1)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
if args.input:
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
df = pd.read_csv(path, encoding=args.encoding, sep=args.sep)
sheet_name = args.sheet_name or path.stem
if len(sheet_name) > 31:
sheet_name = sheet_name[:31]
df.to_excel(out, sheet_name=sheet_name, index=False, engine="openpyxl")
print(f"已转换 {len(df)} 行 -> {out} (sheet: {sheet_name})")
return
paths = [Path(p) for p in args.inputs]
for p in paths:
if not p.exists():
print(f"文件不存在: {p}", file=sys.stderr)
sys.exit(1)
with pd.ExcelWriter(out, engine="openpyxl") as writer:
for path in paths:
df = pd.read_csv(path, encoding=args.encoding, sep=args.sep)
name = path.stem
if len(name) > 31:
name = name[:31]
df.to_excel(writer, sheet_name=name, index=False)
print(f"已转换 {len(paths)} 个 CSV -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
按指定列去重,保留第一条或最后一条,输出到新文件。
用法:
python deduplicate_excel.py --input data.xlsx --keys 编号 --output out.xlsx
python deduplicate_excel.py --input data.xlsx --keys 姓名,日期 --keep last --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="按列去重")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 或 .csv")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--keys", "-k", required=True, help="作为去重键的列名,逗号分隔")
parser.add_argument("--keep", choices=("first", "last"), default="first", help="保留第一条或最后一条")
parser.add_argument("--encoding", default="utf-8-sig", help="输出为 CSV 时的编码")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
keys = [c.strip() for c in args.keys.split(",") if c.strip()]
missing = [c for c in keys if c not in df.columns]
if missing:
print(f"列不存在: {missing}", file=sys.stderr)
sys.exit(1)
before = len(df)
out_df = df.drop_duplicates(subset=keys, keep=args.keep)
after = len(out_df)
removed = before - after
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
if out.suffix.lower() == ".csv":
out_df.to_csv(out, index=False, encoding=args.encoding)
else:
out_df.to_excel(out, index=False, engine="openpyxl")
print(f"去重: {before} -> {after} 行,移除 {removed} 条 -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
将指定 Excel 工作表导出为 CSV。
用法:
python excel_to_csv.py --input file.xlsx --output file.csv
python excel_to_csv.py --input file.xlsx --sheet 明细 --encoding utf-8-sig --output out.csv
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="将 Excel 指定 sheet 导出为 CSV")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", required=True, help="输出 .csv 路径")
parser.add_argument("--sheet", default=0, help="工作表名或索引,默认第一个")
parser.add_argument("--encoding", default="utf-8-sig", help="CSV 编码,默认 utf-8-sig")
parser.add_argument("--sep", default=",", help="分隔符,默认逗号")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
df.to_csv(out, index=False, encoding=args.encoding, sep=args.sep)
print(f"已导出 {len(df)} 行到 {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
按列条件筛选 Excel 行,输出到新 Excel 或 CSV。
条件格式:列名=值(等于)、列名>值、列名>=、<、<=、!=、列名~子串(包含)。
用法:
python filter_excel.py --input data.xlsx --where "状态=已完成" --output out.xlsx
python filter_excel.py --input data.xlsx --where "金额>1000" "地区~北京" --output out.csv
"""
import argparse
import re
import sys
from pathlib import Path
import pandas as pd
def _parse_where(expr):
# 列名 运算符 值,值可带引号
m = re.match(r"^(\w+)\s*(=|!=|>=|<=|>|<|~)\s*(.*)$", expr.strip())
if not m:
return None
col, op, val = m.group(1), m.group(2), m.group(3).strip()
if val.startswith('"') and val.endswith('"'):
val = val[1:-1]
elif val.startswith("'") and val.endswith("'"):
val = val[1:-1]
return col, op, val
def _apply_filter(df, col, op, val):
if col not in df.columns:
raise ValueError(f"列不存在: {col}")
s = df[col].astype(str)
if op == "=":
return df[df[col].astype(str) == str(val)]
if op == "!=":
return df[df[col].astype(str) != str(val)]
if op == "~":
return df[s.str.contains(str(val), na=False, regex=False)]
try:
num = pd.to_numeric(df[col], errors="coerce")
v = pd.to_numeric(val, errors="coerce")
except Exception:
v = val
num = df[col]
if op == ">":
return df[num > v]
if op == ">=":
return df[num >= v]
if op == "<":
return df[num < v]
if op == "<=":
return df[num <= v]
return df
def main():
parser = argparse.ArgumentParser(description="按条件筛选 Excel 行")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 或 .csv")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--where", "-w", action="append", required=True, help="条件,可多次指定(AND)")
parser.add_argument("--encoding", default="utf-8-sig", help="输出为 CSV 时的编码")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
for expr in args.where:
parsed = _parse_where(expr)
if not parsed:
print(f"无效条件: {expr}", file=sys.stderr)
sys.exit(1)
col, op, val = parsed
try:
df = _apply_filter(df, col, op, val)
except ValueError as e:
print(e, file=sys.stderr)
sys.exit(1)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
if out.suffix.lower() == ".csv":
df.to_csv(out, index=False, encoding=args.encoding)
else:
df.to_excel(out, index=False, engine="openpyxl")
print(f"筛选后 {len(df)} 行 -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
将指定列设为「文本」格式,避免纯数字(如长 ID、证件号)被显示为科学计数法。
用法:
python format_columns_as_text.py --input data.xlsx --columns 身份证号,订单号 --output out.xlsx
python format_columns_as_text.py --input data.xlsx --columns A,B,C --output out.xlsx
python format_columns_as_text.py --input data.xlsx --columns 编号 --sheet 0 --output out.xlsx
"""
import argparse
import re
import sys
from pathlib import Path
import openpyxl
def _col_name_to_index(name):
"""列名(A, B, ..., Z, AA, ...)转 1-based 列索引。"""
name = name.upper().strip()
if not re.match(r"^[A-Z]+$", name):
return None
n = 0
for c in name:
n = n * 26 + (ord(c) - ord("A") + 1)
return n
def main():
parser = argparse.ArgumentParser(description="将指定列设为文本格式,避免科学计数法")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", help="输出 .xlsx,默认覆盖原文件")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--columns", "-c", required=True, help="要设为文本的列,逗号分隔:列名(如 身份证号)或列字母(如 A,B)")
parser.add_argument("--header-row", type=int, default=1, help="表头行号(1-based),该行也一并设为文本格式")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
wb = openpyxl.load_workbook(path)
if isinstance(args.sheet, int):
ws = wb.worksheets[args.sheet]
else:
ws = wb[args.sheet]
max_row = ws.max_row or 1
max_col = ws.max_column or 1
header_row = args.header_row
header = [ws.cell(row=header_row, column=j).value for j in range(1, max_col + 1)]
col_specs = [s.strip() for s in args.columns.split(",") if s.strip()]
col_indices = []
for spec in col_specs:
idx = _col_name_to_index(spec)
if idx is not None:
if 1 <= idx <= max_col:
col_indices.append(idx)
else:
print(f"列字母越界: {spec}", file=sys.stderr)
sys.exit(1)
else:
try:
i = header.index(spec) + 1
col_indices.append(i)
except ValueError:
print(f"表头中未找到列名: {spec}", file=sys.stderr)
sys.exit(1)
for col_idx in col_indices:
for row in range(1, max_row + 1):
cell = ws.cell(row=row, column=col_idx)
cell.number_format = "@"
val = cell.value
if val is not None and not isinstance(val, str):
cell.value = str(val)
out = Path(args.output) if args.output else path
out.parent.mkdir(parents=True, exist_ok=True)
wb.save(out)
print(f"已将 {len(col_indices)} 列设为文本格式 -> {out}", file=sys.stderr)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
为 Excel 指定区域添加条件格式:大于/小于/等于/介于/重复值,或双色/三色色阶。
用法:
python format_conditional.py --input data.xlsx --range "C2:C100" --rule gt --value 100 --fill "FF6B6B"
python format_conditional.py --input data.xlsx --column D --rule duplicates --fill yellow
python format_conditional.py --input data.xlsx --range "E2:E50" --rule between --min 0 --max 1 --fill "90EE90"
python format_conditional.py --input data.xlsx --range "F2:F100" --color-scale
"""
import argparse
import re
import sys
from pathlib import Path
import openpyxl
from openpyxl.formatting.rule import CellIsRule, Rule
from openpyxl.styles import PatternFill
from openpyxl.styles.differential import DifferentialStyle
# 常用颜色名 -> 六位十六进制(无前缀)
COLORS = {
"red": "FF6B6B",
"green": "90EE90",
"yellow": "FFFF00",
"orange": "FFA500",
"blue": "87CEEB",
"gray": "D3D3D3",
}
def _parse_fill(fill_arg):
s = str(fill_arg).strip()
if s.startswith("#"):
s = s[1:]
if len(s) == 6 and re.match(r"[0-9A-Fa-f]{6}", s):
return s
return COLORS.get(s.lower(), "FF6B6B")
def _resolve_range(ws, column_arg, range_arg):
if range_arg:
return range_arg
if not column_arg:
return None
col = column_arg.upper()
if len(col) == 1 and col.isalpha():
max_row = ws.max_row or 1
return f"{col}2:{col}{max(2, max_row)}"
return None
def main():
parser = argparse.ArgumentParser(description="为 Excel 区域添加条件格式")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", help="输出 .xlsx,默认覆盖原文件")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--range", "-r", help="区域,如 A2:A100(与 --column 二选一)")
parser.add_argument("--column", "-c", help="列字母,如 A;将对该列从第 2 行到最大行应用格式")
parser.add_argument("--rule", choices=("gt", "gte", "lt", "lte", "eq", "between", "duplicates"), help="条件:大于/大于等于/小于/小于等于/等于/介于/重复值")
parser.add_argument("--value", help="--rule gt/lt/eq 等时的比较值")
parser.add_argument("--min", help="--rule between 时的下限")
parser.add_argument("--max", help="--rule between 时的上限")
parser.add_argument("--fill", default="FF6B6B", help="满足条件时的填充色,如 red 或 FF6B6B")
parser.add_argument("--color-scale", action="store_true", help="使用双色色阶(红-绿),忽略 --rule/--value")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
wb = openpyxl.load_workbook(path)
if isinstance(args.sheet, int):
ws = wb.worksheets[args.sheet]
else:
ws = wb[args.sheet]
cell_range = _resolve_range(ws, args.column, args.range)
if not cell_range:
print("请指定 --range 或 --column", file=sys.stderr)
sys.exit(1)
fill_hex = _parse_fill(args.fill)
fill = PatternFill(start_color=fill_hex, end_color=fill_hex, fill_type="solid")
dxf = DifferentialStyle(fill=fill)
if args.color_scale:
from openpyxl.formatting.rule import ColorScaleRule
rule = ColorScaleRule(
start_type="min", start_value=0, start_color="F8696B",
end_type="max", end_value=100, end_color="63BE7B",
)
ws.conditional_formatting.add(cell_range, rule)
print(f"已添加色阶条件格式: {cell_range}", file=sys.stderr)
elif args.rule == "duplicates":
rule = Rule(type="duplicateValues", dxf=dxf)
ws.conditional_formatting.add(cell_range, rule)
print(f"已添加重复值条件格式: {cell_range}", file=sys.stderr)
elif args.rule == "between":
if args.min is None or args.max is None:
print("--rule between 需同时指定 --min 和 --max", file=sys.stderr)
sys.exit(1)
rule = CellIsRule(operator="between", formula=[str(args.min), str(args.max)], fill=fill)
ws.conditional_formatting.add(cell_range, rule)
print(f"已添加介于 {args.min}-{args.max} 条件格式: {cell_range}", file=sys.stderr)
else:
op_map = {"gt": "greaterThan", "gte": "greaterThanOrEqual", "lt": "lessThan", "lte": "lessThanOrEqual", "eq": "equal"}
if args.rule not in op_map or not args.value:
print("--rule gt/lt/eq 等需指定 --value", file=sys.stderr)
sys.exit(1)
rule = CellIsRule(operator=op_map[args.rule], formula=[str(args.value)], fill=fill)
ws.conditional_formatting.add(cell_range, rule)
print(f"已添加 {args.rule} {args.value} 条件格式: {cell_range}", file=sys.stderr)
out = Path(args.output) if args.output else path
out.parent.mkdir(parents=True, exist_ok=True)
wb.save(out)
print(f"已保存 -> {out}", file=sys.stderr)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
合并多个 Excel 文件或同一文件中的多个 sheet 为单个表,输出一个 .xlsx。
用法:
python merge_sheets.py --inputs a.xlsx b.xlsx --output out.xlsx
python merge_sheets.py --inputs ./data --output out.xlsx
python merge_sheets.py --inputs file.xlsx --sheet Sheet1 --sheet Sheet2 --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def _collect_paths(inputs):
paths = []
for p in inputs:
path = Path(p).resolve()
if not path.exists():
print(f"跳过不存在的路径: {path}", file=sys.stderr)
continue
if path.is_file():
if path.suffix.lower() in (".xlsx", ".xls"):
paths.append(path)
else:
print(f"跳过非 Excel 文件: {path}", file=sys.stderr)
else:
for f in path.glob("*.xlsx"):
paths.append(f)
for f in path.glob("*.xls"):
paths.append(f)
return paths
def main():
parser = argparse.ArgumentParser(description="合并多个 Excel 文件或 sheet 为单个表")
parser.add_argument("--inputs", nargs="+", required=True, help="输入文件或目录")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 路径")
parser.add_argument("--sheet", action="append", default=None, help="仅当输入为单文件时生效,可多次指定要合并的 sheet 名")
parser.add_argument("--header-row", type=int, default=0, help="表头行索引,默认 0")
args = parser.parse_args()
paths = _collect_paths(args.inputs)
if not paths:
print("没有找到任何 Excel 文件", file=sys.stderr)
sys.exit(1)
dfs = []
for path in paths:
try:
if len(paths) == 1 and args.sheet:
for sh in args.sheet:
df = pd.read_excel(path, sheet_name=sh, header=args.header_row, engine="openpyxl")
dfs.append(df)
else:
# 单文件无 --sheet:读所有 sheet
xl = pd.ExcelFile(path, engine="openpyxl")
for name in xl.sheet_names:
df = pd.read_excel(xl, sheet_name=name, header=args.header_row)
dfs.append(df)
except Exception as e:
print(f"读取失败 {path}: {e}", file=sys.stderr)
continue
if not dfs:
print("没有成功读取任何表", file=sys.stderr)
sys.exit(1)
merged = pd.concat(dfs, ignore_index=True)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
merged.to_excel(out, index=False, engine="openpyxl")
print(f"已合并 {len(dfs)} 个表,共 {len(merged)} 行,输出: {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
两表按键列合并(类似 VLOOKUP):左表为主,右表按 key 列匹配并追加列。
用法:
python merge_tables.py --left left.xlsx --right right.xlsx --on 编号 --output out.xlsx
python merge_tables.py --left left.xlsx --right right.xlsx --on 编号 --how inner --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="两表按键列合并(VLOOKUP 式)")
parser.add_argument("--left", "-l", required=True, help="左表 .xlsx(主表)")
parser.add_argument("--right", "-r", required=True, help="右表 .xlsx(被查表)")
parser.add_argument("--on", required=True, help="键列名(两表共有),逗号分隔为多键")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 或 .csv")
parser.add_argument("--sheet-left", default=0, help="左表 sheet 名或索引")
parser.add_argument("--sheet-right", default=0, help="右表 sheet 名或索引")
parser.add_argument("--how", choices=("left", "inner", "outer"), default="left", help="合并方式:left 保留左表全部,inner 交集,outer 并集")
parser.add_argument("--suffixes", default="_left,_right", help="重名列后缀,逗号分隔")
args = parser.parse_args()
lpath = Path(args.left)
rpath = Path(args.right)
if not lpath.exists():
print(f"左表不存在: {lpath}", file=sys.stderr)
sys.exit(1)
if not rpath.exists():
print(f"右表不存在: {rpath}", file=sys.stderr)
sys.exit(1)
try:
left = pd.read_excel(lpath, sheet_name=args.sheet_left, engine="openpyxl")
right = pd.read_excel(rpath, sheet_name=args.sheet_right, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
keys = [k.strip() for k in args.on.split(",") if k.strip()]
for k in keys:
if k not in left.columns:
print(f"左表缺少键列: {k}", file=sys.stderr)
sys.exit(1)
if k not in right.columns:
print(f"右表缺少键列: {k}", file=sys.stderr)
sys.exit(1)
suffixes = [s.strip() for s in args.suffixes.split(",")]
if len(suffixes) != 2:
suffixes = ["_left", "_right"]
merged = pd.merge(left, right, on=keys, how=args.how, suffixes=suffixes)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
if out.suffix.lower() == ".csv":
merged.to_csv(out, index=False, encoding="utf-8-sig")
else:
merged.to_excel(out, index=False, engine="openpyxl")
print(f"合并完成: 左 {len(left)} 行 + 右 {len(right)} 行 -> {len(merged)} 行 -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
重命名 Excel 中的工作表。按原名或索引指定要重命名的 sheet。
用法:
python rename_sheets.py --input file.xlsx --rename "Sheet1:汇总" "Sheet2:明细"
python rename_sheets.py --input file.xlsx --rename "0:首页" "1:数据"
python rename_sheets.py --input file.xlsx --prefix "2024_" --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import openpyxl
def main():
parser = argparse.ArgumentParser(description="重命名 Excel 工作表")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", help="输出 .xlsx,默认覆盖原文件")
parser.add_argument("--rename", "-r", action="append", help="原名:新名 或 索引:新名(索引从 0 起),可多次")
parser.add_argument("--prefix", help="为所有 sheet 名前加前缀")
parser.add_argument("--suffix", help="为所有 sheet 名后加后缀")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
if not args.rename and not args.prefix and not args.suffix:
print("请指定 --rename、--prefix 或 --suffix 之一", file=sys.stderr)
sys.exit(1)
wb = openpyxl.load_workbook(path)
names = wb.sheetnames
if args.rename:
for item in args.rename:
if ":" not in item:
print(f"无效项(需 原名:新名 或 索引:新名): {item}", file=sys.stderr)
sys.exit(1)
left, new_name = item.split(":", 1)
left, new_name = left.strip(), new_name.strip()
if not new_name:
continue
if left.isdigit():
idx = int(left)
if idx < 0 or idx >= len(names):
print(f"索引越界: {idx}", file=sys.stderr)
sys.exit(1)
old_name = names[idx]
else:
old_name = left
if old_name not in names:
print(f"工作表不存在: {old_name}", file=sys.stderr)
sys.exit(1)
if len(new_name) > 31:
new_name = new_name[:31]
if new_name != old_name and new_name in names:
print(f"新名已存在: {new_name}", file=sys.stderr)
sys.exit(1)
wb[old_name].title = new_name
names = wb.sheetnames
if args.prefix or args.suffix:
for i, old in enumerate(wb.sheetnames):
new = (args.prefix or "") + old + (args.suffix or "")
if len(new) > 31:
new = new[:31]
if new != old:
wb[old].title = new
out = Path(args.output) if args.output else path
out.parent.mkdir(parents=True, exist_ok=True)
wb.save(out)
print(f"已保存 -> {out}", file=sys.stderr)
print(" ".join(wb.sheetnames))
if __name__ == "__main__":
main()
openpyxl>=3.1.0
pandas>=2.0.0
xlrd>=2.0.0
#!/usr/bin/env python3
"""
选择列、重命名列、按指定顺序输出。不指定的列会被丢弃。
用法:
python select_columns.py --input data.xlsx --columns 姓名,部门,金额 --output out.xlsx
python select_columns.py --input data.xlsx --columns 姓名,部门,金额 --rename "部门:dept,金额:amount" --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="选择/重命名/排序列")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 或 .csv")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--columns", "-c", required=True, help="保留的列名,按此顺序输出,逗号分隔")
parser.add_argument("--rename", "-r", help="重命名 旧名:新名,多个用逗号分隔,如 部门:dept,金额:amount")
parser.add_argument("--encoding", default="utf-8-sig", help="输出为 CSV 时的编码")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
cols = [c.strip() for c in args.columns.split(",") if c.strip()]
missing = [c for c in cols if c not in df.columns]
if missing:
print(f"列不存在: {missing}", file=sys.stderr)
sys.exit(1)
out_df = df[cols].copy()
if args.rename:
renames = {}
for part in args.rename.split(","):
part = part.strip()
if ":" not in part:
continue
old_name, new_name = part.split(":", 1)
old_name, new_name = old_name.strip(), new_name.strip()
if old_name in out_df.columns:
renames[old_name] = new_name
out_df = out_df.rename(columns=renames)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
if out.suffix.lower() == ".csv":
out_df.to_csv(out, index=False, encoding=args.encoding)
else:
out_df.to_excel(out, index=False, engine="openpyxl")
print(f"已输出 {len(out_df)} 行 x {len(out_df.columns)} 列 -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
将一个大 Excel 按行数分片,或按某列取值拆成多个文件(每个取值一个文件)。
用法:
python split_excel.py --input data.xlsx --by-rows 5000 --output-dir ./chunks
python split_excel.py --input data.xlsx --by-column 地区 --output-dir ./by_region
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def _sanitize_filename(s):
s = str(s).strip()
for c in '\\/:*?"<>|':
s = s.replace(c, "_")
return s[:50] or "unnamed"
def main():
parser = argparse.ArgumentParser(description="按行数或按列取值拆分 Excel")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--by-rows", type=int, help="每 N 行一个文件")
parser.add_argument("--by-column", help="按该列取值拆分,每个取值一个文件")
parser.add_argument("--output-dir", "-o", default=".", help="输出目录")
parser.add_argument("--prefix", default="part", help="按行分片时的文件名前缀")
args = parser.parse_args()
if not args.by_rows and not args.by_column:
print("请指定 --by-rows 或 --by-column", file=sys.stderr)
sys.exit(1)
if args.by_rows and args.by_column:
print("请只指定 --by-rows 或 --by-column 之一", file=sys.stderr)
sys.exit(1)
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
out_dir = Path(args.output_dir)
out_dir.mkdir(parents=True, exist_ok=True)
base = path.stem
if args.by_rows:
n = args.by_rows
for i in range(0, len(df), n):
chunk = df.iloc[i : i + n]
out_path = out_dir / f"{args.prefix}_{i // n + 1:04d}.xlsx"
chunk.to_excel(out_path, index=False, engine="openpyxl")
count = (len(df) + n - 1) // n
print(f"已按每 {n} 行拆成 {count} 个文件 -> {out_dir}")
return
col = args.by_column
if col not in df.columns:
print(f"列不存在: {col}", file=sys.stderr)
sys.exit(1)
for val, sub in df.groupby(col, dropna=False):
name = _sanitize_filename(val) if pd.notna(val) else "null"
out_path = out_dir / f"{base}_{name}.xlsx"
sub.to_excel(out_path, index=False, engine="openpyxl")
print(f"已按列 '{col}' 拆成 {df[col].nunique()} 个文件 -> {out_dir}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
用数据表按行填充模板:模板中 {{列名}} 占位符被替换为数据行对应列的值,每行数据生成一行,输出到同一表。
用法:
python template_fill.py --template template.xlsx --data data.xlsx --output out.xlsx
python template_fill.py --template template.xlsx --data data.csv --pattern-row 2 --output out.xlsx
"""
import argparse
import re
import sys
from pathlib import Path
import pandas as pd
import openpyxl
def _replace_placeholders(text, row_dict):
if text is None or not isinstance(text, str):
return text
result = text
for col, val in row_dict.items():
placeholder = "{{" + str(col) + "}}"
if placeholder in result:
result = result.replace(placeholder, str(val) if pd.notna(val) else "")
# 未匹配的 {{xxx}} 保留或清空
result = re.sub(r"\{\{[^}]+\}\}", "", result)
return result
def main():
parser = argparse.ArgumentParser(description="用数据表按行填充模板中的 {{列名}} 占位符")
parser.add_argument("--template", "-t", required=True, help="模板 .xlsx,含占位符的一行作为样式来源")
parser.add_argument("--data", "-d", required=True, help="数据 .xlsx 或 .csv")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx")
parser.add_argument("--sheet-template", default=0, help="模板 sheet 名或索引")
parser.add_argument("--sheet-data", default=0, help="数据 sheet 名或索引(仅 .xlsx)")
parser.add_argument("--pattern-row", type=int, default=2, help="模板中占位符所在行号(1-based),默认第 2 行")
parser.add_argument("--header-row", type=int, default=1, help="模板表头行号(1-based),默认第 1 行")
args = parser.parse_args()
tpath = Path(args.template)
dpath = Path(args.data)
if not tpath.exists():
print(f"模板不存在: {tpath}", file=sys.stderr)
sys.exit(1)
if not dpath.exists():
print(f"数据文件不存在: {dpath}", file=sys.stderr)
sys.exit(1)
if dpath.suffix.lower() == ".csv":
data_df = pd.read_csv(dpath, encoding="utf-8-sig")
else:
data_df = pd.read_excel(dpath, sheet_name=args.sheet_data, engine="openpyxl")
wb = openpyxl.load_workbook(tpath)
if isinstance(args.sheet_template, int):
ws = wb.worksheets[args.sheet_template]
else:
ws = wb[args.sheet_template]
pattern_row_idx = args.pattern_row
max_col = ws.max_column
for i, row in enumerate(data_df.itertuples(index=False)):
row_dict = dict(zip(data_df.columns, row))
target_row = pattern_row_idx + i
for col_idx in range(1, max_col + 1):
src = ws.cell(row=pattern_row_idx, column=col_idx)
cell = ws.cell(row=target_row, column=col_idx)
cell.value = _replace_placeholders(src.value, row_dict)
try:
if src.has_style and src.font:
cell.font = src.font.copy()
if src.has_style and src.border:
cell.border = src.border.copy()
if src.has_style and src.fill:
cell.fill = src.fill.copy()
if src.number_format:
cell.number_format = src.number_format
if src.has_style and src.alignment:
cell.alignment = src.alignment.copy()
except Exception:
pass
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
wb.save(out)
print(f"已用 {len(data_df)} 行数据填充模板 -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
行列转置:首行变首列,首列变首行。可选保留原首行作为列名。
用法:
python transpose_excel.py --input data.xlsx --output out.xlsx
python transpose_excel.py --input data.xlsx --output out.xlsx --first-col-as-header
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="行列转置")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 或 .csv")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--first-col-as-header", action="store_true", help="转置后用第一列作为列名(否则用 0,1,2...)")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl", header=None)
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
out_df = df.T
if args.first_col_as_header:
out_df.columns = out_df.iloc[0]
out_df = out_df.iloc[1:].reset_index(drop=True)
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
if out.suffix.lower() == ".csv":
out_df.to_csv(out, index=False, encoding="utf-8-sig")
else:
out_df.to_excel(out, index=False, engine="openpyxl")
print(f"已转置 {df.shape[0]}x{df.shape[1]} -> {out_df.shape[0]}x{out_df.shape[1]} -> {out}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
校验 Excel 表:检查必须列、空行、重复键等。
用法:
python validate_excel.py --input data.xlsx
python validate_excel.py --input data.xlsx --require-cols 名称,金额 --key-cols 编号
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="校验 Excel 表结构与数据")
parser.add_argument("--input", "-i", required=True, help="输入 .xlsx 文件")
parser.add_argument("--sheet", default=0, help="工作表名或索引")
parser.add_argument("--key-cols", default="", help="用于检查重复的列名,逗号分隔")
parser.add_argument("--require-cols", default="", help="必须存在的列名,逗号分隔")
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(path, sheet_name=args.sheet, engine="openpyxl")
except Exception as e:
print(f"读取失败: {e}", file=sys.stderr)
sys.exit(1)
errors = []
if args.require_cols:
required = [c.strip() for c in args.require_cols.split(",") if c.strip()]
missing = [c for c in required if c not in df.columns]
if missing:
errors.append(f"缺少必须列: {missing}")
if args.key_cols:
keys = [c.strip() for c in args.key_cols.split(",") if c.strip()]
key_missing = [c for c in keys if c not in df.columns]
if key_missing:
errors.append(f"键列不存在: {key_missing}")
else:
dup = df[df.duplicated(subset=keys, keep=False)]
if not dup.empty:
errors.append(f"存在重复键,共 {len(dup)} 行")
# 全空行
empty = df.dropna(how="all")
if len(empty) < len(df):
errors.append(f"存在 {len(df) - len(empty)} 行全空行")
if errors:
for e in errors:
print(e, file=sys.stderr)
sys.exit(1)
print("校验通过")
sys.exit(0)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
多表 VLOOKUP:主表依次与多个查找表按键列左连接,每次合并追加新列。
用法:
python vlookup_multi.py --main 订单.xlsx --lookups "客户.xlsx:客户ID" "产品.xlsx:产品ID" --output out.xlsx
python vlookup_multi.py --main main.xlsx --lookups "a.xlsx:编号" "b.xlsx:ID" --sheet-main 0 --output out.xlsx
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
def main():
parser = argparse.ArgumentParser(description="多表按键依次左连接(VLOOKUP 链)")
parser.add_argument("--main", "-m", required=True, help="主表 .xlsx")
parser.add_argument("--lookups", "-l", required=True, nargs="+", help="查找表 文件:键列名,如 ref.xlsx:ID,可多个")
parser.add_argument("--output", "-o", required=True, help="输出 .xlsx 或 .csv")
parser.add_argument("--sheet-main", default=0, help="主表 sheet 名或索引")
parser.add_argument("--suffix", default="_lookup", help="追加列与主表重名时的后缀")
args = parser.parse_args()
mpath = Path(args.main)
if not mpath.exists():
print(f"主表不存在: {mpath}", file=sys.stderr)
sys.exit(1)
try:
df = pd.read_excel(mpath, sheet_name=args.sheet_main, engine="openpyxl")
except Exception as e:
print(f"读取主表失败: {e}", file=sys.stderr)
sys.exit(1)
for spec in args.lookups:
if ":" not in spec:
print(f"无效 lookups 项(需 文件:键列): {spec}", file=sys.stderr)
sys.exit(1)
fpath, on_col = spec.split(":", 1)
fpath, on_col = fpath.strip(), on_col.strip()
lpath = Path(fpath)
if not lpath.exists():
lpath = (mpath.parent / fpath).resolve()
if not lpath.exists():
print(f"查找表不存在: {fpath}", file=sys.stderr)
sys.exit(1)
try:
right = pd.read_excel(lpath, sheet_name=0, engine="openpyxl")
except Exception as e:
print(f"读取查找表失败 {fpath}: {e}", file=sys.stderr)
sys.exit(1)
if on_col not in df.columns:
print(f"主表缺少键列: {on_col}", file=sys.stderr)
sys.exit(1)
if on_col not in right.columns:
print(f"查找表 {fpath} 缺少键列: {on_col}", file=sys.stderr)
sys.exit(1)
right_cols = [c for c in right.columns if c != on_col]
if not right_cols:
continue
dup = [c for c in right_cols if c in df.columns]
if dup:
right = right.rename(columns={c: c + args.suffix for c in dup})
right_cols = [c + args.suffix if c in dup else c for c in right_cols]
df = pd.merge(df, right, on=on_col, how="left", suffixes=("", "_drop"))
drop_cols = [c for c in df.columns if c.endswith("_drop")]
df = df.drop(columns=drop_cols, errors="ignore")
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
if out.suffix.lower() == ".csv":
df.to_csv(out, index=False, encoding="utf-8-sig")
else:
df.to_excel(out, index=False, engine="openpyxl")
print(f"已合并 {len(args.lookups)} 个查找表,共 {len(df)} 行 x {len(df.columns)} 列 -> {out}")
if __name__ == "__main__":
main()