Windows 控制台默认 GBK:定时脚本打印一个 ✔ 就能整轮崩掉
踩坑现场:脚本在自己的终端里跑通,挪到 Windows 版青龙的定时任务里,日志最后一行变成UnicodeEncodeError: 'gbk' codec can't encode character '\u2714' in position 298: illegal multibyte sequence。崩溃点就是任务清单里那个勾 —— 输出「✔ 已完成」时炸的,而崩溃发生在打印之后、结果写盘之前,所以这轮任务等于白跑。
一、根因是 stdout 的编码,不是文件编码
源码文件是 UTF-8,字符串在内存里也正常,问题只在写到控制台那一刻:中文 Windows 的 活动代码页是 GBK(cp936),Python 按它编码 stdout,GBK 里没有的字符直接抛错。定时任务的日志也是走 stdout 捕获的,所以面板里看到的断链日志和手工复现是同一个原因。
二、三种修法,按优先级排
# 1) 最省事,Python 3.7+
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
# 2) 兜住老版本,并且容忍无法编码的字符(宁可丢字形也别丢整行)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
# 3) 结果落盘一律显式写 UTF-8 字节,不靠默认编码
open(path, "wb").write(text.encode("utf-8"))第 2 种里的 errors="replace" 是关键:定时任务最怕的不是少一个图标,而是整条记录没写下去。宁可日志里出现问号,也不要让一次打印把后面二十行带走。
三、掉字比崩溃更隐蔽
另一类事故是往 UTF-8 文档里追加中文时,把带 \v 转义的字符串直接写进去,落盘后正文里混进 0x0B(垂直制表符),肉眼看是「少了一行」,grep 却因为行被截断而搜不到。做法是追加前先断言、追加后再统计:
assert "\x0b" not in payload
open(path, "ab").write(payload.encode("utf-8"))
raw = open(path, "rb").read()
assert raw.count(b"\x0b") == 0 # 整份文档都不该有 VT
assert anchor in raw.decode("utf-8") # 并且新段落真的在里面同样的道理适用于任何「脚本写给人看的文档」:写入之后立刻用字节数、锚点字符串、非法控制字符计数三项复核,成本三行,省掉的是第二天对着残缺日志猜。
小结
跨平台脚本里不要用图形符号当唯一状态标记,勾和叉之外还要有文字(已完成/未完成);把 stdout 编码在入口处一次性处理掉;任何追加写都要带读回校验。这三条都是崩溃一次就能记住,但代价是一轮定时任务白跑。
- 点赞
- 收藏
- 关注作者
评论(0)