BeautifulSoup το 2026: Το πιο φιλικό HTML parser είναι και το πιο αργό (κατά 12–17x)

Τελευταία ενημέρωση στις July 17, 2026
BeautifulSoup το 2026: Το πιο φιλικό HTML parser είναι και το πιο αργό (κατά 12–17x)
Σύνοψη AI
Αυτή η αξιολόγηση μετρά το BeautifulSoup ως τον πιο προσιτό HTML parser για Python και βάζει ακριβές κόστος σε αυτή τη φιλικότητα. Συγκρίνει το bs4 με parsers που βασίζονται σε C ως προς την ταχύτητα, την αντοχή σε κακοσχηματισμένο HTML, την κάλυψη CSS selectors, τη διατήρηση αντικειμένων στη μνήμη και την ανάκτηση encoding. Το άρθρο δείχνει ότι το BeautifulSoup είναι αισθητά πιο αργό, συχνά κατά 12 έως 17 φορές, αλλά εξηγεί επίσης γιατί πολλοί developers εξακολουθούν να το προτιμούν: αναγνώσιμο API, ανεκτικό parsing, ισχυρή υποστήριξη selector μέσω soupsieve και πολύ καλή εργονομία για messy one-off scraping δουλειές. Είναι ένας πρακτικός οδηγός για το πότε αυτό το speed tax είναι αποδεκτό και πότε ένας ταχύτερος parser είναι η καλύτερη engineering επιλογή.

Το BeautifulSoup είναι η βιβλιοθήκη που σχεδόν όλοι ανοίγουν πρώτη φορά όταν χρειάζεται να κάνουν scraping σε μια web σελίδα με Python — και πράγματι είναι και ο πιο αργός από τους σοβαρούς HTML parsers. Και τα δύο ισχύουν, χωρίς αυτό να σημαίνει απαραίτητα κάτι κακό. Το ενδιαφέρον είναι ότι το «πιο αργό» δεν είναι απλώς αίσθηση· μεταφράζεται σε συγκεκριμένο, μετρήσιμο κόστος.

Πέρασα το bs4 (δηλαδή το beautifulsoup4, έκδοση 4.15.0, που κυκλοφόρησε τον Ιούνιο του 2026, με άδεια MIT) από έναν συνδυασμό νέων δοκιμών δυνατοτήτων και επαναχρησιμοποιημένων timing data από το ίδιο benchmark rig, και η εικόνα είναι σταθερή: πληρώνεις περίπου ένα δεκαπλάσιο penalty σε ταχύτητα για το πιο φιλικό API και τη μεγαλύτερη ανοχή σε λάθη της κατηγορίας. Αν αυτή η ανταλλαγή αξίζει ή όχι εξαρτάται αποκλειστικά από το workload σου, γι’ αυτό η αξιολόγηση κρατά και τις δύο πλευρές στο τραπέζι.

Τι είναι πραγματικά το BeautifulSoup — και τι δεν είναι

Τα περισσότερα tutorials παραλείπουν το πιο σημαντικό: το BeautifulSoup δεν κάνει parse το HTML. Είναι wrapper. Στο παρασκήνιο δίνει το έγγραφό σου σε έναν από τους τρεις πραγματικούς parsers — το ενσωματωμένο html.parser της Python, το lxml ή το html5lib — και μετά τυλίγει το δέντρο που παράγεται σε ένα ενιαίο, πολύ φιλικό API πλοήγησης και αναζήτησης. Η δουλειά του bs4 δεν είναι να parse-άρει. Είναι να κάνει το αποτέλεσμα ευχάριστο στη χρήση.

Ο ίδιος ο δημιουργός του το αποκαλεί «screen-scraping library», και το μήνυμα ήταν πάντα το ίδιο: δώσ’ του HTML τόσο κακογραμμένο που θα έκανε και browser να στραβώσει, και θα σου βγάλει παρ’ όλα αυτά τα δεδομένα που ζήτησες. Αυτή η φήμη είναι δικαιολογημένη, με μία αστερίσκο που θα δούμε σε λίγο.

Μερικά στοιχεία που αξίζει να ξεκαθαρίσουν από την αρχή:

ΠεδίοΤιμή
Πακέτοbeautifulsoup4 (import ως bs4)
Έκδοση δοκιμής4.15.0 (ανέβηκε 2026-06-07)
Απαίτηση Python>=3.7.0
ΆδειαMIT
Επίσημη σελίδαcrummy.com/software/BeautifulSoup
Πηγή + bug trackerLaunchpadόχι GitHub
ΣυντήρησηΕνεργή (4.15.0 τον Ιούνιο 2026, έξι releases τον τελευταίο χρόνο)

