简介:CSV编辑软件Cassava是一款轻量却功能丰富的表格文本编辑工具,面向需要直接处理CSV数据的办公人员、数据分析初学者与开发者,解决用普通文本编辑器编辑表格时列对齐困难、批量修改繁琐的问题。压缩包共50个文件、约1.78MB,主体为可执行程序Cassava.exe,并附25个html帮助文档与1个css样式文件,系统讲解菜单、选项、宏与快捷键;17个cms宏脚本示例(含日历生成等)与3个csv样例、说明用txt及图标图片,构成一套可直接上手的学习材料。目前已有1445人学习下载。借助自带宏功能与简易电子表格能力,读者既能完成日常CSV的创建、查找替换与格式整理,也能参照脚本示例编写自己的批处理宏,减少重复劳动;帮助文档与示例脚本相互印证,便于按功能模块查阅,适合希望摆脱重型表格软件、追求快速编辑体验的用户参考使用。
1. 一份 200MB 的 CSV 打不开之后:CSV 编辑到底卡在哪
如果你手上有一份两百多兆的订单 CSV,只想改其中两列,会撞上一种很别扭的处境:通用表格软件打开要转几分钟,还只加载前一百万行;纯文本编辑器倒是秒开,可满屏逗号挤成一团,列与列根本对不齐。Cassava 就是填这个缝隙的——一个面向 CSV 与 TSV 的轻量编辑器,认分隔符、认引号、认编码,多份文件用标签页并排改,保存时把原编码和分隔符写回去。它适合天天跟导出文件打交道的人:改字段、清脏值、核对列数;不适合拿来做透视表和图表。下面按先体检、再动手、后验收的顺序,把这份工具怎么用讲透。
2. 先把文件的“方言”摸清:分隔符、引号、编码的三层校验
CSV 听起来像一种格式,实际更像一个家族。同样叫.csv的两个文件,可能一个用逗号、一个用分号,编码可能 UTF-8 也可能 GB18030,字段里可能规规矩矩加引号,也可能裸着塞进一个换行。打开之前不把这些“方言”摸清楚,后面所有操作都是在赌。这一章的三层校验,是我拿到陌生 CSV 时的固定动作:先看字节头判断编码,再统计分隔符,最后看列数分布。三步都过了,才敢把文件拖进编辑器。
2.1 分隔符与引号:RFC 4180 在真实文件里的变形
RFC 4180 给出的规则很朴素:字段之间用逗号分隔;字段本身包含逗号、双引号或换行时,整个字段用双引号包起来;字段内部的双引号写成两个。问题是导出工具各自为政,我见过至少四种变形,每种的打开方式都不一样。
第一种是分号分隔。某些区域设置下小数点是逗号,于是导出时把分隔符换成分号,文件扩展名还叫.csv。用默认逗号打开,整份文件会缩成一列。第二种是制表符分隔,日志系统和数据库导出最常见,扩展名也常常是.csv,实际上更接近 TSV。第三种是反斜杠转义,\,表示字段内的逗号而不是分隔符,这种文件直接丢给标准 CSV 解析器会被拆错列。第四种是行尾多一个分隔符,导致每一行都比表头多出一列空字段,看着不起眼,做列数校验时会被判成“结构损坏”。
| 变形 | 打开后的表现 | 处理方式 |
|---|---|---|
| 分号分隔 | 全部内容挤在第一列 | 打开时手动把分隔符改成; |
| 制表符分隔 | 同上,且列名里带大量空格 | 分隔符设 Tab,另存时改扩展名 |
| 行尾多余分隔符 | 每行比表头多一列空值 | 编辑器里删掉最后一列再保存 |
| 字段内换行未加引号 | 总行数比业务记录数多 | 用脚本按记录重写,不要按行修 |
引号这一侧最常见的坑是“看起来有引号其实没有”。有些导出工具只给包含逗号的字段加引号,一旦字段里有双引号又没转义,解析器就会把后面的内容当成新字段。还有一种情况是整份文件用单引号当引用符,这种文件在标准解析器眼里就是普通字符,列会被拆得乱七八糟。判断方法很简单:看表头那一行有没有引号,如果表头里出现"id","name"这种成对结构,说明是双引号引用;如果出现'id','name',就得在编辑器里把引用字符改成单引号。
提示:判断分隔符不要只看第一行。挑十行以上统计每个候选字符的出现次数,一致率最高的那个才是真分隔符,第一行往往是表头,列名里带逗号的情况并不少见。
2.2 编码与 BOM:UTF-8、GB18030、UTF-8-SIG 的取舍
编码判断的实操顺序是“先试 UTF-8,失败再退 GB18030”,这个顺序不能反。GB18030 的字节覆盖范围极广,几乎任何字节序列都能被它“成功”解码,先试它就会把 UTF-8 文件误判成中文乱码。UTF-8 的字节结构有严格约束,非法序列会直接抛解码错误,所以拿它当第一道筛子最可靠。
BOM 是另一个高频问题。带 BOM 的 UTF-8 文件开头有三个字节EF BB BF,解码后会在第一个列名前面粘一个不可见的\ufeff。你可以搜得到这个列名,但在代码里按列名取值时永远取不到,因为键名实际是'\ufeffid'而不是'id'。这个问题的隐蔽程度很高,我曾经花了一个下午排查一个“列不存在”的报错,最后发现就是 BOM 在作怪。
保存时选哪种编码,取决于下游是谁。给程序读的中间文件,用 UTF-8 无 BOM,兼容性最好;给通用表格软件双击打开的交付文件,用 UTF-8-SIG 或 GB18030 更稳,前者能自动被识别,后者在中文环境下几乎不会乱码。UTF-16 也会出现,通常是某些系统导出时带的,特征是文件开头有FF FE或FE FF,体积比同内容 UTF-8 大将近一倍。
2.3 动手:写一个六十行的体检脚本
编辑器能改内容,但判断不了编码和结构,所以进编辑器之前先跑一遍脚本。下面这个脚本只做三件事:猜编码、猜分隔符、统计前五千条记录的列数分布。
# csv_health.py # 用法: python csv_health.py orders.csv import csv import sys from collections import Counter from itertools import islice CANDIDATES = [',', '\t', ';', '|'] def read_head(path, size=64 * 1024): # 只读文件头,避免大文件一次性读进内存 with open(path, 'rb') as f: return f.read(size) def guess_encoding(raw): # BOM 优先判断,其次是 UTF-8,最后退到 GB18030 if raw.startswith(b'\xef\xbb\xbf'): return 'utf-8-sig' if raw.startswith(b'\xff\xfe') or raw.startswith(b'\xfe\xff'): return 'utf-16' try: raw.decode('utf-8') return 'utf-8' except UnicodeDecodeError: return 'gb18030' def guess_delimiter(lines): best, best_score = None, 0.0 for d in CANDIDATES: counts = [ln.count(d) for ln in lines if ln.strip()] if not counts: continue mode, freq = Counter(counts).most_common(1)[0] if mode == 0: continue score = freq / len(counts) # 用众数占比衡量“每行分隔符数量是否稳定” if score > best_score: best, best_score = d, score return best, best_score def main(path): raw = read_head(path) enc = guess_encoding(raw) text = raw.decode(enc, errors='replace') lines = text.splitlines() delim, score = guess_delimiter(lines) print(f'编码猜测: {enc}') print(f'分隔符猜测: {delim!r} 一致率 {score:.1%}') if delim is None: return with open(path, 'r', encoding=enc, newline='') as f: reader = csv.reader(f, delimiter=delim) widths = Counter(len(row) for row in islice(reader, 5000)) print('列数分布(前 5000 条记录):', dict(widths)) if len(widths) > 1: print('警告: 列数不一致,重点查引号内逗号和字段内换行') if __name__ == '__main__': main(sys.argv[1])脚本里几个参数值得说明。read_head的size默认取 64KB,对编码和分隔符判断足够,文件更大也不会拖慢速度;但要注意,如果文件本身小于 64KB 且最后一行被截断,统计时会被当成不完整行,这不影响结论。guess_encoding的三段判断顺序是刻意的,BOM 判断放最前面,因为 BOM 是确定性证据,不靠猜。guess_delimiter里score的计算方式是一致率,也就是有多少比例的行用了相同的分隔符出现次数,一般真分隔符的一致率会在 80% 以上,低于 50% 就要怀疑文件结构有问题,比如混了两种分隔符。
输出里的列数分布是信息量最大的一项。如果分布里只有一个数字,说明结构规整;如果出现两个以上,就要定位到具体行去看。常见原因是某个字段里塞了没加引号的逗号,或者某个字段里有换行符。这时候别急着用编辑器打开,先用csv模块按记录读一遍,把列数异常的记录筛出来,修完再进编辑器。
注意:脚本的编码判断是“抽样判断”,只看文件头 64KB。如果文件前半段是纯 ASCII、后半段才出现中文,可能会被误判成 UTF-8 然后在读取阶段报错。遇到这种情况,把
read_head的大小调到 1MB 再跑一次。
3. 在 Cassava 里修一份脏数据:视图、替换与列操作
文件体检完之后,重活才开始。一份从业务系统导出的 CSV,常见病症就那么几种:日期格式三种混用、手机号中间带空格、空值有的是空字符串有的是NULL、某些列首尾带不可见字符。这些活手工改不现实,得靠编辑器的批量能力。这一章按“打开—调整视图—批量替换—回写”的顺序走一遍,中途会插一个备份脚本,因为批量替换是所有操作里最容易翻车的一步。
3.1 打开时的四个开关,决定后面顺不顺
Cassava 打开文件时会读一组设置项,常见的有四组:分隔符、引用字符、编码、换行符。这四项如果和文件实际结构对不上,后面所有编辑都是在错误的视图上做,改完保存只会把错误固化下来。
分隔符这一项,我一般关掉自动识别,手动指定。自动识别在混合结构文件上判断错误的概率不低,尤其是那种前几行是说明文字、后面才是表头的文件。手动指定成体检脚本给出的结果,一次就对。引用字符默认是双引号,如果体检时发现文件用单引号包裹字段,这里要同步改,否则编辑器会把单引号当成普通字符,字段里的逗号依然会被拆列。
编码这一项直接按脚本结果选。这里有个细节:同样是 UTF-8,带 BOM 和不带 BOM 在部分编辑器里会被当成两种编码,如果选错,第一个列名会带一个看不见的字符。选 UTF-8-SIG 能吃掉 BOM,选普通 UTF-8 则会把 BOM 留在内容里。换行符这一项经常被忽略,但它决定保存时写回 CRLF 还是 LF。如果这份文件在版本管理里,换行符被改掉会导致整份文件显示为全量修改,diff 直接变黑匣子,谁也看不出你到底改了哪几行。
最后一项是“首行是否作为表头”。勾上之后,第一行会固定显示在顶部,滚动时不会跑掉;不勾则当成普通数据行。对大文件来说,勾上这一项能省掉大量上下翻找的时间。
3.2 列视图怎么调才顺手
编辑器打开大文件后,第一眼看到的往往是挤在一起的列。Cassava 的列宽可以拖动调整,我通常只调前三四列——ID、时间、状态这几个定位用的字段,后面的宽列(比如备注、详情)保持默认,需要看的时候再拉宽。把列宽调好之后,滚动时的可读性提升非常明显。
行号是另一个高频使用的功能。定位到某一行有两种方式:直接跳转到指定行号,或者按内容搜索。前者适合“校验脚本告诉我第 3821 行列数不对”这种场景,后者适合“找出所有状态为失败的行”。两种方式配合使用,基本能覆盖日常定位需求。
多标签是 Cassava 相对纯文本编辑器的明显优势。改一份导出文件时,我习惯同时打开三份:待改的原始文件、上一版已经确认正确的参照文件、以及一份只有几十行的样本文件。样本文件用来试正则,确认命中数对了再应用到全量文件;参照文件用来比对某几行的字段值,直接复制粘贴比来回切窗口快得多。
排序功能要谨慎用。按某一列排序会打乱原始行序,如果这份文件后续还要按原顺序导回业务系统,排序就是不可逆的破坏。我的做法是:需要排序时先另存一份副本,在副本上排,排完把需要的行复制回原文件,而不是直接在原文件上排。
3.3 正则替换:把清洗规则写成一张表
批量清洗的核心是查找替换,而查找替换能不能用,取决于是否支持正则表达式。如果你的版本带正则选项,下面这几条规则基本能覆盖八成的脏数据场景。
| 目标 | 正则 | 替换为 | 注意 |
|---|---|---|---|
| 去字段首尾空白 | ^\s+|\s+$ | 空 | 要确认替换范围是逐字段而不是整行 |
| 日期斜杠转短横 | (\d{4})/(\d{1,2})/(\d{1,2}) | \1-\2-\3 | 月日个位数不会自动补零,需二次处理 |
| 连续空格压缩 | {2,} | 单个空格 | 别误伤有意义的缩进 |
| 空值统一 | ^(NULL|null|N/A|-)$ | 空 | 替换前先看命中数,防止误伤合法值 |
| 手机号脱敏 | (1\d{2})\d{4}(\d{4}) | \1****\2 | 必须限定在指定列上执行 |
用这张表有一个前提:先在小样本上验证。做法是把前一百行单独存成一个文件,在样本上跑一遍替换,用“查找全部”看命中数量。命中数明显偏多,说明正则写宽了,比如{2,}把某些字段里的对齐空格也算了进去;命中数为零,说明转义或者字符集写错了。样本上验证通过,再应用到全量文件。
替换之前必须备份,这一步不能省。备份不只是复制一份文件,还要记录哈希值,这样改完之后能确认备份和原件完全一致。
# backup_once.py # 用法: python backup_once.py orders.csv import hashlib import shutil import sys import time from pathlib import Path def sha256(path): h = hashlib.sha256() with open(path, 'rb') as f: for chunk in iter(lambda: f.read(1 << 20), b''): h.update(chunk) return h.hexdigest() src = Path(sys.argv[1]) stamp = time.strftime('%Y%m%d_%H%M%S') dst = src.with_name(f'{src.stem}_{stamp}{src.suffix}') shutil.copy2(src, dst) print('原文件', sha256(src)) print('备份 ', sha256(dst))这里shutil.copy2会连同文件的修改时间一起复制,后面用文件时间做比对时不会因为备份动作本身产生差异。哈希用分块读取,1MB 一块,几百兆的文件也不会吃内存。两个哈希打印出来应该完全一致,如果不一致,说明复制过程中文件被其他程序写入了,这时候要停下来查是谁在写,而不是继续往下改。
3.4 保存回写:编码、分隔符、换行三重确认
改完之后,保存这一步最容易出问题。直接按保存,编辑器会用打开时的设置写回,这一般没问题;但如果中途改过编码或者分隔符设置,保存就会把文件结构改掉。所以我的习惯是:改完内容后不直接保存,而是走“另存为”,在另存对话框里确认三项——编码、分隔符、换行符。
编码要确认是否和原件一致。原件是 UTF-8 无 BOM,另存时选了 UTF-8-SIG,文件开头就会多三个字节,下游程序读第一列时会带上不可见字符。分隔符要确认是否还是原来那个,尤其是打开时手动改过分隔符的文件,另存时很容易忘记改回去。换行符要确认是 CRLF 还是 LF,和原件保持一致。
另存完成之后,立刻跑一次校验,确认行数、列数、表头和预期一致,再决定要不要覆盖原件。这个顺序看起来啰嗦,但它把“改错了”和“存错了”两件事分开了,出问题时能快速定位是哪一步的锅。
4. 大文件与多份文件:切分、合并、diff 的落地做法
编辑器的舒适区是有边界的。文件大到几百兆、上千万行,任何图形界面都会变卡,这时候正确的做法不是硬扛,而是先切分,改完再合并。另一类场景是多份文件需要对齐字段后合并,或者改完之后要证明“没改坏”。这一章讲三个动作:切、合、验。
4.1 超过舒适区就先切,别跟编辑器较劲
切分的依据是记录数而不是字节数。同样是 100MB,宽表可能只有十万行,窄表可能有三百万行,前者编辑器还能应付,后者就该切了。我的经验阈值是单文件不超过五十万条记录,超过就切,每份五万到十万条,改起来流畅,出问题也容易定位到具体哪一份。
切分脚本要注意一件事:必须按 CSV 记录切,不能按物理行切。字段里带换行的文件,一个记录会跨多个物理行,按行切会把一条记录劈成两半,合并回去就是坏数据。
# split_csv.py # 用法: python split_csv.py big.csv 50000 utf-8-sig import csv import sys from pathlib import Path src = Path(sys.argv[1]) rows_per_file = int(sys.argv[2]) if len(sys.argv) > 2 else 50000 encoding = sys.argv[3] if len(sys.argv) > 3 else 'utf-8-sig' with open(src, 'r', encoding=encoding, newline='') as f: reader = csv.reader(f) header = next(reader) # 表头单独拿出来,每份都写一遍 part, n, total = 1, 0, 0 out = open(src.with_name(f'{src.stem}_part{part:03d}.csv'), 'w', encoding=encoding, newline='') writer = csv.writer(out) writer.writerow(header) for row in reader: if n == rows_per_file: # 写满一份就换文件 out.close() part += 1 n = 0 out = open(src.with_name(f'{src.stem}_part{part:03d}.csv'), 'w', encoding=encoding, newline='') writer = csv.writer(out) writer.writerow(header) writer.writerow(row) n += 1 total += 1 out.close() print(f'{total} 条记录,切成 {part} 份')rows_per_file控制每份的记录数,五万是个折中值,既能保证编辑器流畅,又不会切出太多碎片文件。encoding参数要和原件一致,读的时候用 UTF-8-SIG 能自动吃掉 BOM,写的时候也用同一个编码,保证切片和原件的编码一致。csv.reader会自动处理引号内的换行和逗号,这是它比按行切分可靠的根本原因。切片文件命名带了三位序号,排序后顺序不会乱。
切完之后,如果只需要改某几列,还有一个更省事的做法:不切分,直接写脚本改。切分适合需要人工逐条判断的场景,纯规则化的批量修改用脚本一次跑完更快。两条路不冲突,看改动的性质选。
4.2 多份 CSV 合并:字段对齐比合并本身难
合并多份 CSV,难点从来不是把行拼起来,而是字段对不齐。同一个业务字段,A 文件叫user_id,B 文件叫uid,C 文件叫用户ID,直接拼会得到一张列数翻倍、大量空值的表。所以合并之前先做一次字段盘点,把各文件的表头列出来,人工确认哪些是同一个字段,再决定是重命名还是做映射。
# merge_csv.py # 用法: python merge_csv.py part001.csv part002.csv part003.csv import csv import sys from pathlib import Path files = [Path(p) for p in sys.argv[1:]] headers, rows = [], [] for p in files: with open(p, 'r', encoding='utf-8-sig', newline='') as f: reader = csv.DictReader(f, restval='') # 缺列用空字符串补齐 for col in (reader.fieldnames or []): if col not in headers: headers.append(col) # 按出现顺序做并集 rows.extend(reader) with open('merged.csv', 'w', encoding='utf-8-sig', newline='') as f: writer = csv.DictWriter(f, fieldnames=headers, restval='', extrasaction='ignore') writer.writeheader() writer.writerows(rows) print(f'{len(rows)} 条记录,{len(headers)} 列')这段代码的关键在DictReader的restval='',某个文件缺少某列时,读出来是空字符串而不是None,避免了后续写文件时出现字面量None。extrasaction='ignore'处理另一种情况:某个文件里有别的文件都没有的列,写的时候直接跳过,不会报错。字段并集按文件出现顺序排列,第一个文件的列序会保留在最前面,读起来比较符合直觉。
字段名不一致的情况,这份脚本不会自动救。稳妥做法是在合并前加一层映射字典,把uid手工映射成user_id,映射关系写进配置,不要靠模糊匹配,模糊匹配在字段名相近时会静默合并错列。
4.3 改完怎么验收:四个指标加一次抽样
验收不是“打开看一眼没问题”,而是要有可比对的指标。我固定看四项:记录数、列数、表头内容、文件哈希。前两项确认结构没被破坏,第三项确认列顺序没被改,第四项用来证明“除了我改的地方,别的字节没动”。
# verify_csv.py # 用法: python verify_csv.py old.csv new.csv import csv import hashlib import sys from collections import Counter def profile(path, encoding='utf-8-sig'): h = hashlib.sha256() with open(path, 'rb') as f: for chunk in iter(lambda: f.read(1 << 20), b''): h.update(chunk) with open(path, 'r', encoding=encoding, newline='') as f: reader = csv.reader(f) header = next(reader, []) widths = Counter(len(r) for r in reader) # 统计每条记录的字段数 return {'sha256': h.hexdigest()[:16], 'cols': len(header), 'header': header[:5], 'width_dist': dict(widths)} for p in sys.argv[1:]: info = profile(p) print(p) for k, v in info.items(): print(f' {k}: {v}')width_dist是最能说明问题的一项。如果新旧文件的分布都是{12: 100000},说明列数完全一致;如果新文件出现{12: 99990, 13: 10},说明有十条记录被多拆了一列,大概率是替换时把某个字段里的逗号暴露出来了。header[:5]只打印前五个列名,够用来确认列顺序,又不会刷屏。
哈希值在两个文件之间必然不同,因为内容确实改了,它的用途是记录基线——改之前跑一次,改之后再跑一次,把两个哈希写进变更记录,下次有人问“这版和上版差在哪”,至少有据可查。抽样则是人工的最后一道关,从改动涉及的列里随机挑二十行,和原件逐字段对比,这一步机器替代不了,因为只有人知道业务上什么值算对。
5. 避坑:六类翻车现场的现象、原因与解法
下面这六条都是我在 CSV 编辑上真实踩过的坑,按编码、数字、结构分了三组。每一条都按“看到什么—为什么—怎么办”写,遇到类似症状可以直接对号入座。
5.1 编码类:乱码与隐形 BOM
第一条,中文变问号或方块。现象是文件打开后中文全部显示为????或者一片方块,英文和数字正常。原因是用 UTF-8 去解码了一份 GB18030 的文件,非法字节被替换成了占位符。最危险的处理方式是在编辑器里反复另存,每存一次就多一层损坏,因为已经损坏的字符没法还原。正确做法是先关掉不保存,跑一次体检脚本确认编码,再用正确编码重新打开。如果文件已经被错误保存过,只能从备份恢复,这也是为什么改之前一定要留原件。
第二条,保存后下游程序说第一列不存在。现象是代码里按列名取第一列时报键不存在,但打印表头明明有这个列名。原因是文件带了 BOM,解码后列名前面多了\ufeff,肉眼看不出来。解决分两步:读取侧统一用utf-8-sig编码,它会自动剥掉 BOM;写入侧明确约定,给程序用的文件一律写 UTF-8 无 BOM,给人工双击打开的文件才写带 BOM 的版本。这个约定写进团队规范,比每次出问题再排查省事得多。
5.2 数字类:前导零与长数字
第三条,00123变成123。现象是编码类字段、工号、邮编这类带前导零的值,保存后零没了。原因不在 CSV 编辑器,而在中间经过了通用表格软件:它按列推断类型,把整列当成了数字,另存时自然丢掉了前导零。解法是全程在文本编辑器里改,不经过表格软件;如果必须经过,导入时把该列显式指定为文本类型。已经丢了零的文件,可以在编辑器里用正则按列宽补回来,比如六位工号统一补到六位,但前提是你能确定原始位宽。
第四条,十八位订单号变成1.23E+17。现象和上一条同源,但更危险,因为科学计数法会丢失末尾精度,123456789012345678可能变成123456789012345680,而且不可逆。原因同样是表格软件把它当成了浮点数。解法是识别出所有“长得像数字但不是数量”的列——订单号、身份证号、银行卡号、运单号——在编辑器里统一加引号或者加前缀,让下游任何工具都不敢把它当数字处理。这件事在数据交付前做一次,比事后追着修便宜得多。
5.3 结构类:引号内逗号与字段内换行
第五条,某几行的列数比表头多。现象是校验脚本报列数分布不一致,定位到具体行发现该行被多拆了一列。原因是字段内容里有一个逗号,但导出时没有给这个字段加引号,解析器把它当成了分隔符。这类问题没法用替换修,因为无法区分“这个逗号是分隔符还是内容”。正确做法是写脚本重新规范化:按已知的列数从右往左对齐,把多出来的部分合并回中间那个字段,再统一加上引号写回去。修完之后立刻跑列数校验,确认分布恢复成单一值。
第六条,总行数比业务记录数多。现象是文件显示一百万行,业务上只有八十万条记录。原因是某些字段里带了换行符,一条记录占了多个物理行,按行计数就会偏多。判断方法很简单:用csv模块按记录读一遍,得到的数量和业务系统对得上,就说明是物理行和记录的差异。处理时不要在编辑器里按行删除,那会把记录劈坏;用脚本按记录读写一遍,输出规范化的 CSV,问题自然消失。
注意:这六条里有四条的共同根源是“在错误的层面上操作”——在物理行层面处理记录、在文本层面处理数字、在猜测层面处理编码。每次动手前先问一句“我现在操作的是行还是记录、是文本还是数字”,能避掉大半的坑。
6. 把编辑动作固化成流程:模板、批处理与验收清单
单次修一份文件靠手艺,天天修就得靠流程。我现在的做法是给每类文件建一个目录模板:raw/放原件,backup/放带时间戳的备份,work/放编辑中的文件,out/放交付件,根目录放一个README.md记录这份文件的编码、分隔符、列数基线和改动说明。模板建一次,后面每次只需要复制目录,省掉的是“改了哪个版本”这种反复确认。
批处理的部分,把前面几个脚本串起来就是一个最小流水线:体检脚本先跑,输出编码和分隔符;备份脚本跟上,留哈希;编辑器里改完另存到out/;验收脚本对比raw/和out/。四步里有三步是脚本,人只负责中间那步判断和修改。这套流程跑熟之后,一份十万行的文件从拿到到交付大概十分钟,其中八分钟花在人工抽检上。
| 阶段 | 动作 | 通过标准 |
|---|---|---|
| 体检 | 跑编码与分隔符检测 | 一致率高于 80%,列数分布单一 |
| 备份 | 复制原件并记录哈希 | 两个哈希一致 |
| 编辑 | 样本验证后全量替换 | 查找全部命中数符合预期 |
| 回写 | 另存时确认编码、分隔符、换行 | 三项与原件的约定一致 |
| 验收 | 对比行数、列数、表头、抽样 | 结构指标一致,抽样二十行无异常 |
进阶一点的用法是把清洗规则也固化下来。常用的正则替换规则写在README.md里,按文件类型分组,下次遇到同类文件直接照着表跑一遍,不用重新想。如果手上的 Cassava 版本带宏或者脚本接口,可以把“打开指定编码、应用一组替换、另存为指定编码”录成一个动作,一键跑完;如果没有,用外部脚本做同样的事,效果一样,只是多敲一行命令。判断标准很简单:这个动作你要重复三次以上,就值得固化。
还有一个容易被忽略的技巧是保留一份“黄金样本”。从每类文件里挑五十行,人工确认过每个字段都正确,存成golden.csv。以后每次改完,把新文件的前五十行和黄金样本做一次逐字段比对,任何非预期的差异都会立刻暴露。这个做法比看哈希有用,因为哈希只能告诉你“变了”,黄金样本能告诉你“哪里变了”。
从那以后我每次拿到新的 CSV,都强制走一遍体检、备份、样本验证、验收这四步,哪怕只是改一个字段的值。费不了两分钟,但省掉的是返工和解释。希望帮
本文还有配套的精品资源,点击获取