xlsx → 企业微信智能表格 映射填充
把 Excel/WPS 表格的列按映射规则转换后批量写入智能表格子表。核心原则:先抽数据、再按目标字段类型逐列转换、最后读回验证——而不是把整张表硬塞进去。
耗时几乎全部来自串行的 CLI 网络调用(单次约 1.1-1.5s)。所有脚本都已做「减少调用次数 + 并发 + 缓存」,不要绕开脚本手写调用逻辑。
何时使用
- 用户给出
.xlsx/.xls文件 + 一个智能表格链接(doc.weixin.qq.com/smartsheet/...),要求映射/导入/填写/同步 - 用户要求把 Excel 里某些列更新到已有智能表格子表
- 用户要求核对两边数据差异并补齐
前置检查
执行任何写操作前:
- 读
wecomcli-shared(CLI 版本 ≥1.2.1)与wecomcli-smartsheet(安全约束、接口路由、字段类型与记录值格式) - 跑一次真实只读调用做 smoke test,不要用
auth show --status:
wecom-cli smartsheet sheets list --json '{"docid": "<docid>"}'
auth show --status 只验机器人级授权、不验文档级授权,会给假绿灯——实测它返回 authorized 而真实调用返回 850003 authorization expired。命中 850003 时重试与 Webhook 兜底都无效,必须让用户在「工作台-智能机器人」重新授权(见 references/troubleshooting.md §11)。
目标表的字段结构、选项 ID 一律以 fields list 的实时返回为准,不得凭印象填写。
推荐路径:一条命令跑全流程
python scripts/run_pipeline.py --xlsx "<路径>" --docid <docid> --sheet "<子表名>" \
--stage-dir ./stage --header-row 2 --start-row 3 --end-row 47 \
--key-column 商品名称 [--mapping-template assets/mapping-辛选提报表.json]
串起 extract → fields → probe → mapping → build → upload → write → verify,在 4 个检查点打印摘要并退出(不占 stdin),同时给出可直接复制的续跑命令:
| 检查点 | 触发条件 | 要做什么 |
|---|---|---|
| probe | 有列被拒/间歇 | 被拒列在 mapping 里 drop: true;间歇列写入带重试 |
| mapping | 无现成 mapping | 审校 gen_mapping.py 生成的草稿 |
| build | 有警告或未处理列 | 逐条确认警告、确认未处理列是丢弃还是目标表缺字段 |
| write | 记录数 > 100 | 按 references/safety.md 先向用户说明影响范围并取得确认 |
确认后加 --resume-from <阶段> 继续,例如 --resume-from build --mapping stage/mapping.draft.json。
分步路径(需要精细控制时)
Step 1 · 拉取目标表结构
wecom-cli smartsheet fields list --json '{"docid": "<docid>", "sheet_title": "<子表名>", "limit": 200}' > fields.json
fields.json 是后续所有类型转换的依据,必须落盘保存。
Step 2 · 探测单选/多选列的可写性
写入单选/多选列时可能收到 640027 不能修改包含级联配置的单选/多选列属性。不要根据这句文案下结论——已实测确认未配置级联的表上也会出现,且同一列同值重复请求结果不稳定。文案里的「级联配置」是服务端固定话术(详见 references/troubleshooting.md §1)。
python scripts/probe_select_writability.py --docid <docid> --sheet "<子表名>" \
--fields fields.json --cache stage/probe-cache.json --out stage/probe.json \
[--key-column 商品名称]
脚本用分组测试 + 早停 + 二分定位:先用一条记录打包写入全部选择列,成功即全部可写(1-3 次调用收工);失败则重试 3 次排除抖动,仍失败才二分递归定位坏列。结果按「字段结构哈希」缓存 7 天,表结构不变则重跑 0 次调用。
| 判定 | 含义 | 处置 |
|---|---|---|
writable |
首次即成功 | 正常映射 |
intermittent |
重试后成功 | 写入时带重试,不要 drop |
rejected |
重试 3 次全败 | mapping 里 drop: true,并说明这是实测结论而非文案推断 |
null_silent |
errcode=0 但读回为空 | 检查字段类型或选项是否失效 |
需要精确定位哪一列抖动时加 --split-intermittent(默认关闭,因为间歇列的处置与具体是哪列无关)。
Step 3 · 抽取 Excel 数据与图片
python scripts/extract_xlsx.py "<xlsx路径>" --out ./stage \
--sheet <工作表名> --header-row 2 --start-row 3 --end-row 47
产出 stage/rows.json 与 stage/images/。已处理 WPS DISPIMG 内嵌图片、标准 OOXML drawing 兜底、公式单元格取缓存值(#REF! 等错误值置空,绝不写进目标表)、跳过全空占位行。图片在 rows.json 里是 {"__image__": "<本地路径>"}。
Step 4 · 生成 mapping 草稿
python scripts/gen_mapping.py --rows stage/rows.json --fields fields.json \
--template assets/mapping-辛选提报表.json --out stage/mapping.draft.json
五级策略自动配对(模板命中 → 完全同名 → 归一化同名 → 子串 → 相似度),校验类型兼容性,自动 drop 不可写类型,输出 value_aliases 候选。
只需审校删改,不要从零手写——mapping 的 key 必须与 Excel 表头逐字一致(含换行符),手写 40+ 条极易不匹配,导致「未处理列」→ 改 → 重跑的返工循环。审校重点:_needs_review 条目,以及 stdout 里「找不到相近选项」的源值。
Step 5 · 构建 payload(不上传图片)
python scripts/build_payload.py --rows stage/rows.json --fields fields.json \
--mapping stage/mapping.draft.json --docid <docid> --sheet "<子表名>" \
--out stage/payload.draft.json --report-json stage/build-report.json
图片位置只留占位符,转换与上传解耦:报告有问题时不必白等一轮上传。必看警告条数与未处理列清单——未处理列 = 源表有、目标表没有的列,需人工确认是丢弃还是目标表缺字段。
Step 6 · 并发上传图片
python scripts/upload_images.py --draft stage/payload.draft.json --out stage/payload.json \
--image-cache stage/imgcache.json --concurrency 4
45 张图从串行 3-5 分钟降到约 1 分钟;缓存命中后 0 次调用、秒级完成。单张失败自动重试 3 次,仍失败则该字段从 payload 移除(不影响其他字段),重跑本命令只续传失败部分。
Step 7 · 写入与验证
python scripts/write_records.py --payload stage/payload.json --dry-run # 先看首条
python scripts/write_records.py --payload stage/payload.json # 实写
python scripts/write_records.py --verify --docid <docid> --sheet "<子表名>" --expect 45
接口返回成功 ≠ 写入正确,必须读回核对条数与关键字段值。已内置:单批失败自动重试 5 次(指数退避)、按 CLI 实际调用模式自适应批大小、重试耗尽后打印可复制的 --skip N 续跑命令而非崩溃。
补写已有记录(不是追加)用逐列模式,一列被拒不拖垮其他列:
python scripts/write_records.py --per-column --payload stage/payload.json --key-column 商品名称
上下文纪律
fields.json / rows.json / payload.json 体积大,禁止整份 Read 进上下文。用这些命令取需要的部分:
python scripts/build_payload.py --rows stage/rows.json --columns # 源表真实表头(JSON 数组,换行符原样)
python scripts/build_payload.py ... --summary # 只看三行统计
python scripts/build_payload.py ... --warnings-only # 只看警告与未处理列
探测结论直接看 stdout 的「判定结果」段,或 stage/probe.json 的 verdicts 字段。
mapping 配置格式
{
"default_target_type": "text",
"image_field_title": "商品图片",
"mappings": {
"源表列名(必须完全一致,含换行符)": {
"target": "目标字段名", "type": "number", "title": "图片标题",
"drop": false, "_note": "映射说明", "_needs_review": true
}
},
"value_aliases": { "目标字段名": { "源值": "目标选项文本" } }
}
type可省略,省略时按fields.json里目标字段类型自动推断drop: true跳过该列,并在报告中单列出来value_aliases处理源值与目标选项文本不一致(源表写女鞋,目标表选项只有服装鞋包)_note/_needs_review是给人看的,写入时忽略
值转换规则
| 目标类型 | 转换方式 |
|---|---|
text |
str().strip();占位符视作空 |
number / currency |
提取数字;无法解析时报警告留空,不静默填 0 |
single_select |
按选项文本查 property_single_select.options[].id;查不到则报警告留空 |
select |
按 ,,、;;\n 拆分成多值,逐个查 ID |
user |
生成 [{"userName": "姓名"}];失败时改用 userId(用 wecomcli-contact 查) |
image |
占位符 → upload_images.py 两步上传 → imageUrl |
wwgroup |
API 不支持写入,一律 drop |
date_time |
必须是 "YYYY-MM-DD HH:mm:ss",不能丢秒 |
占位符清单:/ - -- 无 N/A n/a NA na null NULL None 空
图片链路
本地图片 → media upload → media_id → smartsheet images upload → url → CellImageValue.imageUrl
由 upload_images.py 完成,必须带 --image-cache(Windows 下缓存 key 大小写不敏感,同图换路径写法也能命中)。链路与故障处理细节见 references/mapping-cookbook.md 第七节。
常见报错速查
| 报错 | 处置 |
|---|---|
640027 不能修改包含级联配置的单选/多选列属性 |
别信文案。未配置级联的表上也会出现且间歇发生。先按重试处理,重试耗尽才 drop |
850003 / authorization expired |
机器人文档级授权过期。重试与 Webhook 均无效,让用户重新授权 |
851003 / no authority |
企业可见范围超 10 人。停止 CLI 重试,见 references/troubleshooting.md §2 的 Webhook 兜底 |
538005 |
无全部权限,SQL 查询降级为 records list |
命令行太长 |
argv 超限。脚本会自动降档;若仍命中,用 --verbose-resolve 排查 node 直调为何失效 |
| 单选写入留空 | 源值与选项文本不一致,加进 value_aliases |
WinError 2 / UnicodeDecodeError |
见 references/troubleshooting.md §6、§7 |
脚本启动时会打印 CLI 调用模式:node-direct(argv 上限 32767)或 cmd-shim(上限 8191,批大小自动降到 4000)。看到 cmd-shim 说明 node 直调失效,仍能跑通但明显变慢,用 --verbose-resolve 排查。
安全与交互约束
- 单次新增/修改超过 100 条记录,必须先用自然语言说明影响范围并取得用户确认
- 写操作前先读 2-3 条现有记录,对齐已有用词与选项习惯
- 探测产生的临时记录必须清理干净并告知用户(分组探测在全部可写时只产生 1-3 条)
- 删除任何记录前必须先确认删除范围(见
references/safety.md) - 内部 ID(docid / field_id / record_id / media_id)不出现在给用户的回复里,用字段名、记录主键指代
- 目标表字段类型可能与直觉不符(如某表的
备注实际是number),写入前务必核对类型
参考文件
references/mapping-cookbook.md—— 各类型列的映射配方、单选别名表、表头陷阱、行与记录的主键匹配references/troubleshooting.md—— 报错速查、探测策略、Webhook 兜底、Windows argv 上限references/safety.md—— 批量写入与删除的安全红线assets/mapping-辛选提报表.json—— 辛选提报表 → 商务自用表 的现成映射,作gen_mapping.py --template用
脚本清单
| 脚本 | 作用 |
|---|---|
scripts/_cli.py |
CLI 定位与调用(共用),暴露调用模式与 argv 上限 |
scripts/run_pipeline.py |
串起全流程,在 4 个检查点停下等确认 |
scripts/extract_xlsx.py |
抽数据 + 提内嵌图片 → rows.json |
scripts/gen_mapping.py |
自动生成 mapping 草稿 + 别名候选 |
scripts/probe_select_writability.py |
分组探测选择列可写性,结果缓存 7 天 |
scripts/build_payload.py |
按 mapping 转换值 → payload.draft.json + 报告 |
scripts/upload_images.py |
并发上传图片、回填 url,可续传 |
scripts/write_records.py |
批量写入 / 逐列补写 / 写后验证,带重试 |
所有脚本用 subprocess 的 arg 列表直接传参(不走 shell),避免转义与注入问题。Windows 下统一加 CREATE_NO_WINDOW,不会闪控制台窗口。命令一律用 python(Windows 上通常没有 python3)。