我把保存的网页变成了可编辑的文档,这个方法太好用了!
墨池有雨
读完需要
分钟
速读仅需 2 分钟
/ HTML转Markdown脚本分享 /
作为一个喜欢收集资料的人,我有个习惯——看到好文章就用 SingFile 扩展保存成 HTML。这样格式、图片、动图都能保留,看起来和原网页一模一样。

但问题是,这些 HTML 文件用起来特别不方便:
想编辑整理?得在代码里扒拉
想发给 AI 学习?HTML 标签太多干扰
想在手机上看?加载慢还乱码
想存到笔记软件?很多软件不支持 HTML
1
既然找不到现成的解决方案,我就自己写了个 Python 脚本。这个脚本能把 HTML 转换成 Markdown 格式,还能把网页里的图片下载到本地。
1.1
脚本的核心功能
简单说就是:HTML 文件进去,Markdown 文件出来,图片都在本地文件夹里。
# 核心就是这个函数def html_to_md_with_local_images():# 读取HTML# 下载图片# 转换成Markdown# 保存结果
1.2
怎么处理图片?
这是最麻烦的部分,因为网页图片有好几种形式:
网络图片 – 有 http 开头的链接
Base64 图片 – 直接嵌在 HTML 代码里
相对路径图片 – 只有文件名或相对路径
我的脚本都能识别并处理:
# 如果是网络图片,就下载if src.startswith('http'):download_image(url)# 如果是Base64图片,就解码保存if src.startswith('data:image'):save_base64_image(data)# 其他情况也有相应处理
1.3
实际使用效果
转换前:文章.html + 一堆依赖文件转换后:文章.md + images/图片文件夹
Markdown 文件里图片引用是这样的:


