Mỗi bài viết về “trình phân tích HTML Python nhanh nhất” cuối cùng cũng sẽ nhắc đến selectolax, và gần như bài nào cũng kết luận bằng câu “nhanh hơn BeautifulSoup rất nhiều”. Điều đó đúng. Nhưng phần mà ít ai đi đến cùng là chuyện gì xảy ra khi đặt selectolax lên bàn cân với lxml — vì ở đó, chữ “nhanh nhất” cần thêm dấu sao.
Vì vậy tôi đã benchmark đàng hoàng: selectolax (cả hai backend) so với lxml, BeautifulSoup trên html.parser và trên lxml, cùng parsel, trải trên năm kích thước trang từ 1 KB đến 10 MB, mỗi phép đo là trung vị của ba lần chạy tách biệt trong từng tiến trình. selectolax bỏ xa BeautifulSoup và ngang hàng với lxml ở tổng thể — rồi lại thua lxml ở riêng bước phân tích cú pháp thuần túy. Toàn bộ số liệu bên dưới chỉ là tạm thời và được lấy trên một máy duy nhất (macOS arm64, Python 3.14.2); các script đã được commit sẵn, nên hãy tự chạy trên máy của bạn trước khi trích dẫn tôi.
selectolax thực ra là gì, và không phải là gì?
selectolax là một binding Python cho hai engine C — Modest và Lexbor — dùng để phân tích HTML5 và truy vấn bằng CSS selector. Nó không phải crawler, không phải trình duyệt, cũng không phải “scraper” theo kiểu bấm nút là xong. Đây là công cụ bạn đưa cho một khối HTML sau khi đã lấy về xong. Mô tả ngắn gọn của chính maintainer là: “A fast HTML5 parser with CSS selectors, written in Cython, using Modest and Lexbor engines.”
Có hai backend, và khác biệt giữa chúng quan trọng hơn phần tài liệu thể hiện:
LexborHTMLParser(engine Lexbor) — bản README khuyên dùng từ năm 2024.HTMLParser(engine Modest) — bản gốc, mà chính README đó cũng nói thư viện C nền tảng của nó “không còn được duy trì nữa”.
Trước khi bàn đến tốc độ, có vài điểm đáng biết. Tại snapshot repo chụp ngày 2026-07-10, selectolax có 1.653 sao, bản phát hành mới nhất là v0.4.10 (tháng 5/2026), và PyPI ghim hỗ trợ Python >=3.9,<3.15. Cài đặt là phần ít kịch tính nhất của bài review này: pip install selectolax kéo về một wheel cp314 dựng sẵn nặng 2,3 MB và chạy ngay trên Python 3.14 — không tải trình duyệt, không cần bước doctor, không biên dịch gì cả. Đó là lợi thế âm thầm của một parser thuần túy so với công cụ dựa trên trình duyệt: chỉ cần import là chạy.
Có một điểm cần nói sớm về giấy phép: binding Python là MIT, nhưng wheel đi kèm các engine đã được biên dịch sẵn, và chúng có giấy phép riêng — Modest là LGPL-2.1, Lexbor là Apache-2.0. Vì vậy nói “selectolax là MIT” thì đúng với phần Python code nhưng chưa đủ với binary mà bạn thực sự phân phối. Nếu đội pháp chế của bạn quan tâm đến các thành phần tái phân phối, đây là chi tiết cần đánh dấu.
Câu hỏi tốc độ, trả lời bằng số liệu thật
Bài toán tôi đo là: phân tích chuỗi HTML, lấy toàn bộ text của <h3 class="title">, và lấy mọi href của <a>. Độ trễ trung vị tính bằng mili giây, lấy theo trung vị của ba lần chạy tiến trình riêng biệt; độ dao động giữa các lần chạy giữ dưới khoảng 5% với các parser dựa trên C ở đa số kích cỡ. Trước khi đo bất kỳ ô nào, đầu ra của mỗi parser đều được rút gọn thành content hash để loại ngay parser nào âm thầm làm ít việc hơn — trên các trang này, cả sáu đều khớp ở mọi kích cỡ, nên đây là so sánh công bằng đúng nghĩa. Dữ liệu đầy đủ nằm trong file bench_parse.json đã commit.

