目标:把历史微信头像(都是我家猫的照片)做成一套微信表情包,并在微信表情开放平台提交上架。
整体流程分四步:素材挖掘(微信本地缓存 + agy 识图)→ 文案创作(Gemini 生成 + 人工把关)→ 表情制作(PIL 生成 240×240 透明圆角图)→ 平台提交(Playwright 自动化)。最终「玳瑁烟大王」17 张静态表情提交成功,进入审核。
[!timeline] 整体流程
- 10:00 · 定位微信头像缓存,140+7839 张待筛选
- 10:20 · 发现 agy 可识图,prompt 写图片路径即可
- 10:40 · 蒙太奇批量识别,140 张 → 70 张猫
- 11:00 · 正样本匹配过滤,确认 63 张是我的猫
- 11:10 · 感知哈希去重,仅 27 张不同照片
- 11:30 · 文案 v1 直译,无网感
- 12:00 · 文案 v2 网红梗,太油
- 12:30 · 文案 v3 简短口语,定稿 17 个
- 13:30 · 文字样式迭代 6 版,透明圆角定稿
- 15:00 · 横幅重做(高清原图裁剪)
- 15:30 · Playwright 登录,创建专辑
- 16:00 · 名称被占用 → 玳瑁烟大王
- 17:00 · 素材/含义词/弹窗全部处理
- 17:30 · 提交成功,等待审核
1. 素材挖掘
1.1 微信头像缓存在哪
微信 Mac 版把所有出现过的头像缓存在本地。消息数据库是 SQLCipher 加密的,头像缓存是明文 jpg:
~/Library/Containers/com.tencent.xinWeChat/.../2.0b4.0.9/
├── Avatar/ # 应用级:140 张,时间跨度 2021-2026
└── cc7e82cd.../Avatar/ # 账户级:7839 张 + 572 张 HD
1.2 批量识图:蒙太奇 + agy
逐张识别太慢。把 25 张缩略图拼成 5×5 蒙太奇,让 Gemini 一次识别哪些格子是猫。140 张只用了 6 次调用,识别出 70 张猫图。
识图工具是 Antigravity CLI(agy)。关键用法:图片绝对路径必须写在 prompt 文本里,agy 才会把它作为多模态输入附加:
agy -p "识别这张图: /path/to/img.jpg" --dangerously-skip-permissions
1.3 从”猫”到”我的猫”
70 张猫图里混着好友的猫头像。做法:
- 人工确认 15 张正样本
- 颜色直方图 + 感知哈希粗筛(区分度不足,背景色干扰大)
- 让 Gemini 看”样本拼图 + 待确认拼图”,按毛色/花纹判断是否同一只个体
最终确认 63 张。
1.4 去重:63 张其实只有 27 张不同
感知哈希去重后发现:微信把同一张图以不同文件名缓存了多次。63 张去重后只剩 27 张不同的照片。这正是”历史头像”的本质——每次换头像,同一张图可能被存成多个 hash 文件名。
27 张选 24 张做一套,够用。后因我对其中 7 张不满意且无更多素材,改为 17 张(微信最少 16 张)。
2. 文案创作
2.1 “网感”的迭代
| 版本 | 做法 | 结果 |
|---|---|---|
| v1 | 按姿态直译(“震惊!”“求摸摸”) | 太平,无网感 |
| v2 | 网红梗堆砌(“泰裤辣”“栓Q”“我真的会谢”) | 更差,太油 |
| v3 | 简短口语有画面感,参考”仰望大佬”“彻底摆烂” | 通过 |
结论:网感 ≠ 网红梗。简短(2-6 字)、日常口语、有画面感、略带幽默,不要梗堆砌。
我附加的约束:禁脏话、禁”喵”字、”仰望大佬”必须保留。
2.2 选择交互
光给文案我对不上图。生成带图选择卡:每张猫图 + 编号 + 3~5 个备选文案,我报”编号+数字”即可。
最终 17 个文案:
| 编号 | 文案 | 编号 | 文案 |
|---|---|---|---|
| 01 | 什么情况? | 09 | 彻底摆烂 |
| 02 | 被拿捏了 | 10 | 拒绝打工 |
| 03 | 你在教我做事? | 11 | 有事? |
| 04 | 吃瓜中 | 12 | 思考复杂的人生 |
| 05 | 听不懂你说啥 | 13 | 别问我 |
| 06 | 让我来拉扯一下 | 14 | 优雅 |
| 07 | 审判你的罪行 | 15 | 仰望大佬 |
| 08 | 悄悄探头 | 16 | 电量仅剩1% |
| 17 | 原地隐身 |
3. 表情制作
3.1 文字样式迭代 6 版
| 版本 | 样式 | 问题 |
|---|---|---|
| v1 | 白底 + 下方文字条 | 整体不满意 |
| v2 | 文字叠图上 + 黑描边 | 字太大、融合差 |
| v3 | 白字柔和阴影,固定底部 | 位置太固定 |
| v4 | 智能找空白区放文字 | 长文案被圆角截掉 |
| v5 | 动态字号 + 边界约束 | 圆角变黑色 |
| v6 | RGBA 透明圆角 | 定稿 |
智能定位:分 4×4 网格找局部方差最小的块(最平坦区域)放文字,避开猫脸,每张位置自然不同。
黑角 bug 的根源:canvas.convert('RGB') 会把透明像素变成纯黑(0,0,0),圆角区域因此变黑角。修复:直接保存 RGBA PNG(微信支持透明背景)。
3.2 横幅:三次被否
微信要求横幅 750×400、图片中避免出现任何文字。前 3 版:带文字(违规)→ 贴纸拼图(“几个图片拼的”)→ 渐变装饰(“太丑”)。
问题本质:把 240×240 的贴纸拉大到 320px,又糊又变形。正确做法是用高清原图按比例裁剪(1080×1080 → 750:400),清晰自然。
4. 平台提交
4.1 登录
Playwright 无法复用 Safari 登录态,启动自动化浏览器重新扫码,再用 storage_state 保存登录态供 headless 复用。playwright install 版本与本地 Chromium 不匹配,直接指定已有浏览器可执行路径解决。
4.2 表单卡点
按踩坑顺序记录:
名称被占用。”猫猫日常”提交时提示已存在。通用名都易重名,改用猫的毛色特征起名——我家猫是”玳瑁烟色”,定名「玳瑁烟大王」,一次通过。
fill() 不触发 Vue 事件。Playwright fill() 填入的值保存后丢失——表单用 Vue 双向绑定,需要模拟真实键盘输入并 dispatch 事件:
await el.click()
await el.fill('')
await page.keyboard.type(text, delay=60)
await el.dispatch_event('input')
await el.dispatch_event('change')
级联下拉选角色。”角色/内容”是两级级联:先选”宠物/动物角色”,再出现二级菜单选”猫”。第一级选项被覆盖层拦截 pointer events,需 force=True 或 JS click()。
含义词必填 + 4 字静默截断。每个表情必须填”含义词”(聊天页显示名),输入框是点击表情卡片后才出现的(placeholder="输入含义词"),需先激活全部 17 个再填。6 字的”你在教我做事”被静默截成 4 字”你在教我”——填完必须回头核对。
裁剪弹窗。上传横幅/封面/图标后有”裁剪 XXX”对话框,不点”确定”素材等于没传(提交时报”横幅不能为空”)。
类型与主题。默认”动态表情”,选”静态”后有确认弹窗,漏点会变回动态;主题 radio 必选(选了”怼人/斗图”)。
agy 免费额度。批量识别 HD 头像时连续超时,根因是 Individual quota reached(免费额度用完,3.5 小时重置)。文本 prompt 还能过,图片请求(耗 token 高)直接卡死。
4.3 提交
全部字段验证通过后提交,页面返回”表情提交成功,请耐心等待审核”。作品 ID stiker_178619483496f62efcde2da135,审核通常 1-5 个工作日。
| 项目 | 内容 |
|---|---|
| 名称 | 玳瑁烟大王 |
| 类型 | 静态表情专辑(17 张) |
| 角色 | 宠物/动物角色 → 猫 |
| 风格 | 日常、软萌可爱、搞笑 |
5. 顺带产出
把 agy 的识图/搜索能力封装成了可复用工具:
agy_bridge.py:MCP server,暴露agy_vision(批量识图)/agy_websearch/agy_askagy-vision-websearch插件:agy plugin validate通过,已安装- 能力研究报告:
docs/agy_capability_report.md
总结
- 微信不保留自己的头像历史。本地缓存是”出现过的人”的头像,自己的旧头像只能靠好友端缓存间接找回,且同图多缓存导致大量重复,去重后素材远少于预期。
- AI 配文案不等于网感。网红梗堆砌反而最不讨喜;简短口语、有画面感的文案才耐看。审美仍需人工把关。
- 自动化提交的坑集中在”表单事件”。Vue 双向绑定、级联下拉、裁剪弹窗、静默截断,每个都是”看起来成功实际失败”的陷阱,必须保存后重新打开页面验证。