将mem.ai中最大的一篇图片笔记导出的历程
将mem.ai中最大的一篇图片笔记导出的历程
mem.ai本来很方便,有点什么零碎想法都可以粘贴到那里。但其优势也正是劣势所在:某篇笔记经年累月后积攒了太大的体积,最近就总也打不开了,好像连累得其他笔记也都打开缓慢。所以,就想将其导出到Notion,Notion处理大体积文件完全没问题!
想打开这篇笔记已经很难了,经过无数次引导刷新,总算打开了,全选、复制、粘贴到Notion,好,结束了?
并没有
因为发现粘过来Notion的所有图片,其地址链接仍然在mem.ai也就是Google这里,一旦哪天它发现盗链或者干脆图库崩了,那这笔记就没法看了!
于是开始吧,又一次折腾之旅。
第一步,将Notion中的笔记内容导出为Markdown
用Typora打开此MD文件,发现所有图片内容都能正常显示,只不过查看其地址,都是Google的地址。
第二步,将笔记内所有图片下载到本地并关联
导出的是一个zip文件,将其中的md文件解压到目标文件夹。用Typora打开后,如图所示,Notion 在导出时根本没有下载图片,因为它在 Markdown 里将这些内容解析成了纯文本网址(Text Link),而不是 Markdown 的图片语法 。既然 Notion 认为这只是几行普通网址,导出时就不会去下载图片文件。
观察截图中的 URL,这其实是 Mem 的代理图片地址,里面已经包含了图片的原始真实地址(例如 [https://cdn.beekka.com/blogimg/asset/](https://cdn.beekka.com/blogimg/asset/)...),但并非真正的Markdown标准图片(显示)语法。
让 Notion 导出的 Markdown 包含标准图片语法
的方法使用 Emeditor 一键正则替换(如果你想在本地生成标准
.md)如果你手头有之前解压出来的
.md文件,想在本地把它快速改成标准 Markdown 图片格式(即加上![]包裹):
- 用 VS Code 或 Emeditor 打开该
.md文件。- 按
Ctrl + H打开查找和替换。- 勾选 正则表达式模式(正则图标
.\*)。- 输入以下内容(原链接格式类似:
[https://www.mem.storage/proxy?url=https%3A%2F%2Fcdn.beekka.com%2Fblogimg%2Fasset%2F202407%2Fbg2024070211.webp&token=59ce0544-cee1-41e9-b0c1-0a9baaf9d84d](https://www.mem.storage/proxy?url=https%3A%2F%2Fcdn.beekka.com%2Fblogimg%2Fasset%2F202407%2Fbg2024070211.webp&token=59ce0544-cee1-41e9-b0c1-0a9baaf9d84d)):
- 查找(Find):
\[https?://[^\s\)]*?token=([a-f0-9-]+)\]\((https?://[^\s\)]+?\.(?:webp|png|jpg|jpeg|gif|svg))(?:&token=[^\s\)]*)?\)- 替换为(Replace):
- 点击 全部替换(Replace All)。
最快修复方案:用 Python 脚本一键批量下载图片并替换
如果你导出的 zip 包里有个 .md 文件,可以用以下 Python 脚本自动提取这些链接中的真实图片,下载到本地并自动修改 Markdown 语法,这样重新导入 Notion 就完美了:
- 打开刚才解压出来的文件夹,在里面新建一个名为
fix_md.py的文件。 - 将以下代码粘贴进去并保存:
1 | import os |
因为 Mem 官方对此类私有存储图片的鉴权逻辑做了修改:必须携带登录状态下的 Cookie 凭证 才能访问
mem.storage/proxy端点,仅靠链接末尾带的token=已经无法单独通过脚本直接下载(会返回 403 错误)。但是,即使带着下面的种种条件(包括cookie/token/headers),依然无法下载需要谷歌鉴权的图片文件,于是对上面代码做了精简,以下内容仅供学习参考。
针对这种情况,这里为你提供两个解决方案:
方案 A:让 Python 脚本携带你的 Cookie 下载(最推荐)
利用你在浏览器中登录 Mem 的会话 Cookie,让 Python 伪装成你的浏览器去下载。
重点:获取你的 Mem Cookie
打开浏览器登录 Mem.ai。
按
F12打开开发者工具,切换到 Network (网络) 标签页。随意点击左侧的一篇笔记或刷新一下页面,在 Network 列表中找到任意一个请求(如
graphql或proxy?url=...)。点击该请求,在右侧 Headers (请求头) 中找到
Cookie:,右键选择 Copy value (复制值)。关键不只有 Cookie,还有 Mem 的核心认证机制所使用的 Bearer / OAuth Token。
在截图的请求头中,
Authorization字段下有一长串OAuthAccessToken eyJhbGciOi...,这才是访问 Mem 云端资源的真实通行证!我们需要把这个 Token 提取出来,加进请求头里(同时保留 Cookie 和正确的代理重组逻辑)。
storage.googleapis.com 的谷歌私有存储图片依然报错,下载不来。对比这两种链接可以发现根源所在:
- AIGIF 的链接:真实地址是
cdn.beekka.com(公共图床),由于不限制访问,脚本解码后直接去请求这个 CDN 就能顺利下载。而且,下载完毕后即**自动于md文件中完成同文件夹下assets文件夹的路径替换**。 - Imagenie 的链接:真实地址是
[storage.googleapis.com/memvp-25499](https://storage.googleapis.com/memvp-25499)...(Mem 存储在谷歌云上的私有图片)。这个地址需要配合 Mem 代理服务 加上 你的登录 Cookie 以及token才能放行,直连或格式拼错就会抛出 403。
现在的情况是,前一种链接能够顺利下载,而后一种Imagenie链接,即使加上了Cookie和token也是依然不能下载,看来就是权限的问题。
这时候又出现另一种思路。
既然mem.ai的网页(偶尔)能打开笔记并正常显示图片,就表明所有图片(一定要用page down在页面下拉到最底)均已完成缓存加载,那就直接到缓存里去取下载不来的图片不就好了?!
在运行打包脚本前,先在 Mem 网页里快速按住
Page Down或按住空格键,一路滚动到底部。确保页面上所有的图片都加载呈现出来。
此时再运行控制台脚本,就能抓取到全部图片(数量就会对上了)。
直接在浏览器控制台(Console)一键批量下载
因为浏览器控制台发起的 fetch 会自动带上当前页面的所有 Session、Cookie 和 Header,所以直接在 Console 运行下载代码是不会触发 403 的。
- 在 Chrome 中打开 Mem.ai 该笔记页面(确保图片能正常显示)。
- 按
F12切换到 Console(控制台) 标签页。 - 粘贴以下代码并回车,它会自动提取页面里所有的 Google/Mem 图片并触发浏览器下载:
要实现“自动读取 MD 文件里的所有链接,并一次性批量下载剩余的全部GCP图片”,最流畅的方案是:写一个 Python 自动化脚本,自动把 Markdown 中的所有未能完成下载的GCP图片 URL 提取出来,生成控制台代码,你在浏览器里粘贴运行即可一键打包。
第一步:运行 Python 脚本生成浏览器提取代码
在你的电脑上运行这段 Python 脚本。它会读取 .md 文件,把所有图片的原始 URL 自动解析并整理好,直接在控制台输出一段可执行代码:
1 | import os |
第二步:一键运行并下载 ZIP
- 打开刚刚生成的
browser_runner.js,Ctrl + A 复制全部内容。 - 打开已经登录 Mem 的浏览器,按
F12打开控制台(Console)。 - 粘贴并回车,浏览器就会自动把你在 MD 文件里的所有图片逐个提取,并自动打包成
mem_all_assets.download.zip触发下载!
PS:有时会遇到鉴权隔段时间失效的情形,在Console中复制命令会提示401无鉴权。这时还有一套解决方案:
当控制台依然全量返回 401 鉴权失败时,说明 Mem 的后端在过去一段时间更新了静态资源的代理鉴权机制:它不再使用 localStorage 中的 JWT Token 校验,而是改用统一的网页前端主 API Token(Session Credentials)或直接依赖内存中的 Cookie 握手。
既然自动化注入 Header 仍触发 401,这里为你提供一个 100% 成功、零鉴权问题的最终解法:直接利用浏览器已经加载在 DOM/内存中的 <img> 标签或 Blob URL,把图片通过 Canvas 画布绘制并导出为本地文件。
一键破局:DOM 渲染法(控制台脚本)
请直接在打开了该 Mem 笔记的页面控制台(F12 -> Console)粘贴运行以下代码。这段代码不发起任何重新鉴权的网络请求,而是直接抓取当前网页上已经渲染出来的图片像素:
1 | (async () => { |
操作建议与注意点:
- 先在 Mem 笔记页面中滑动到底部:确保所有的 122 张图片都已经完成了懒加载(Lazy Load),并在页面上呈现。
- 复制粘贴上述代码并按回车:脚本会把页面上能看到的真实像素图像直接画到 Canvas 里导出为 ZIP,完全绕过任何 HTTP 接口层面的 401 拦截。
第三步:把下载的 ZIP 解压,并替换 MD 链接
解压得到的 assets 文件夹放到 MD 同级目录下。原网址如:
打开 VS Code 载入你的 .md 文件,按 Ctrl + H 打开正则替换(勾选 .*):
查找 (Find):
1
(!\[[a-f0-9-]+\])\(https?://[^\s\)]*?%2F(?:images|mem-uploads)%2F([a-f0-9-]+)%2Fimage(\.[a-zA-Z0-9]+)\)
替换为 (Replace):
Code snippet
1
$1(assets/$2$3)
正则原理拆解
(!\[[a-f0-9-]+\])➔ 捕获组$1: 完整保留前半段的![59ce0544-cee1-41e9-b0c1-0a9baaf9d84d]。
%2Fimages%2F: 吃掉前面一直到%2Fimages%2F的所有域名及代理前缀,并在替换结果中替换为assets/。
([a-f0-9-]+)➔ 捕获组$2: 精准捕获图片的 36 位 UUID 哈希值(即374da85e-19cf-4452-9e1f-3b095d7aabac)。
(\.[a-zA-Z0-9]+)➔ 捕获组$3: 捕获%2Fimage后面的动态扩展名(如.png、.webp等),原样保留在末尾。
如上操作后,相信绝大部分图片都已经改成了本地链接'assets\xxxx.png'之类,如何查找仍未替换成本地链接的图片链接?
方法一:在 VS Code 中使用正则表达式精准查找(最直观)
打开你的 .md 文件,按 Ctrl + F 打开查找框,勾选右上角的 .\*(开启正则表达式),输入以下正则:
1 | !\[.*?\]\(https?://[^\s\)]+\) |
📌 原理拆解:
!\[.*?\]:匹配图片的前缀![alt文本]。\(https?://[^\s\)]+\):匹配以http://或https://开头的完整远程 URL。- 这样就可以自动跳过所有已经修改为
(assets/...)或(assets\...)的本地路径,只高亮并定位依然是网络链接的图片!你可以按Enter逐个检查剩余的链接。
方法二:用 Python 脚本自动扫描并汇总剩余远程链接
如果你想把所有漏网之鱼的行号、图片 URL 统一提取出来看,可以在同级目录下运行这个简单脚本:
1 | import os |
脚本运行后会直接输出具体的行号和未替换的 URL,方便你针对性地进行手动修改或补全。
有张图片不知什么格式,被刚才的代码自动更改为png打不开,怎么办?
出现这种情况是因为脚本默认给它赋予了 .png 后缀,但图片的实际底层数据其实是 WebP、JPEG、GIF 或 SVG 等格式。扩展名不匹配导致图片查看器或 Typora 无法正常解码解析。
解决办法非常简单,分为“定位并修复单张图片”和“批量修正可能混错格式的图片”两种:
方法一:查看图片的“真身”格式(最直接)
你可以通过以下 2 种小技巧找到它的真实格式,然后把文件名后缀改过来:
- 用浏览器打开该图片:
- 在文件管理器里,把这张打不开的
.png文件直接拖进 Chrome / Edge 浏览器。 - 浏览器具有极强的容错解码能力,会自动渲染显示图片。
- 在图片上右键选择“另存为”,观察弹窗默认提示的保存类型(例如
WebP 图像或JPEG 图像)。
- 在文件管理器里,把这张打不开的
- 用记事本查看文件头(Header):
- 用记事本(Notepad)打开这个打不开的文件,看最前面几个字符:
- 如果开头包含
RIFF...WEBP➔ 真实格式是.webp,将文件名重命名为.webp即可。 - 如果开头包含
JFIF或Exif➔ 真实格式是.jpg。 - 如果开头包含
GIF89a➔ 真实格式是.gif。 - 如果开头包含
<svg➔ 真实格式是.svg。
- 如果开头包含
- 用记事本(Notepad)打开这个打不开的文件,看最前面几个字符:
修改后缀名(例如把 xxxx.png 改为 xxxx.webp),并在 Markdown 里把链接同步改过来(如 )就能立刻正常显示了!
方法二:运行 Python 脚本自动校正 assets 目录下所有图片的真实扩展名
如果你不确定还有没有其他图片的后缀被误标注了,可以运行下面这个自动检测脚本。它会读取图片的文件魔数(二进制 Header),自动把后缀名修正为它真正的格式,并同步自动更新 Markdown 里的文件名!
自动化修复脚本:
1 | import os |
运行完这个脚本后,打不开的图片格式就会被自动改成 .webp / .jpg 等真实格式,Typora 就能完美加载了!
如何计算出一个md文件中有多少图片?比如通过assets\字样查找?又或者有更准确更方便的方式?
要统计一个 Markdown 文件中有多少张图片,直接查找 assets\ 可以大概估算,但不够准确(因为文中普通的文本路径、代码块或重复路径也可能会触发匹配)。
最准确、最方便的方式主要分为 “编辑器工具内查找” 和 “脚本精准统计” 两种:
方法一:在 VS Code 中使用正则查找(最方便)
在 VS Code 中打开你的 .md 文件,按 Ctrl + F 打开查找框,勾选右上角的 .\*(开启正则表达式):
1. 统计所有图片(无论本地 assets 还是网络图片):
查找正则:
1
!\[.*?\]\(.*?\)
查看结果:查找框右侧会直接显示
1 of 128类似的数字,后面的128就是该文档中的图片总数。统计数字在Emeditor中未见,但可以通过查找、提取看出有多少图片
2. 精准统计本地 assets 目录下的图片:
查找正则:
1
!\[.*?\]\(assets[\\/].*?\)
这种方式可以精确排查只引用了
assets\或assets/文件夹的本地图片,排除掉了普通的网络图片。
方法二:用 Python 脚本统计并去重(最准确,含详细分析)
如果你想知道图片引用总次数、使用了多少张不重复的图片,以及是否有损坏/不存在的本地图片,直接运行下面这段 Python 脚本:
1 | import os |
总结建议:
- 如果只是平时快速看一下:在 VS Code 中用
!\[.*?\]\(assets[\\/].*?\)正则查看匹配数量最快。 - 如果需要核对图片有没有漏下、本地文件齐不齐:跑一次 Python 脚本最省心。
最后,将带图片的md文件导回Notion
因为附带了图片集(在assets文件夹内),所以只回传一个md文件肯定是白折腾的!需要将md主文件及其对应的assets文件夹图片集(md文件内是assets/或assets\相对路径格式)放在同级目录下,然后选择md文件和assets文件夹,将其打包成zip文件(注:不可为rar等其他格式,notion不认)。
然后将这个zip文件导入到Notion中
新建New page
如上所示,在New page中,上传了md主文件和assets文件夹,打开md主文件后,即自动显示文章内容(含所有已上传至Notion的图片),完美!
PS: Notion会在导入项中报错
这是 Notion 解析 ZIP 包时产生的“虚惊一场”式误报。
从下方的绿框可以看到:“3 pages and 282 attachments were imported successfully”(3 个页面和 282 个附件/图片已成功导入)。既然你需要的文章和图片在 Notion 里都能正常打开显示,说明核心内容已经完整上传并关联成功,不需要做任何重复操作。
为什么会弹出“162 pages could not be imported”的红框报错?
assets目录里的图片被误判为“页面”: Notion 的 ZIP 导入引擎在扫描包内结构时,会尝试将包里的文件都识别为 Markdown 页面。如果assets文件夹里包含了某些非标准后缀(或被之前脚本误改名的图片文件),Notion 尝试将它们解析为文本 Page 失败,就会触发报错计数。- 隐形系统文件干扰: 在 Mac 上压缩或 Windows 解压过程中生成的隐藏文件(如
.DS_Store、Thumbs.db或__MACOSX缓存夹),会被 Notion 的导入解析器试图当作 Page 读取,导致大量Could not be imported的假报错。 - 静态附件重复计算: Notion 成功把 282 张图片当作“附件(attachments)”绑定到了对应的 3 个 Markdown 页面上,但随后又对这些独立图片文件报了“无法转换成 Page”的警告。
建议操作
- 完全无需处理:只要那 3 篇 Markdown 文章排版正常、里面的图片能实时加载显示,就可以直接关闭这个报错页面,安心使用。
- 删掉生成的“Zip Import”汇总页面:Notion 在导入时会创建一个名为
Zip Import - 阮一峰博客摘录test.zip...的父级 Page,你可以把里面的 3 篇文章拖拽到侧边栏或其他分类目录里,然后直接删除这个父级页面即可。
2026-8-26日更新
将原巨大笔记在mem.ai上完成拆分,然后有些图片无法正常显示的终极解决办法
既然大笔记打开困难,那就制作成系列的小笔记,但(直接从原笔记部分复制)拆分后的小笔记却有些图片显示不出,如图所示:
实际上这个破图灰框中的链接是可以点击打开的,是国内的一个图床地址,应该是从Ryf老师的博客中直接复制了图片,导致mem.ai中始终保留了原始的图床链接。而再将其内容拆分出新笔记时,Google拉取这个图片的链接出现了障碍(原笔记中此类图片是正常显示的)。
试验了各种办法,包括采用下述console方法用Blob令所有此类图片在mem.ai页面上正常显示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 (async () => {
console.log("🚀 开始自动模拟点击按钮并就地恢复图片...");
// 1. 获取所有包裹 "cdn.beekka.com" 的按钮节点
const errorButtons = Array.from(document.querySelectorAll('.common-editor-image-node-error-link'))
.filter(btn => btn.innerText.includes('cdn.beekka.com'));
if (errorButtons.length === 0) {
alert("⚠️ 未找到 cdn.beekka.com 点击按钮!请检查页面是否完全渲染。");
return;
}
console.log(`📸 成功锁定 ${errorButtons.length} 个目标按钮,开始依次提取处理...`);
let successCount = 0;
// 劫持 window.open,防止弹出 29 个真实新标签页骚扰浏览器,直接捕获其目标 URL
const originalOpen = window.open;
let capturedUrls = [];
window.open = function(url) {
if (url) capturedUrls.push(url);
return null; // 阻止实际弹窗
};
for (let i = 0; i < errorButtons.length; i++) {
const btn = errorButtons[i];
capturedUrls = []; // 清空上一次记录
// 模拟触发按钮点击
btn.click();
// 微小延迟等待事件响应
await new Promise(r => setTimeout(r, 100));
// 获取点击捕获到的真正图片 URL
let targetUrl = capturedUrls[0];
// 如果点击未触发 window.open,尝试寻找属性或上一级 React 节点数据
if (!targetUrl) {
const errBox = btn.closest('.react-renderer.node-image');
if (errBox) {
const match = errBox.innerHTML.match(/https?:\/\/[^\s"'<>]+\.(?:webp|png|jpg|jpeg|gif)/i);
if (match) targetUrl = match[0];
}
}
if (targetUrl) {
// 解决 Mixed Content 拦截
targetUrl = targetUrl.replace(/^http:/, 'https:');
try {
// 后台静默抓取真实图片 Blob
const res = await fetch(targetUrl);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
// 构建图片 DOM
const parentNode = btn.closest('.react-renderer.node-image') || btn.closest('[data-node-view-wrapper]');
const realImg = document.createElement('img');
realImg.src = blobUrl;
realImg.style.maxWidth = "100%";
realImg.style.borderRadius = "8px";
realImg.style.margin = "8px 0";
realImg.style.display = "block";
// 原地替换灰框,保持周围文本/版式 100% 不变
if (parentNode) {
parentNode.innerHTML = '';
parentNode.appendChild(realImg);
}
successCount++;
console.log(`✅ [${successCount}/${errorButtons.length}] 成功提取并渲染图片: ${targetUrl}`);
} catch (e) {
console.warn(`❌ 图片下载失败 (${targetUrl}):`, e);
}
} else {
console.warn(`⚠️ 按钮 ${i + 1} 点击未拦截到有效 URL`);
}
}
// 还原 window.open
window.open = originalOpen;
alert(`🎉 全部完成!成功捕获并就地恢复 ${successCount} 张图片,文章版式 100% 完美完好!`);
})();
显示是正常了,但Blob只是个临时的图片指针,始终没找到可行方法将此类已经摘取的图片上传到Google。
所以没办法,就迂回一下:现在的状态是,mem.ai地址(即Google地址)的图片显示没问题,只有cdn.beekka.com地址的图片为破图灰框,那就在console中将其按原文顺序下载下来(并按次序命名);然后再按图片顺序将图片一张张拖放进mem.ai页面中的对应位置。是麻烦了点,但也是目前唯一可行的了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 (async () => {
console.log("🚀 开始精准去重并按顺序下载 beekka 图片...");
// 1. 只精准获取最外层的图片节点,彻底解决父子节点双重抓取问题
const errorContainers = Array.from(document.querySelectorAll('.react-renderer.node-image'));
if (errorContainers.length === 0) {
alert("⚠️ 未检测到图片节点,请确保页面已滚动展开!");
return;
}
// 劫持 window.open
const originalOpen = window.open;
let capturedUrl = null;
window.open = function(url) {
if (url) capturedUrl = url;
return null;
};
const rawUrls = [];
for (let box of errorContainers) {
capturedUrl = null;
const btn = box.querySelector('.common-editor-image-node-error-link');
if (btn && btn.innerText.includes('beekka')) {
btn.click();
await new Promise(r => setTimeout(r, 30));
}
let targetUrl = capturedUrl;
if (!targetUrl) {
const match = (box.outerHTML || "").match(/https?:\/\/[^\s"'<>]*cdn\.beekka\.com[^\s"'<>]*\.(?:webp|png|jpg|jpeg|gif)/i);
if (match) targetUrl = match[0];
}
if (targetUrl) {
targetUrl = targetUrl.replace(/^http:/, 'https:');
rawUrls.push(targetUrl);
}
}
window.open = originalOpen; // 还原 window.open
// 2. 数组内存去重(防止相邻相同地址再次重复)
const uniqueUrls = Array.from(new Set(rawUrls));
if (uniqueUrls.length === 0) {
alert("⚠️ 未解析出有效的 beekka 图片链接!");
return;
}
console.log(`📸 成功过滤重复项,锁定 ${uniqueUrls.length} 张独立图片,准备下载...`);
// 3. 动态加载 JSZip
if (typeof JSZip === 'undefined') {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
document.head.appendChild(script);
await new Promise(resolve => script.onload = resolve);
}
const zip = new JSZip();
let successCount = 0;
for (let i = 0; i < uniqueUrls.length; i++) {
const url = uniqueUrls[i];
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const blob = await res.blob();
let ext = url.split('.').pop().split('?')[0].toLowerCase();
if (!['webp', 'png', 'jpg', 'jpeg', 'gif'].includes(ext)) {
ext = blob.type.split('/')[1] || 'webp';
}
// 严格按去重后的 1, 2, 3... 序号命名
const fileName = `${i + 1}.${ext}`;
zip.file(fileName, blob);
successCount++;
console.log(`✅ [${successCount}/${uniqueUrls.length}] 已打包去重图片: ${fileName}`);
} catch (e) {
console.warn(`❌ 下载失败: ${url}`, e);
}
}
// 4. 打包并自动触发下载
const zipBlob = await zip.generateAsync({ type: "blob" });
const a = document.createElement('a');
a.href = URL.createObjectURL(zipBlob);
a.download = `beekka_images_unique_${Date.now()}.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
alert(`🎉 成功去重!本次打包了 ${successCount} 张唯一图片,序号 1~${successCount} 对应文章段落!`);
})();
到此,mem.ai中巨篇文章的拆分工作亦已非完美完成!