| Trang | selectolax (Lexbor) | selectolax (Modest) | lxml | parsel | BS (lxml) | BS (html.parser) |
|---|---|---|---|---|---|---|
| 1 KB | 0.027 | 0.029 | 0.036 | 0.044 | 0.281 | 0.323 |
| 10 KB | 0.160 | 0.171 | 0.166 | 0.228 | 1.705 | 2.049 |
| 100 KB | 1.464 | 1.565 | 1.423 | 2.020 | 16.5 | 20.6 |
| 1 MB | 14.901 | 16.9 | 14.177 | 20.9 | 181.9 | 232.6 |
| 10 MB | 159.9 | 247.9 | 172.9 | 231.9 | 2261.6 | 2788.7 |
So với BeautifulSoup: khoảng 12-17 lần, và con số truyền miệng đang nói thấp quá
Đổi các số này thành tỷ lệ, selectolax-Lexbor nhanh hơn khoảng 12 lần so với BeautifulSoup(html.parser) ở trang 1 KB, tăng lên khoảng 17 lần ở 10 MB, và nhanh hơn khoảng 10-14 lần so với BeautifulSoup(lxml) trên cùng dải kích thước. Con số thường được nhắc trên mạng — “selectolax nhanh hơn BeautifulSoup khoảng 4-5 lần” — là quá thấp nếu so với html.parser, và chỉ gần đúng khi so với BeautifulSoup dùng lxml ở phía dưới. Hệ số thực sự phụ thuộc vào bạn đang nói đến biến thể BeautifulSoup nào và mỗi trang bạn trích xuất nhiều đến đâu.
Điều này cũng khớp với benchmark trong README của chính dự án, nơi cho thấy selectolax nhanh hơn 25,5 lần so với BeautifulSoup(html.parser). Không có con số nào là sai. Bài đo trong README (lấy title, links, scripts và meta từ các trang chủ nhỏ) làm khối lượng trích xuất ít hơn ở các trang nhỏ, nên phần overhead mỗi lần parse của BeautifulSoup bị lộ rõ hơn. Nếu giới hạn trong phạm vi hợp lý, có thể nói: selectolax nhanh hơn BeautifulSoup khoảng 10-15 lần trong các tác vụ parse-and-extract thực tế, còn mạnh hơn trên trang nhỏ và khi phần trích xuất nhẹ.
Nếu bottleneck hiện tại của bạn là một đống code BeautifulSoup đang “ngốn” page, đây là kiểu migration hoàn toàn đáng tiền. Trường hợp đó thì không có gì gây tranh cãi. Trường hợp tiếp theo mới thú vị.
So với lxml: hòa — và lxml thắng ở phần mà nhiều người quên tách riêng
Nhìn lại hai hàng 100 KB và 1 MB. Lexbor và lxml chỉ cách nhau trong phạm vi khoảng 5%, dải dao động giữa các lần chạy chồng lấn nhau, và theo phương pháp của tôi thì đó là hòa — không có người thắng, cũng không thể gọi là “nhanh hơn”. Chỗ duy nhất selectolax thật sự nhỉnh lên là trang 10 MB (159,9 ms so với 172,9 ms, chênh 8,1% với khoảng đo không chồng nhau). Vì vậy ở tác vụ tổng thể, selectolax ngang lxml và chỉ vượt khi tài liệu rất lớn.

