# selectolax for Fast HTML Parsing with CSS Selectors

> selectolax is a Python HTML parser built with Cython and the Lexbor engine, aimed at developers who need CSS-based extraction from HTML documents. Its main trade-off is a native build path in exchange for a focused API, documented parser speed, and version-specific compatibility constraints.

**rushter/selectolax** — Python binding to Modest and Lexbor engines. Fast HTML5 parser with CSS selectors for Python.

- Repository: https://github.com/rushter/selectolax
- Website: https://selectolax.readthedocs.io/en/latest/index.html
- Stars: 1,712 · Forks: 95
- Language: Python
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/rushter-selectolax

## What selectolax does for HTML extraction

selectolax solves a specific problem: parsing HTML in Python and locating elements with CSS selectors. It is intended for developers building crawlers, scrapers, and document-processing utilities that need to retrieve text, attributes, links, or HTML fragments without writing a separate tree-walking routine for every field. The README describes it as an HTML5 parser written in Cython that uses the Lexbor engine.

The package operates after an HTML document has been obtained. It does not fetch URLs, manage HTTP sessions, schedule requests, or execute JavaScript. Those responsibilities belong to other components. This distinction matters when evaluating it for a crawler: selectolax can parse and query returned HTML, but the surrounding application must retrieve that document first.

Its primary interface is compact. A parser object such as `LexborHTMLParser` accepts HTML, and callers use `css` to retrieve all matches or `css_first` to retrieve the first match. A selected node exposes text, attributes, its tag, its parent, and its serialized HTML through the API shown in the README. The example selects an `h1` by ID, calls `text()`, reads `attributes`, and gathers text from nodes matching `.post`.

The repository topics include crawler, CSS, HTML5, parser, scraper, and web-scraping, which places the library in document extraction. However, its Python package metadata also carries the `Development Status :: 5 - Production/Stable` classifier. That is a release classification, not proof that every input or selector behaves identically across environments.

The README links to the Sphinx documentation, a larger examples section, and `CHANGES.md`. Those are the appropriate places to check detailed API contracts and version changes. The high-level interface is easy to understand, but production adoption still depends on how the parser handles the malformed and partial HTML commonly produced by real sites.

## How Lexbor and Cython process HTML

selectolax is not a parser implemented entirely in Python. Its Python-facing layer is written in Cython, while Lexbor performs the underlying HTML parsing work. The repository contains a `lexbor` directory, and `setup.py` contains logic for locating Lexbor source files, compiling platform-specific C files, and building the extension. This explains why some installations require compiler tooling or the Cython package.

The documented data flow begins when an HTML string is passed to `LexborHTMLParser`. The engine builds a parse tree, and the Python binding exposes CSS selection against that tree. Calling `css` returns a collection of matching nodes that can be iterated in Python. Calling `css_first` returns the first matching node. Once a node is selected, methods and properties expose its content and structural context.

The documented selector API supports more than plain tag names. One example uses `div > :nth-child(2n+1):not(:has(a))`, combining a child combinator, positional selection, negation, and `:has`. The README also documents a selectolax-specific `lexbor-contains` pseudo-class for matching text. Adding the `i` flag performs case-insensitive matching, while a query without that flag is case-sensitive.

This architecture keeps the direct manipulation API familiar to Python developers while moving parsing into native code. It does not add browser behavior to the parse tree. The README does not document JavaScript execution, network activity inside the parser, mutation methods, or automatic browser rendering. HTML generated only after client-side scripts run must therefore be supplied in a different form or handled with another tool.

The repository description also says that selectolax provides bindings to the Modest and Lexbor engines. The README examples and `pyproject.toml`, however, focus on the Lexbor-backed API. Because no Modest-specific compatibility or performance comparison appears in those sources, choosing between those bindings requires checking the API reference directly.

## Installing selectolax and extracting the first node

The normal installation path is the package published on PyPI. Install it with:

```bash
pip install selectolax
```