Αυτό το «όχι GitHub» έχει μεγαλύτερη σημασία απ’ όσο φαίνεται. Το bs4 είναι μια βιβλιοθήκη 20 ετών που ζει στο crummy.com και στο Launchpad, οπότε το κλασικό GitHub-star vibe check δεν λέει πολλά. Κρίνε την υγεία του από τη συχνότητα κυκλοφοριών, και με αυτό το μέτρο δείχνει ζωντανό και ενεργό.

Μια λεπτομέρεια για την άδεια, χρήσιμη αν απαντάς σε ομάδα compliance: ο wrapper είναι MIT, αλλά το τι ακριβώς φέρνει στη dependency tree σου το «χρήση bs4» εξαρτάται από το backend που θα εγκαταστήσεις. Το html.parser ανήκει στη standard library της Python (άδεια PSF, χωρίς επιπλέον dependencies). Το lxml είναι BSD, αλλά βασίζεται σε libxml2/libxslt — εξωτερικό C dependency που είτε το κάνεις compile είτε το παίρνεις ως prebuilt wheel. Το html5lib είναι καθαρά Python και MIT. Αν θέλεις το πιο καθαρό dependency footprint, το ενσωματωμένο html.parser στο δίνει — και τυχαίνει να είναι και το backend με το μεγαλύτερο «αλλά». Περισσότερα σε λίγο.

Το κόστος ταχύτητας, σε αριθμούς

Ας βάλουμε πρώτα τον αριθμό πάνω στο τραπέζι, γιατί αυτό είναι το βασικό takeaway και θα ήταν ανέντιμο να το κρύψουμε. Σε ένα ρεαλιστικό task parse-then-extract — parse τη συμβολοσειρά, βγάλ’ όλα τα <h3 class="title"> και όλα τα <a href> — το BeautifulSoup είναι ο πιο αργός parser σε αυτή τη σύγκριση, και μάλιστα όχι κοντά.

BeautifulSoup speed tax: 232 ms versus 15 ms C parsers

Αυτά τα timings προέρχονται ξανά από το selectolax benchmark rig (ίδιο μηχάνημα, ίδια μεθοδολογία 3 runs, ως 2026-07-13). Η αξιολόγηση αυτή δεν ξαναέτρεξε δικό της timing benchmark, ώστε να αποφευχθεί contention στη CPU και διπλή δουλειά. Median p50 latency, σε milliseconds:

Μέγεθος σελίδας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) τρέχει περίπου 12–17x πιο αργά από έναν C parser όπως το selectolax-Lexbor, και το να αλλάξεις backend σε lxml το φέρνει πίσω μόνο στις 10.5–14x φορές πιο αργά — πάλι δηλαδή μια ολόκληρη τάξη μεγέθους πίσω. Ο λόγος είναι δομικός, όχι bug: όποιο backend κι αν κάνει το parsing, το bs4 χτίζει πλήρες Python object (Tag ή NavigableString) για κάθε node. Αυτό το layer υλοποίησης αντικειμένων είναι ένα κόστος που οι C parsers απλώς δεν πληρώνουν.

Παρατήρησε ότι ο πολλαπλασιαστής μεγαλώνει όσο μεγαλώνουν οι σελίδες — 12.0x στο 1 KB, 17.4x στα 10 MB. Αυτό δείχνει ότι δεν μιλάμε για fixed startup overhead που απλώς αποσβένεται. Είναι per-node tax που κλιμακώνεται γραμμικά με τον αριθμό των nodes που χτίζεις.

Τώρα όμως το ξαναπλαισιώνουμε, γιατί το «10x πιο αργό» ακούγεται πιο τρομακτικό απ’ όσο είναι συχνά στην πράξη. Σε μια σελίδα 1 MB, μιλάμε για 232 ms αντί για 15 ms. Αν η δουλειά σου είναι «scrape μερικές εκατοντάδες έως μερικές χιλιάδες σελίδες, μερικές εκατοντάδες KB η καθεμία», αυτή η απόλυτη διαφορά είναι σχεδόν αόρατη — δεν θα τη νιώσεις και η βελτιστοποίηση δεν θα σου δώσει κάτι ουσιαστικό. Αν όμως κάνεις pipeline ενός εκατομμυρίου σελίδων, ο ίδιος λόγος ταχύτητας είναι η διαφορά ανάμεσα σε job που τελειώνει και σε job που δεν τελειώνει ποτέ. Ίδιος αριθμός, αντίθετη απόφαση. Κρίνε με βάση τον πραγματικό όγκο, όχι το benchmark.

Όχι, η αλλαγή backend δεν το λύνει

Υπάρχει ένας επίμονος μύθος ότι αν δώσεις στο bs4 το lxml backend, παίρνεις και την ταχύτητα του lxml. Δεν ισχύει, και αξίζει να καταλάβεις γιατί. Σε batch CSS query 100.000 nodes (επιλογή όλων των <a> και ανάγνωση του href, με ήδη χτισμένο tree), η διαφορά στο throughput είναι εμφανής:

