将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 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 101 102 103 104 import osimport reimport urllib.parseimport requestsfrom requests.packages.urllib3.exceptions import InsecureRequestWarningrequests.packages.urllib3.disable_warnings(InsecureRequestWarning) md_files = [f for f in os.listdir('.' ) if f.endswith('.md' ) and not f.startswith('._' )] if not md_files: print ("❌ 未找到 .md 文件!" ) exit() md_file = md_files[0 ] print (f"📄 当前处理文件: {md_file} " )images_dir = "assets" os.makedirs(images_dir, exist_ok=True ) with open (md_file, 'r' , encoding='utf-8' ) as f: content = f.read() session = requests.Session() session.trust_env = False global_counter = 0 def resolve_smart_filename (raw_url, idx ): """提取图片唯一标识 UUID 或真实文件名""" try : actual_url = raw_url if 'url=' in raw_url: actual_url = urllib.parse.unquote(raw_url.split('url=' )[1 ].split('&' )[0 ]) uuid_match = re.search(r'/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/' , actual_url, re.I) if uuid_match: return f"{uuid_match.group(1 )} .png" pathname = urllib.parse.urlparse(actual_url).path filename = pathname.split('/' )[-1 ] if filename and len (filename) >= 3 and 'proxy' not in filename and 'fimage' not in filename and filename != 'image.png' : return filename ext = ".png" if ".webp" in actual_url.lower(): ext = ".webp" elif ".jpg" in actual_url.lower() or ".jpeg" in actual_url.lower(): ext = ".jpg" return f"img_{idx + 1 } {ext} " except Exception: return f"img_{idx + 1 } .png" md_image_pattern = r'!\[(.*?)\]\((https?://[^\s\)]+)\)' def process_md_image (match ): global global_counter alt_text = match .group(1 ) raw_url = match .group(2 ) global_counter += 1 img_name = resolve_smart_filename(raw_url, global_counter) img_path = os.path.join(images_dir, img_name) if os.path.exists(img_path) and os.path.getsize(img_path) > 200 : print (f"⏩ [{global_counter} ] 已存在,直接链接本地: {img_name} " ) return f"" target_url = raw_url if 'url=' in raw_url: target_url = urllib.parse.unquote(raw_url.split('url=' )[1 ].split('&' )[0 ]) print (f"⬇️ [{global_counter} ] 正在尝试下载: {img_name} " ) headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } try : resp = session.get(target_url, headers=headers, timeout=10 , verify=False ) if resp.status_code == 200 and len (resp.content) > 200 : with open (img_path, 'wb' ) as f: f.write(resp.content) print (f" └─ ✅ 下载成功: {img_name} " ) return f"" else : print (f" └─ ❌ 请求失败/需鉴权拦截 (状态码 {resp.status_code} ),保留原链接" ) return match .group(0 ) except Exception as e: print (f" └─ ❌ 网络连接失败,保留原链接" ) return match .group(0 ) new_content = re.sub(md_image_pattern, process_md_image, content) with open (md_file, 'w' , encoding='utf-8' ) as f: f.write(new_content) print (f"\n🎉 处理完毕!通用开放图片已全部存入 assets/ 目录。" )
因为 Mem 官方对此类私有存储图片的鉴权逻辑做了修改:必须携带登录状态下的 Cookie 凭证 才能访问 mem.storage/proxy 端点,仅靠链接末尾带的 token= 已经无法单独通过脚本直接下载(会返回 403 错误)。
针对这种情况,这里为你提供两个解决方案:
方案 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 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 101 import osimport remd_files = [f for f in os.listdir('.' ) if f.endswith('.md' ) and not f.startswith('._' )] if not md_files: print ("❌ 未找到 .md 文件!" ) exit() md_file = md_files[0 ] print (f"📄 读取 Markdown 文件: {md_file} " )with open (md_file, 'r' , encoding='utf-8' ) as f: content = f.read() md_image_pattern = r'!\[.*?\]\((https?://[^\s\)]+)\)' raw_urls = re.findall(md_image_pattern, content) urls = [] for url in list (dict .fromkeys(raw_urls)): if 'mem.storage' in url or 'googleapis.com' in url or re.search(r'\.(png|jpg|jpeg|webp|gif|svg)' , url, re.I): urls.append(url) print (f"🔍 共提取到 {len (urls)} 条有效图片链接(已自动过滤常规网页链接)" )urls_js_array = ",\n " .join([f'"{url} "' for url in urls]) js_code = f""" (async () => {{ 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(r => script.onload = r); const zip = new JSZip(); const folder = zip.folder("assets"); const urls = [ {urls_js_array} ]; console.log(`🚀 开始批量提取 ${{urls.length}} 张真正图片...`); let count = 0; for (let i = 0; i < urls.length; i++) {{ const rawUrl = urls[i]; let fileName = `img_${{i + 1}}.png`; // 1. 匹配 UUID const uuidMatch = rawUrl.match(/([a-f0-9]{{8}}-[a-f0-9]{{4}}-[a-f0-9]{{4}}-[a-f0-9]{{4}}-[a-f0-9]{{12}})/i); // 2. 匹配 CDN 原文件名 let cdnName = ''; if (rawUrl.includes('url=')) {{ try {{ const decoded = decodeURIComponent(rawUrl.split('url=')[1].split('&')[0]); cdnName = decoded.split('/').pop(); }} catch(e) {{}} }} if (uuidMatch) {{ fileName = `${{uuidMatch[1]}}.png`; }} else if (cdnName && cdnName.length > 3 && !cdnName.includes('proxy') && !cdnName.includes('fimage')) {{ fileName = cdnName; }} try {{ const resp = await fetch(rawUrl, {{ credentials: 'include' }}); if (resp.ok) {{ const blob = await resp.blob(); folder.file(fileName, blob); count++; console.log(`✅ [${{count}}/${{urls.length}}] 下载成功: ${{fileName}}`); }} else {{ console.warn(`⚠️ 跳过非图片/无权限资源 (${{resp.status}}): ${{fileName}}`); }} }} catch (err) {{ console.warn(`⚠️ 跨域/无法连接被拦截,已忽略: ${{fileName}}`); }} }} if (count > 0) {{ console.log('⚡ 打包中...'); const content = await zip.generateAsync({{ type: "blob" }}); const a = document.createElement('a'); a.href = URL.createObjectURL(content); a.download = "mem_all_assets.download.zip"; a.click(); console.log('🎉 批量提取完成!已跳过网页链接,ZIP 自动下载。'); }} else {{ console.error('😭 无图片下载成功'); }} }})(); """ with open ("browser_runner.js" , "w" , encoding="utf-8" ) as f: f.write(js_code) print ("\n✅ 生成完毕!重新复制 `browser_runner.js` 到浏览器控制台运行即可。" )
第二步:一键运行并下载 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 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 (async () => { 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 (r => script.onload = r); const zip = new JSZip (); const folder = zip.folder ("assets" ); const imgElements = Array .from (document .querySelectorAll ('img' )); console .log (`🚀 页面共找到 ${imgElements.length} 个 DOM 图片元素,准备提取像素...` ); let count = 0 ; for (let i = 0 ; i < imgElements.length ; i++) { const img = imgElements[i]; if (img.naturalWidth < 50 || img.naturalHeight < 50 ) continue ; try { const canvas = document .createElement ('canvas' ); canvas.width = img.naturalWidth ; canvas.height = img.naturalHeight ; const ctx = canvas.getContext ('2d' ); ctx.drawImage (img, 0 , 0 ); const src = img.src || '' ; let fileName = `img_${count + 1 } .png` ; const uuidMatch = src.match (/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i ); if (uuidMatch) { fileName = `${uuidMatch[1 ]} .png` ; } const dataUrl = canvas.toDataURL ('image/png' ); const base64Data = dataUrl.replace (/^data:image\/png;base64,/ , "" ); folder.file (fileName, base64Data, { base64 : true }); count++; console .log (`✅ [${count} ] 提取 DOM 图像成功: ${fileName} ` ); } catch (e) { console .warn (`⚠️ 无法读取 DOM 像素 (受跨域 Canvas 污染限制):` , img.src ); } } if (count > 0 ) { console .log ('⚡ 打包导出中...' ); const content = await zip.generateAsync ({ type : "blob" }); const a = document .createElement ('a' ); a.href = URL .createObjectURL (content); a.download = "mem_dom_assets.zip" ; a.click (); console .log ('🎉 DOM 图像提取完成!已自动触发 ZIP 下载。' ); } else { console .error ('😭 页面 DOM 中未能捕捉到可绘制的图片,请确保页面已完全向下滚动加载所有图片!' ); } })();
操作建议与注意点:
先在 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)% 2 F([a-f0-9 -]+)%2 Fimage(\.[a-zA-Z0-9 ]+)\)
替换为 (Replace) :
Code snippet
正则原理拆解
(!\[[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 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 import osimport remd_files = [f for f in os.listdir('.' ) if f.endswith('.md' ) and not f.startswith('._' )] if not md_files: print ("❌ 未找到 .md 文件!" ) exit() md_file = md_files[0 ] print (f"📄 正在检查文件: {md_file} \n" )remote_img_pattern = r'!\[(.*?)\]\((https?://[^\s\)]+)\)' unreplaced_count = 0 with open (md_file, 'r' , encoding='utf-8' ) as f: for line_num, line in enumerate (f, 1 ): matches = re.findall(remote_img_pattern, line) for alt, url in matches: unreplaced_count += 1 print (f"📍 第 {line_num} 行:" ) print (f" Alt: {alt if alt else '(无)' } " ) print (f" URL: {url} \n" ) if unreplaced_count == 0 : print ("🎉 太棒了!文件中所有图片均已成功替换为本地路径,没有任何遗漏!" ) else : print (f"⚠️ 统计完成:共有 {unreplaced_count} 处图片链接尚未替换成本地路径。" )
脚本运行后会直接输出具体的行号 和未替换的 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 。
修改后缀名(例如把 xxxx.png 改为 xxxx.webp),并在 Markdown 里把链接同步改过来(如 )就能立刻正常显示了!
方法二:运行 Python 脚本自动校正 assets 目录下所有图片的真实扩展名 如果你不确定还有没有其他图片的后缀被误标注了,可以运行下面这个自动检测脚本。它会读取图片的文件魔数(二进制 Header) ,自动把后缀名修正为它真正的格式,并同步自动更新 Markdown 里的文件名 !
自动化修复脚本: 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 import osimport redef get_real_ext (filepath ): try : with open (filepath, 'rb' ) as f: header = f.read(16 ) if header.startswith(b'\x89PNG\r\n\x1a\n' ): return '.png' elif header.startswith(b'\xff\xd8\xff' ): return '.jpg' elif header.startswith(b'GIF89a' ) or header.startswith(b'GIF87a' ): return '.gif' elif header.startswith(b'RIFF' ) and header[8 :12 ] == b'WEBP' : return '.webp' elif b'<svg' in header.lower() or b'<?xml' in header.lower(): return '.svg' except Exception: pass return None md_files = [f for f in os.listdir('.' ) if f.endswith('.md' ) and not f.startswith('._' )] if not md_files: print ("❌ 未找到 .md 文件!" ) exit() md_file = md_files[0 ] assets_dir = "assets" if not os.path.exists(assets_dir): print ("❌ 未找到 assets 文件夹!" ) exit() print (f"🔍 开始检查 {assets_dir} 目录下的图片文件真实格式...\n" )with open (md_file, 'r' , encoding='utf-8' ) as f: md_content = f.read() fixed_count = 0 for file in os.listdir(assets_dir): file_path = os.path.join(assets_dir, file) if os.path.isdir(file_path): continue real_ext = get_real_ext(file_path) old_ext = os.path.splitext(file)[1 ].lower() if real_ext and real_ext != old_ext: base_name = os.path.splitext(file)[0 ] new_filename = base_name + real_ext new_file_path = os.path.join(assets_dir, new_filename) os.rename(file_path, new_file_path) old_ref = f"assets/{file} " .replace('\\' , '/' ) new_ref = f"assets/{new_filename} " .replace('\\' , '/' ) old_ref_win = f"assets\\{file} " new_ref_win = f"assets\\{new_filename} " md_content = md_content.replace(old_ref, new_ref) md_content = md_content.replace(old_ref_win, new_ref_win) fixed_count += 1 print (f"🔧 修正文件: {file} ➔ {new_filename} (真实格式: {real_ext} )" ) if fixed_count > 0 : with open (md_file, 'w' , encoding='utf-8' ) as f: f.write(md_content) print (f"\n🎉 处理完成!自动修正了 {fixed_count} 张图片的实际格式及 MD 对应链接。" ) else : print ("✅ 所有本地图片的扩展名与其实际二进制格式完全匹配!" )
运行完这个脚本后,打不开的图片格式就会被自动改成 .webp / .jpg 等真实格式,Typora 就能完美加载了!
如何计算出一个md文件中有多少图片?比如通过assets\字样查找?又或者有更准确更方便的方式? 要统计一个 Markdown 文件中有多少张图片,直接查找 assets\ 可以大概估算,但不够准确 (因为文中普通的文本路径、代码块或重复路径也可能会触发匹配)。
最准确、最方便的方式主要分为 “编辑器工具内查找” 和 “脚本精准统计” 两种:
方法一:在 VS Code 中使用正则查找(最方便) 在 VS Code 中打开你的 .md 文件,按 Ctrl + F 打开查找框,勾选右上角的 .\*(开启正则表达式) :
1. 统计所有 图片(无论本地 assets 还是网络图片):
2. 精准统计本地 assets 目录 下的图片:
方法二:用 Python 脚本统计并去重(最准确,含详细分析) 如果你想知道图片引用总次数 、使用了多少张不重复的图片 ,以及是否有损坏/不存在的本地图片 ,直接运行下面这段 Python 脚本:
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 import osimport remd_files = [f for f in os.listdir('.' ) if f.endswith('.md' ) and not f.startswith('._' )] if not md_files: print ("❌ 未找到 .md 文件!" ) exit() md_file = md_files[0 ] with open (md_file, 'r' , encoding='utf-8' ) as f: content = f.read() all_images = re.findall(r'!\[.*?\]\((.*?)\)' , content) html_images = re.findall(r'<img[^>]+src=["\'](.*?)["\']' , content, re.I) total_images = all_images + html_images assets_images = [img for img in total_images if img.startswith('assets' ) or 'assets\\' in img or 'assets/' in img] remote_images = [img for img in total_images if img.startswith('http://' ) or img.startswith('https://' )] other_images = [img for img in total_images if img not in assets_images and img not in remote_images] missing_files = [] for img_path in set (assets_images): clean_path = img_path.strip().replace('/' , os.sep).replace('\\' , os.sep) if not os.path.exists(clean_path): missing_files.append(img_path) print (f"📄 分析文件: {md_file} " )print ("=" * 40 )print (f"🖼️ 图片引用总次数 : {len (total_images)} 次" )print (f"📂 本地 assets 图片 : {len (assets_images)} 张" )print (f"🌐 剩余网络远程图片 : {len (remote_images)} 张" )if other_images: print (f"❓ 其他相对路径图片 : {len (other_images)} 张" ) print (f"🧩 唯一不重复图片数 : {len (set (total_images))} 张" )print ("=" * 40 )if missing_files: print (f"⚠️ 警告: 有 {len (missing_files)} 张 assets 路径的图片在本地文件夹中未找到文件!" ) for mf in missing_files: print (f" └─ 缺失: {mf} " ) else : print ("✅ 完美!所有 assets 引用的图片在本地均存在。" )
总结建议:
如果只是平时快速看一下 :在 VS Code 中用 !\[.*?\]\(assets[\\/].*?\) 正则查看匹配数量最快。
如果需要核对图片有没有漏下、本地文件齐不齐 :跑一次 Python 脚本最省心。