Κάθε άρθρο για τον «ταχύτερο Python HTML parser» κάποια στιγμή θα αναφέρει το selectolax, και σχεδόν πάντα η ιστορία σταματά στο «πολύ πιο γρήγορο από το BeautifulSoup». Αυτό ισχύει. Εκείνο που συνήθως μένει απ’ έξω είναι τι γίνεται όταν βάλεις το selectolax δίπλα στο lxml — γιατί εκεί το «ταχύτερο» θέλει αστερίσκο.
Γι’ αυτό το μέτρησα σωστά: selectolax (και στα δύο backends του) απέναντι σε lxml, BeautifulSoup σε html.parser και σε lxml, και parsel, σε πέντε μεγέθη σελίδας από 1 KB έως 10 MB, με κάθε μέτρηση να είναι η διάμεσος από τρεις ξεχωριστές εκτελέσεις διεργασιών. Το selectolax νίκησε το BeautifulSoup με μεγάλη διαφορά και ισοφάρισε το καθαρό lxml — αλλά έχασε στο σκέτο parsing από το lxml. Όλοι οι αριθμοί παρακάτω είναι προσωρινοί και προέρχονται από ένα μηχάνημα (macOS arm64, Python 3.14.2). Τα scripts έχουν γίνει commit, οπότε τρέξ’ τα στο δικό σου περιβάλλον πριν με παραθέσεις.
Τι είναι πραγματικά το selectolax (και τι δεν είναι)
Το selectolax είναι ένα Python binding σε δύο C engines — Modest και Lexbor — που κάνουν parse HTML5 και το ερωτάνε με CSS selectors. Δεν είναι crawler, δεν είναι browser, δεν είναι «scraper» με την έννοια του κουμπιού που πατάς και δουλεύει μόνο του. Είναι το εργαλείο που του δίνεις ένα blob από HTML αφού πρώτα το έχεις κατεβάσει. Η δική του περιγραφή από τον maintainer είναι: «A fast HTML5 parser with CSS selectors, written in Cython, using Modest and Lexbor engines.»
Υπάρχουν δύο backends, και η διαφορά τους έχει μεγαλύτερη σημασία απ’ όση δείχνει το documentation:
LexborHTMLParser(Lexbor engine) — αυτό που προτείνει το README από το 2024 και μετά.HTMLParser(Modest engine) — το αρχικό backend, του οποίου η υποκείμενη C βιβλιοθήκη «δεν συντηρείται πλέον», σύμφωνα με το ίδιο README.
Μερικά πράγματα που αξίζει να ξέρεις πριν μπούμε στην ταχύτητα. Στο repo snapshot της 2026-07-10, το selectolax είχε 1.653 stars και η πιο πρόσφατη έκδοση ήταν η v0.4.10 (Μάιος 2026), ενώ το PyPI το καρφιτσώνει για Python >=3.9,<3.15. Η εγκατάσταση είναι το λιγότερο δραματικό κομμάτι όλης αυτής της αξιολόγησης: το pip install selectolax κατέβασε ένα precompiled wheel 2.3 MB για cp314 και λειτούργησε αμέσως σε Python 3.14 — χωρίς browser download, χωρίς βήμα doctor, χωρίς compilation. Αυτό είναι το ήσυχο πλεονέκτημα ενός καθαρού parser σε σχέση με ένα εργαλείο που βασίζεται σε browser. Απλώς γίνεται import και τρέχει.
Να σημειώσουμε από τώρα κιόλας ένα θέμα αδειοδότησης: το Python binding είναι MIT, αλλά το wheel ενσωματώνει compiled engines, και αυτά έχουν τις δικές τους άδειες — το Modest είναι LGPL-2.1, το Lexbor είναι Apache-2.0. Άρα το «selectolax είναι MIT» είναι αληθές για τον Python κώδικα, αλλά ελλιπές για το binary που τελικά διανέμεις. Αν η νομική σου ομάδα νοιάζεται για redistributed components, αυτό είναι το σημείο που πρέπει να επισημανθεί.
Το ερώτημα της ταχύτητας, με πραγματικούς αριθμούς
Η δουλειά που χρονομέτρησα ήταν η εξής: parse του HTML string, εξαγωγή του κειμένου από κάθε <h3 class="title">, εξαγωγή του href από κάθε <a>. Latency σε milliseconds, ως διάμεσος από τρεις ξεχωριστές εκτελέσεις διεργασιών· η διασπορά μεταξύ runs έμεινε κάτω από ~5% για τους parsers που βασίζονται σε C στα περισσότερα μεγέθη. Πριν μετρηθεί οποιοδήποτε κελί, το output κάθε parser μειώθηκε σε content hash ώστε ένας parser που έκανε σιωπηρά λιγότερη δουλειά να εντοπιστεί και να αποκλειστεί — σε αυτές τις σελίδες και οι έξι συμφωνούσαν σε όλα τα μεγέθη, άρα πρόκειται για καθαρή σύγκριση μήλο με μήλο. Τα πλήρη δεδομένα βρίσκονται στο committed bench_parse.json.