ParserQuery p50Nodes/sec
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) φτάνει περίπου τις 399.000 nodes/second — δηλαδή περίπου 6.3–7.5x πιο αργό από τις τρεις C μηχανές, παρότι το ίδιο του το backend είναι το lxml. Το backend επιταχύνει το χτίσιμο του tree. Το querying και η πλοήγηση εξακολουθούν να περνούν από το soupsieve προς τα bs4 Tag objects, και κάθε matched node εξακολουθεί να «ντύνεται» σε Python object. Άρα το mental model «δίνω στο bs4 lxml και γίνεται lxml-fast» είναι λάθος: το backend επιταχύνει ένα στάδιο, αλλά το πιο αργό στάδιο δεν είναι αυτό.

Η μνήμη και το cold start συμπληρώνουν το κόστος. Σε έγγραφο 10 MB, το bs4 χρησιμοποιεί περίπου 1.5–1.75x περισσότερο resident memory από το selectolax ή το lxml (218–226 MB έναντι 129–145 MB) — ίδια βασική αιτία, ένα Python object ανά node. Και το import του bs4 θέλει περίπου 33.4 ms αντί για 14.1 ms του lxml.html, άρα είναι 2.36x πιο αργό στο import. Αυτό το τελευταίο είναι αμελητέο σε long-running process, αλλά για CLI εργαλείο ή serverless function που κάνει συνεχώς cold starts, είναι ένα μικρό πραγματικό κόστος που αξίζει να ξέρεις.

Γιατί τα περισσότερα threads δεν θα σε σώσουν

Αν η πρώτη σου σκέψη για ένα αργό CPU-bound task είναι «ας το δώσω σε threads», το bs4 θα τιμωρήσει αυτή τη σκέψη. Σε page 1 MB που έγινε parse 48 φορές, single-threaded σε σχέση με τέσσερα threads:

Parser1 thread4 threadsSpeedup
selectolax-Lexbor0.563 s0.159 s3.54x
lxml0.459 s0.378 s1.21x
bs4 (lxml)6.945 s26.842 s0.26x

Διάβασε το τελευταίο row δύο φορές. Τα τέσσερα threads έκαναν το bs4 περίπου 3.9x πιο αργό, όχι πιο γρήγορο. Το εμπειρικό σήμα είναι «πιθανότατα κρατά το GIL»: το tree construction του bs4 είναι καθαρό Python, άρα σειριοποιείται κάτω από το Global Interpreter Lock, και το να προσθέτεις threads απλώς βάζει scheduling overhead σε μια δουλειά που δεν μπορεί να τρέξει πραγματικά παράλληλα. Το selectolax παίρνει το περίπου 3.5x speedup επειδή ο C core του απελευθερώνει το lock· το bs4 δεν έχει τέτοιο περιθώριο.

Για την εποχή του free-threading, το πρακτικό συμπέρασμα είναι αυτό: αν χρειάζεται να παραλληλοποιήσεις BeautifulSoup, πήγαινε σε multiprocessing (ProcessPoolExecutor), όχι σε threads. Το selectolax και το lxml μπορούν να κλιμακώσουν με threads· το bs4 όχι. Μια επιφύλαξη για την αυστηρότητα: αυτή είναι μία μόνο παρατήρηση σε ένα thread count (4) και σε ένα page size (1 MB), και ο μηχανισμός «κρατά το GIL» είναι υπόθεση που προκύπτει από το wall-clock behavior, όχι κάτι που επιβεβαίωσα instrumenting ποιο code path κρατά το lock. Η κατεύθυνση είναι ξεκάθαρη· ο ακριβής μηχανισμός παραμένει προσωρινός.

Το default backend είναι η παγίδα. Διάβασέ το πρώτα

Αν κρατήσεις μόνο ένα πράγμα από αυτή την αξιολόγηση, κράτα αυτό. Ένα απλό BeautifulSoup(html) χωρίς δεύτερο όρισμα χρησιμοποιεί το html.parser, και το html.parser δεν υλοποιεί τους κανόνες HTML5 για προαιρετικά end tags. Αυτό ακούγεται ακαδημαϊκό μέχρι να αρχίσει να χαλάει σιωπηλά τα δεδομένα σου.

BeautifulSoup backend tolerance matrix: html.parser 12/15, lxml and html5lib 15/15

Πέρασα 15 σκόπιμα κακοσχηματισμένα HTML δείγματα από όλα τα backends, με backend-agnostic structural assertion προεγγεγραμμένο για καθένα πριν τρέξει το τεστ (ώστε να μη μπορεί κανείς να διαλέξει νικητή εκ των υστέρων). Τα σκορ:

BackendΠληροί την προσδοκία / 15
lxml15
html5lib15
html.parser12