Sau đó tôi tách riêng phần dựng cây khỏi phần truy vấn CSS, và kết quả lật ngược theo cách mà nhiều bài viết bỏ sót. Với pure parsing, không query gì cả, lxml nhanh hơn selectolax-Lexbor khoảng 33-34% trên máy này — 77,9 ms so với 116,6 ms ở trang 10 MB. Ở tác vụ đầy đủ, hai bên cuối cùng cũng hội tụ, và giả thuyết làm việc của tôi (chưa phải điều tôi chứng minh bằng một thí nghiệm truy vết nguyên nhân) là: trên những trang này, truy vấn CSS chỉ chiếm một phần nhỏ tổng thời gian, nên lợi thế ở bước parse của lxml bị “pha loãng” cho tới khi tổng số chạm nhau.
Đây là nhận định dễ bị phản biện nhất trong toàn bài review, và tôi muốn nói rõ lý do. Nó đi ngược lại nhận thức phổ biến, và benchmark công khai duy nhất tôi tìm thấy có tách riêng parse-only — aows.jpt.sh — lại báo ngược lại, tức selectolax nhanh hơn khoảng 4 lần. Vì thế tôi đã đặt rào chắn cho kết quả này: đây là phép đo trên một nền tảng duy nhất (macOS arm64, Python 3.14, wheel cp314 dựng sẵn — chưa kiểm tra Linux x86_64 hay build từ source), được đối chiếu ở bốn kích cỡ trang và giữ nguyên ở tất cả, rồi còn kiểm tra lại bằng hai API lxml khác nhau để loại trừ lỗi từ API. Cả hai API của lxml đều nhanh hơn selectolax-Lexbor ở mọi kích cỡ. Tôi không trình bày “lxml parse nhanh hơn” như một chân lý đã ngã ngũ — tôi trình bày nó như kết quả mà bộ test của tôi tạo ra, kèm script, và có khác với phần lớn số liệu công khai. Hãy chạy trên máy của bạn.
Thêm một lát cắt nữa: khi query 100.000 <a> trên một trang phẳng, lxml và selectolax-Modest hòa nhau (33,30 ms so với 34,19 ms, khoảng đo chồng lấn), còn selectolax-Lexbor chậm hơn cả hai khoảng 15%. Điểm chung của cả ba engine C là đều nhanh hơn parsel hoặc BeautifulSoup từ 5-7 lần khi selection hàng loạt, vốn là do mô hình “mỗi node là một object Python” mới thật sự kéo chậm. Vì vậy câu “selectolax là nhanh nhất cho bulk CSS selection” cũng không đúng — Modest chỉ hòa lxml, còn Lexbor thua lxml.
Kết luận mà tôi thực sự dám đứng tên: lợi thế của selectolax so với lxml không phải là tốc độ tổng quát trên toàn tác vụ. Nó chỉ thắng ở trang rất lớn. Điểm đáng chọn của nó nằm ở chỗ khác — API dễ chịu, hành vi tốt hơn với input rác, và CSS hiện đại — và đó cũng là phần còn lại của bài review này.
Bộ nhớ và cold start: hãy xếp theo RSS, đừng xếp theo profiler
Bộ nhớ là nơi tôi phải sửa lại số liệu cũ của chính mình, và chính sự sửa này mới là điều quan trọng. Khi đo bằng độ chênh RSS trên trang 10 MB với tracemalloc tắt, BeautifulSoup dùng bộ nhớ nhiều hơn selectolax hoặc lxml khoảng 1,5-1,8 lần — dao động từ 1,51 lần (BS-lxml ở 218,4 MB so với Lexbor ở 144,6 MB) đến 1,75 lần ở đầu trên. selectolax và lxml nằm chung một nhóm gọn nhẹ; lxml là nhẹ nhất theo RSS.

Một lần đo trước đây của tôi báo “~3x”, và con số đó sai vì một lý do rất đáng học: lúc đó tôi đo khi tracemalloc đang chạy, còn phần theo dõi của tracemalloc cho mỗi lần cấp phát đã làm RSS hiển thị của parser phát sinh nhiều allocation trông như tăng gần gấp đôi. Vậy nên lưu ý cho bất kỳ ai benchmark bộ nhớ parser: hãy xếp theo RSS khi tắt profiler. Xếp parser bằng đỉnh tracemalloc sẽ đặc biệt làm sai thứ tự của các parser dựa trên C — nó khiến selectolax-Lexbor trông nặng hơn Modest, trong khi theo RSS thực tế chúng khá gần nhau. BeautifulSoup thực sự nặng nhất ở đây; chỉ là nó không nặng đến mức gấp 3 lần như một thiết bị đo bị “dính bẩn” từng cho thấy.
Cold start là chi tiết nhỏ nhưng có thật: selectolax import mất khoảng 14 ms, gần như ngang lxml và nhanh hơn bs4 hoặc parsel khoảng 2,3 lần. Nếu bạn phát hành CLI tool hoặc serverless function mà thời gian import tính vào mỗi lần chạy, chênh lệch đó đáng để chú ý.
Độ phủ CSS selector: mạnh, nhưng vẫn có vài lỗ hổng thật
Phần coverage CSS được kiểm tra trên ma trận 41 trường hợp, mỗi selector đối chiếu với bộ fixture có đáp án đúng đã biết, cộng thêm một lượt cố tình tạo lỗi để phá engine Lexbor. Mỗi trường hợp chạy trong một subprocess riêng, và điều đó hóa ra là cần thiết — có một selector đủ làm sập cả interpreter. Kết quả:

| Engine | PASS | WRONG | UNSUPPORTED | PROCESS_ABORT |
|---|---|---|---|---|
| soupsieve | 41 | 0 | 0 | 0 |
| selectolax Lexbor | 39 | 0 | 2 | 0 |
| lxml (cssselect) | 37 | 1 | 3 | 0 |
| parsel (cssselect) | 37 | 1 | 3 | 0 |
| selectolax Modest | 35 | 3 | 2 | 1 |
Khi đưa các selector “cố tình gây khó” vào, Lexbor không phải người thắng tuyệt đối — soupsieve mới là người thắng, với 41/41 sạch sẽ so với 39/41 của Lexbor. Hai ca Lexbor bỏ sót là :lang(en) và :dir(rtl), nó từ chối bằng lỗi parse. Ngoài ra nó hoàn hảo ở mọi thứ khác, bao gồm :has(), :is(), :where(), và các attribute không phân biệt hoa thường.
Điểm Lexbor tỏa sáng là khi so với stack cssselect. Selector chủ lực trong README — div > :nth-child(2n+1):not(:has(a)) — trả đúng tập kết quả trên cả hai engine của selectolax và trên soupsieve, nhưng trả sai tập trên lxml và parsel mà không báo lỗi. Một scraper sao chép selector này sang Scrapy hoặc parsel sẽ âm thầm ra kết quả sai. Nói cho chính xác: cssselect đã parse được :has() từ phiên bản 1.2.0 (2022), và tôi đã test bản 1.4.0, nên đây là kiểu “được hỗ trợ nhưng đánh giá sai compound”, chứ không phải “không hỗ trợ”. Việc trả sai âm thầm trên compound này không nằm trong tracker của cssselect, vốn chỉ ghi lại giới hạn của :has() dưới dạng lỗi được ném ra. Lexbor cũng xử lý được cờ attribute không phân biệt hoa thường [data-role="LEAD" i] mà cssselect từ chối hẳn.
Tuy vậy, có hai khoảng trống sẽ quyết định việc migration. selectolax không hỗ trợ XPath — cả hai backend đều không có xpath() — và cũng không có pseudo-element ::text / ::attr(), vì đó là extension của parsel/Scrapy chứ không phải CSS chuẩn. Nếu scraper hiện tại của bạn phụ thuộc XPath, đây là rào cản lớn nhất; bạn sẽ phải viết lại selector, không phải chỉ đổi thư viện. Ngược lại, Lexbor có pseudo-class :lexbor-contains("text" i) để tìm text không phân biệt hoa thường, điều mà lxml, parsel và CSS chuẩn không có, và nó hoạt động đúng như tài liệu nói.
Độ bền với HTML xấu, nơi selectolax thực sự kiếm điểm
Scraping thật sự nghĩa là ném đủ loại rác vào parser và hy vọng nó không gục. Tôi đã chạy 18 input mang tính phá hoại, và đây là nhóm mà case của selectolax trước lxml mạnh nhất.
Đưa lxml.html.fromstring một chuỗi rỗng hoặc chỉ toàn khoảng trắng, nó sẽ ném ParserError("Document is empty"). Cả hai engine của selectolax đều trả về một cây rỗng hợp lệ thay vì lỗi. Với scraper đang loop qua danh sách URL mà có phản hồi rỗng, đây là một thứ đỡ phải bọc try/except cho mọi đường đi. selectolax cũng xử lý được cây 100.000 phần tử mà không tràn stack.
Lồng sâu là nơi khác biệt sắc nét nhất. Ở mức 1.000 và 5.000 lớp <div> lồng nhau, lxml âm thầm bỏ phần nội dung sâu nhất trong khi selectolax vẫn giữ nguyên. libxml2 giới hạn độ sâu parse khoảng 256 lớp và cắt cụt cây mà không báo lỗi, nên text sâu nhất đơn giản là không còn truy cập được. Cả hai engine của selectolax đều trả về toàn bộ cây. Đây là mặt gương của cái bẫy <template> sẽ nói tiếp sau: ở đó Lexbor bỏ nội dung mà các parser khác giữ; còn ở đây lxml bỏ nội dung mà selectolax giữ.
Không phải ô nào cũng thắng. Backend Modest làm sập toàn bộ Python interpreter bằng SIGABRT khi gặp :dir() — không phải exception có thể bắt, mà là kill tiến trình thẳng tay. Đây là cảnh báo thực sự về độ bền nếu bạn vẫn dùng backend cũ, và đúng kiểu lỗi chỉ lộ ra khi nó kéo đổ job production lúc 3 giờ sáng.
Hai cái bẫy mất dữ liệu âm thầm cần biết trước khi đưa vào production
Hai điểm này không phải phát hiện mới — cả hai đều đã được upstream ghi nhận — nhưng cả hai đều làm mất dữ liệu thật, âm thầm, và README không nói to về chúng.
Lexbor bỏ qua <a> bên trong <template>
Trên trang MDN live mà tôi test, selectolax-Lexbor tìm thấy 497 link trong khi lxml, cả hai backend của BeautifulSoup, và thậm chí backend Modest của chính selectolax đều tìm thấy 508. Mười một link bị thiếu là một bộ chọn ngôn ngữ và một link thảo luận nằm trong các phần tử <template> (trang này dùng Lit web components).