| Σελίδα | 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 |
Απέναντι στο BeautifulSoup: περίπου 12-17x, και ο μύθος το υποτιμά
Αν μετατρέψεις αυτούς τους αριθμούς σε λόγους, το selectolax-Lexbor βγαίνει περίπου 12x πιο γρήγορο από το BeautifulSoup(html.parser) σε σελίδα 1 KB, φτάνοντας περίπου 17x στα 10 MB, και περίπου 10-14x πιο γρήγορο από το BeautifulSoup(lxml) στο ίδιο εύρος. Ο αριθμός που κυκλοφορεί online — «το selectolax είναι περίπου 4-5x πιο γρήγορο από το BeautifulSoup» — είναι πολύ χαμηλός σε σχέση με το html.parser και περίπου σωστός μόνο σε σύγκριση με το BeautifulSoup που βασίζεται στο lxml. Το πραγματικό πολλαπλάσιο εξαρτάται από το ποιο BeautifulSoup εννοείς και από το πόσα πράγματα εξάγεις ανά σελίδα.
Αυτό συμφωνεί και με το δικό τους benchmark στο README, που δείχνει πλεονέκτημα 25.5x απέναντι στο BeautifulSoup(html.parser). Κανένας από τους δύο αριθμούς δεν είναι λάθος. Η εργασία του README (τίτλος, links, scripts και meta από μικρές homepages) κάνει λιγότερη εξαγωγή σε μικρότερες σελίδες, άρα βαραίνει περισσότερο το per-parse overhead του BeautifulSoup. Σε πιο ρεαλιστικό εύρος, το συμπέρασμα είναι: το selectolax είναι περίπου 10-15x πιο γρήγορο από το BeautifulSoup σε δουλειά parsing + extraction, και ακόμη περισσότερο σε μικροσκοπικές σελίδες με ελαφρύ extraction.
Αν το σημερινό bottleneck σου είναι ένα σωρό κώδικα BeautifulSoup που «τρώνει» σελίδες, αυτή είναι η μετακίνηση που αποδίδει άμεσα. Αυτό δεν αμφισβητείται ιδιαίτερα. Το επόμενο όμως ναι.
Απέναντι στο lxml: ισοπαλία — και το lxml κερδίζει εκεί όπου οι περισσότεροι ξεχνούν να απομονώσουν
Κοίτα τα rows των 100 KB και 1 MB. Το Lexbor και το lxml βρίσκονται μέσα σε ~5% μεταξύ τους, τα per-run bands τους αλληλεπικαλύπτονται, και με τη δική μου μεθοδολογία αυτό είναι ισοπαλία — χωρίς νικητή, χωρίς «πιο γρήγορο». Το μοναδικό σημείο όπου το selectolax ξεφεύγει πραγματικά είναι η σελίδα των 10 MB (159.9 ms έναντι 172.9 ms, διαφορά 8.1% με μη αλληλοεπικαλυπτόμενα intervals). Άρα στην πλήρη εργασία το selectolax ισοφαρίζει το lxml και το περνά μόνο στα πολύ μεγάλα έγγραφα.