Οι τρεις αποτυχίες έχουν κοινή αιτία. Πάρε για παράδειγμα ένα table χωρίς κλειστά tags: <table><tr><td>a<td>b<tr><td>c<td>d</table>. Με html.parser, το extracted cell text βγαίνει ['abcd','bcd','cd','d'] — κάθε <td> καταπίνει ό,τι ακολουθεί, επειδή ο parser εμφωλεύει τα cells αντί να τα κλείνει. Τα lxml και html5lib επιστρέφουν σωστά ['a','b','c','d']. Το ίδιο συμβαίνει και με απλά list items: <li>a<li>b<li>c δίνει nested ['abc','bc','c'] στο html.parser και καθαρό ['a','b','c'] στα άλλα δύο. Ακόμη και τα duplicate attributes γυρίζουν ανάποδα — <div id="first" id="second"> κρατά το "second" στο html.parser, ενώ το lxml/html5lib κρατά το "first", όπως λέει και το HTML5 spec.

Γιατί αυτό είναι επικίνδυνο και όχι απλώς ενοχλητικό; Επειδή συμβαίνει χωρίς να σηκωθεί error. Ένα scraper που κάνει αφελώς BeautifulSoup(html) και πετυχαίνει unclosed table ή list — κάτι δυστυχώς πολύ συνηθισμένο σε παλιά sites, χειροποίητο HTML και templates που ξέχασαν closing tag — θα ενώσει γειτονικό cell text σε ένα πεδίο, θα σου δώσει βρώμικα δεδομένα και δεν θα παραπονεθεί ποτέ. Η λύση είναι ένα μόνο όρισμα: BeautifulSoup(html, "lxml") ή BeautifulSoup(html, "html5lib").

Για να είμαστε δίκαιοι με το html.parser, τα άλλα 12 από τα 15 κακοσχηματισμένα δείγματα βγήκαν ίδια και στα τρία backends — mis-nested tags όπως <b><i></b></i>, missing html/body skeletons, unquoted attributes, orphaned closing tags, unclosed comments, nested forms, mixed case και άλλα. Η ανοχή του bs4 είναι πραγματικά ισχυρή συνολικά· η απόκλιση συγκεντρώνεται σχεδόν αποκλειστικά στην κατηγορία των optional-end-tags. Και τίποτα από αυτά δεν είναι ανακάλυψη — τα δικά του docs, στο κομμάτι «Differences between parsers», ήδη λένε ότι το html.parser είναι «less lenient». Αυτό που προσθέτει ο malformed matrix είναι τα συγκεκριμένα, αναπαραγώγιμα cases όπου το «less lenient» μετατρέπεται σε λάθος output.

Τι δεν χάνεις: Το API και το CSS είναι το καλύτερο κομμάτι

Άρα το bs4 είναι αργό, single-threaded και έχει παγίδα στο default backend. Κι όμως, πολλοί εξακολουθούν να το προτιμούν, γιατί το «φιλικό» κομμάτι της ανταλλαγής είναι απόλυτα αληθινό — και το τεστ το επιβεβαιώνει.

BeautifulSoup soupsieve CSS coverage wins with 41/41 plus 20/20

Έτρεξα 29 δοκιμές API που κάλυπταν search, CSS, πλοήγηση στο tree, εξαγωγή κειμένου και τροποποίηση DOM. Και οι 29 πέρασαν, με κάθε αποτέλεσμα να υπολογίζεται συγκρίνοντας το πραγματικό return με αναμενόμενη τιμή, όχι με οπτική εκτίμηση. Δύο από αυτές τις δυνατότητες είναι ergonomics που οι C parsers απλώς δεν δίνουν:

  • Function predicates σε find / find_all. Μπορείς να γράψεις soup.find(lambda t: t.name == "a" and "btn" in t.get("class", [])) και να εκφράσεις μια σύνθετη συνθήκη σε μία γραμμή Python — χωρίς το «πάρε τα όλα, μετά φίλτραρε» ως διπλό βήμα.
  • Ονομασμένη, αμφίδρομη πλοήγηση στο tree. .parent, .next_sibling, .find_parent, .stripped_strings, .descendants — οι διαδρομές διαβάζονται σαν φυσική γλώσσα και κινούνται και προς τις δύο κατευθύνσεις. Το selectolax χρειάζεται περισσότερα βήματα σε κάποια από αυτά ή δεν τα προσφέρει καθόλου.

Αυτό είναι το «σου αγοράζει χρόνο developer» κομμάτι, αλλά με concrete στοιχεία. Δεν είναι marketing· είναι 29 πράσινα checks.