Nguyên nhân là hợp lệ: theo chuẩn HTML5, nội dung trong <template> được parse vào một fragment riêng, không nằm trong DOM bình thường, và Lexbor bám sát điều đó — tree.css("a") không đi xuống nội dung template. lxml, cả hai backend của BeautifulSoup và Modest thì làm phẳng nội dung template vào cây chính, nên chúng nhìn thấy các link này. Đây là một issue đã được ghi nhận (selectolax#146, với nguyên nhân gốc ở engine là lexbor#170), và cả hai cách đọc đều có lý do riêng — Lexbor có thể nói là “đúng chuẩn” hơn. Nhưng người dùng backend được khuyến nghị lại có thể âm thầm mất dữ liệu mà không thấy lỗi. Nói ngược lại cũng đáng nhắc: các parser còn lại phơi bày nội dung template vô hiệu mà trình duyệt thực sự không render, nên chúng có thể đưa cho bạn dữ liệu “ảo” mà người dùng không nhìn thấy. Cách thoát đáng tin nhất là dùng backend Modest, hoặc một thư viện khác, cho riêng trang đó.
Byte không phải UTF-8 làm .text() bị hỏng âm thầm
Nếu đưa cho selectolax các byte không hợp lệ theo UTF-8, việc parse vẫn thành công — lỗi hỏng dữ liệu chỉ lộ ra sau đó, và còn tệ hơn crash sạch. Với "<p>café éè</p>".encode("latin-1"), .text() của Lexbor trả về ký tự thay thế, .text() của Modest âm thầm bỏ byte lỗi, và cả hai engine chỉ ném UnicodeDecodeError khi bạn chạm vào .html. Binding này giải mã theo UTF-8 strict ở bước đọc lại, không phải ở bước parse. Điểm này liên quan đến một issue đã biết của selectolax về độ nghiêm ngặt của encode/decode.
Cách sửa chỉ một dòng và nên thành phản xạ: hãy tự decode byte trước — LexborHTMLParser(resp.content.decode("latin-1")) — và cả hai engine sẽ trả về 'café éè' đúng. Trong thực tế, hãy luôn đưa cho selectolax một str, không phải raw bytes không phải UTF-8. README không nói rõ điều này.
Các khía cạnh production khác (chỉ là một quan sát, nên xem như tín hiệu)
Những kết quả tiếp theo tôi đo một lần, không lặp ba lần, nên xin xem chúng như tín hiệu hơn là con số đã đóng đinh.
Khả năng scale theo thread là phần thú vị. Khi parse một trang 1 MB 48 lần trên bốn thread, selectolax cho tốc độ tăng thực tế khoảng 3,5-3,9 lần — dấu hiệu thực nghiệm của một thư viện giải phóng GIL trong lúc parse C — trong khi BeautifulSoup(lxml) lại chậm hơn đáng kể khi dùng thread, dấu hiệu của việc công việc bị nối hàng trên GIL. lxml nằm ở giữa và chưa đủ kết luận. Trong thời kỳ free-threading mà Python đang tiến tới, việc selectolax parse song song giữa các thread trong khi BeautifulSoup không làm được là một lợi thế thật, dù vẫn còn tạm thời. Đây là một số lượng thread trên một kích cỡ trang, và cơ chế là giả thuyết chứ chưa phải điều tôi xác nhận bằng cách instrument mã C.
Về memory leak: sau 2.000 vòng lặp parse-extract-drop ở trang 1 MB, không parser nào thể hiện kiểu RSS tăng tuyến tính như leak — mỗi bên đều ổn định trong một dải working set giới hạn. Tôi tin kết quả này đặc biệt vì đã chạy một đối tượng kiểm chuẩn có leak đã biết qua cùng công cụ, và nó tăng lên +198 MB đúng như thiết kế, chứng minh công cụ có thể nhìn thấy leak và đơn giản là không tìm thấy leak nào trong các parser. Và một node handle vẫn còn giữ sau khi tree sở hữu nó đã ra khỏi scope vẫn dùng được, không segfault. Tất cả đều chỉ là một quan sát, chưa phải soak test nhiều giờ.
selectolax phù hợp ở đâu — và khi nào nên chuyển giao cho lớp khác
Tất cả những gì ở trên chỉ xoay quanh một việc: biến HTML bạn đã có thành dữ liệu có cấu trúc, thật nhanh. selectolax làm rất tốt việc đó. Nhưng nó cố ý không làm các việc như lấy trang về, render JavaScript, xoay proxy, giải CAPTCHA, hay tự đoán xem bạn muốn phần tử nào. Tất cả những thứ đó vẫn là code của bạn. selectolax là lớp parse, và nó không giả vờ là thứ gì hơn.
Đó là ranh giới mà dịch vụ trích xuất được quản lý nằm ở phía trên parser chứ không thay thế parser. Nếu bạn không muốn tự xây và duy trì cả stack fetch-render-anti-bot-extract, Thunderbit cung cấp điều đó qua API, MCP server và CLI — POST /distill biến một trang thành Markdown sạch, còn POST /extract trả về JSON có cấu trúc khớp schema, với việc render JS và chống bot đã được xử lý sẵn cho bạn. Đây là một lớp khác của vấn đề: hãy chọn selectolax khi bạn đã có HTML và muốn tốc độ parse thô trong tầm kiểm soát của mình, và chọn thứ như API, MCP server hoặc CLI của Thunderbit khi bạn muốn phần lấy dữ liệu và trích xuất được lo xong, còn mình chỉ cần dữ liệu có cấu trúc trả về. Không phải thay thế — mà là một độ cao khác trên cùng một stack.
Dùng thử Thunderbit cho trích xuất dữ liệu web
Ưu điểm, nhược điểm, và ai nên dùng nó
Khi nào selectolax thắng:
- Nhanh hơn BeautifulSoup khoảng 12-17 lần trên tác vụ parse-and-extract thực tế, ổn định trên ba bậc độ lớn của kích thước trang.
- Nhẹ bộ nhớ (cùng nhóm với lxml, nhẹ hơn BeautifulSoup khoảng 1,5-1,8 lần) và import khoảng 14 ms.
- Ứng xử tốt với các input làm lxml gãy — rỗng, chỉ khoảng trắng, và lồng sâu bất thường.
- CSS hiện đại gồm
:has(),:is(),:where(), attribute không phân biệt hoa thường, và:lexbor-contains()chỉ có ở Lexbor. - DOM đọc/ghi an toàn với
None: phần tử thiếu trả vềNonehoặc[]thay vì ném lỗi, và bạn thật sự có thể sửa rồi serialize lại cây. - Duy trì tích cực (v0.4.10, giữa 2026) và cài đặt rất đơn giản.
Khi nào không:
- Không nhanh hơn lxml một cách rộng khắp — hòa ở tác vụ đầy đủ, và thua ở bước pure-parse trong benchmark của tôi.
- Không có XPath và không có
::text/::attr()— rào cản migration lớn nếu scraper của bạn dựa trên XPath. - Có hai bẫy mất dữ liệu âm thầm: nội dung
<template>trên Lexbor, và byte không phải UTF-8 qua.text(). - Backend Modest là bản cũ và sẽ SIGABRT khi gặp
:dir(). - Mọi con số ở đây đều chỉ từ một nền tảng (macOS arm64, Python 3.14) và vẫn là tạm thời.
Có nên dùng selectolax không? Có, nếu bạn muốn tốc độ parse ngang tầm lxml với API thân thiện hơn, an toàn hơn khi gặp None, và xử lý input rỗng hay lỗi cú pháp tốt hơn rõ rệt — và bạn chấp nhận sống trong thế giới chỉ CSS. Nếu codebase của bạn xây trên XPath, chi phí viết lại là có thật và nên cân nhắc nghiêm túc. Còn nếu bạn đang săn “trình phân tích nhanh nhất duy nhất”, câu trả lời chính xác từ benchmark này là selectolax và lxml đủ sát nhau để yếu tố phân định không còn là tốc độ thô, mà là độ tiện dụng và độ bền. Thực ra đó cũng là lý do chọn công cụ tốt hơn.
Dùng thử Thunderbit cho trích xuất dữ liệu web Get Started Free
Câu hỏi thường gặp
selectolax có nhanh hơn BeautifulSoup không?
Có, rất rõ ràng — nhanh hơn khoảng 12-17 lần so với BeautifulSoup(html.parser) và khoảng 10-14 lần so với BeautifulSoup(lxml) trong một bài toán parse-and-extract thực tế, duy trì ổn định từ trang 1 KB đến 10 MB (macOS arm64, Python 3.14). Con số “4-5 lần” thường được nhắc đến là đánh giá thấp khoảng cách với html.parser.
selectolax có nhanh hơn lxml không? Không phải trên diện rộng. Ở tác vụ parse-and-extract đầy đủ, hai bên hòa nhau tại 100 KB và 1 MB, và selectolax chỉ thắng ở trang 10 MB. Còn ở parse thuần không query, lxml thực tế nhanh hơn selectolax khoảng 33-34% trên máy của tôi — một kết quả đi ngược số đông mà tôi đã ghi rõ là chỉ trên một nền tảng, nên bạn hãy kiểm tra trên phần cứng của mình.
Nên dùng backend Lexbor hay Modest?
Hầu như luôn là Lexbor — đây là engine được duy trì, đầy đủ tính năng, và README khuyến nghị, với coverage CSS tốt hơn. Ngoại lệ duy nhất là trang giấu nội dung trong <template>, khi đó hành vi đúng chuẩn của Lexbor lại làm mất nội dung còn Modest thì giữ được. Tuy nhiên Modest có các cạnh sắc, gồm cả việc làm sập interpreter ngay khi gặp :dir().
selectolax có hỗ trợ XPath không?
Không. Cả hai backend đều không có xpath(), nên selectolax chỉ CSS. Nếu scraper của bạn phụ thuộc XPath, việc migration đồng nghĩa với viết lại selector, và đây là chi phí lớn nhất khi chuyển từ stack dựa trên lxml hoặc parsel sang selectolax.
Vì sao output selectolax của tôi bị lỗi ký tự hoặc thiếu phần tử?
Thường có hai nguyên nhân. Nếu text trả về ký tự thay thế hoặc mất dấu, rất có thể bạn đã đưa raw bytes không phải UTF-8 — hãy decode về str trước (resp.content.decode("latin-1")) rồi mới parse. Nếu link hoặc phần tử bị thiếu trên một site hiện đại, chúng có thể nằm trong thẻ <template> mà backend Lexbor không đi xuống; hãy chuyển sang Modest hoặc parser khác cho trang đó.


