最近做了一个小工具叫 Huan(换) https://github.com/cycleuser/Huan 。
功能说起来很简单:给它一个网址,它帮你把对应的网页转换成一组干净的 Markdown 文件。
写这个工具的过程中有一些思考,觉得值得拿出来和大家聊聊,尤其是对于正在学习 Python 工程实践的同学来说,这个项目虽然不大,但涉及到的设计模式和技术选型还算有代表性。
为什么要做这个
大语言模型火了之后,一个非常现实的问题浮出水面:你要用 LLM 做检索增强生成(也就是常说的 RAG),那知识库里的文档从哪来?
很多时候,答案是网页。开源项目的技术文档挂在网站上,学术论文有在线版本,教程指南散布在各种博客和文档站里。这些内容的原始格式都是 HTML,而 HTML 这个东西,它是给浏览器看的,原本设计的时候其实不能算是给语言模型看的。
一个普通网页的 HTML 源码里,真正有信息量的正文可能只占一小部分,剩下的全是导航栏、广告脚本、Cookie 弹窗、追踪代码和各种布局用的 div 标签。如果你把这些东西原封不动地丢给 LLM,首先浪费了上下文窗口的 Token 额度,其次在做向量检索的时候,那些导航文字和模板内容会干扰嵌入向量的质量,导致搜出来的东西不准。
所以需要一个中间步骤:把网页转换成干净的、结构化的文本格式。Markdown 是一个很自然的选择——它保留了标题、列表、代码块这些结构信息,同时几乎就是纯文本,Token 开销很低。
Huan 做的就是这件事。
怎么安装和使用
最简单的方式,直接 pip 安装即可:
pip install huan
如果想安装最新开发版,可以从 GitHub 克隆源码:
git clone https://github.com/cycleuser/Huan.git
cd Huan
pip install -e .
建议额外安装 readability-lxml 来获得更好的正文提取质量:
pip install -e ".[readability]"
如果目标网站用了大量 JavaScript 渲染,可以用浏览器后端:
pip install -e ".[browser]"
huan https://geopytool.com --fetcher browser
也可以在 Python 代码里直接调用:
from huan import SiteCrawler
converter = SiteCrawler(
start_url="https://geopytool.com",
output_dir="./archive",
max_pages=50,
fetcher_type="browser",
download_images=True,
extractor="readability",
)
converter.crawl()
项目是 MIT 协议开源的,代码量不大,适合阅读和修改。如果你正在学 Python,或者对网页内容处理、LLM 数据准备这些话题感兴趣,这个项目应该能提供一些有用的参考。
项目地址:https://github.com/cycleuser/Huan
它具体能做什么
用一句话概括:从一个种子 URL 出发,广度优先遍历整个网站,把每个页面转成一个 Markdown 文件,同时保留网站原本的目录结构。
安装和使用都尽量做得简单。克隆仓库之后 pip install -e . 就能用,命令行敲 huan 加网址就开始转换。比如:
huan https://geopytool.com
转换出来的文件按照网站的 URL 路径组织成本地文件夹。geopytool.com/category/doc.html 会变成 geopytool.com/category/doc.md,图片会下载到本地并改写成相对路径引用。每个 Markdown 文件顶部还带一段 YAML front matter,记录了标题、作者、发布日期、语言、Token 估算等元数据。
功能列一下:
- 三种内容提取模式。默认用 Mozilla 的 Readability 算法做正文提取,效果最好;也支持基于标签匹配的启发式提取,以及不做过滤的全文模式。
- 四种 HTTP 获取后端。默认用 requests 库,速度快但只能处理静态页面;需要 JavaScript 渲染的网站可以切换到 curl_cffi、DrissionPage(调用系统浏览器)或 Playwright(无头 Chromium)。
- 数学公式转换。页面里用 MathML、MathJax 或 KaTeX 渲染的公式会被转成 LaTeX 格式的美元符号记法。
- 复杂表格处理。带 colspan 和 rowspan 的合并单元格表格会被展开成规则网格,避免 Markdown 表格乱掉。
- 代码块语言识别。HTML 里的 language-python 之类的 class 标注会保留到 Markdown 的围栏代码块里。
- 增量模式。默认跳过已经转换过的文件,方便做定期更新。
- 无限滚动支持。用浏览器后端时可以自动滚动页面来触发懒加载内容。