Έπειτα απομόνωσα την κατασκευή του tree από το CSS querying, και το αποτέλεσμα αντιστρέφεται με τρόπο που τα περισσότερα write-ups δεν πιάνουν. Για καθαρό parsing, χωρίς καθόλου query, το lxml ήταν σταθερά ~33-34% πιο γρήγορο από το selectolax-Lexbor σε αυτό το μηχάνημα — 77.9 ms έναντι 116.6 ms στη σελίδα των 10 MB. Στην πλήρη εργασία τα δύο συγκλίνουν έτσι κι αλλιώς, και η λειτουργική μου υπόθεση (όχι κάτι που απέδειξα με attribution experiment) είναι ότι σε αυτές τις σελίδες το CSS query είναι μικρό κομμάτι του συνολικού χρόνου, οπότε το προβάδισμα του lxml στο parse-step αμβλύνεται μέχρι να έρθουν κοντά τα totals.
Αυτό είναι και το πιο εύκολα αμφισβητήσιμο σημείο όλης της αξιολόγησης, και θέλω να είμαι ξεκάθαρος για το γιατί. Αντιστρέφει τη συνηθισμένη σοφία, και το ένα δημοσιευμένο benchmark που βρήκα και απομονώνει μόνο το parsing — aows.jpt.sh — αναφέρει το αντίθετο, με το selectolax περίπου 4x πιο γρήγορο. Γι’ αυτό έβαλα φραγμούς: το αποτέλεσμα είναι single-platform (macOS arm64, Python 3.14, prebuilt cp314 wheels — σε Linux x86_64 ή source build δεν έχει δοκιμαστεί), ελέγχθηκε σε τέσσερα μεγέθη σελίδας και κράτησε παντού, και επαληθεύτηκε ξανά με δύο διαφορετικά lxml APIs για να αποκλειστεί artifact του API. Και τα δύο lxml APIs ξεπέρασαν το selectolax-Lexbor σε όλα τα μεγέθη. Δεν παρουσιάζω το «το lxml κάνει parse πιο γρήγορα» ως αδιαμφισβήτητο γεγονός — το παρουσιάζω ως αυτό που έβγαλε το δικό μου bench, με το script διαθέσιμο, σε αντίθεση με τους περισσότερους δημοσιευμένους αριθμούς. Τρέξ’ το στο δικό σου.
Ένα ακόμη κομμάτι: στο query 100.000 <a> σε επίπεδη σελίδα, το lxml και το selectolax-Modest ισοφαρίζουν (33.30 ms έναντι 34.19 ms, με επικάλυψη bands), ενώ το selectolax-Lexbor μένει περίπου ~15% πίσω και από τα δύο. Αυτό που έχουν κοινό και οι τρεις C engines είναι ότι είναι 5-7x πιο γρήγοροι από το parsel ή το BeautifulSoup στο bulk selection, όπου πραγματικά σε τραβάει πίσω το μοντέλο Python-object-per-node. Άρα το «το selectolax είναι το ταχύτερο στο bulk CSS selection» δεν στέκει κι αυτό — το Modest απλώς ισοφαρίζει το lxml, και το Lexbor χάνει από αυτό.
Το συμπέρασμα που θα υπερασπιζόμουν εγώ είναι το εξής: το πλεονέκτημα του selectolax απέναντι στο lxml δεν είναι ευρύ, full-task speed. Κερδίζει μόνο στη μεγαλύτερη σελίδα. Η αξία του στηρίζεται σε άλλα πράγματα — εργονομία API, συμπεριφορά σε σκουπίδια/κακή είσοδο και μοντέρνα CSS — και εκεί βρίσκεται το υπόλοιπο της αξιολόγησης.
Μνήμη και cold start: σύγκρινε με RSS, όχι με τον profiler σου
Η μνήμη είναι το σημείο όπου πρέπει να διορθώσω τους δικούς μου προηγούμενους αριθμούς, και αυτή η διόρθωση είναι το σημαντικό. Μετρημένο ως RSS delta στη σελίδα των 10 MB με απενεργοποιημένο tracemalloc, το BeautifulSoup χρησιμοποιεί περίπου 1.5-1.8x περισσότερη μνήμη από το selectolax ή το lxml — το εύρος ξεκινά από 1.51x (BS-lxml στα 218.4 MB έναντι Lexbor στα 144.6 MB) και φτάνει έως 1.75x στο πάνω άκρο. Το selectolax και το lxml βρίσκονται στην ίδια «λεπτή» κατηγορία, με το lxml να είναι το πιο ελαφρύ με βάση το RSS.

