跳转至

HTML 解析器(Soup)

零依赖的 HTML 解析器,提供类 BeautifulSoup 的 API —— 仅使用标准库,支持 Python 3.10+。

可替代: beautifulsoup4(常用子集)

概述

Soup 模块基于 html.parser.HTMLParser 构建轻量级 DOM 树。支持 findfind_allselectselect_oneget_textdecomposefind_parent —— 涵盖了绝大多数实际网页抓取脚本所使用的 BeautifulSoup 子集。

文件 描述 依赖
soup.py 纯 Python 实现 无(仅标准库:rehtml.parser

在你的项目中使用

只需将单个 .py 文件复制到你的项目中:

cp soup/soup.py your_project/

然后直接导入:

from soup import Soup

使用示例

基本解析

from soup import Soup

html = "<html><body><p class='msg'>Hello <b>world</b></p></body></html>"
soup = Soup(html)
print(soup.find("p", class_="msg").text)
# Hello world

find 和 find_all

html = """
<ul>
  <li class="item">Apple</li>
  <li class="item">Banana</li>
  <li class="item special">Cherry</li>
</ul>
"""
soup = Soup(html)

# 查找第一个匹配
first = soup.find("li")
print(first.text)  # Apple

# 查找所有匹配
items = soup.find_all("li", class_="item")
print([i.text for i in items])  # ['Apple', 'Banana', 'Cherry']

CSS 选择器

html = """
<div id="main">
  <p class="intro">Welcome</p>
  <p class="body">Content</p>
</div>
"""
soup = Soup(html)

# 按 ID 选择
main = soup.select_one("#main")

# 按类名选择
intro = soup.select_one(".intro")
print(intro.text)  # Welcome

# 按标签选择
paragraphs = soup.select("p")
print(len(paragraphs))  # 2

# 后代选择器
body_p = soup.select_one("div p.body")
print(body_p.text)  # Content

# 子选择器
children = soup.select("div > p")
print(len(children))  # 2

# 属性选择器
soup2 = Soup('<a href="/home">Home</a><a href="/about">About</a>')
links = soup2.select("a[href]")
print(len(links))  # 2

# 伪选择器
html3 = """
<ul>
  <li class="a">First</li>
  <li class="b">Second</li>
  <li class="c">Third</li>
</ul>
"""
soup3 = Soup(html3)

# :first-child / :last-child
print(soup3.select_one("li:first-child").text)  # First
print(soup3.select_one("li:last-child").text)   # Third

# :only-child
soup4 = Soup("<div><span>Alone</span></div>")
print(soup4.select_one("span:only-child").text)  # Alone

# :not(selector)
others = soup3.select("li:not(.a)")
print([li.text for li in others])  # ['Second', 'Third']

get_text

soup = Soup("<p>Hello <b>world</b></p>")
print(soup.get_text())          # Hello world
print(soup.get_text(separator=" | "))  # Hello  | world
print(soup.get_text(strip=True))  # Helloworld

属性访问

soup = Soup('<a href="/page" class="nav active" id="link1">Click</a>')
a = soup.find("a")

# 访问属性
print(a["href"])         # /page
print(a["id"])           # link1
print(a.get("class"))    # ['nav', 'active']
print(a.get("missing", "default"))  # default

树操作

from soup import Soup

html = "<ul><li>A</li><li>B</li></ul>"
soup = Soup(html)
ul = soup.find("ul")

# append —— 在末尾添加子节点
new_li = soup.new_tag("li", {"class": "new"})
new_li.children.append("C")
ul.append(new_li)

# insert —— 在指定位置插入子节点
another = soup.new_tag("li")
another.children.append("Z")
ul.insert(0, another)

# extract —— 从父节点移除但保留节点本身
removed = ul.children[1].extract()

# replace_with —— 用另一个节点替换
replacement = soup.new_tag("li")
replacement.children.append("X")
ul.children[0].replace_with(replacement)

# unwrap —— 移除标签但保留子节点
soup2 = Soup("<div><b>bold text</b></div>")
soup2.find("b").unwrap()
print(soup2.get_text())  # bold text

HTML 序列化

soup = Soup('<div class="box"><p>Hello <b>world</b></p></div>')
div = soup.find("div")
print(div.to_html())
# <div class="box"><p>Hello <b>world</b></p></div>

# str() 也会生成 HTML
print(str(div))
# <div class="box"><p>Hello <b>world</b></p></div>

属性设置

soup = Soup('<a href="/old">Link</a>')
a = soup.find("a")

# 设置属性
a["href"] = "/new"
a["class"] = ["nav", "active"]

# 删除属性
del a["class"]
print(a.to_html())  # <a href="/new">Link</a>

创建新标签

soup = Soup("<div></div>")
tag = soup.new_tag("span", {"id": "greeting"})
tag.children.append("Hello!")
soup.find("div").append(tag)
print(soup.find("div").to_html())
# <div><span id="greeting">Hello!</span></div>

decompose(移除元素)

html = "<div><p>Keep</p><script>remove me</script></div>"
soup = Soup(html)
for script in soup.find_all("script"):
    script.decompose()
print(soup.get_text())  # Keep

find_parent

soup = Soup("<div><ul><li>Item</li></ul></div>")
li = soup.find("li")
print(li.find_parent("div").name)  # div
print(li.find_parent().name)       # ul

支持的 CSS 选择器

选择器 示例 描述
标签 p 按标签名匹配
类名 .intro 按类名匹配
ID #main 按 ID 匹配
属性 [href] 匹配具有某属性的元素
属性值 [href="/home"] 匹配属性值
后代 div p 匹配 div 内的 p
子元素 div > p 匹配直接子元素
复合 p.intro 匹配具有 intro 类的 p
:first-child li:first-child 匹配第一个子元素
:last-child li:last-child 匹配最后一个子元素
:only-child span:only-child 匹配唯一子元素
:not() li:not(.active) 匹配不满足内部选择器的元素

API 参考

Soup(markup, parser="html.parser")

解析 HTML 文档并提供类 BeautifulSoup 的 API。

参数:

名称 类型 默认值 描述
markup str -- 要解析的 HTML 字符串。
parser str "html.parser" 忽略(仅为 BS4 API 兼容性存在)。

Tag 方法

方法 描述
find(name, class_, **attrs) 查找第一个匹配的子元素。
find_all(name, class_, **attrs) 查找所有匹配的子元素。
select(selector) 查找所有匹配 CSS 选择器的元素。
select_one(selector) 查找第一个匹配 CSS 选择器的元素。
get_text(separator="", strip=False) 获取所有文本内容。
decompose() 从父元素中移除此元素。
find_parent(name=None) 查找最近的父元素,可按标签名过滤。
get(attr, default=None) 获取属性值。
append(child) 在末尾添加子节点(Tag 或 str)。
insert(index, child) 在指定位置插入子节点。
extract() 从父节点移除,返回自身(保留子节点)。
replace_with(new_node) 用另一个节点替换此节点。
unwrap() 移除此标签但保留其子节点(重新挂载到父节点)。
to_html() 将此元素序列化为 HTML 字符串。

Tag 属性

属性 类型 描述
.text str 所有文本内容(get_text() 的快捷方式)。
.name str 标签名(如 "div")。
.attrs dict 属性字典。class 存储为列表。
.children list 子节点(Tagstr)。
.parent Tag \| None 父元素。

Soup 工厂方法

方法 描述
new_tag(name, attrs=None) 创建一个新的独立 Tag 节点。

与 BeautifulSoup 的对比

特性 zerodep soup BeautifulSoup
依赖 无(仅标准库) soupsieve,可选 lxml/html5lib
文件数 单文件 多文件包
解析器后端 html.parser html.parserlxmlhtml5lib
find / find_all
CSS 选择器 标签、类、ID、属性、组合器、伪选择器 完整(通过 soupsieve)
树操作(append/insert/extract)
HTML 序列化(to_html) 是(prettify)
NavigableString 否(使用纯 str
解析速度(小) 149 μs 446 μs(慢 2.99x)
解析速度(大) 12.7 ms 37.1 ms(慢 2.93x)

适用场景(zerodep): 需要基本的 HTML 解析(find、select、get_text),零依赖且高性能。

适用场景(BeautifulSoup): 需要完整 CSS 选择器规范(:nth-child():has() 等)、多解析器后端或 NavigableString 功能。

性能测试

beautifulsoup4 在小、中、大 HTML 文档上进行了基准测试。

详见 Soup 性能测试