Δύο παγίδες αξίζει να σημειωθούν, γιατί μια δίκαιη αξιολόγηση πρέπει να βλέπει και τις δύο πλευρές. Πρώτον, τα boolean attributes: το <input disabled> επιστρέφει κενή συμβολοσειρά "" για το disabled στο bs4 (το selectolax επιστρέφει None). Και τα δύο είναι falsy, άρα το if node.get("disabled") χάνει σιωπηλά ένα boolean attribute που υπάρχει πραγματικά και στις δύο βιβλιοθήκες — ο ασφαλής έλεγχος είναι "disabled" in tag.attrs. Δεύτερον, το get_text(strip=True) κολλάει κείμενο nodes χωρίς διαχωριστικό μετά το stripping, οπότε το "...with " + "link1" γίνεται "withlink1". Δώσε separator=" " όταν χρειάζεσαι όρια λέξεων. Κανένα από τα δύο δεν είναι αποκλειστικά θέμα bs4· είναι gotchas που εμφανίζονται σε περισσότερες βιβλιοθήκες.

Και τώρα το σημείο που εκπλήσσει πολλούς: η επιλογή bs4 δεν σε κάνει να χάσεις σε CSS coverage. Η CSS engine του, το soupsieve, είναι η πιο πλήρης υλοποίηση σε όλη αυτή τη σύγκριση. Στο base matrix 41 περιπτώσεων (ξαναχρησιμοποιημένο από το selectolax rig), το soupsieve έβγαλε 41/41 — το μοναδικό τέλειο σκορ στο πεδίο, μπροστά από το selectolax-Lexbor με 39/41 και το cssselect (lxml/parsel) με 37/41. Έπειτα έτρεξα 20 επιπλέον extended cases που τεκμηριώνει το soupsieve και πήγε 20/20, περιλαμβάνοντας selectors που το Lexbor απορρίπτει εντελώς: :lang(en), το soupsieve-only :-soup-contains('featured'), :is(), :where() και :has(> a). Τα πραγματικά κενά είναι μόνο το XPath (το soupsieve είναι μόνο CSS) και τα ::text / ::attr() pseudo-elements του parsel, που είναι επεκτάσεις του Scrapy. Αν ζεις μέσα στο XPath, αυτή η μετάβαση θα σε πονέσει.

Το συμπέρασμα εδώ είναι καθαρό: αυτό που θυσιάζεις επιλέγοντας BeautifulSoup είναι η ταχύτητα. Όχι η ergonomics του API, και σίγουρα όχι η κάλυψη CSS.

Δύο production gotchas που αξίζει να υπολογίσεις

Πέρα από το default backend, δύο συμπεριφορές θα σε χτυπήσουν ειδικά σε long-running ή μη UTF-8 workloads.

Reference cycles: κάλεσε decompose() σε μεγάλα loops

Κάθε bs4 Tag κρατά αναφορά και στον γονέα του και στα παιδιά του, άρα σχηματίζεται reference cycle. Το reference counting του CPython δεν μπορεί να καθαρίσει μόνο του ένα cycle — αυτή είναι δουλειά του generational garbage collector. Για να δω πόσο μετράει αυτό, έχτισα και διέγραψα ένα tree 300 φορές με το GC κλειστό και μετά μέτρησα πόσα Tag objects έμειναν ακόμη στη μνήμη:

BeautifulSoup reference cycles retain 120,900 objects with GC off and 0 with GC on

ΣενάριοTags που έμειναν μετά το del
GC off120,900 (300 κύκλοι, τίποτα δεν καθαρίστηκε)
GC on26,598 (ο generational GC ενεργοποιήθηκε στη διάρκεια του loop)
Με υποχρεωτικό gc.collect()0 (όλα καθαρίστηκαν)
Control χωρίς κύκλο (λίστα από strings, GC off)delta 0

Με το GC off, το del soup δεν καθάρισε τίποτα — και τα 120.900 objects έμειναν στη μνήμη, επειδή το reference cycle νικά το reference counting. Ένα μόνο gc.collect() έσβησε όλα τα αντικείμενα. Η control ομάδα χωρίς κύκλο (απλή λίστα από strings, γνωστό ότι δεν έχει cycle) έδειξε delta μηδέν, άρα η συσσώρευση προήλθε από το cycle του bs4 και όχι από θόρυβο μέτρησης. Τα ίδια τα docs του bs4 λένε ότι τα objects είναι «densely interconnected ... exactly the sort a garbage collector would have trouble with», άρα αυτό είναι τεκμηριωμένη συμπεριφορά· το τεστ απλώς προσθέτει τον αριθμό των retained objects και την απόδειξη ότι το collect() τα μηδενίζει.

Ο πρακτικός κανόνας: σε pipeline που κάνει parse πολλά μεγάλα pages σε tight loop, αν ο κώδικάς σου — ή κάποια ρύθμιση υψηλού throughput — απενεργοποιεί το GC ή δεν το ενεργοποιεί συχνά, τα bs4 trees θα παραμένουν και η μνήμη θα ανεβαίνει. Κάλεσε soup.decompose() μετά από κάθε page — το bs4 το παρέχει ακριβώς για να σπάσει το cycle και να ελευθερώσει τη μνήμη νωρίτερα. Τα C trees του selectolax και του lxml δεν έχουν καθόλου αυτό το πρόβλημα.