运行截图
Token 效率到底能差多少
说 Markdown 比 HTML 省 Token 这件事,空口说没什么说服力,不如直接拿数据看。我用 geopytool.com 的一个安装指南页面做了实际测量,用 GPT-4 的 cl100k_base 分词器计算 Token 数:
| 指标 | 原始 HTML | Markdown (Huan) | 压缩比 |
|---|---|---|---|
| 字符数 | 12,070 | 3,521 | 3.4x |
| Token 数 | 3,236 | 1,012 | 3.2x |
从 3,236 个 Token 降到 1,012 个,减少了 68.7%。这意味着同样大小的上下文窗口里,你能塞进去的有效文档数量多了两倍以上。对于做 RAG 系统来说,这不是一个可有可无的优化,而是直接关系到检索能覆盖多少知识。
作为对照,如果把 HTML 标签全部剥掉只留纯文本(丢失所有结构信息),Token 数是 490。也就是说 Markdown 格式用大约 500 个额外 Token 的代价保留了完整的文档结构——标题层级、代码块、列表等等。这笔交易是划算的,因为语言模型对这些结构线索很敏感,有结构的输入比扁平纯文本能带来更准确的理解和更好的生成效果。
和大语言模型的接口
这个工具的输出天然适合接入 LLM 工作流。几个典型场景:
用来做 RAG 知识库,把某个技术文档站整站转换成 Markdown,然后用 LangChain 或者 LlamaIndex 之类的框架对这些 Markdown 文件做切块、嵌入、索引。因为内容干净、有结构,切出来的块质量比直接用 HTML 好得多。
用来做微调数据。如果你想在特定领域的知识上微调一个模型,首先需要这个领域的干净文本。从相关网站批量转换出来的 Markdown 文件可以作为语料来源。YAML front matter 里的元数据还能帮你按日期、语言等条件做过滤和筛选。
用来构建知识图谱。结构化的 Markdown 比原始 HTML 更容易做进一步的信息抽取。标题层级对应知识的层次关系,链接对应实体之间的关联。
作为教学案例看这个项目
写这个工具的时候,我有意识地在代码组织上做了一些可以当教学参考的设计。对于正在学 Python 的同学来说,这个项目的规模恰好不大不小——不像一个 hello world 那样简单到没什么可看的,也不像大型框架那样复杂到无从下手。下面说几个我觉得比较有教学价值的点。
可插拔的后端架构。 获取网页这一步,不同的网站有不同的技术特点,不可能一种方案通吃。项目里定义了一个统一的 Fetcher 接口,然后分别实现了 RequestsFetcher、CurlCffiFetcher、DrissionPageFetcher 和 PlaywrightFetcher 四个后端。用户通过命令行参数选择用哪个,工厂函数 create_fetcher() 负责根据参数创建对应的实例。这是策略模式的一个典型应用场景,但我们不需要把它讲得很抽象——你只要理解"同一件事有多种做法,需要在运行时选择"这个需求,自然就知道为什么要这样设计了。
管线式的处理流程。 整个转换过程是一条清晰的流水线:URL 发现、页面获取、内容提取、预处理(数学公式、表格、代码块)、Markdown 生成、文件保存。每个阶段做的事情边界明确,方便单独理解和修改。这种管线思维在数据处理领域非常常见,不管是做 ETL 还是做机器学习的特征工程,本质上都是这个套路。
BFS 遍历与增量更新。 网站的页面通过超链接相互连接,从一个种子页面出发发现所有可达页面,这就是一个经典的图遍历问题。项目用了 Python 标准库的 deque 做 BFS 队列,用 set 做已访问记录。增量模式的处理也值得看——已存在的普通页面直接跳过,但会从已有的 Markdown 文件里提取链接继续遍历;列表类页面则总是重新获取以发现新内容。这个设计决策的考量过程本身就是一个很好的学习素材。
元数据提取的工程细节。 从一个 HTML 页面里提取标题、作者、发布日期这些信息,听起来简单,实际上每个字段都有各种可能的来源和格式。标题可能在 title 标签里,也可能在 og:title 的 meta 标签里,也可能在 h1 标签里。日期可能是 ISO 格式也可能是人类可读格式。代码里对这些情况做了逐一处理,没有什么高深的算法,但很能体现真实工程中"处理边界情况"占据大量工作量这一事实。
pyproject.toml 与可选依赖。 项目的构建配置完全使用 pyproject.toml,遵循 PEP 621 标准。核心依赖只有 requests、beautifulsoup4、html2text 和 certifi 四个包。readability-lxml、curl-cffi、DrissionPage、playwright 这些较重的包都放在可选依赖组里,用户按需安装。这是一个值得推广的做法——保持核心尽量轻量,把非必需的功能做成可选扩展。
预览时标签不可点
Close
更多
Name cleared
微信扫一扫赞赏作者
Like the AuthorOther Amount
赞赏后展示我的头像
作品
暂无作品
Like the Author
Other Amount
¥
最低赞赏 ¥0
OK
Back
Other Amount
更多
赞赏金额
¥
最低赞赏 ¥0
1
2
3
4
5
6
7
8
9
0
.
Python语言程序设计 · 目录
Python语言程序设计
上一篇Windows 系统下从清华大学 TUNA 镜像安装 Miniconda3 完整指南下一篇简单说说Python里面的「函数是第一公民(First-Class Citizen)」
Close
更多
搜索「」网络结果
Close
调整当前正文文字大小
更多
100%