Σε προηγούμενη εκτέλεση είχα γράψει «~3x», και αυτό ήταν λάθος για έναν διδακτικό λόγο: η μέτρηση έγινε με το tracemalloc ενεργό, και το bookkeeping του tracemalloc ανά allocation περίπου διπλασιάζει το φαινομενικό RSS του parser που κάνει τις περισσότερες allocations. Άρα ένα χρήσιμο σχόλιο για όποιον μετράει μνήμη parser: βάζε σειρά με βάση το RSS με τον profiler off. Η κατάταξη parsers βάσει tracemalloc peak μπερδεύει ειδικά τα C-backed εργαλεία — έκανε το selectolax-Lexbor να φαίνεται βαρύτερο από το Modest, ενώ με πραγματικό RSS είναι κοντά. Το BeautifulSoup είναι πράγματι το πιο βαρύ εδώ· απλώς δεν είναι βαρύ με το 3x περιθώριο που έδειξε ένα «μολυσμένο» όργανο.
Το cold start είναι μικρό αλλά πραγματικό: το selectolax κάνει import σε περίπου 14 ms, περίπου στο ίδιο επίπεδο με το lxml και ~2.3x πιο γρήγορα από το bs4 ή το parsel. Αν στέλνεις CLI tool ή serverless function όπου ο χρόνος import μετράει σε κάθε κλήση, αυτή η διαφορά αξίζει να αναφερθεί.
Κάλυψη CSS selector: δυνατή, με μερικά πραγματικά κενά
Η κάλυψη CSS δοκιμάστηκε με matrix 41 περιπτώσεων, όπου κάθε selector ελέγχθηκε απέναντι σε fixture με γνωστό σωστό αποτέλεσμα, μαζί με ένα επιθετικό pass που είχε στόχο να σπάσει τον Lexbor engine. Κάθε περίπτωση έτρεξε σε ξεχωριστό subprocess, κάτι που αποδείχθηκε απαραίτητο — σε μία από αυτές το interpreter καταρρέει ολόκληρο. Τα αποτελέσματα:

