一款基于 Python 的跨平台表格分割工具:把一张大表按行数切成多个小文件。 同时提供 GUI 与 CLI,两者共用同一套核心逻辑,行为与错误提示完全一致。
格式与数据质量
- 📥 输入:CSV / TSV / Excel(
.xlsx/.xlsm/.xls)/ JSON / JSON Lines,支持拖放 - 🔤 编码自动探测:BOM → UTF-8 → GB18030(GBK) → Big5 → CP1252 → Latin-1 回退。
国内常见的 GBK 导出文件不再直接报
UnicodeDecodeError - 🧬 保真模式(默认开启):按文本读取,保留
00123这样的前导零、避免 19 位长整数被转成 科学计数法、避免整数列因空值变成1.0 - 📄 多工作表选择:
.xlsx含多个 sheet 时自动出现下拉框,不再默默只处理第一个 - 🇨🇳 导出 CSV/TSV 时写入 UTF-8 BOM,Excel 打开中文不再乱码
稳定与性能
- ⚡ 体量自适应读取:小于 64 MB 一次性载入(更快),超过则流式分块(内存占用与文件大小无关)
- 🛡️ 需要全量载入的格式(JSON /
.xls)有体积上限;读取 xlsx 前校验解压比,防御解压炸弹 - 🧾 原子写入:先写
.part临时文件再os.replace,中途失败不会留下无法分辨的半成品 - 🛑 真实取消:协作式取消令牌贯穿预检与分块循环,随时中止且不再写文件;同一时刻只允许一个任务
- 🔁 同名文件策略:覆盖 或 保留并加序号(
_1、_2),启动前告知将生成多少文件 - 🧯 完整堆栈写入日志,界面只显示一句可操作的中文/英文提示
体验
- 🌐 双语界面一键切换(运行中切换也不会丢进度文本)
- 🌙 现代深色界面(PySide6 / Qt,统一配色、圆角与内边距;高 DPI 自动缩放,无需配置)
- 🔍 文件预览:选完文件立刻看到编码、总行数、列名与前 20 行,不用靠文件名猜选对没有
- 🖱️ 原生拖放(Qt 自带能力,不需要额外依赖)
- 🏷️ 标题栏显示版本号(如
通用表格分割器 v1.0.0),打包后的 exe 属性里也带同一版本号 - 💾 记住上次的行数/编号/格式/目录
- 🔗 完成后给出摘要,并可一键打开输出目录
- Python 3.9 – 3.12
- 需要 PySide6(
pip会自动安装,无需单独配置);Qt 6 自带高 DPI 缩放
# 方式一:从源码安装(推荐,同时提供 table-splitter 命令)
pip install -e .
# 方式二:只要运行时依赖
pip install -r requirements.txt
# 可选:开发工具(pytest / ruff / pre-commit / pyinstaller)
pip install -e ".[dev]"界面基于 PySide6(Qt 6):统一深色主题、原生拖放、文件预览区与原生表格控件。 代价是打包体积约 87 MB——PySide6 是完整的 Qt 运行时。
缺依赖会自动问你要不要装。
run.bat(源码启动)与packaging\build.bat(打包) 启动前都会跑一遍依赖自检(packaging/ensure_deps.py):缺什么会列出来,问你是否现在 从 PyPI 安装,确认后自动装好再继续,不用手工敲pip install。想跳过询问直接装用
--no-ask;反过来,CI 与build.bat --no-install会通过 环境变量UTS_NO_AUTO_INSTALL=1关掉自动安装,保持"缺依赖就直接失败"—— 这样pyproject.toml漏写依赖时能当场暴露,而不是被自动补装掩盖过去。
各依赖都带兼容上界。
pandas2.x 已无法写出.xls,因此本项目只支持读取.xls, 不提供.xls导出。
Windows 上最省事的方式是双击 run.bat(自动定位项目根目录、挑解释器、校验依赖,
图形界面用 pythonw 启动,不会多留一个黑窗口)。
也可以直接用命令:
run.bat # Windows:等价于下面的 GUI 启动
python splitter.py # 兼容旧用法
python -m universal_table_splitter # 等价
table-splitter-gui # 安装后可用- 选择或拖入要分割的文件(拖入文件夹会被当作输出目录)
- 设置参数:每份行数、编号位数、导出格式(多 sheet 时选工作表)
- 点击「开始分割」,运行中可随时取消
run.bat 还支持直接切分文件,把表格拖到脚本图标上即可:
run.bat 表格.csv -o 输出 -n 5000 :: 参数原样透传给命令行
run.bat --dry-run :: 只显示解析到的根目录与将要执行的动作
run.bat -- --help :: 「--」之后的内容交给程序解析# 基本用法:每 10 万行一个文件,导出 CSV 到 ./out
table-splitter big.csv -o out -n 100000
# 编号 001 起、导出 xlsx、中文提示
python -m universal_table_splitter data.csv -o out -n 5000 -d 001 -f xlsx --lang cn
# 多工作表 + 保留原文件(同名时自动加序号)
table-splitter book.xlsx --sheet "订单明细" --overwrite index
# 关闭保真模式(让 pandas 推断类型)、转义公式前缀
table-splitter data.csv --no-fidelity --escape-formulas
# 超大文件强制流式(内存有界)
table-splitter huge.csv -n 200000 --stream
# 运行中按 Ctrl+C 可安全中止;--clean-partial 会删除已生成的部分文件
table-splitter huge.csv --clean-partial常用参数:
| 参数 | 说明 |
|---|---|
input |
输入文件(CSV / TSV / Excel / JSON / JSONL) |
-o, --output |
输出目录,默认与输入文件同目录 |
-n, --rows |
每份行数,1 – 1000000,默认 1000 |
-d, --digits |
编号位数,默认 001(即 3 位);超出位数会自然增宽(999 → 1000) |
-f, --format |
导出格式:csv / tsv / xlsx / json / html,默认与输入同族 |
--sheet |
Excel 工作表名称 |
--no-fidelity |
关闭保真模式,允许 pandas 自动推断列类型 |
--escape-formulas |
给 = + - @ 开头的单元格加前导 ',阻断 Excel 公式注入(会修改单元格内容) |
--overwrite |
overwrite(默认)或 index(保留原文件并加序号) |
--stream |
强制流式读取 |
--clean-partial |
取消/失败时删除已生成的文件 |
--lang |
cn / en |
界面上的「编号格式」输入的是编号位数,001 表示生成 001、002…010。
它不是 Python 格式字符串,输入 03d 会被判为非法。
| 开启(默认) | 关闭 | |
|---|---|---|
| 读取方式 | dtype=str + keep_default_na=False |
交给 pandas 推断 |
00123 |
00123 |
123 |
9007199254740993 |
原样 | 可能丢精度/变科学计数法 |
| 整数列含空值 | 原样 | 整列变 float,导出成 1.0 |
| 空单元格 | 空字符串 | NaN(导出 CSV 为空) |
搬运数据时建议保持开启;需要让 Excel 把数字当数字用时再关闭。
实测(Windows / Python 3.10 / pandas 2.3,tests/data/danbooru_art_full.csv,32.6 MB,
419,789 行 × 4 列,每份 10 万行):
| 指标 | 数值 |
|---|---|
| 生成文件数 | 5 |
| 预检 + 写出总耗时 | ≈ 7.6 s(其中约 5.2 s 是 pandas to_csv 的固有开销) |
| 保真模式读取 | ≈ 1.6 s(反而比类型推断的 2.9 s 更快) |
| 一次性载入峰值内存 | ≈ 132 MB |
| 流式模式峰值内存 | 与文件大小无关,仅与分块大小相关 |
取消/覆盖/残留:写入是原子的(.part + os.replace),取消时不会留下半写的文件;
若已经写完若干文件,界面会询问是否删除它们。
| 内容 | Windows | macOS | Linux |
|---|---|---|---|
| 日志 | %LOCALAPPDATA%\universal_table_splitter\logs\app.log |
~/Library/Logs/universal_table_splitter/ |
~/.local/state/universal_table_splitter/logs/ |
| 设置 | %APPDATA%\universal_table_splitter\settings.json |
~/Library/Application Support/universal_table_splitter/ |
~/.config/universal_table_splitter/ |
universal_table_splitter/
├── core/ # 业务层:不 import Qt,可被 CLI 与测试直接复用
│ ├── plan.py # 纯函数:分块计划、编号、输入解析
│ ├── readers.py # 编码探测 / 分隔符嗅探 / 流式读取 / 安全预检
│ ├── writers.py # 导出注册表 / 原子写入 / 公式转义 / OS 错误翻译
│ ├── job.py # SplitJob + run_split:校验 → 预检 → 执行 → 进度 → 取消
│ └── deps.py # 可选依赖自检
├── ui/ # 界面层:PySide6,只负责交互与渲染
│ ├── app.py # 入口:QApplication 初始化 + 主题加载 + 开窗口
│ ├── main_window.py # 主窗口装配与流程调度(预检 → 确认 → 执行)
│ ├── preview.py # 文件预览区:后台读元信息与前 N 行
│ ├── worker.py # QThread 工作线程,跨线程一律走 Signal
│ ├── theme.py # 深色调色板 + 全局 QSS
│ └── widgets.py # 可复用控件(PathPicker 等)
├── i18n.py # 全部面向用户的文案(业务代码不允许硬编码自然语言)
├── settings.py # 跨平台配置目录与偏好持久化
├── logging_setup.py # 轮转日志 + 全局异常钩子
├── cli.py # 命令行入口(复用 core)
└── __main__.py # 无参数 → GUI;有参数 → CLI
关键约定:
- 业务层不依赖界面层:
core里没有任何 Qt 引用,因此可以被pytest完整覆盖; - 子线程不碰控件:所有跨线程结果都通过
Signal回主线程,这是 Qt 里唯一的线程安全边界; - 错误只携带 i18n key:
AppError("err.sheet_not_found", sheet="S2"),文案由 UI/CLI 渲染; - 取消是协作式的:
threading.Event从预检一路传到分块循环,在块边界检查。
Universal_Table_Splitter/
├── run.bat # ★ 快速启动:自动定位项目根、校验环境、日志
├── splitter.py # 兼容入口(也是 PyInstaller 的入口脚本)
├── pyproject.toml # 项目元数据 + 依赖 + pytest/ruff 配置(唯一事实来源)
├── requirements.txt # 仅运行时依赖,便于 `pip install -r`
├── README.md CHANGELOG.md LICENSE
├── .github/workflows/release.yml # ★ 自动打包 + 自动发布(仅 Windows,不跑测试)
├── .pre-commit-config.yaml # 提交前 ruff 检查与格式化
├── docs/
│ ├── RELEASE.md # ★ 发布流程 + GitHub 侧配置清单(Splitter 令牌/权限)
│ └── screenshot.png # 界面预览(打包版实拍)
├── packaging/
│ ├── build.bat # ★ 打包脚本:单文件 exe,支持 -o 输出目录 / -i 图标
│ ├── table_splitter.spec # PyInstaller 配置(由 build.bat 调用)
│ └── output/ # 打包产物与日志(默认输出目录,不进版本库)
├── universal_table_splitter/ # 源码(见上文「架构」)
└── tests/
├── data/README.md # 大体积测试数据说明(数据本身不进版本库)
└── test_*.py # 12 个测试文件
给外部贡献者的重要说明
main分支受保护,且仅维护者可直接推送。 请向dev分支提交 Pull Request;直接向main提交的 PR 大概率不会受理。
推荐流程:
- Fork 本仓库到你自己账号
- 从
dev分支切出特性分支(git checkout -b feature/xxx) - 提交改动并推送到你的 Fork
- 向本仓库的
dev分支 开 Pull Request,等待 CI 通过与合并
分支流向:feature/xxx → dev(日常集成)→ main(发布版本)
pip install -e ".[dev]" # 或 pip install -r requirements.txt
pytest # 211 个用例,约 15 秒
pytest -m slow # 另需 tests/data/danbooru_art_full.csv
pytest --cov=universal_table_splitter
ruff check . # 静态检查
ruff format . # 格式化
pre-commit install # 提交前自动跑上面的检查慢速集成测试依赖 30 MB 的真实数据集。把
danbooru_art_full.csv放到tests/data/即可运行(获取方式与验证要点见tests/data/README.md);文件缺失时这 5 个用例会被跳过, 不影响其余用例。
关于两个 .bat 的编码(贡献者必读):
run.bat与packaging/build.bat是 GBK(cp936) + CRLF,并且脚本内故意不调用chcp。 cmd.exe 按"当前控制台代码页"解码批处理文件,而goto/call按字节偏移跳转; 一旦在文件中途chcp(例如chcp 65001),先前算好的偏移会与新解码方式错位, cmd 会从某一行中间继续解析,把注释/命令尾部当命令执行,刷出一堆is not recognized as an internal or external command。 保持"文件编码 == 控制台代码页"即可彻底避免,中文 Windows 默认就是 936。 请勿把这两个文件另存为 UTF-8,也不要给它们加chcp。 若控制台代码页不是 936(例如 VS Code 默认的 UTF-8 终端),脚本会打印一行英文提示, 中文提示会显示为乱码,但功能完全正常。
测试重点覆盖了下面这些最容易出错的场景,作为回归护栏:
| 用例 | 防护的缺陷 |
|---|---|
test_cancel_after_first_chunk_stops_writing |
「取消」实际上不生效 |
test_start_operation_rejects_concurrent_worker |
取消后再次点击会并发写同一批文件 |
test_xls_export_is_gone |
.xls 导出必然失败 |
test_windows_backslashes_are_not_escaped |
拖放路径里的空格与反斜杠被破坏 |
test_gbk_file_is_readable |
GBK 中文 CSV 直接报错 |
test_csv_is_written_with_bom |
中文 CSV 在 Excel 中乱码 |
test_fidelity_keeps_leading_zeros |
前导零/长数字静默失真 |
test_multi_sheet_selection |
多 sheet 只读第一个 |
test_atomic_write_leaves_no_temp_file_on_failure |
失败留下残渣文件 |
test_language_switch_keeps_live_progress_text |
运行中切换语言丢失进度 |
test_all_keys_referenced_in_code_exist |
错误文案中英混杂 |
使用打包脚本(自动处理依赖检测、图标、输出目录与日志):
packaging\build.bat :: 打包到 packaging\output\dist
packaging\build.bat -o D:\发布 :: 自定义输出目录
packaging\build.bat -i assets\app.ico :: 指定图标
packaging\build.bat -n 表格分割器 :: 指定 exe 名称
packaging\build.bat --no-clean :: 复用构建缓存,重打包更快
packaging\build.bat --dry-run :: 只看将要执行什么,不真正打包
packaging\build.bat -h :: 查看全部选项特点:
- 单个 exe,已内置 Python 与全部依赖(pandas / openpyxl / PySide6 等), 可直接拷到没装 Python 的 Windows 机器运行;exe 的属性里带版本号(与标题栏同源);
- 打包前强制校验依赖完整性:缺任何一项运行时依赖都会中止构建并列出缺什么。
这一步挡的是"在缺依赖的机器上打出一个悄悄少了功能的 exe"。缺依赖时会先问你要不要
联网装上(
--no-install可关掉); - 首次打包约 3–6 分钟,体积约 87 MB(PySide6 与 pandas 各占一大块);
- 输出目录、临时目录、图标、exe 名称都可自定义;相对路径按项目根目录解析, 双击运行也不会把产物丢到系统目录;
- 完整构建输出实时打印并写入
<输出目录>\build.log;失败时脚本会打印日志尾部, 并区分「包未安装」「图标缺失」「构建失败」「产物未生成」等原因; - 打包前自动检查 Python 版本、运行时依赖与 PyInstaller,缺失时询问是否安装
(
--no-install可关闭询问)。
也可以直接使用 spec:
pyinstaller packaging/table_splitter.spec。 由于 PyInstaller 在读入.spec后会忽略--icon/--name等命令行选项, 脚本改用环境变量UTS_ICON/UTS_APP_NAME把这两项传给 spec。
GitHub Actions 只负责打包与发布,不运行任何测试。打一个标签即可完成全流程:
# 1) 先改版本号(唯一来源)
# universal_table_splitter/__init__.py -> __version__ = "1.0.0"
# 2) 推标签,剩下的交给 Actions
git tag -a v1.0.0 -m "1.0.0"
git push origin v1.0.0Actions 会自动:打包 Windows 单文件 exe → 构建 wheel/sdist → 创建 Release 并附上全部产物与
SHA256 校验和。所有作业都跑在 windows-latest 上,不在 Ubuntu 上打包、不产出任何 Linux 制品。
标签与 __version__ 不一致时会直接失败并提示,避免版本错配。
发布使用仓库 Secret Splitter(${{ secrets.Splitter }})作为令牌,未配置时自动回退到
内置 GITHUB_TOKEN,日志里会打印实际使用的来源。也可以在 Actions → Release → Run workflow
手动触发(在分支上触发只产出构建物、不建 Release)。首次使用前建议先配好该 Secret,
完整清单见 docs/RELEASE.md。
- Excel 读取依赖 openpyxl / xlrd,单元格格式、公式、合并单元格不会被保留
(xlsx 以
data_only=True读取,公式取缓存值); .xls只能读取,不能导出;- 导出 HTML 是静态表格,不含样式与 JavaScript;
- xlsx 写入速度受 openpyxl 限制,行数很多时建议导出 CSV/TSV;
--escape-formulas会给单元格加前导',Excel 中会看到这个字符,属预期行为;- 界面是固定深色主题,不跟随系统深浅色设置(Qt 的 Fusion 风格 + 自定义 QSS 保证了跨平台一致);
- 打包体积约 87 MB——PySide6 是完整的 Qt 运行时。
Q:中文变成乱码? 导出 CSV 已带 UTF-8 BOM,Excel 可正常识别。如果仍乱码,请改用 xlsx 导出。
Q:提示「无法识别文本编码」? 文件可能是少见编码,请用记事本/Notepad++ 另存为 UTF-8 或 GBK 后重试。
Q:导出 Excel 时报缺少依赖?
pip install openpyxl。程序启动时也会检测并提前提示。
Q:界面显示乱码?
Linux 需安装中文字体(如 fonts-noto-cjk);Windows/macOS 默认支持。
程序会自动挑选一个带中文字形的界面字体(微软雅黑 / 苹方 / Noto Sans CJK),
全都找不到时才会退回 Qt 默认字体。
Q:拖放没反应? 拖放由 Qt 原生提供,不需要任何额外依赖。若拖入的是不支持的扩展名 (不是 CSV / TSV / Excel / JSON),界面会忽略它并提示文件格式不受支持。 打包好的 exe 同样自带拖放能力。
Q:想批量/自动化处理?
用 CLI(见上文),或直接调用 universal_table_splitter.core.run_split。
pip install -e .
python -m universal_table_splitter # GUI
table-splitter data.csv -o out -n 100000 # CLIKey behaviours: automatic encoding detection (UTF-8 / GBK / Big5 / …), fidelity mode on by
default (keeps leading zeros and long integers as text), streaming reads for files over 64 MB,
BOM-tagged CSV output for Excel, real cooperative cancellation, atomic writes, and identical
logic/validation between the GUI and the CLI (--lang en for English messages).
