BeautifulSoup 在 2026 年:最友好的 HTML 解析器,也是最慢的那个(慢 12–17 倍)

最后更新于 July 17, 2026
BeautifulSoup 在 2026 年:最友好的 HTML 解析器,也是最慢的那个(慢 12–17 倍)
AI 摘要
这篇评测将 BeautifulSoup 视为 Python 中最容易上手的 HTML 解析器,并用精确的数据量化了这份“友好”的代价。文章把 bs4 与 C 语言加速的解析器在速度、破损 HTML 容错、CSS 选择器覆盖率、对象保留和编码恢复等维度进行了对比。结果显示,BeautifulSoup 通常要慢 12 到 17 倍,但作者也解释了开发者为什么仍然会选择它:API 可读性强、解析宽容、soupsieve 选择器支持完整,非常适合处理杂乱的一次性抓取任务。这是一篇实用指南,帮助你判断什么时候值得接受这笔速度税,什么时候改用更快的解析器更符合工程效率。

BeautifulSoup 几乎是每个 Python 网页抓取者第一次都会想到的库,而它也确实是几款主流 HTML 解析器里最慢的。前半句和后半句都是真的,而且这并不是在批评它。更有意思的是,“最慢”最终被证明是一个非常具体、可以量化、甚至可以用成本换来的数字,而不是一种模糊感受。

我把 bs4(也就是 beautifulsoup4,版本 4.15.0,于 2026 年 6 月发布,MIT 许可证)放进了一组新的能力测试,并复用了同一套基准环境里的计时数据,结果非常一致:你用大约一个数量级的速度损失,换来的是业内最友好的 API 和最强的容错能力。这个交换值不值,完全取决于你的工作负载,所以这篇评测会把这两面都摆出来讲清楚。

BeautifulSoup 到底是什么,不是什么

大多数教程都会跳过最关键的一点:BeautifulSoup 并不直接负责解析 HTML。它本质上是一个包装器。底层它会把你的文档交给三种真正的解析器之一——Python 自带的 html.parserlxmlhtml5lib——然后把它们生成的树包装成一个统一、非常顺手的导航和搜索 API。bs4 自己的工作不是“解析”,而是让解析结果更好用、更好走读。

它的作者把它称作“屏幕抓取库(screen-scraping library)”,这套定位一直没变:给它一份连浏览器看了都会皱眉的 HTML,它还是能把你要的数据抠出来。这份口碑是靠实力打出来的,不过这里有一个后面要讲的例外。

先把几个基础信息钉牢:

字段
包名beautifulsoup4(导入名为 bs4
测试版本4.15.0(上传于 2026-06-07)
Python 要求>=3.7.0
许可证MIT
官方主页crummy.com/software/BeautifulSoup
源码 + Bug 跟踪Launchpad —— 不是 GitHub
维护状态持续活跃(2026 年 6 月为 4.15.0,过去一年发布了 6 个版本)

“不是 GitHub”这一点比看上去更重要。bs4 是一个 20 年历史的库,主页在 crummy.com,问题跟踪在 Launchpad,所以那种看 GitHub star 数判断热度的方式并不适用。判断它是否健康,应该看发布节奏;按这个标准,它显然运转良好。

关于许可证还有一个细节,尤其是你需要向合规团队交代时:bs4 这个包装层是 MIT 许可证,但你真正装进依赖树里的内容,取决于你选的后端。html.parser 属于 Python 标准库(PSF 许可证,没有额外依赖)。lxml 是 BSD 许可证,但它依赖 libxml2/libxslt——这是外部 C 依赖,要么自己编译,要么使用预编译 wheel。html5lib 则是纯 Python,MIT 许可证。如果你想要最干净的依赖栈,内置的 html.parser 最省事——而它恰好也是最容易踩坑的后端。后面马上展开。

速度税到底有多高,给你算清楚

先把数字摆出来,因为这就是标题里最重要的内容,藏着掖着就不诚实了。在一个接近真实场景的“解析后提取”任务里——先解析字符串,再取出所有 <h3 class="title"> 和所有 <a href>——BeautifulSoup 是这组对比里最慢的解析器,而且不是慢一点点。

BeautifulSoup 速度税:232 ms 对比 15 ms 的 C 解析器

这些时间数据复用了 selectolax 的基准测试环境(同一台机器、同样的 3 次运行方法,数据截至 2026-07-13);本文没有重新跑自己的计时测试,以避免 CPU 竞争和重复劳动。下面是中位数 p50 延迟,单位毫秒:

页面大小bs4 (html.parser)bs4 (lxml)selectolax-Lexborlxmlbs4-hp 慢了bs4-lxml 慢了
1 KB0.3230.2810.0270.03612.0x10.5x
10 KB2.0491.7050.1600.16612.8x10.7x
100 KB20.59916.5401.4641.42314.1x11.3x
1 MB232.558181.85514.90114.17715.6x12.2x
10 MB2788.7462261.557159.935172.93317.4x14.1x

所以,bs4(html.parser) 大约比 selectolax-Lexbor 这样的 C 解析器慢 12–17 倍;即便换成 lxml 后端,也只能拉回到 10.5–14 倍,仍然落后一个完整数量级。原因是结构性的,不是 bug:不管底层由谁来解析,bs4 都会给每一个节点都创建一个完整的 Python 对象(TagNavigableString)。这个把节点对象化的层,C 解析器根本不用交这笔税。

注意这个倍率会随着页面变大而上升——1 KB 时是 12.0x,10 MB 时变成 17.4x。这说明它不是那种能被摊薄的固定启动开销,而是按节点数线性增长的“逐节点税”。

但换个角度看,“慢 10 倍”听起来比实际可怕得多。比如在 1 MB 页面上,232 ms 对 15 ms。如果你的任务是“抓几百到几千个页面,每个几百 KB”,这个绝对差距几乎感受不到——你不会因此吃亏,优化它也换不来什么实际收益。但如果你的任务是一百万页流水线,同样的倍率就会决定一个任务能不能按时跑完。数字不变,结论完全相反。所以要拿你的真实体量来评估,而不是只看 benchmark。

不,换后端并不能把它变快

常有人以为,给 bs4 指定 lxml 后端,就等于拿到了 lxml 的速度。其实不是,而且这个误解很值得拆开说。看一个 100,000 节点的批量 CSS 查询(一次性选出所有 <a> 并读取 href,树已提前构建好),吞吐差距非常明显:

解析器查询 p50节点/秒
lxml33.30 ms3,002,646
selectolax-Modest34.19 ms2,924,550
selectolax-Lexbor39.46 ms2,534,027
bs4 (lxml)250.56 ms399,111

bs4(lxml) 每秒大约能处理 39.9 万个节点,比这三个 C 引擎慢 6.3–7.5 倍,即使它自己的底层后端就是 lxml 也一样。后端只会加速树的“构建”阶段;查询和遍历还是要经过 soupsieve 和 bs4 的 Tag 对象,每个匹配节点都得再包一层 Python 对象。所以“给 bs4 上 lxml 就能获得 lxml 速度”这个想法是不对的:后端确实加速了一个阶段,但最慢的那个阶段并不在这里。

内存和冷启动也在继续增加成本。对于一个 10 MB 文档,bs4 的常驻内存大约是 selectolax 或 lxml 的 1.5–1.75 倍(218–226 MB 对 129–145 MB)——根因还是一样:每个节点一个 Python 对象。导入 bs4 也要大约 33.4 ms,而 lxml.html 只要 14.1 ms,所以 bs4 的导入速度慢 2.36 倍。对于长时间运行的进程,这点差距几乎可以忽略;但如果你做的是 CLI 工具,或者经常冷启动的 serverless 函数,这就是一个真实存在、值得知道的小成本。

为什么加线程帮不上忙

如果你面对一个 CPU 密集型的慢任务,第一反应是“上线程”,那 bs4 会狠狠惩罚这个习惯。拿一个 1 MB 页面重复解析 48 次来测,单线程和四线程的结果如下:

解析器1 线程4 线程加速比
selectolax-Lexbor0.563 s0.159 s3.54x
lxml0.459 s0.378 s1.21x
bs4 (lxml)6.945 s26.842 s0.26x

请把最后一行再看一遍。四个线程让 bs4 变成了 慢 3.9 倍,不是更快。经验信号非常明确:它大概率持有 GIL。bs4 的树构建是纯 Python 代码,所以会被全局解释器锁串行化;往里堆线程,只会给不能并行执行的任务增加调度开销。selectolax 能拿到约 3.5 倍加速,是因为它的 C 核心会释放锁;bs4 没有这个空间。

对于未来的 free-threading 时代,这个结论的实际意义是:如果你要并行化 BeautifulSoup,应该用多进程(ProcessPoolExecutor),不要用线程。selectolax 和 lxml 可以在线程层面扩展,bs4 不行。这里补一个严谨性说明——这只是一次观测:单一线程数(4)、单一页面大小(1 MB)下的结果;“持有 GIL”这个机制是从墙钟时间行为推断出来的假设,而不是我逐行插桩确认的结论。方向很清楚,但具体机制仍属于推断。

默认后端是坑,一定先看这里

如果你只记住本文一个点,那就记这个。直接 BeautifulSoup(html) 而不传第二个参数时,默认用的是 html.parser,而 html.parser 并不实现 HTML5 的可选结束标签规则。听上去像教科书细节,直到它静悄悄把你的数据弄坏。

BeautifulSoup 后端容错矩阵:html.parser 12/15,lxml 和 html5lib 15/15

我把 15 个故意写坏的 HTML 样本分别喂给这三个后端,并在运行前就为每个样本预先注册了一个与后端无关的结构性断言(这样就没人能事后挑赢家)。结果如下:

后端满足预期 / 15
lxml15
html5lib15
html.parser12

这三次失败都来自同一个根因。比如一个没有闭合的表格:<table><tr><td>a<td>b<tr><td>c<td>d</table>。在 html.parser 下,提取出来的单元格文本会变成 ['abcd','bcd','cd','d']——每个 <td> 都把后面的内容一起吞了,因为解析器把单元格嵌套起来了,而不是把它们正确闭合。lxml 和 html5lib 都能正确返回 ['a','b','c','d']。裸露的列表项也是同样道理:<li>a<li>b<li>c 在 html.parser 下会得到嵌套的 ['abc','bc','c'],而另外两个后端会给出干净的 ['a','b','c']。重复属性也会翻车——<div id="first" id="second"> 在 html.parser 下会保留 "second",而 lxml/html5lib 会保留 "first",这才符合 HTML5 规范。

为什么这危险,而不只是烦人?因为它发生时不会报错。一个爬虫如果只是随手写了 BeautifulSoup(html),碰到未闭合的表格或列表——这在老旧网站、手写 HTML、忘记补闭合标签的模板里都极其常见——就可能把相邻单元格文本串在一起,直接把脏数据交到你手上,而且全程不吭声。修复方法只需要加一个参数:BeautifulSoup(html, "lxml")BeautifulSoup(html, "html5lib")

公平地说,html.parser 也不是一无是处。前面 15 个样本里有 12 个,在三个后端下结果完全一致。比如错误嵌套的标签 <b><i></b></i>、缺少 html/body 骨架、未加引号的属性、孤立的结束标签、未闭合注释、嵌套表单、大小写混用等等,bs4 的容错其实整体非常强;差异几乎都集中在“可选结束标签”这一类上。而且这些都不是什么新发现——bs4 自己的 “Differences between parsers” 文档早就直说了:html.parser “没那么宽容”。这张故障矩阵补充的,是那些“没那么宽容”会具体变成错误输出的可复现案例。

你没放弃的东西:API 和 CSS 才是它最强的部分

所以 bs4 虽然慢、单线程,还藏着默认后端的坑,为什么大家还是会选它?因为“友好”这一半交换是真实存在的,而且实测成立。

BeautifulSoup 的 soupsieve CSS 覆盖率优势:41/41 再加 20/20

我跑了 29 个 API 探针,覆盖搜索、CSS、树导航、文本提取和 DOM 修改。29 个全部通过,而且每个结果都不是凭肉眼判断,而是把实际返回值和预期值做比较得出的。这里面有两个能力,是 C 解析器根本不给你的:

  • find / find_all 里传函数谓词。 你可以写 soup.find(lambda t: t.name == "a" and "btn" in t.get("class", [])),用一行 Python 就表达复杂条件——不用先“全选出来,再过滤”两步走。
  • 有名字、双向的树导航。 .parent.next_sibling.find_parent.stripped_strings.descendants——这些遍历接口读起来像自然语言,而且可以双向走。selectolax 对其中一些要绕很多步,有些干脆没有。

这就是“帮你省开发时间”的那一半,而且不是营销话术,是 29 个绿色通过。

顺带提两个容易踩的坑,公平评测得把两边都讲到。第一,布尔属性:<input disabled> 在 bs4 中会返回空字符串 "" 作为 disabled 的值(selectolax 返回 None)。二者都是假值,所以写 if node.get("disabled") 会把一个其实存在的布尔属性静悄悄漏掉——安全做法是 "disabled" in tag.attrs。第二,get_text(strip=True) 在去掉空白后会直接拼接文本,不会自动加分隔符,所以 "...with " 加上 "link1" 会变成 "withlink1"。当你需要保留词边界时,记得传 separator=" "。这两个坑都不是 bs4 独有,属于跨库共通问题。

接下来是最让人意外的一点:选择 bs4 并不会牺牲 CSS 覆盖率。它的 CSS 引擎 soupsieve 是这组对比里最完整的实现。在 41 项基础矩阵里(复用了 selectolax 的基准),soupsieve 拿到了 41/41——是全场唯一满分,领先 selectolax-Lexbor 的 39/41 和 cssselect(lxml/parsel)的 37/41。然后我又跑了 20 个 soupsieve 文档里宣传的扩展案例,它也拿到了 20/20,包括 Lexbor 直接不支持的选择器::lang(en)、soupsieve 专有的 :-soup-contains('featured'):is():where():has(> a)。真正的缺口只有两个:XPath(soupsieve 只做 CSS,不做 XPath)以及 parsel 的 ::text / ::attr() 伪元素,那是 Scrapy 的扩展。如果你一直生活在 XPath 世界里,这个迁移会很难受。

所以这一节的结论很明确:选择 BeautifulSoup 你牺牲的是速度,不是 API 易用性,更不是 CSS 覆盖能力。

两个在生产环境里值得专门预算的坑

除了默认后端以外,还有两个行为在长时间运行或非 UTF-8 工作负载下特别容易咬你一口。

引用环:长循环里要调用 decompose()

每个 bs4 的 Tag 既会引用它的父节点,也会引用它的子节点,于是形成了一个引用环。CPython 的引用计数本身没法自动回收这种环,这要靠分代垃圾回收器来处理。为了看它到底有多重要,我在关闭 GC 的情况下反复构建并删除一棵树 300 次,然后统计内存里还残留多少 Tag 对象:

BeautifulSoup 引用环在关闭 GC 时保留 120,900 个对象,开启时为 0

场景del 之后仍保留的 Tag 数
关闭 GC120,900(300 次循环,未回收任何对象)
开启 GC26,598(分代 GC 在循环中途触发)
强制执行 gc.collect()0(全部回收)
无环对照组(字符串列表,关闭 GC)差值 0

在关闭 GC 时,del soup 什么都没回收——120,900 个对象全部还留在内存里,因为引用环把引用计数机制绕开了。一次 gc.collect() 就把它们全部清掉了。无环对照组(一个普通字符串列表,已知没有引用环)差值为 0,证明这次积累确实来自 bs4 的引用环,而不是测量噪声。bs4 自己的文档也明确说,这些对象“彼此紧密交织……正是垃圾回收器最头疼的那类结构”,所以这是有文档依据的行为;本测试补充的是具体保留数量,以及 collect() 能把它归零的证据。

实战规则很简单:如果你的流水线要在紧密循环里解析很多大页面,而且你的代码(或者某些高吞吐设置)关闭了 GC,或者触发 GC 的频率不够,bs4 的树就会滞留,内存会持续上涨。每抓完一页就调用 soup.decompose()——bs4 专门提供这个方法,就是为了打断引用环、尽早释放资源。selectolax 和 lxml 的 C 树没有这个问题。

编码:UnicodeDammit 是 bs4 的一个低调优势

bs4 还带了一个快解析器没有的组件:UnicodeDammit,它会嗅探文档编码并自动转成 Unicode。我给它做了一组 8 种“声明编码 vs 实际编码”的测试:

BeautifulSoup 的 UnicodeDammit 在 8 种编码场景里找回了 5 种

场景真实编码UnicodeDammit 猜测是否恢复
utf8_no_declutf-8utf-8
utf16_bomutf-16utf-16le
gbk_chinesegbkgb18030是(超集)
shiftjisshift_jiscp932是(超集)
latin1_declared_utf8latin-1(声明为 utf-8)iso-8859-1是(忽略了谎报)
latin1_no_decllatin-1cp720
cp1252_no_declcp1252cp862
utf8_declared_latin1utf-8(声明为 latin-1)iso-8859-1否(照着谎报走了)

8 组里成功了 5 组。UTF-8、带 BOM 的 UTF-16、GBK、Shift-JIS,甚至标错声明的 latin-1 都正确恢复了;而且像 GBK→gb18030、Shift-JIS→cp932 这种超集猜测也照样能正确解码。两种失败模式也值得知道:很短的 latin-1/cp1252 字节样本会被误判成 DOS 代码页,因为统计检测在短输入上不够可靠,而 DOS 画框字符又和 Latin-1 的码点有重叠;还有一种是 <meta charset> 声明本身就错了,这时 UnicodeDammit 会相信这个声明。bs4 文档也提醒了这两点——样本可能“短到让 Unicode, Dammit 无法锁定它”,而且数据越多,猜测越准。

和 selectolax 相比,后者会对非 UTF-8 字节静默误解码,要求你自己先做解码;在这一点上,bs4 确实更占优势,因为它至少会尝试猜测,而且常常能猜对。但它不是保证正确。对于已知编码的内容,别猜,直接显式传入:BeautifulSoup(bytes, from_encoding="...")

三种后端在真实页面上真的会不一致吗?

前面的故障矩阵展示的是故意写坏的 HTML 会让各后端分道扬镳。那自然会追问:现实里这会不会真的影响结果?所以我又拿 11 个真实抓取页面做了测试——BBC、Wikipedia、Craigslist、MDN、old.reddit、Python 文档、Hacker News、Books to Scrape、webscraper.io、whitehouse.gov,以及一个 JS 渲染的 quotes 页面——对比链接、标题和图片数量。

结果是:三个后端在 11 个页面上全部一致,零分歧。也就是说,前面那个“坑点”里出现的后端差异,只会在故意破损的 HTML 上显现;对于结构足够正常、即便有点“脏”的现代生产站点,后端选择不会改变你提取到的内容。换句话说:主流、结构良好的站点上,html.parser 完全够用,而且还能省掉依赖。只有当你抓的是明显非标准、手写或年代久远的 HTML 时,后端选择才会真正影响结果,这时候才值得换成 lxml 或 html5lib。

那次测试还有一个真实边角案例值得一提。MDN 页面里有一个 <template> 元素,而所有 bs4 后端都返回了 508 个链接——这意味着 bs4 会把 <template> 里的内容直接拍平进主树。这一点和 lxml 一样,但和 selectolax-Lexbor 相反:后者严格遵守 HTML5 规范(<template> 是一个无害的 DocumentFragment),只返回 497 个链接,悄悄把 template 里的 11 个链接丢掉了。所以 bs4 会把 <template> 内的数据一起抓出来——这有时很有用,但也可能让你拿到浏览器根本不会渲染的“幽灵内容”。两种行为都不算错,它们只是对规范的不同解释;你需要知道自己拿到的是哪一种。

BeautifulSoup 适合什么,不适合什么

与其把这些都压成一个 0–100 的总分(那样会把真正重要的权衡隐藏掉),不如按维度给一张评分卡,每一行附一个提醒:

维度测试结果读者提醒
安装 / 首次运行纯包装器,无浏览器/环境配置;html.parser 零依赖;都有预编译 wheellxml 后端需要 C 依赖
相对 C 解析器的速度慢 12–17 倍(html.parser)/ 10.5–14 倍(lxml 后端),各尺寸都一样单一机器;复用了 selectolax 数据
CSS 查询吞吐在 10 万节点上慢约 6–7.5 倍;lxml 后端也救不了复用数据;承担 Python Tag 的代价
内存比 selectolax/lxml 高 1.5–1.75 倍;最重复用数据;按 RSS 计量
导入冷启动慢 2.36 倍(33.4 vs 14.1 ms)复用数据;小项
线程扩展性bs4-lxml 在 4 线程下反而慢约 3.9 倍(持有 GIL)单次观测;建议用多进程
API 易用性29/29 通过;支持函数谓词 find 和双向导航空字符串布尔属性、strip 后词边界是坑
CSS 覆盖率soupsieve 最强:41/41 基础 + 20/20 扩展;支持 :lang没有 XPath,没有 ::text
三后端容错lxml/html5lib 15/15;html.parser 12/15差异只出现在破损 HTML
真实页面一致性3 后端 11/11 一致;都拍平 <template>(508)对结构良好的站点,后端无关紧要
引用环 GC树是引用环;300 次循环保留 120,900 个对象,collect() 清零长循环需要 decompose()
编码处理UnicodeDammit 8 取 5;短样本容易误判,错误声明会被信任单次观测
维护状态活跃(4.15.0,2026 年 6 月);MIT主页在 crummy/Launchpad,不在 GitHub

那么 BeautifulSoup 适合谁?适合那些更看重可读 API 和宽容解析,而不是绝对吞吐的用户,尤其是中等规模任务:原型、一次性抓取、内部工具、以及“开发时间比运行时间更贵”的团队。谁应该考虑别的方案?百万页级流水线——速度税会滚成真金白银;需要线程级并行的工作负载;以及任何已经深度绑定 XPath 的场景。

再补一句它在真实爬虫栈中的位置,以及 Thunderbit 自己的工具位于哪一层。BeautifulSoup 的前提是:你已经拿到了 HTML。它不负责拉网页,不负责渲染 JavaScript,也不处理反爬或 CAPTCHA——这些是完全不同的一层,而且在现代网页上是真正难做的工作。AI 爬取 API 正是处在另一层:Thunderbit 的开发者栈——REST API、MCP 服务器和 CLI——负责抓取、JS 渲染和反爬问题,然后直接返回干净的 Markdown(POST /distill)或与 schema 匹配的结构化 JSON(POST /extract),你甚至不需要自己写选择器。两者不是竞争关系,而是互补关系。bs4 负责解析你已经拿到的 HTML;Thunderbit 的 API、MCP 和 CLI 负责帮你拿到原本就不容易触及的 HTML。如果你的瓶颈在解析,bs4 是个不错的答案;如果你的瓶颈在“获取”,那是另一层的问题。

立即试用 Thunderbit 进行网页数据提取

最后结论

BeautifulSoup 提供了这组对比里最友好的 API、对破损 HTML 最强的容错能力,以及最完整的 CSS 引擎——代价则是大约一个数量级的速度损失和最重的内存占用。这个交换关系就是全部事实,直说就是这样。默认的 html.parser 后端是唯一真正的坑:它会悄悄把未闭合的表格和列表解析坏,所以只要输入可能不规整,就传 "lxml""html5lib"。线程不会让它更快,多进程才会。在长循环里,记得每页调用一次 decompose(),避免引用环越积越多。

最后补两个限制。本文所有结果都来自单一平台(macOS arm64、Python 3.14、预编译 wheel),而且时间倍率复用的是 selectolax 的基准数据(同一套测试,截止 2026-07-13),不是本文重新跑出来的,所以它们继承了单平台局限;如果换成 Linux x86_64 或源码编译环境,具体数字可能会变。其次,这些结果里没有任何“新发现”:bs4 本身就是一个 20 年历史的库,本文测试到的每一个行为,要么已经有文档,要么已经公开记录。价值不在于爆料,而在于把文档里只会定性描述的权衡,变成了可量化的数字。

常见问题

BeautifulSoup 慢吗?
慢,而且是可测量地慢。在“先解析再提取”的任务里,默认 html.parser 后端大约比 selectolax-Lexbor 这样的 C 解析器慢 12–17 倍,换成 lxml 后端也慢 10.5–14 倍,因为它会给每个节点都创建 Python 对象。这个差距有没有影响,取决于规模:在 1 MB 页面上是 232 ms 对 15 ms,几千页规模几乎感觉不到,但一百万页流水线里就会决定成败。

BeautifulSoup 应该用哪个解析器:html.parser、lxml 还是 html5lib?
如果是结构正常、主流网站,默认 html.parser 完全够用,而且没有额外依赖。但它不实现 HTML5 的可选结束标签规则,所以碰到未闭合的表格或列表时,会把相邻文本错着并在一起,而且不会报错。只要输入可能是破损、手写或年代较久的 HTML,就显式传 "lxml""html5lib"——在一组破损 HTML 测试里,它们都拿到了干净的 15/15,而 html.parser 只有 12/15。

BeautifulSoup 能用线程并行吗?
不能。bs4 的树构建是纯 Python,并且会持有 GIL,所以加线程只会更慢,不会更快——测试里四线程跑一个 1 MB 页面,速度比单线程还慢约 3.9 倍。如果要并行化 bs4,请用多进程(ProcessPoolExecutor)。像 selectolax 和 lxml 这类 C 核心库,才适合在线程级别扩展。

BeautifulSoup 对损坏的 HTML 处理得好吗?
整体来说是好的——在一组破损样本(错误嵌套标签、缺少骨架、未加引号属性等等)里,三个后端都能较好恢复。唯一明显的弱点是默认的 html.parser 和可选结束标签:未闭合的 <td>/<li> 会被嵌套而不是正确闭合,导致提取文本被污染。换成 lxmlhtml5lib 后,这类问题就会消失。

BeautifulSoup 和 lxml 哪个更好?
它们是不同的工具。lxml 在树构建和查询上都快得多,而且支持 XPath。BeautifulSoup 则把 lxml 等解析器包装成了一个更友好的 API,而且借助 soupsieve 拥有更完整的 CSS 覆盖。只是不要指望给 bs4 换上 lxml 后端就能达到 lxml 的速度——后端只会加速解析,查询和遍历仍然要承担 bs4 每节点 Python 对象的成本,所以在大规模批量选择时它仍会慢大约 6–7.5 倍。

试试 Thunderbit 进行网页数据提取 Get Started Free

Ke
Ke
Thunderbit 首席技术官 | 高级数据科学家与机器学习专家 Ke Shen 拥有近十年的机器学习和数据科学经验,毕业于哥伦比亚大学,曾任 Walmart Labs 高级数据科学家。他在 Python、R、Java 和统计学方面拥有深厚且备受同行认可的专业能力,并分享如何将复杂的 AI 算法从理论落地到生产级架构的实战经验。
目录
Thunderbit · AI 网页数据代理

1 次点击 中从任何页面提取数据

受到超过 250,000+ 用户的信赖
提供免费计划
使用 AI 提取数据
轻松将数据传输到 Google Sheets、Airtable 或 Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week