2
安装需要的库(一次性工作)
pip install beautifulsoup4 html2text requests
-
把你的 HTML 文件改名为 input.html
-
运行脚本
python html_to_md.py
-
得到结果
-
output.md – 转换后的 Markdown 文件
-
images/ – 所有图片都在这里
3
3.1
1. 整理技术文章
以前保存的技术文章,现在都能转成 Markdown,然后:
放到 Obsidian 笔记里
用 Typora 编辑整理
分类存到不同文件夹
3.2
2. 给 AI 提供学习材料
现在给 AI 喂资料简单多了:
直接把 Markdown 文件贴进去
AI 能看懂结构和内容
不用再清理 HTML 标签
3.3
3. 在手机上看文章
把 Markdown 文件传到手机:
用 Markdown 阅读器打开
加载快,排版清晰
图片都在本地,不用担心失效
4
4.1
转换效果怎么样?
大部分网页转换效果都很好,特别是:
博客文章、技术文档
新闻、专栏文章
论坛帖子
有些特别复杂的网页(比如整页都是 JavaScript 的),转换后可能需要微调。
4.2
图片会不会很大?
脚本会自动处理图片文件名,用 MD5 生成短文件名,避免重名。
如果网页图片很多,images文件夹可能会比较大。不过现在硬盘空间都够用,而且图片本地化后,再也不怕原网站删图了。
4.3
能不能批量处理?
当然可以,写个简单循环就行:
import osfor file in os.listdir('.'):if file.endswith('.html'):# 对每个HTML文件进行转换convert_file(file)
5
5.1
1. 一劳永逸
装好环境后,转换一篇文章只要几秒钟。
5.2
2. 完全控制
自己的脚本,想怎么改就怎么改:
想调整转换规则?改几行代码
想保存到不同位置?改输出路径
想处理特定网站?加个特殊规则
5.3
3. 格式保留好
比直接复制粘贴强多了:
标题层次保留
列表格式正确
代码块保持原样
图片位置不变
6
有的网页转换后格式乱了?
试试调整 html2text 的参数
或者换个 HTML 解析库
图片下载失败?
脚本会跳过失败的图片继续转换
可以在代码里加重试机制
转换速度慢?
主要是下载图片耗时
可以先把 HTML 里的图片链接批量处理
7
现在我的知识库都是 Markdown 格式的,整理、搜索、分享都方便多了。
如果你也经常保存网页,然后又苦恼没法好好利用这些资料,可以试试这个方法。不一定非要用我的脚本,关键是要找到适合自己的信息处理流程。
工具的意义,就是让重复的事情自动化,把时间留给更重要的事。
源码
import reimport os
import base64
import hashlib
from urllib.parse import urlparse, unquote
from bs4 import BeautifulSoup
import html2text
import requests
def save_base64_image(base64_str, img_dir='images'):
"""保存base64编码的图片"""
if not os.path.exists(img_dir):
os.makedirs(img_dir)
try:
# 匹配base64图片格式
match = re.match(r'data:image/(\w+);base64,(.*)', base64_str)
if not match:
return None
img_format, data = match.groups()
# 使用MD5生成唯一文件名
filename = f"image_{hashlib.md5(data.encode()).hexdigest()[:8]}.{img_format}"
filepath = os.path.join(img_dir, filename)
# 解码并保存
with open(filepath, 'wb') as f:
f.write(base64.b64decode(data))
return filename
except Exception as e:
print(f"保存base64图片失败: {str(e)}")
return None
def save_url_image(url, img_dir='images'):
"""保存URL图片"""
if not os.path.exists(img_dir):
os.makedirs(img_dir)
try:
# 从URL中提取文件名
parsed_url = urlparse(url)
path = unquote(parsed_url.path)
filename = os.path.basename(path)
# 如果没有扩展名,添加jpg扩展名
if not '.' in filename:
filename = f"image_{hashlib.md5(url.encode()).hexdigest()[:8]}.jpg"
# 确保文件名唯一
counter = 1
original_name, original_ext = os.path.splitext(filename)
while os.path.exists(os.path.join(img_dir, filename)):
filename = f"{original_name}_{counter}{original_ext}"
counter += 1
filepath = os.path.join(img_dir, filename)
# 下载图片
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
# 保存图片
with open(filepath, 'wb') as f:
f.write(response.content)
return filename
except Exception as e:
print(f"保存URL图片失败 {url}: {str(e)}")
return None
def html_to_md_with_local_images(input_file='input.html', output_file='output.md', img_dir='images'):
"""将HTML转换为Markdown,并将图片保存在本地"""
try:
# 读取HTML文件
with open(input_file, 'r', encoding='utf-8') as f:
html_content = f.read()
print(f"正在解析HTML文件...")
# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(html_content, 'html.parser')
# 创建图片目录
if not os.path.exists(img_dir):
os.makedirs(img_dir)
# 查找所有图片
img_tags = soup.find_all('img')
print(f"找到 {len(img_tags)} 张图片")
# 用于记录图片替换关系
img_replacements = {}
# 处理每张图片
for i, img in enumerate(img_tags, 1):
src = img.get('src', '')
alt = img.get('alt', '') or f'图片{i}'
if not src:
print(f"跳过第 {i} 张图片(无src)")
continue
print(f"处理第 {i} 张图片: {src[:50]}...")
filename = None
# 判断图片类型并保存
if src.startswith('data:image'):
# base64图片
filename = save_base64_image(src, img_dir)
elif src.startswith('http://') or src.startswith('https://'):
# 网络图片
filename = save_url_image(src, img_dir)
elif src.startswith('//'):
# 协议相对URL
filename = save_url_image('https:' + src, img_dir)
else:
# 相对路径或本地路径
print(f"跳过第 {i} 张图片(不支持的类型)")
continue
if filename:
# 创建Markdown图片格式
md_img = f""
img_replacements[str(img)] = md_img
print(f" 保存为: {filename}, MD格式: {md_img}")
else:
print(f" 保存失败")
# 获取HTML文本
html_text = str(soup)
# 替换所有图片标签为Markdown格式
for img_tag, md_img in img_replacements.items():
html_text = html_text.replace(img_tag, md_img)
# 创建自定义HTML转Markdown转换器
class CustomHTML2Text(html2text.HTML2Text):
def __init__(self):
super().__init__()
self.ignore_links = False
self.ignore_images = True # 我们已经手动处理了图片
self.ignore_emphasis = False
self.body_width = 0
self.unicode_snob = True
self.mark_code = True
def handle_img(self, tag, attrs, start):
# 跳过图片处理,因为我们已经替换为Markdown格式
pass
# 转换为Markdown
converter = CustomHTML2Text()
markdown_content = converter.handle(html_text)
# 清理多余的空白行
markdown_content = re.sub(r'\n{3,}', '\n\n', markdown_content)
# 移除残留的HTML标签
markdown_content = re.sub(r'<[^>]+>', '', markdown_content)
# 写入输出文件
with open(output_file, 'w', encoding='utf-8') as f:
f.write(markdown_content)
print(f"\n转换完成!")
print(f"输入文件: {input_file}")
print(f"输出文件: {output_file}")
print(f"图片目录: {img_dir}/")
print(f"共处理图片: {len(img_replacements)} 张")
return markdown_content
except FileNotFoundError:
print(f"错误:找不到输入文件 '{input_file}'")
except Exception as e:
print(f"转换过程中出现错误: {str(e)}")
if __name__ == "__main__":
html_to_md_with_local_images()
夜雨聆风