| 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 |
Μόλις μπουν στη μέση επιθετικά selectors, ο Lexbor δεν είναι ο αδιαφιλονίκητος νικητής — το soupsieve είναι, με καθαρό 41/41 έναντι του 39/41 του Lexbor. Τα δύο misses του Lexbor είναι τα :lang(en) και :dir(rtl), τα οποία απορρίπτει με parse error. Είναι τέλειο σε όλα τα υπόλοιπα, συμπεριλαμβανομένων των :has(), :is(), :where() και των case-insensitive attributes.
Εκεί που ο Lexbor ξεχωρίζει είναι απέναντι στο stack του cssselect. Ο βασικός selector του README — div > :nth-child(2n+1):not(:has(a)) — επιστρέφει το σωστό σύνολο και στους δύο selectolax engines και στο soupsieve, αλλά το λάθος σύνολο στο lxml και στο parsel, χωρίς να εμφανιστεί κανένα error. Ένα scraper που αντιγράφει αυτόν τον selector σε Scrapy ή parsel παίρνει σιωπηλά λάθος αποτελέσματα. Για να το πούμε με ακρίβεια: το cssselect υποστηρίζει το :has() από την έκδοση 1.2.0 (2022), και δοκίμασα την 1.4.0, άρα εδώ μιλάμε για «υποστηρίζεται αλλά αξιολογεί λάθος το compound», όχι για «δεν υποστηρίζεται». Η συμπεριφορά του silent-wrong-set σε αυτό το συγκεκριμένο compound δεν υπάρχει στο cssselect tracker, ο οποίος καταγράφει τα όρια του :has() ως raised errors. Ο Lexbor επίσης χειρίζεται το case-insensitive attribute flag [data-role="LEAD" i], το οποίο το cssselect απορρίπτει εξαρχής.
Υπάρχουν όμως δύο κενά που θα κρίνουν τις μεταναστεύσεις. Το selectolax δεν υποστηρίζει καθόλου XPath — κανένα backend δεν εκθέτει xpath() — και δεν έχει pseudo-elements ::text / ::attr(), αφού αυτά είναι επέκταση του parsel/Scrapy και όχι πραγματικό CSS. Αν τα υπάρχοντα scrapers σου βασίζονται σε XPath, αυτό είναι το μεγαλύτερο εμπόδιο που θα συναντήσεις· θα ξαναγράφεις selectors, όχι απλώς θα αλλάζεις βιβλιοθήκη. Από την άλλη, ο Lexbor περιλαμβάνει το pseudo-class :lexbor-contains("text" i) για case-insensitive text matching, κάτι που δεν προσφέρουν ούτε το lxml, ούτε το parsel, ούτε το standard CSS, και δουλεύει όπως περιγράφεται.
Αντοχή σε άσχημο HTML, εκεί που το selectolax κερδίζει τα λεφτά του
Το πραγματικό scraping σημαίνει ότι ταΐζεις έναν parser σκουπίδια και ελπίζεις να μην καταρρεύσει. Έτρεξα 18 επιθετικές εισόδους, και αυτή είναι η κατηγορία όπου το επιχείρημα του selectolax απέναντι στο lxml είναι το πιο ισχυρό.
Δώσε στο lxml.html.fromstring ένα κενό string ή μόνο whitespace και θα πετάξει ParserError("Document is empty"). Και οι δύο selectolax engines επιστρέφουν έγκυρο, κενό tree αντί γι’ αυτό. Για έναν scraper που περνάει λίστα από URLs όπου κάποιες απαντήσεις έρχονται άδειες, αυτό σημαίνει ένα try/except λιγότερο γύρω από τα πάντα. Το selectolax επίσης διαχειρίστηκε 100.000 στοιχεία χωρίς stack overflow.
Το βαθύτερο nesting έβγαλε και τη μεγαλύτερη διαφορά. Σε 1.000 και 5.000 επίπεδα nested <div>, το lxml κόβει σιωπηλά το βαθύτερο περιεχόμενο ενώ το selectolax το διατηρεί. Το libxml2 βάζει όριο βάθους parse περίπου στα 256 επίπεδα και περικόπτει το tree χωρίς error, οπότε το βαθύτερο κείμενο απλώς χάνεται. Και οι δύο selectolax engines επιστρέφουν ολόκληρο το tree. Είναι το αντίστροφο του <template> trap που θα αναφέρω αμέσως μετά: εκεί ο Lexbor αφήνει έξω περιεχόμενο που οι άλλοι κρατούν· εδώ το lxml αφήνει έξω περιεχόμενο που το selectolax κρατά.
Δεν ήταν όλα νίκες. Το backend Modest τερματίζει ολόκληρο το Python interpreter με SIGABRT όταν συναντήσει :dir() — όχι ένα raised exception που μπορείς να πιάσεις, αλλά βίαιο κλείσιμο της διεργασίας. Αυτό είναι πραγματική προειδοποίηση για όποιον είναι ακόμη στο legacy backend και ακριβώς το είδος προβλήματος που μένει αόρατο μέχρι να ρίξει ένα production job στις 3 το πρωί.
Δύο παγίδες σιωπηλής απώλειας δεδομένων που πρέπει να ξέρεις πριν το βάλεις σε παραγωγή
Καμία από τις δύο δεν είναι ανακάλυψη — και οι δύο είναι documented upstream — αλλά και οι δύο κοστίζουν πραγματικά δεδομένα, σιωπηλά, και καμία δεν φωνάζει στο README.
Ο Lexbor χάνει <a> μέσα σε <template>
Στη ζωντανή σελίδα MDN που δοκίμασα, το selectolax-Lexbor βρήκε 497 links ενώ το lxml, και τα δύο BeautifulSoup backends, και ακόμα και το δικό του Modest backend βρήκαν 508. Τα έντεκα που έλειπαν ήταν ένας language switcher και ένα link για discussions, κρυμμένα μέσα σε <template> στοιχεία (η σελίδα χρησιμοποιεί Lit web components).

