把微信收藏表情导出来,再分批导入飞书
微信里收藏了很多表情包,换到飞书以后,真正麻烦的是「怎么批量搬过去」。
这次迁移时,我没有在微信客户端找到收藏表情的批量导出入口;飞书的导入界面一次最多接受 50 张图片。下面按当时的界面和限制记录做法,后续版本需要重新确认。
最后走的是一条很朴素的路线:
先让微信网页版看到这些表情,再从网页里把图片资源取出来,最后按 50 个一组打成 zip。微信负责把表情发出来,浏览器负责拿资源,脚本负责收集和分组。
操作步骤
实际操作分两段。
第一段,从微信导出到本地。
打开微信网页版:
https://wx.qq.com/扫码登录后,打开「文件传输助手」,或者一个专门用来转发表情的小群。
然后在手机微信里,把要导出的收藏表情逐个发到这个聊天里。等网页版能看到这些表情以后,打开浏览器 DevTools 的 Console。
执行前先打开脚本源码检查。即使没有外部依赖,加载到已登录页面里的脚本仍能访问页面内容;不愿授予这类访问能力,就不要运行。博客地址只是分发入口,不是安全保证。
Chrome 第一次在 DevTools Console 里粘贴代码时,可能会要求先输入 allow pasting。这是浏览器为了防止用户误粘恶意代码做的提醒。不要随手跳过这条提醒:只执行能确认来源、并且愿意信任的脚本。
确认后执行:
(() => {
const s = document.createElement('script');
s.src = 'https://shengsheng.fun/files/wechat-emoji-to-feishu/scripts/wechat-emoji-exporter.js';
document.head.appendChild(s);
})();脚本会在页面右下角显示进度,完成后浏览器会下载一个类似这样的文件:
wechat-emojis-2026-05-29-12-30-00.zip第二段,从本地导入飞书。
解压以后,目录大概是这样:
wechat-emojis/
group-01/
wechat-emoji-0001.gif
...
wechat-emoji-0050.png
group-02/
wechat-emoji-0051.jpg
...为适配当时的导入上限,每个 group-* 文件夹最多放 50 个表情。导入时按文件夹分批选择。
为什么要绕到微信网页版
这个方案利用的是微信网页版已经加载到聊天里的表情图片,并不读取微信的「收藏表情库」。收藏表情发到聊天里以后,微信网页版为了展示这条消息,就必须把对应图片加载出来。
在页面里,这类自定义表情大致会变成这样的元素:
<img class="custom_emoji msg-img" src="...webwxgetmsgimg...">脚本做的第一件事,就是只找当前聊天里的这些 img.custom_emoji.msg-img。头像、公众号图片、聊天列表图标都不在导出范围内。
找到以后,再用当前网页已有的登录态请求这些图片资源。这里没有读取 Cookie,也没有把图片上传到别的地方;所有处理都发生在当前浏览器里。
导出范围就是当前聊天中、微信网页版能加载出来的自定义表情。尚未发出的收藏表情不在页面里,脚本也拿不到。
为什么不直接贴 400 行代码
最早直接在控制台里跑了一段完整脚本。它能用,但不适合分享。
完整脚本除了几行 DOM 查询,还包含这些逻辑:
- 定位当前聊天滚动容器。
- 向上滚动,让历史消息懒加载出来。
- 收集
custom_emoji图片地址。 - 下载图片二进制。
- 按文件头识别
gif、png、jpg、webp。 - 按内容去重。
- 手写 zip 结构。
- 按 50 个一组生成目录。
- 触发浏览器下载。
直接把完整代码贴到文档里,读者复制起来很痛苦,也不方便后续修 bug。
所以完整脚本放成博客静态资源:
https://shengsheng.fun/files/wechat-emoji-to-feishu/scripts/wechat-emoji-exporter.js控制台里只需要插入一个 script 标签。
这里没有用 GitHub Raw 当脚本 CDN。GitHub Raw 更适合看源码或下载文件,不适合作为网页里的 <script src> 长期加载;浏览器可能会因为 MIME 类型和 nosniff 策略拒绝执行。放在自己博客的静态资源里,路径、缓存和响应类型都更容易掌控。
脚本里几个取舍
第一,文件格式不看 Content-Type。
实测微信接口返回的 header 不一定准确。有些 GIF 或 PNG 可能也会被标成 image/jpeg。如果只看 Content-Type,动图很容易被保存成错误后缀。
所以脚本会读文件头,也就是常说的 magic number:
GIF:47 49 46PNG:89 50 4E 47JPG:FF D8WEBP:RIFF ... WEBP
第二,zip 里不做二次压缩。
表情文件通常已经是 GIF、PNG、JPG 或 WebP,本身就是压缩格式。再做 deflate 收益不大,但会让纯浏览器脚本复杂很多。
所以脚本手写的是一个最小 ZIP:每个文件作为 stored entry 放进去,再生成 central directory。这样浏览器里不需要额外加载 JSZip 之类的第三方库。
第三,按内容去重。
同一个表情如果发了两次,URL 可能不同,但图片内容一样。脚本会用文件大小和 CRC32 做一个轻量去重,避免最后 zip 里重复出现同一张图。
第四,默认按 50 个一组,在 zip 里提前分好目录,避免导入时手动计数。
使用时要注意什么
这个方案有几个前提。
- 能登录微信网页版。
- 想导出的表情已经发到当前聊天。
- 网页版已经实际加载出这些表情。
- 最好在专用聊天里操作,不要混在日常群聊里导出。
如果表情特别多,可以分几批发。脚本会尝试向上滚动当前聊天加载历史消息,但网页版本身如果没加载到,脚本也拿不到。
这个小工具只解决搬运问题
把收藏表情发到聊天里、登录网页版、分批导入飞书,仍然要手动完成。脚本负责滚动加载聊天记录、从 DOM 收集已加载的图片、下载去重,并在浏览器里打包分组;它没有直接访问收藏库,也没有操作飞书导入界面。
如果以后微信网页版结构变了,最容易坏的地方大概是表情元素选择器和图片接口 URL。脚本已经把这些逻辑集中在一起,后续需要调整时,也比重新手动整理几百个表情轻松。