The README says compilation errors can occur when an outdated selectolax version is used with a newer version of Python. Its documented fallback is:

```bash
pip install selectolax[cython]
```

The extra installs Cython when compilation is required. The command does not install a separate command-line parser. Users import `LexborHTMLParser` from the package to use the Python API.

After installation, the following README example parses HTML, retrieves one heading, prints its attributes, and collects matching post elements:

```python
from selectolax.lexbor import LexborHTMLParser

html = """
<h1 id="title" data-updated="20201101">Hi there</h1>
<div class="post">Lorem Ipsum is simply dummy text of the printing and typesetting industry. </div>
<div class="post">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>
"""
tree = LexborHTMLParser(html)

print(tree.css_first('h1#title').text())
print(tree.css_first('h1#title').attributes)
print([node.text() for node in tree.css('.post')])
```

The first printed value is `Hi there`. The second is the dictionary containing `id` and `data-updated`. The final value contains the two post strings. This is a real selection workflow: parse once, select nodes, then read the fields required by the application.

The package metadata requires Python `>=3.9,<3.16`. That range excludes Python 3.16 and versions older than 3.9. The classifiers name CPython and list support for several Python releases, but they do not document PyPy support. Projects using another interpreter should verify wheel availability and build requirements before adding selectolax to a locked dependency set.

## Advanced selectors and project development commands

Advanced selector support reduces the amount of procedural filtering required after parsing. The following README example selects every second child beginning with the first, then excludes nodes containing an anchor. It also inspects each node's attributes, text, tag, parent, and serialized HTML:

```python
from selectolax.lexbor import LexborHTMLParser

html = "<div><p id=p1><p id=p2><p id=p3><a>link</a><p id=p4><p id=p5>text<p id=p6></div>"
selector = "div > :nth-child(2n+1):not(:has(a))"

for node in LexborHTMLParser(html).css(selector):
    print(node.attributes, node.text(), node.tag)
    print(node.parent.tag)
    print(node.html)
```

For text matching, `lexbor-contains` supports case sensitivity as part of the selector:

```python
from selectolax.lexbor import LexborHTMLParser
html = "<div><p>hello </p><p id='main'>lexbor is AwesOme</p></div>"
parser = LexborHTMLParser(html)
results = parser.css('p:lexbor-contains("awesome" i)')
results = parser.css('p:lexbor-contains("AwesOme")')
assert len(results) == 1
assert results[0].text() == "lexbor is AwesOme"
```

The case-insensitive query is overwritten by the case-sensitive query in this exact README example. The second query selects only the paragraph whose text includes `AwesOme` with that capitalization.

For repository development, the README documents a recursive clone because the repository includes Lexbor through `.gitmodules`:

```bash
git clone --recursive  https://github.com/rushter/selectolax
cd selectolax
pip install -r requirements_dev.txt
python setup.py install
```

The Makefile provides the following commands for rebuilding during development:

```bash
make clean
make dev
```

It also defines `test`, `test_all`, `coverage`, `lint`, and `docs`. The `test_all` target sets `SELECTOLAX_TEST_ALL=1` and is described as including Lexbor version-gated tests. The repository also includes `examples/simple_example.py`, `examples/benchmark.py`, tests, and a `typesafety` directory. These are the visible places for examples, parsing checks, and typing checks, although the README does not publish a compatibility matrix for individual operating systems or Python patch releases.

## Benchmark claims and parser alternatives

The README presents a benchmark that extracts a title, links, scripts, and a meta tag from main pages of 754 domains. It directs readers to `examples/benchmark.py`. Its table reports 2.39 seconds for selectolax with Lexbor, 9.09 seconds for lxml with Beautiful Soup, 16.10 seconds for `html5_parser`, and 61.02 seconds for Beautiful Soup with `html.parser`.

These are published project figures, not independent measurements from this review. The README does not list the hardware, exact Python version, complete dependency versions, or every execution detail. The results can justify examining selectolax for selector-heavy extraction, but they cannot guarantee a particular throughput in another application.

