一、JustHTML 是什么?—— 一次“氛围工程”的完美实践
JustHTML 由瑞典开发者 Emil Stenström 创建,它是一个用纯 Python 编写的 HTML5 解析库,其核心代码仅有约 3,000 行。它最大的特点是 “零依赖”——无需编译 C 扩展,无需安装任何系统库,可以无缝运行在 PyPy、Pyodide 等任何 Python 环境中。
JustHTML 源自 Mozilla Servo 浏览器引擎的 Rust 解析器 html5ever,经过完全重写为 Python 代码。它 100% 通过了由浏览器厂商维护的官方 html5lib-tests 测试套件(超过 9,200 个测试用例)。这意味着,它的解析结果与主流浏览器高度一致,能够完美处理各种“非标准”的 HTML 输入。
请在微信客户端打开
著名开发者 Simon Willison 将 JustHTML 的开发过程称为 “氛围工程”(Vibe Engineering) 的典范——Emil 在数月的业余时间中,利用 GitHub Copilot 等 AI 编码助手,完成了一个完整、健壮的 HTML5 解析器。随后,Simon 仅用 4.5 小时就利用 AI 工具将其完整移植到了 JavaScript,可见其设计的清晰与高效。
二、安装与基础解析:开箱即用
2.1 安装
JustHTML 的安装极其简单,只需一行命令:
pip install justhtml2.2 基础解析示例
JustHTML 最基本的功能是解析 HTML 字符串并生成 DOM 树。通过 .to_html() 方法即可将其转换回字符串。
from justhtml import JustHTML# 1. 解析一个完整的 HTML 片段html_snippet = '<p>Hello, <b>world</b>!</p>'doc = JustHTML(html_snippet, fragment=True)print(doc.to_html())# 输出: <p>Hello, <b>world</b>!</p># 2. 解析不完整/错误的标签 (JustHTML 会像浏览器一样自动修复)# 例如,缺少闭合标签的 <i>malformed_html = '<p><b>Hi<i>there</b>!'doc_fixed = JustHTML(malformed_html, fragment=True)print(doc_fixed.to_html())# 输出: <p><b>Hi<i>there</i></b><i>!</i></p>
注意 fragment=True 参数:它告诉解析器输入是 HTML 片段而非完整文档,因此不会自动添加 <html> 和 <body> 标签,这在处理用户输入时非常有用。
三、安全清洗:默认安全的 HTML 处理
JustHTML 内置了类似 Bleach 的白名单清洗功能。默认情况下,它会自动移除 <script> 等潜在危险的标签和属性,从源头防止 XSS 攻击。
from justhtml import JustHTML# 默认的安全清洗 (移除了 <script> 和危险的 href)dangerous_html = '''<p>Hello<script>alert(1)</script><ahref="javascript:alert(1)">bad</a><ahref="https://example.com">ok</a></p>'''safe_doc = JustHTML(dangerous_html, fragment=True)print(safe_doc.to_html())# 输出: <p>Hello<a>bad</a><ahref="https://example.com">ok</a></p>
3.1 自定义清洗规则
如果你需要更精细的控制,可以通过 Sanitizer 类自定义白名单规则:
from justhtml import JustHTML, Sanitizercustom_sanitizer = Sanitizer()custom_sanitizer.allow_tag("b")custom_sanitizer.allow_tag("i")custom_sanitizer.allow_attr("class") # 允许 class 属性html = '<pclass="intro">Hello <b>world</b>!</p>'doc = JustHTML(html, fragment=True, sanitizer=custom_sanitizer)print(doc.to_html())# 输出: <pclass="intro">Hello <b>world</b>!</p>
3.2 禁用清洗
如果你信任输入的 HTML 并且不需要安全清洗,可以通过 sanitize=False 禁用:
doc = JustHTML(trusted_html, fragment=True, sanitize=False)四、CSS 选择器查询:类 jQuery 的查找体验
JustHTML 提供了 query() 和 query_one() 两个方法,让你可以使用标准的 CSS 选择器来快速定位元素。这比传统的 DOM 遍历要高效得多。
from justhtml import JustHTMLhtml_content = """<main><article class="post"><h2>文章标题</h2><p>这是一段 <em>重要的</em> 内容。</p><p class="summary">这是文章摘要。</p></article><div class="sidebar"><p>边栏内容</p></div></main>"""# 解析文档doc = JustHTML(html_content, fragment=True)# 查询所有段落all_paragraphs = doc.query("p")print(f"找到 {len(all_paragraphs)} 个段落。") # 输出: 找到 3 个段落。# 查询类为 "summary" 的段落summary_paragraph = doc.query_one("p.summary")print(f"摘要内容: {summary_paragraph.to_html()}")# 输出: 摘要内容: <p class="summary">这是文章摘要。</p># 使用复杂的选择器: 查找 <article> 内部的所有 <p> 元素article_paragraphs = doc.query("article p")for p in article_paragraphs:print(p.to_html())# 输出:# <p>这是一段 <em>重要的</em> 内容。</p># <p class="summary">这是文章摘要。</p>
4.1 支持的选择器语法
JustHTML 支持丰富的 CSS 选择器语法,包括:
类型选择器: p
类选择器: .highlight
ID 选择器: #main
属性选择器: [href], [href="value"]
后代组合器: div p
子元素组合器: div > p
相邻兄弟组合器: h1 + p
伪类: :first-child, :last-child, :nth-child(n), :contains("text")
五、DOM 转换:链式处理节点
JustHTML 允许你在解析后对 DOM 进行一系列链式修改。这在处理用户生成的内容或对现有 HTML 进行微调时非常有用。
from justhtml import JustHTML, Unwrap, Linkify, SetAttrs, Drophtml_content = '''<p>Visit example.com for more info.<span class="highlight">Important!</span><span class="remove-me">Spam content here.</span></p>'''# 应用多个转换:# 1. Unwrap("span.highlight"): 移除 <span> 标签,但保留其内部文本 ("Important!")# 2. Drop("span.remove-me"): 完全删除指定元素及其内容# 3. Linkify(): 自动将文本中的 URL ("example.com") 转换为可点击的 <a> 标签# 4. SetAttrs("a", rel="nofollow"): 为所有 <a> 标签添加 rel="nofollow" 属性doc_transformed = JustHTML(html_content,fragment=True,sanitize=False, # 信任输入内容,关闭默认清洗transforms=[Unwrap("span.highlight"),Drop("span.remove-me"),Linkify(),SetAttrs("a", rel="nofollow")])print(doc_transformed.to_html())# 输出: <p>Visit <a href="http://example.com" rel="nofollow">example.com</a> for more info. Important!</p>
五、从零构建 HTML:编程式生成
如果你需要动态生成 HTML 结构,JustHTML 同样提供了强大的支持。你可以使用 element 函数以编程方式创建元素,这比拼接字符串要安全、清晰得多。
from justhtml import JustHTMLfrom justhtml.builder import element# 构建一个简单的文章结构article_tree = element("article",{"class": "blog-post"}, # 属性字典element("h2", "我的博客文章"),element("p", "这是文章的第一段。"),element("div", {"class": "meta"}, "发布于 2025-12-14"),element("a", {"href": "/more"}, "阅读更多..."))# 将构建好的树交给 JustHTML 进行标准化处理doc = JustHTML(article_tree, fragment=True, sanitize=False)print(doc.to_html(pretty=True))# 输出:# <article class="blog-post"># <h2>我的博客文章</h2># <p>这是文章的第一段。</p># <div class="meta">发布于 2025-12-14</div># <a href="/more">阅读更多...</a># </article>
七、实用技巧与最佳实践
7.1 与 BeautifulSoup 集成
JustHTML 可以与 BeautifulSoup 无缝集成。通过实现一个自定义的 JustHTMLTreeBuilder,你可以使用 BeautifulSoup 熟悉的 API(如 find_all())的同时,享受 JustHTML 标准合规的 HTML5 解析能力。
# 概念性示例(需要实现自定义 TreeBuilder)# from bs4 import BeautifulSoup# from justhtml_tree_builder import JustHTMLTreeBuilder# soup = BeautifulSoup(html, "justhtml")
7.2 性能优化建议
批量处理:如果需要解析大量 HTML 片段,使用 fragment=True 可以避免生成不必要的 <html> 和 <body> 标签,减少内存开销。
合理使用清洗:如果输入的 HTML 完全可信,通过 sanitize=False 禁用清洗可以略微提升性能。
使用转换器:尽量使用内置的转换器(如 Drop、Unwrap)进行 DOM 操作,而非手动遍历节点,它们的实现已经过充分优化。
7.3 处理深度嵌套的 HTML
需要注意的是,由于 Python 递归深度的限制(默认 1000),JustHTML 在解析极端深度嵌套的 HTML(如嵌套 1000 层以上的 <div>)时可能会触发 RecursionError。这是 Python 语言本身的限制,而非 JustHTML 的缺陷。如果你的应用需要处理此类输入,可以考虑:
import syssys.setrecursionlimit(5000) # 提高递归深度限制
或者在应用层面预先检测并拒绝深度异常的输入。
八、JustHTML 的工作流程
以下流程图清晰地展示了 JustHTML 处理 HTML 时的典型流程,帮助你理解各个功能模块是如何协同工作的:

九、总结与展望
JustHTML 不仅仅是一个解析器,它是一个集解析、清洗、查询、转换和构建于一体的瑞士军刀,专为追求简洁、可靠和高效的 Python 开发者设计。
核心优势回顾:
纯 Python,零依赖:无 C 扩展,无系统依赖,可在任何 Python 环境运行
浏览器级正确性:100% 通过 html5lib-tests 测试套件
默认安全:内置白名单清洗,从源头防止 XSS 攻击
CSS 选择器:熟悉的 jQuery 式语法,快速定位元素
链式转换:内置多种 DOM 转换器,可组合使用
代码精简:约 3,000 行核心代码,易于理解和调试
无论你是需要处理不可靠的网络数据、清洗用户提交的富文本,还是需要动态生成 HTML,JustHTML 都能为你提供一个优雅且强大的解决方案。它的纯 Python 特性也使其成为在 Jupyter Notebook、云函数或 Pyodide 等受限环境中的理想选择。
推荐阅读:
JustHTML 官方文档
JustHTML GitHub 仓库
Simon Willison 对 JustHTML 的深度分析
在线试用 JustHTML 的 Playground