Η αιτία είναι υπαρκτή: σύμφωνα με το HTML5 spec, το περιεχόμενο του <template> parse-άρεται σε ξεχωριστό inert fragment, όχι στο κανονικό DOM, και ο Lexbor το ακολουθεί αυστηρά — το tree.css("a") δεν κατεβαίνει μέσα στο template content. Το lxml, και τα δύο BeautifulSoup backends, και το Modest flatten-άρουν το template content μέσα στο κύριο tree, οπότε βρίσκουν αυτά τα links. Αυτό είναι documented open issue (selectolax#146, με το engine root cause στο lexbor#170), και οι δύο ερμηνείες είναι υπερασπίσιμες — ο Lexbor ίσως είναι πιο σωστός ως προς το spec. Όμως ο developer στο προτεινόμενο backend χάνει σιωπηλά αυτά τα δεδομένα, χωρίς κανένα error. Αξίζει να πούμε και το αντίστροφο: οι άλλοι parsers εμφανίζουν inert template content που ο browser δεν αποδίδει ποτέ, άρα μπορεί να σου δώσουν φανταστικά δεδομένα που ο χρήστης δεν βλέπει. Η αξιόπιστη διέξοδος γι’ αυτό το συγκεκριμένο page είναι το backend Modest ή κάποια άλλη βιβλιοθήκη.
Μη UTF-8 bytes διαφθείρουν σιωπηλά το .text()
Δώσε στο selectolax bytes που δεν είναι έγκυρο UTF-8 και το parsing πετυχαίνει — η αλλοίωση εμφανίζεται αργότερα, και είναι χειρότερη από ένα καθαρό crash. Στο "<p>café éè</p>".encode("latin-1"), το .text() του Lexbor επιστρέφει replacement characters, το .text() του Modest κόβει σιωπηλά τα bytes που προκαλούν πρόβλημα, και και οι δύο engines πετάνε UnicodeDecodeError μόνο όταν αγγίξεις το .html. Το binding κάνει decode ως strict UTF-8 στο read-back, όχι στο parse. Αυτό σχετίζεται με ένα γνωστό issue του selectolax για την αυστηρότητα encode/decode.
Η λύση είναι μία γραμμή και πρέπει να γίνει συνήθεια: κάνε πρώτα εσύ το decode στα bytes — LexborHTMLParser(resp.content.decode("latin-1")) — και τότε και οι δύο engines επιστρέφουν σωστά το 'café éè'. Στην πράξη, δίνε πάντα στο selectolax ένα str, ποτέ raw non-UTF-8 bytes. Αυτό δεν το λέει ξεκάθαρα το README.
Πτυχές παραγωγής (μία μέτρηση, άρα δες τες ως ενδείξεις)
Τα επόμενα αποτελέσματα τα μέτρησα μία φορά, όχι σε τρεις επαναλήψεις, οπότε τα σημειώνω ως ενδείξεις και όχι ως οριστικούς αριθμούς.
Το πιο ενδιαφέρον είναι η κλιμάκωση με threads. Σε parsing ενός page 1 MB, 48 φορές σε τέσσερα threads, το selectolax έδειξε επιτάχυνση wall-clock περίπου 3.5-3.9x — το εμπειρικό αποτύπωμα μιας βιβλιοθήκης που απελευθερώνει το GIL κατά το C parsing — ενώ το BeautifulSoup(lxml) έγινε αρκετές φορές πιο αργό με threads, ένδειξη ότι η δουλειά σειριοποιείται πάνω στο GIL. Το lxml βρέθηκε στη μέση και το αποτέλεσμα δεν ήταν ξεκάθαρο. Για την εποχή του free-threading προς την οποία κινείται η Python, το selectolax που παραλληλοποιεί το parsing σε threads εκεί όπου το BeautifulSoup δεν το κάνει είναι ένα πραγματικό, έστω προσωρινό, πλεονέκτημα. Μιλάμε όμως για έναν αριθμό threads και ένα page size, και ο μηχανισμός είναι υπόθεση, όχι κάτι που επιβεβαίωσα με instrumentation στον C κώδικα.
Για leaks: σε 2.000 βρόχους parse-extract-drop στο 1 MB, κανένας από τους τρεις parsers δεν έδειξε τη γραμμική άνοδο RSS που θα περίμενες από leak — όλοι σταθεροποιήθηκαν σε ένα περιορισμένο band εργαζόμενου συνόλου μνήμης. Εμπιστεύομαι ειδικά αυτό το αποτέλεσμα επειδή πέρασα από το ίδιο όργανο ένα γνωστό subject με leak και ανέβηκε σε +198 MB όπως έπρεπε, άρα το εργαλείο μπορούσε να δει leak και απλώς δεν βρήκε κανένα στους parsers. Και ένα node handle που έμεινε ζωντανό μετά τη λήξη του tree του παρέμεινε χρησιμοποιήσιμο, χωρίς segfault. Όλα αυτά είναι single-observation, όχι πολύωρο stress test.
Πού ταιριάζει το selectolax — και πού παραδίδει τη σκυτάλη
Όλα τα παραπάνω αφορούν ένα και μόνο έργο: να μετατρέψεις HTML που ήδη έχεις σε δομημένα δεδομένα, γρήγορα. Το selectolax είναι πολύ καλό σε αυτή τη δουλειά. Αυτό που συνειδητά δεν κάνει είναι να κατεβάζει τη σελίδα, να κάνει render JavaScript, να αλλάζει proxies, να λύνει CAPTCHAs ή να αποφασίζει ποια στοιχεία θέλεις. Όλα αυτά παραμένουν δικός σου κώδικας. Το selectolax είναι το parse layer, και δεν προσποιείται κάτι περισσότερο.
Εκεί είναι και το σημείο όπου μια managed υπηρεσία extraction στέκεται πάνω από έναν parser αντί να τον αντικαθιστά. Αν δεν θες να χτίσεις και να συντηρείς μόνος σου το fetch-render-anti-bot-extract stack, το Thunderbit το προσφέρει ως API, MCP server και CLI — το POST /distill μετατρέπει μια σελίδα σε καθαρό Markdown και το POST /extract επιστρέφει structured JSON που ταιριάζει με schema, με JS rendering και anti-bot χειρισμένα για σένα. Είναι άλλο επίπεδο του προβλήματος: θα διάλεγες selectolax όταν έχεις ήδη το HTML και θέλεις ωμή ταχύτητα parsing υπό τον δικό σου έλεγχο, και κάτι σαν το API, MCP server ή CLI του Thunderbit όταν θέλεις να αναλάβει άλλος το fetching και το extraction και εσύ απλώς να πάρεις πίσω δομημένα δεδομένα. Όχι swap — διαφορετικό ύψος στο ίδιο stack.
Δοκίμασε το Thunderbit για Εξαγωγή Δεδομένων από Web
Πλεονεκτήματα, μειονεκτήματα και ποιος πρέπει πραγματικά να το χρησιμοποιήσει
Πού κερδίζει το selectolax:
- ~12-17x πιο γρήγορο από το BeautifulSoup σε ρεαλιστική εργασία parse-and-extract, σταθερά σε τρεις τάξεις μεγέθους page-size.
- Ελαφριά μνήμη (στην κατηγορία του lxml, ~1.5-1.8x πιο οικονομικό από το BeautifulSoup) και import περίπου 14 ms.
- Ανθεκτικό στις εισόδους που ρίχνουν το lxml — άδειες, μόνο whitespace και παθολογικά βαθύ nesting.
- Μοντέρνο CSS, όπως
:has(),:is(),:where(), case-insensitive attributes και το αποκλειστικό του Lexbor:lexbor-contains(). - DOM με ασφάλεια σε
None: τα στοιχεία που λείπουν επιστρέφουνNoneή[]αντί να πετούν exception, και μπορείς όντως να κάνεις mutate και να ξαναserialise-άρεις το tree. - Ενεργή συντήρηση (v0.4.10, μέσα στο 2026) και απλή εγκατάσταση.
Πού δεν κερδίζει:
- Δεν είναι γενικά πιο γρήγορο από το lxml — ισοπαλία στην πλήρη εργασία, και χάνει στο καθαρό parsing στο δικό μου bench.
- Δεν έχει XPath και δεν έχει
::text/::attr()— σκληρό εμπόδιο για scrapers που βασίζονται σε XPath. - Δύο παγίδες σιωπηλής απώλειας δεδομένων: περιεχόμενο
<template>στο Lexbor και μη UTF-8 bytes μέσω.text(). - Το backend Modest είναι legacy και θα ρίξει SIGABRT στο
:dir(). - Όλοι οι αριθμοί εδώ είναι single-platform (macOS arm64, Python 3.14) και προσωρινοί.
Να χρησιμοποιήσεις selectolax; Ναι, αν θέλεις ταχύτητα parsing επιπέδου lxml με πιο φιλικό, None-safe API και αισθητά καλύτερη συμπεριφορά σε άδειες ή χαλασμένες εισόδους — και είσαι εντάξει να κινηθείς μόνο στο CSS territory. Αν η βάση κώδικά σου είναι χτισμένη πάνω σε XPath, το κόστος rewrite είναι πραγματικό και πρέπει να το ζυγίσεις ειλικρινά. Και αν κυνηγάς «τον έναν και μοναδικό πιο γρήγορο parser», η ακριβής απάντηση από αυτό το bench είναι ότι το selectolax και το lxml είναι τόσο κοντά ώστε ο πραγματικός κριτής να είναι η εργονομία και η ανθεκτικότητα, όχι η ωμή ταχύτητα. Αυτό είναι, τελικά, και καλύτερος λόγος για να διαλέξεις εργαλείο.
Δοκίμασε το Thunderbit για Εξαγωγή Δεδομένων από Web Get Started Free
Συχνές ερωτήσεις
Είναι το selectolax πιο γρήγορο από το BeautifulSoup;
Ναι, ξεκάθαρα — περίπου 12-17x πιο γρήγορο από το BeautifulSoup(html.parser) και 10-14x πιο γρήγορο από το BeautifulSoup(lxml) σε μια ρεαλιστική εργασία parse-and-extract, με σταθερά αποτελέσματα από σελίδες 1 KB έως 10 MB (macOS arm64, Python 3.14). Το συχνά αναφερόμενο «4-5x» υποτιμά το χάσμα απέναντι στο html.parser.
Είναι το selectolax πιο γρήγορο από το lxml; Όχι γενικά. Στην πλήρη εργασία parse-and-extract ισοφαρίζουν στα 100 KB και 1 MB, με το selectolax να κερδίζει μόνο στη σελίδα των 10 MB. Στο καθαρό parsing χωρίς query, το lxml ήταν στην πραγματικότητα ~33-34% πιο γρήγορο στο δικό μου μηχάνημα — αποτέλεσμα που πάει κόντρα στη συνήθη άποψη, γι’ αυτό και το κρατώ ως single-platform εύρημα που πρέπει να το επιβεβαιώσεις στο δικό σου hardware.
Να χρησιμοποιήσω το backend Lexbor ή το Modest;
Lexbor, σχεδόν σε όλες τις περιπτώσεις — είναι το συντηρούμενο, πλήρες engine που προτείνει το README, με καλύτερη κάλυψη CSS. Η μία εξαίρεση είναι μια σελίδα που κρύβει περιεχόμενο μέσα σε <template>, όπου η συμπεριφορά του Lexbor, σωστή ως προς το spec, αφαιρεί αυτό το περιεχόμενο ενώ το Modest το κρατά. Το Modest έχει επίσης αιχμηρές γωνίες, όπως το hard crash του interpreter στο :dir().
Υποστηρίζει το selectolax XPath;
Όχι. Κανένα backend δεν εκθέτει μέθοδο xpath() — το selectolax είναι μόνο για CSS. Αν τα scrapers σου βασίζονται σε XPath, η μετάβαση σημαίνει ξαναγράφεις selectors, και αυτό είναι το μεγαλύτερο κόστος όταν φεύγεις από stack βασισμένο σε lxml ή parsel.
Γιατί το output του selectolax είναι αλλοιωμένο ή λείπουν στοιχεία;
Δύο συνήθεις ύποπτοι. Αν το κείμενο επιστρέφει με replacement characters ή λείπουν τόνοι/σύμβολα, πιθανότατα έδωσες raw bytes που δεν είναι UTF-8 — κάν’ τα πρώτα str (resp.content.decode("latin-1")) πριν κάνεις parse. Αν λείπουν links ή στοιχεία σε μοντέρνα site, μπορεί να βρίσκονται μέσα σε <template> tags στα οποία το backend Lexbor δεν κατεβαίνει· για εκείνη τη σελίδα χρησιμοποίησε Modest ή άλλο parser.


