soup5ever
A BeautifulSoup 4 tree builder backed by
Rust's html5ever. Same HTML5 trees as
BeautifulSoup(html, "html5lib"), 6–14x faster.
from bs4 import BeautifulSoup
import soup5ever
soup = BeautifulSoup(html, "html5ever")
Installation
pip install soup5ever
Wheels for Linux (x86_64, aarch64; glibc and musl) and macOS (arm64), one abi3 wheel per
platform for CPython 3.10+. The only dependency is beautifulsoup4>=4.13; no Rust, libxml2
or html5lib needed.
Usage
Importing soup5ever registers the "html5ever" parser (also "soup5ever"). The generic
features "html5" and "html" are registered at the lowest priority, so importing it doesn't
change what BeautifulSoup(markup, "html5") or BeautifulSoup(markup) select.
The usual options work (from_encoding, element_classes, multi_valued_attributes,
store_line_numbers, attribute_dict_class). Like html5lib, parse_only isn't supported.
Compatibility
The target is BeautifulSoup(html, "html5lib"); where html5lib and the HTML standard
disagree, soup5ever follows the standard.
- BS4's own tests for its html5lib builder: 93/93 pass.
- html5lib-tests tree construction: 1,588/1,592 match the spec. Against html5lib the trees
are identical in 1,417 cases; the other 175 are classified automatically (167 html5lib
behind the spec, 4 BS4 html5lib-adapter bugs, 4
<selectedcontent>misses both share). - 182 targeted differential cases and ~23k fuzz inputs (triaged with Chromium): every remaining difference is an html5lib bug or listed below.
Differences from html5lib:
- Current HTML standard where html5lib 1.1 (2020) is behind: the 2025
<select>rules,<template>in<head>,</p>in SVG/MathML,<main>/<summary>,<search>,<dialog>, ruby. Seetests/corpus.pyfor each case. - BS4's html5lib adapter never applies the Noah's Ark clause (
AttrListhas no__eq__); soup5ever does. - Undeclared, valid UTF-8 bytes are decoded as UTF-8 (html5lib guesses windows-1252 without
chardet). Decoding follows the WHATWG Encoding Standard, not Python's codecs. - Lone surrogates in a
strbecome U+FFFD. sourceline/sourceposmatch html5lib for ~98.5% of elements.
Shared with html5lib: no parse_only, no Script/Stylesheet string subclasses, no
<selectedcontent> cloning.
Experimental: html5ever-experimental
soup = BeautifulSoup(html, "html5ever-experimental")
Builds the same trees as "html5ever", 10–30% faster, by creating BeautifulSoup's objects
without running their constructors (Tag.__init__, NavigableString.__new__): the extension
sets the attributes those would set, once each and already linked into the tree.
That relies on BeautifulSoup internals, verified against beautifulsoup4 4.13–4.15:
- The first parse checks that the installed BeautifulSoup still sets exactly the attributes soup5ever expects. If not, it warns once and falls back to the regular construction.
- Custom
element_classeswith their own__init__/__new__/__setattr__/setup,__getattribute__or properties for the attributes BS4 sets,attribute_dict_classes with their own__setitem__, and builder subclasses that override how tags are set up get the regular construction automatically. - What it can't detect: a BeautifulSoup release that keeps those attributes but changes what they mean.
The test suite compares every object's full state (vars() in order, value types, links)
with "html5ever"'s over the whole corpus, the html5lib-tests and the fuzz inputs, and runs
BS4's own builder tests against it. It is never selected through the generic "html5" or
"html" features.
Benchmarks
BeautifulSoup(markup, parser) on an in-memory str, median of 7 rounds, 4-vCPU Linux VM,
CPython 3.11:
| document | html5lib | html5ever | speedup |
|---|---|---|---|
| small page (3 KiB) | 2.4 ms | 250 µs | 9.6x |
| large page (1.2 MiB) | 888 ms | 101 ms | 8.8x |
| malformed tag soup | 483 ms | 34 ms | 14.2x |
| deeply nested | 243 ms | 26 ms | 9.3x |
| table-heavy | 507 ms | 69 ms | 7.3x |
| SVG/MathML-heavy | 323 ms | 51 ms | 6.4x |
Parsing in Rust is 14–32% of soup5ever's time; the rest is BeautifulSoup's own object
constructors. html5ever-experimental skips most of the constructor work (it has its own
column in the benchmark output). To reproduce:
uv pip install -e .[dev]
python benchmarks/run.py
Development
uv venv venv && source venv/bin/activate
uv pip install -e .[dev] # builds the Rust extension
python scripts/fetch_upstream_tests.py # BS4's builder tests + html5lib-tests
pytest
Re-run uv pip install -e .[dev] after changing Rust code.
edwh fmt && edwh lint # Python (ruff)
cargo fmt && cargo clippy # Rust (lint levels in Cargo.toml)
pytest includes a 3000-input differential fuzz run; pytest --fuzz 20000 --fuzz-seed 7
runs a bigger one.
License
MIT
Metadata
Release files for soup5ever 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| soup5ever-0.2.0.tar.gz | 83.7 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| soup5ever-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ x86-64 | Details |
| soup5ever-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ ARM64 | Details |
| soup5ever-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| soup5ever-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| soup5ever-0.2.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 3.1 MB
Release files / soup5ever-0.2.0.tar.gz
| Download URL | soup5ever-0.2.0.tar.gz |
|---|---|
| Size | 83.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0689ea7d5371c164e28a0c709ce1c3ee193a135310310097590f8e7304adff4c
|
|
BLAKE2b-256 checksum How to use checksums |
30d5ea6b48b58ddba2dcd148672fa99ffaaea3dc668ba8147428f1086e9623d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / soup5ever-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | soup5ever-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 629.8 kB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
06fe640d1ba43dfb4877431811d2ecc7bd67251281f0dc8d97c14be7af5ce1f0
|
|
BLAKE2b-256 checksum How to use checksums |
8396137a76de2d277d17814992ed1d7e16d65733ac45b5d889ddd8895442109c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / soup5ever-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl
| Download URL | soup5ever-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 612.2 kB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
cf8de880f3c1f9d7bbabcde31b0f55e58ad6a05416387cfd2e727280156964ee
|
|
BLAKE2b-256 checksum How to use checksums |
a7aeea3d32aa80107eeca6d80f76aa50153431c8de951aa641d5530e52597ade
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / soup5ever-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | soup5ever-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 631.5 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
012003c992e4128db81a264a560fe64ae54b189e4ad34901b0f78d748e764035
|
|
BLAKE2b-256 checksum How to use checksums |
01529e8a34fed6aa9b0c9b1ce3b1a49e16447bad14200585d6568af349142796
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / soup5ever-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | soup5ever-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 613.3 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
9d2fd6be4c907af1f18b895b07d894882a19d0f6cdc85dd4b646643b3212b950
|
|
BLAKE2b-256 checksum How to use checksums |
c289c593217236decf6d6bd4db61372fcac2c670403088f20621fd7626c19f81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / soup5ever-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | soup5ever-0.2.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 569.2 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
22cf9b29d50ef17c2b5ea25fdc7083f9e1a1593b5370b2e21dc069e13885e5db
|
|
BLAKE2b-256 checksum How to use checksums |
95af2f7e6a223f234c4590a502ab4549fa82aa68e1a4a5e90c80fef1bb125f01
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|