直接另存为无法显示图片和样式,因微信用data-src替代src并依赖JS动态渲染;需用Python+BeautifulSoup修复HTML或Playwright获取渲染后DOM。

直接用浏览器“另存为”保存的 HTML 文件,打开后图片不显示、样式错乱、内容空白——这不是你操作错了,是微信故意把关键资源延迟加载、用 data-src 替换 src、还混入了大量 JS 渲染逻辑。真要拿到可离线阅读的 HTML,得绕过这些障眼法。
为什么“另存为”得到的 HTML 打不开图片
微信公众号页面几乎全部依赖 JS 动态注入内容:js_content 容器初始为空,图片地址藏在 data-src 属性里,字体、样式表也通过 JS 异步加载。浏览器“另存为”只抓取了静态骨架,没执行 JS,所以保存下来的 HTML 本质是个空壳。
-
data-src不会被浏览器自动解析为图片请求,必须手动替换为src - 正文文字可能被包裹在
<span>或<p>里,但父容器有style="display:none"或visibility:hidden - 部分样式(如字体、行高)由内联
<style>标签动态插入,另存时可能缺失
用 Python + BeautifulSoup 解析并修复 HTML
核心思路:先请求原始 HTML,再用 BeautifulSoup 提取真实正文、还原图片链接、补全隐藏样式。不是简单保存源码,而是“重建可用 HTML”。
- 用
requests.get(url)获取响应,确保response.encoding = 'utf-8',否则中文标题变乱码 - 定位正文:优先找
id="js_content",若不存在则 fallback 到class="rich_media_content" - 批量替换:
soup.find_all(['img', 'iframe']),把所有data-src、data-wx-src值赋给src;对data-src为空的,检查是否有src已存在,避免覆盖 - 移除干扰:删掉
script标签、含weui或qqmusic的iframe(它们无法离线加载) - 补样式:在
<head>里插入基础 CSS,比如body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.6; }
示例关键片段:
立即学习“前端免费学习笔记(深入)”;
content = soup.find(id="js_content") or soup.find(class_="rich_media_content")
for img in content.find_all("img"):
if img.get("data-src"):
img["src"] = img["data-src"]
del img["data-src"]
elif img.get("data-wx-src"):
img["src"] = img["data-wx-src"]
del img["data-wx-src"]
# 移除 script 避免执行报错
for s in content.find_all("script"):
s.decompose()用 Playwright / Puppeteer 直接获取渲染后 HTML
如果你需要 100% 忠实还原(含 JS 执行后的排版、视频占位图、音频控件),就得用无头浏览器。注意:这不是“下载源码”,而是“截图式提取 DOM”——它拿到的是最终渲染树的 document.body.outerHTML,已无 data-src 和隐藏逻辑。
- Playwright 推荐用
page.content(),不是page.inner_html("body"),后者会漏掉<head>中的样式 - 务必加等待:微信页面常有懒加载,需
page.wait_for_selector("#js_content", state="visible", timeout=10000) - 禁用图片加载可提速(但会导致
src为空):page.route("**/*.{png,jpg,gif}", lambda route: route.abort()),仅适用于只需文本结构的场景 - 导出前建议调用
page.evaluate("document.fonts.load('12px serif')")确保字体就绪,避免 PDF 转换时文字缺失
付费文章或登录墙怎么办
一旦文章设为付费或仅限关注用户可见,requests 直接请求会返回 403 或跳转到登录页。此时“源码”本身就不对外暴露,必须模拟登录态。
- PC 微信客户端打开文章后,用 Fiddler / Charles 抓包,找带
__biz和mid参数的请求,提取其中的uin、key、pass_ticket - 把这些 Cookie 和 Header 复制进 Python 请求中:
headers["Cookie"] = "wxuin=xxx; wxsid=xxx; ..." - 注意有效期:这类凭证通常 2 小时失效,且绑定设备指纹,不能长期复用
- 更稳的方式是让 Playwright 复用已登录的 Chrome 用户目录:
chromium.launch_persistent_context(user_data_dir="/path/to/WeChatBrowserProfile")
真正麻烦的不是技术实现,而是微信不断调整 DOM 结构和反爬策略——今天好用的 id="js_content",明天可能变成 data-content-id="main"。每次批量保存前,最好人工开一两篇文章,用浏览器开发者工具确认最新容器标识。



