A real alternative is Beautiful Soup combined with lxml. Beautiful Soup provides Python parsing and selection interfaces, while lxml supplies the underlying parser used in the README's comparison. A second documented alternative is Beautiful Soup with Python's built-in `html.parser`. These approaches differ in backend availability and implementation language. They may be preferable where an application already depends on Beautiful Soup or cannot accept selectolax's native extension build.

`html5_parser` is another direct comparison because it targets HTML5 parsing through a different library design. The README does not document feature-by-feature selector equivalence among the alternatives. Before changing an existing scraper, developers should compare behavior on the actual malformed fragments and selector expressions used by that scraper, not only parse time.

The benchmark focuses on extraction from a defined collection of pages. A crawler with larger documents, deeper selector expressions, or repeated mutation of the parsed tree represents a different workload. The project does not publish memory measurements or an API for changing its parsing mode.

## Licensing, maintenance, and parser boundaries

The selectolax package is distributed under the MIT license. The README identifies Lexbor separately as using the Apache-2.0 license. Distributions that include the native engine may therefore need to account for both licenses. The repository includes `LICENSE` and links to Lexbor's Apache-2.0 license, which are the relevant sources for a compliance review. This is a technical note about licensing scope, not legal advice.

The repository is not archived. Its last push was on 2026-10-14. The listed releases are v1.0.0 from 2026-10-03, v0.4.13 from 2026-09-29, and v0.4.12 from 2026-09-18. An upgrade from an earlier 0.4.x version should include a review of `CHANGES.md`. The README does not describe a long-term support branch or a formal compatibility window for older releases.

The native build path is a real limitation in environments without a compiler or an available wheel. The Dockerfile addresses this by using `python:3.12-slim`, installing `gcc`, `libc-dev`, and `make`, then running `python setup.py install`. That file demonstrates a build container, not a documented minimal production runtime image.

selectolax is also the wrong tool when the input depends on JavaScript execution. It parses HTML supplied to it, but the README does not document a browser engine. A team needing rendered DOM state must use browser automation for acquisition and may then parse the resulting HTML separately.

The visible upgrade cost is concentrated in native-code and interface boundaries. Contributors may need a compiler, Cython, and Lexbor source, while users need to consider Python version support and platform wheel availability. Application-level migration costs cannot be quantified from the README because it does not describe an automatic migration from an earlier parser or a compatibility shim for other extraction libraries.

## Conclusion

Developers extracting repeated fields from HTML should consider selectolax when its native installation path fits their environment and Python version. Projects requiring a pure Python parser, Python support outside 3.9 through 3.15, or browser-rendered DOM access should verify those boundaries first. Before adopting it in a crawler, confirm that `LexborHTMLParser` returns the expected tree and selector results for the target HTML.

## FAQ

### How do I install selectolax in Python?

Install the package with `pip install selectolax`. If compilation fails, the README gives `pip install selectolax[cython]` as the documented alternative, and `pyproject.toml` requires Python 3.9 through 3.15.

### How do I extract an HTML element with selectolax?

Pass an HTML string to `LexborHTMLParser`, then call `css_first` or `css` with a selector. A selected node exposes `text()`, `attributes`, `tag`, `parent`, and `html`.

### Can selectolax parse JavaScript-rendered web pages?

The README documents parsing supplied HTML with Lexbor and selecting nodes with CSS selectors. It does not document JavaScript execution or browser rendering, so obtaining the rendered HTML remains another application's responsibility.

## Sources

- [License: MIT](https://github.com/rushter/selectolax/blob/master/LICENSE)
- [Project website](https://selectolax.readthedocs.io/en/latest/index.html)
- [README](https://github.com/rushter/selectolax/blob/master/README.md)
- [Releases](https://github.com/rushter/selectolax/releases)
- [rushter/selectolax on GitHub](https://github.com/rushter/selectolax)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rushter-selectolax