Encoding: το UnicodeDammit είναι το κρυφό πλεονέκτημα του bs4

Το bs4 φέρνει ένα component που οι γρήγοροι parsers δεν έχουν: το UnicodeDammit, το οποίο ανιχνεύει το encoding ενός εγγράφου και το μετατρέπει αυτόματα σε Unicode. Του έδωσα έναν 8-case πίνακα «δηλωμένο vs πραγματικό charset»:

BeautifulSoup UnicodeDammit recovers 5 of 8 encoding cases

ΠερίπτωσηΠραγματικό encodingΤι μάντεψε το 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Όχι (ακολούθησε το ψέμα)

Πέντε στα οκτώ ανακτήθηκαν. UTF-8, UTF-16 με BOM, GBK, Shift-JIS, αλλά και λανθασμένα label-αρισμένο latin-1, επέστρεψαν σωστά, και οι υπερσύνολες εκτιμήσεις (GBK→gb18030, Shift-JIS→cp932) αποκωδικοποιούνται μια χαρά. Οι δύο αποτυχίες είναι χρήσιμες να τις ξέρεις: μικρά byte samples latin-1/cp1252 μπερδεύονται με DOS code pages, επειδή ο στατιστικός ανιχνευτής δεν είναι αξιόπιστος σε σύντομα inputs και τα DOS box-drawing characters επικαλύπτονται με code points του Latin-1· και όταν ένα <meta charset> δηλώνει απλώς λάθος encoding, το UnicodeDammit εμπιστεύεται τη δήλωση. Τα docs του bs4 το επισημαίνουν και τα δύο — ένα sample μπορεί να είναι «so short that Unicode, Dammit can't get a lock on it», και όσο περισσότερα δεδομένα έχεις τόσο καλύτερη η εκτίμηση.

Σε σχέση με το selectolax, που μπορεί να αλλοιώσει σιωπηλά non-UTF-8 bytes και περιμένει να τα αποκωδικοποιήσεις μόνος σου, αυτό είναι πραγματικό πλεονέκτημα: το bs4 τουλάχιστον προσπαθεί να μαντέψει και συχνά πετυχαίνει. Αλλά δεν είναι εγγύηση. Για γνωστό encoding, μην αφήνεις τη μαντεψιά να παίρνει τις αποφάσεις — να είσαι explicit: BeautifulSoup(bytes, from_encoding="...").

Διαφωνούν ποτέ πραγματικά τα backends σε πραγματικές σελίδες;

Το malformed matrix δείχνει ότι τα backends αποκλίνουν σε εσκεμμένα broken input. Το προφανές επόμενο ερώτημα είναι αν αυτό έχει σημασία στον πραγματικό κόσμο, οπότε πέρασα και τα τρία backends σε 11 πραγματικές fetched σελίδες — BBC, Wikipedia, Craigslist, MDN, old.reddit, Python docs, Hacker News, Books to Scrape, webscraper.io, whitehouse.gov και μια JS-rendered quotes σελίδα — συγκρίνοντας counts από links, headings και images.

Και τα τρία συμφώνησαν και στις 11 σελίδες. Μηδενική απόκλιση. Αυτό σημαίνει ότι η διαφωνία των backends από την ενότητα της παγίδας εμφανίζεται μόνο σε deliberately malformed HTML· όταν ένα σύγχρονο production site είναι δομημένο αρκετά καλά — ακόμη κι αν είναι «messy» — η επιλογή backend δεν αλλάζει τι θα εξαγάγεις. Η πρακτική ανάγνωση: για mainstream, καλά σχηματισμένα sites, το html.parser είναι απολύτως ΟΚ και γλιτώνει ένα dependency. Μόνο όταν κάνεις scraping σε εμφανώς μη standard, χειρόγραφο ή παλιό HTML αρχίζει το backend choice να αλλάζει τα αποτελέσματα, και τότε αξίζει να πας σε lxml ή html5lib.

Μια παρένθεση από εκείνη την εκτέλεση, γιατί είναι πραγματικό edge case. Η σελίδα MDN περιέχει στοιχείο <template>, και όλα τα bs4 backends επέστρεψαν 508 links — δηλαδή το bs4 «ισιώνει» το περιεχόμενο του <template> μέσα στο κύριο tree. Αυτό το βάζει στην ίδια πλευρά με το lxml και απέναντι από το selectolax-Lexbor, που ακολουθεί αυστηρά το HTML5 spec (το <template> είναι inert DocumentFragment) και επιστρέφει 497, πετώντας σιωπηλά τα 11 links μέσα στο template. Άρα το bs4 θα πιάσει δεδομένα μέσα σε <template> — χρήσιμο, αλλά και τρόπος να εμφανιστεί «φανταστικό» content που ο browser δεν θα έδειχνε ποτέ. Κανένα από τα δύο δεν είναι λάθος· είναι διαφορετικές ερμηνείες του spec, και πρέπει να ξέρεις ποια παίρνεις.

Πού ταιριάζει το BeautifulSoup — και πού όχι

Αντί να συμπιέσω όλα αυτά σε ένα ενιαίο σκορ 0–100 (που θα έκρυβε ακριβώς τα trade-offs που έχουν σημασία), ορίστε η αξιολόγηση ανά διάσταση, με μία επιφύλαξη σε κάθε γραμμή:

ΔιάστασηΤι έδειξαν τα testsΤι να προσέξει ο αναγνώστης
Εγκατάσταση / πρώτο runΚαθαρό wrapper, χωρίς browser/setup· ο html.parser χωρίς deps· όλα με prebuilt wheelsΤο backend lxml χρειάζεται C dependency
Ταχύτητα σε σχέση με C parsers12–17x πιο αργό (html.parser) / 10.5–14x (backend lxml), σε όλα τα μεγέθηΈνα rig· επαναχρησιμοποιημένα data από selectolax
Throughput σε CSS queries~6–7.5x πιο αργό σε 100k nodes· το backend lxml δεν το σώζειΕπαναχρησιμοποίηση· πληρώνει τον Python Tag φόρο
Μνήμη1.5–1.75x selectolax/lxml· το πιο βαρύΕπαναχρησιμοποίηση· μετρήθηκε μέσω RSS
Import cold start2.36x πιο αργό (33.4 έναντι 14.1 ms)Επαναχρησιμοποίηση· μικρό κόστος
Κλιμάκωση με threadsΤο bs4-lxml είναι ~3.9x πιο αργό με 4 threads (κρατά το GIL)Μία παρατήρηση· χρησιμοποίησε multiprocessing
Ergonomics του API29/29 probes· function-predicate find + αμφίδρομη πλοήγησηΠαγίδες σε empty-string bool-attr και word boundaries του strip
Κάλυψη CSSΤο soupsieve είναι το ισχυρότερο: 41/41 base + 20/20 extended· υποστηρίζει :langΌχι XPath, όχι ::text
Ανοχή σε 3 backendslxml/html5lib 15/15· html.parser 12/15Απόκλιση μόνο σε malformed HTML
Συμπεριφορά σε πραγματικές σελίδεςΤα 3 backends συμφωνούν 11/11· όλα flatten το <template> (508)Σε καλά σχηματισμένα sites το backend δεν μετράει
Reference-cycle GCΤο tree είναι cycle· 300 loops κράτησαν 120,900 objects, το collect τα μηδένισεΜεγάλα loops θέλουν decompose()
EncodingUnicodeDammit 5/8· μπερδεύεται σε μικρά samples, ακολουθεί λάθος δηλώσειςΜοναδική παρατήρηση
ΣυντήρησηΕνεργό (4.15.0, Ιούνιος 2026)· MITΕξωτερική «κατοικία» σε crummy/Launchpad, όχι GitHub

Άρα για ποιον είναι το BeautifulSoup; Για όποιον δίνει μεγαλύτερη αξία σε readable API και ανεκτικό parsing από ό,τι σε ακατέργαστο throughput, δουλεύοντας σε μεσαίο όγκο — prototypes, one-off scrapes, εσωτερικά εργαλεία, ομάδες όπου ο χρόνος του developer κοστίζει περισσότερο από το runtime. Ποιος να κοιτάξει αλλού; Pipelines ενός εκατομμυρίου σελίδων όπου το speed tax γίνεται πραγματικό χρήμα, workloads που χρειάζονται parallelism σε επίπεδο threads και όποιος είναι δεμένος με XPath.

Μια σημείωση για το πού μπαίνει αυτό σε ένα πραγματικό scraping stack, και πού έρχεται το δικό μας εργαλείο. Το BeautifulSoup υποθέτει ότι έχεις ήδη το HTML. Δεν κατεβάζει σελίδες, δεν render-άρει JavaScript και δεν λύνει anti-bot defenses ή CAPTCHAs — αυτό είναι ξεχωριστή δουλειά, και πραγματικά δύσκολη στο σύγχρονο web. Εκεί είναι που ένα AI scraping API κάθεται σε άλλο layer: το developer stack του Thunderbit — ένα REST API, ένας MCP server και ένα CLI — αναλαμβάνει το fetch, το JS rendering και το anti-bot πρόβλημα, και μετά επιστρέφει είτε καθαρό Markdown (POST /distill) είτε schema-matched structured JSON (POST /extract) χωρίς να γράψεις καθόλου selectors. Τα δύο δεν είναι ανταγωνιστές· είναι συμπληρωματικά. Το bs4 κάνει parse το HTML που ήδη έχεις· το API, MCP και CLI του Thunderbit σου δίνουν το HTML που δεν έφτανες εύκολα εξαρχής. Αν το bottleneck σου είναι το parsing, το bs4 είναι μια χαρά απάντηση. Αν το bottleneck σου είναι η απόκτηση του περιεχομένου, τότε μιλάμε για άλλο layer.

Δοκιμάστε το Thunderbit για εξαγωγή web δεδομένων

Το συμπέρασμα με μία φράση

Το BeautifulSoup σου δίνει το πιο φιλικό API, τη μεγαλύτερη ανοχή σε κακοσχηματισμένο HTML και την πιο πλήρη CSS engine σε αυτή τη σύγκριση — με αντάλλαγμα περίπου δεκαπλάσιο κόστος σε ταχύτητα και το βαρύτερο αποτύπωμα στη μνήμη. Αυτή είναι όλη η συμφωνία, ξεκάθαρα. Το default backend html.parser είναι η μία πραγματική παγίδα: παραμορφώνει σιωπηλά tables και lists χωρίς κλειστά tags, οπότε πέρασε "lxml" ή "html5lib" όποτε το input μπορεί να είναι άσχημο. Τα threads δεν θα το επιταχύνουν — το multiprocessing θα το κάνει. Και σε μακρόχρονα loops, κάνε decompose() σε κάθε page για να μη συσσωρεύονται τα reference cycles.

Δύο περιορισμοί για να κλείσουμε. Όλα εδώ μετρήθηκαν σε μία πλατφόρμα μόνο (macOS arm64, Python 3.14, prebuilt wheels), και οι πολλαπλασιαστές ταχύτητας επαναχρησιμοποιούνται από το selectolax rig (ίδιο bench, ως 2026-07-13) αντί να ξανατρέξουν — άρα κληρονομούν αυτόν τον μονο-πλατφορμικό περιορισμό, και μια Linux x86_64 ή source-compiled εγκατάσταση θα μπορούσε να μετακινήσει λίγο τα ακριβή νούμερα. Και τίποτα από αυτά τα αποτελέσματα δεν είναι ανακάλυψη-έκπληξη: το bs4 είναι βιβλιοθήκη 20 ετών, άρα κάθε συμπεριφορά που δοκιμάστηκε είναι είτε τεκμηριωμένη είτε ήδη δημόσια γνωστή. Η αξία δεν είναι το «αποκλειστικό». Είναι ότι βάζουμε πραγματικό νούμερο σε trade-offs που τα docs συνήθως περιγράφουν μόνο ποιοτικά.

Συχνές Ερωτήσεις

Είναι αργό το BeautifulSoup; Ναι, και μετρήσιμα. Σε task parse-plus-extract τρέχει περίπου 12–17x πιο αργά από έναν C parser όπως το selectolax-Lexbor με το default html.parser, και 10.5–14x πιο αργά με το lxml backend, επειδή χτίζει Python object για κάθε node. Το αν αυτό μετράει εξαρτάται από την κλίμακα: σε page 1 MB είναι 232 ms αντί για 15 ms, αόρατο για λίγες χιλιάδες σελίδες αλλά καθοριστικό για pipeline ενός εκατομμυρίου σελίδων.

Ποιο BeautifulSoup parser να χρησιμοποιήσω — html.parser, lxml ή html5lib; Για καλά σχηματισμένα, mainstream sites, το default html.parser είναι μια χαρά και δεν προσθέτει dependencies. Όμως δεν υλοποιεί τα HTML5 optional-end-tags, άρα σε tables ή lists χωρίς κλεισίματα ενώνει κείμενο από διπλανές περιοχές χωρίς error. Όταν το input μπορεί να είναι κακοσχηματισμένο, χειροποίητο ή

Ke
Ke
CTO στη Thunderbit | Senior Data Scientist & ML Expert Με σχεδόν μια δεκαετία εμπειρίας στη μηχανική μάθηση και την επιστήμη δεδομένων, ο Ke Shen είναι απόφοιτος του Columbia University και πρώην Senior Data Scientist στα Walmart Labs. Με βαθιά, αναγνωρισμένη από συναδέλφους τεχνογνωσία σε Python, R, Java και Στατιστική, μοιράζεται δοκιμασμένες στην πράξη γνώσεις για το πώς οι σύνθετοι αλγόριθμοι τεχνητής νοημοσύνης περνούν από τη θεωρία σε αρχιτεκτονική έτοιμη για παραγωγή.

Δοκίμασε το Thunderbit

Εξήγαγε leads και άλλα δεδομένα σε μόλις 2 κλικ. Με AI.

Απόκτησε το Thunderbit Είναι δωρεάν
Εξαγωγή δεδομένων με AI
Μετέφερε εύκολα δεδομένα σε Google Sheets, Airtable ή Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week