BeautifulSoup en 2026: el parser HTML más amigable también es el más lento (entre 12 y 17 veces)

Última actualización el July 17, 2026
BeautifulSoup en 2026: el parser HTML más amigable también es el más lento (entre 12 y 17 veces)
Resumen con IA
Esta reseña mide BeautifulSoup como el parser HTML más accesible de Python y cuantifica con precisión el coste de esa facilidad. Compara bs4 con parsers basados en C en velocidad, tolerancia a HTML malformado, cobertura de selectores CSS, retención de objetos y recuperación de codificación. El artículo muestra que BeautifulSoup es mucho más lento, a menudo entre 12 y 17 veces, pero también explica por qué los desarrolladores siguen eligiéndolo: APIs legibles, parseo tolerante, un excelente soporte de selectores con soupsieve y una ergonomía sobresaliente para tareas de scraping puntuales y desordenadas. Es una guía práctica para decidir cuándo el coste de velocidad compensa y cuándo conviene un parser más rápido.

BeautifulSoup es la biblioteca a la que casi todo el mundo recurre la primera vez que extrae una página web con Python, y la verdad es que sí: también es la más lenta entre los parsers HTML serios. Ambas cosas son ciertas, y ninguna es una crítica. Lo interesante es que ese “más lenta” es una cifra precisa, medible y comparable, no una simple impresión.

Probé bs4 (es decir, beautifulsoup4, versión 4.15.0, publicada en junio de 2026 y con licencia MIT) con una mezcla de pruebas nuevas de capacidad y datos de rendimiento reutilizados del mismo banco de pruebas, y el panorama es consistente: a cambio de perder aproximadamente un orden de magnitud en velocidad, obtienes la API más amigable y la mayor tolerancia a errores del sector. Si ese intercambio vale la pena o no depende por completo de tu carga de trabajo, así que esta reseña deja ambas caras sobre la mesa.

Qué es realmente BeautifulSoup (y qué no es)

La mayoría de los tutoriales se saltan la parte que más importa: BeautifulSoup no hace el parseo de HTML. Es una capa envoltorio. Por dentro, le pasa tu documento a uno de tres parsers reales — el html.parser integrado de Python, lxml o html5lib — y luego envuelve el árbol resultante con una única API de navegación y búsqueda muy cómoda. La tarea de bs4 no es parsear. Es hacer agradable el recorrido por el resultado.

Su propio autor lo llama una “screen-scraping library”, y la promesa siempre ha sido la misma: apúntala a un HTML tan roto que un navegador se pondría a temblar, y aun así sacará los datos que pediste. Esa fama está bien ganada, con un asterisco que veremos más adelante.

Antes de seguir, conviene fijar algunos datos:

CampoValor
Paquetebeautifulsoup4 (se importa como bs4)
Versión probada4.15.0 (subida el 2026-06-07)
Requisito de Python>=3.7.0
LicenciaMIT
Sitio oficialcrummy.com/software/BeautifulSoup
Código fuente + reportes de erroresLaunchpadno GitHub
MantenimientoActivo (4.15.0 en junio de 2026, seis lanzamientos en el último año)

Ese detalle de “no GitHub” importa más de lo que parece. bs4 es una biblioteca con 20 años de historia que vive en crummy.com y Launchpad, así que la típica validación por estrellas de GitHub aquí no aplica. Juzga su estado por el ritmo de lanzamientos, y en ese sentido está muy viva.

Un matiz importante sobre la licencia, por si tienes que responder ante un equipo de cumplimiento: el envoltorio es MIT, pero lo que realmente arrastras en tu árbol de dependencias al usar bs4 depende del backend que instales. html.parser pertenece a la biblioteca estándar de Python (licencia PSF, sin dependencias extra). lxml es BSD, pero se apoya en libxml2/libxslt, una dependencia externa en C que debes compilar o instalar desde una wheel precompilada. html5lib es puro Python y MIT. Si quieres la huella de dependencias más limpia, el html.parser integrado te la da — aunque, casualmente, también es el backend con el mayor inconveniente. Volveremos a eso enseguida.

El coste de velocidad, cuantificado

Pongamos la cifra por delante, porque es el titular y ocultarla sería deshonesto. En una tarea realista de parsear y extraer — parsear la cadena, recoger todos los <h3 class="title"> y todos los <a href> — BeautifulSoup es el parser más lento de esta comparación, y no por poco.

BeautifulSoup speed tax: 232 ms versus 15 ms C parsers

Estos tiempos se reutilizan del banco de pruebas de selectolax (misma máquina, misma metodología de 3 ejecuciones, estado a 2026-07-13); esta reseña no repite sus propios benchmarks para evitar contención de CPU y trabajo duplicado. Latencia mediana p50, en milisegundos:

Tamaño de páginabs4 (html.parser)bs4 (lxml)selectolax-Lexborlxmlbs4-hp más lentobs4-lxml más lento
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

Así que bs4(html.parser) va aproximadamente entre 12 y 17 veces más lento que un parser en C como selectolax-Lexbor, y cambiar al backend lxml solo lo reduce a 10.5–14 veces — sigue siendo un orden de magnitud completo por detrás. La razón es estructural, no un bug: da igual qué backend haga el parseo, bs4 construye un objeto Python completo (Tag o NavigableString) para cada nodo. Esa capa de materialización de objetos es un peaje que los parsers en C simplemente no pagan.

Fíjate en que el multiplicador sube conforme crecen las páginas — 12.0x en 1 KB, 17.4x en 10 MB. Eso te dice que no se trata de un coste fijo de arranque que puedas amortizar. Es un peaje por nodo que escala linealmente con la cantidad de nodos creados.

Ahora, el matiz importante, porque “10x más lento” suena más dramático de lo que suele ser. En una página de 1 MB, hablamos de 232 ms frente a 15 ms. Si tu trabajo consiste en “extraer unos cientos o unos pocos miles de páginas de varios cientos de KB”, esa diferencia absoluta es prácticamente invisible — no la vas a notar, y optimizarla no te aporta nada. Si tu trabajo es una tubería de un millón de páginas, la misma proporción marca la diferencia entre un proceso que termina y otro que no. La misma cifra, veredicto opuesto. Evalúala contra tu volumen real, no contra el benchmark.

No, cambiar de backend no lo arregla

Existe el mito persistente de que puedes darle a bs4 el backend lxml y obtener la velocidad de lxml. No se puede, y merece la pena entender por qué. En una consulta CSS por lotes de 100.000 nodos (seleccionar cada <a> y leer su href, con el árbol ya construido), la diferencia de rendimiento es muy clara:

ParserConsulta p50Nodos/segundo
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) logra unos 399.000 nodos/segundo — aproximadamente entre 6.3 y 7.5 veces más lento que los tres motores en C, pese a que su propio backend es lxml. El backend acelera la fase de construcción del árbol. La consulta y el recorrido siguen pasando por soupsieve hacia objetos Tag de bs4, y cada nodo encontrado sigue envolviéndose en Python. Así que la idea mental de “le das lxml a bs4 y pasa a ir como lxml” es incorrecta: el backend acelera una fase, y la fase más lenta no es esa.

Memoria y arranque en frío completan el coste. En un documento de 10 MB, bs4 usa aproximadamente entre 1.5 y 1.75 veces más memoria residente que selectolax o lxml (218–226 MB frente a 129–145 MB) — misma causa de fondo: un objeto Python por nodo. Y importar bs4 tarda unos 33.4 ms frente a 14.1 ms en lxml.html, así que su import es 2.36 veces más lento. Eso último es irrelevante en un proceso largo, pero en una herramienta CLI o una función serverless que arranca en frío constantemente, es un coste pequeño pero real que conviene conocer.

Por qué más hilos no te van a salvar

Si tu reflejo ante una tarea lenta y ligada a CPU es “mete más hilos”, bs4 te castigará por ello. En una página de 1 MB procesada 48 veces, comparando un hilo frente a cuatro:

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

Lee la última fila dos veces. Cuatro hilos hicieron que bs4 fuera aproximadamente 3.9 veces más lento, no más rápido. La señal empírica es “probablemente mantiene el GIL”: la construcción del árbol en bs4 es puro Python, así que se serializa bajo el Global Interpreter Lock, y añadir hilos solo suma sobrecarga de planificación a un trabajo que en realidad no puede ejecutarse en paralelo. selectolax consigue su aceleración de ~3.5x porque su núcleo en C libera el bloqueo; bs4 no tiene ese margen.

Para la era del free-threading, la conclusión práctica es esta: si necesitas paralelizar BeautifulSoup, usa multiprocessing (ProcessPoolExecutor), no hilos. selectolax y lxml sí pueden escalar con hilos; bs4 no. Una salvedad metodológica: esto proviene de una única observación con un solo número de hilos (4) sobre una sola página (1 MB), y el mecanismo de “mantiene el GIL” es una hipótesis inferida del comportamiento temporal, no algo que haya confirmado instrumentando qué ruta de código toma el bloqueo. La dirección es clara; el mecanismo exacto, provisional.

El backend por defecto es la trampa. Léelo antes de nada.

Si solo te quedas con una cosa de esta reseña, que sea esta. Un BeautifulSoup(html) sin segundo argumento usa html.parser, y html.parser no implementa las reglas de etiquetas de cierre opcional de HTML5. Eso suena académico hasta que corrompe silenciosamente tus datos.

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

Pasé 15 muestras de HTML deliberadamente malformadas por los tres backends, con una aserción estructural agnóstica al backend predefinida para cada una antes de ejecutar las pruebas (así nadie puede elegir al ganador después del hecho). Las puntuaciones:

BackendCumple la expectativa / 15
lxml15
html5lib15
html.parser12

Los tres fallos comparten la misma raíz. Toma una tabla sin cerrar: <table><tr><td>a<td>b<tr><td>c<td>d</table>. Con html.parser, el texto extraído de las celdas sale como ['abcd','bcd','cd','d'] — cada <td> se traga todo lo que viene después porque el parser anida las celdas en lugar de cerrarlas. lxml y html5lib devuelven correctamente ['a','b','c','d']. Las listas <li> sueltas hacen exactamente lo mismo: <li>a<li>b<li>c produce ['abc','bc','c'] con html.parser, y el limpio ['a','b','c'] con los otros dos. Los atributos duplicados también cambian: <div id="first" id="second"> conserva "second" en html.parser, pero "first" en lxml/html5lib, y la especificación HTML5 dice que debe conservarse el primero.

Y aquí está lo peligroso, no solo lo molesto: ocurre sin lanzar ningún error. Un scraper que haga alegremente BeautifulSoup(html) y se encuentre con una tabla o lista sin cerrar — algo tristemente común en sitios antiguos, HTML escrito a mano y plantillas que olvidaron una etiqueta de cierre — mezclará el texto de celdas adyacentes en un solo campo, te devolverá datos sucios y no se quejará ni una vez. La solución es pasar un argumento: BeautifulSoup(html, "lxml") o BeautifulSoup(html, "html5lib").

Para ser justos con html.parser, en las otras 12 de las 15 muestras malformadas el resultado fue idéntico en los tres backends — etiquetas mal anidadas como <b><i></b></i>, ausencia de la estructura base html/body, atributos sin comillas, etiquetas de cierre huérfanas, comentarios sin cerrar, formularios anidados, mayúsculas y minúsculas mezcladas, y más. La tolerancia de bs4 es realmente fuerte en general; la divergencia se concentra casi por completo en la familia de etiquetas de cierre opcional. Y nada de esto es nuevo: la propia documentación de bs4 sobre “Differences between parsers” ya dice en lenguaje claro que html.parser es “less lenient”. Lo que añade esta matriz de HTML malformado son los casos específicos y reproducibles en los que esa menor tolerancia se convierte en salida incorrecta.

Lo que no pierdes: la API y el CSS son su mejor parte

Así que bs4 es lento, no escala bien con hilos y tiene una trampa en el backend por defecto. Aun así, la gente sigue recurriendo a él, porque la mitad “amigable” del intercambio es totalmente real — y lo demuestra en las pruebas.

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

Ejecuté 29 pruebas de API cubriendo búsqueda, CSS, navegación del árbol, extracción de texto y modificación del DOM. Las 29 pasaron, y cada resultado se calculó comparando el valor devuelto real con un valor esperado, no a ojo. Dos de esas capacidades son especialmente ergonómicas y los parsers en C simplemente no las ofrecen:

  • Predicados de función en find / find_all. Puedes escribir soup.find(lambda t: t.name == "a" and "btn" in t.get("class", [])) y expresar una condición compleja en una sola línea de Python — sin el paso intermedio de “selecciona todo y luego filtra”.
  • Navegación del árbol con nombre y en ambas direcciones. .parent, .next_sibling, .find_parent, .stripped_strings, .descendants — los recorridos se leen casi como inglés y funcionan en ambos sentidos. selectolax necesita varios pasos para algunas de estas operaciones, o ni siquiera las ofrece.

Esa es la parte que realmente te ahorra tiempo de desarrollo. No es marketing; son 29 aciertos en verde.

Dos trampas a tener en cuenta, porque una reseña justa debe señalar ambas caras. Primero, los atributos booleanos: <input disabled> devuelve una cadena vacía "" para disabled en bs4 (selectolax devuelve None). Ambos son falsy, así que if node.get("disabled") puede pasar por alto silenciosamente un atributo booleano que en realidad sí está presente en cualquiera de las dos bibliotecas — la comprobación segura es "disabled" in tag.attrs. Segundo, get_text(strip=True) concatena el texto de los nodos sin separador después de hacer el strip, así que "...with " + "link1" acaba como "withlink1". Pasa separator=" " cuando necesites conservar límites de palabra. Ninguna de las dos trampas es exclusiva de bs4; ambas son problemas comunes entre bibliotecas.

Y ahora la parte que sorprende: elegir bs4 no te hace perder cobertura CSS. Su motor CSS, soupsieve, es la implementación más completa de toda esta comparación. En la matriz base de 41 casos (reutilizada del banco de pruebas de selectolax), soupsieve obtuvo 41/41 — la única puntuación perfecta del campo, por delante de 39/41 de selectolax-Lexbor y 37/41 de cssselect (lxml/parsel). Luego ejecuté 20 casos adicionales que la documentación de soupsieve anuncia como compatibles, y logró 20/20, incluidos selectores que Lexbor rechaza directamente: :lang(en), el exclusivo de soupsieve :-soup-contains('featured'), :is(), :where() y :has(> a). Las únicas carencias reales son XPath (soupsieve es solo CSS) y los pseudo-elementos ::text / ::attr() de parsel, que son extensiones de Scrapy. Si trabajas en XPath, esa migración te dolerá.

El veredicto de esta sección es claro: lo que sacrificas al elegir BeautifulSoup es velocidad. No sacrificas ergonomía de la API, y desde luego no sacrificas cobertura CSS.

Dos problemas de producción que conviene presupuestar

Más allá del backend por defecto, hay dos comportamientos que te van a morder especialmente en cargas largas o en trabajos no UTF-8.

Ciclos de referencia: llama a decompose() en bucles largos

Cada Tag de bs4 mantiene una referencia a su padre y a sus hijos, lo que forma un ciclo de referencias. El conteo de referencias de CPython no puede liberar un ciclo por sí solo — esa es tarea del recolector de basura generacional. Para ver cuánto importa esto, construí y eliminé un árbol 300 veces con el GC desactivado, y luego conté los objetos Tag que seguían en memoria:

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

EscenarioTags retenidos después de del
GC desactivado120,900 (300 ciclos, nada recuperado)
GC activado26,598 (el GC generacional actuó en mitad del bucle)
Después de forzar gc.collect()0 (todo recuperado)
Control sin ciclos (lista de cadenas, GC desactivado)delta 0

Con el GC apagado, del soup no recuperó nada — los 120.900 objetos siguieron residentes, porque el ciclo de referencias derrota al conteo de referencias. Un único gc.collect() eliminó todos. El grupo de control sin ciclos (una lista simple de cadenas, conocida por no tener ciclos) mantuvo un delta de cero, lo que demuestra que la acumulación venía del ciclo de bs4 y no de ruido de medición. La propia documentación de bs4 dice que los objetos están “densely interconnected ... exactly the sort a garbage collector would have trouble with”, así que este comportamiento está documentado; lo que aporta la prueba es el conteo exacto de objetos retenidos y la demostración de que collect() los lleva a cero.

La regla práctica: en una tubería que analiza muchas páginas grandes en un bucle apretado, si tu código (o algún ajuste de alto rendimiento) desactiva el GC o no lo dispara con suficiente frecuencia, los árboles de bs4 se quedarán más tiempo del debido y la memoria irá subiendo. Llama a soup.decompose() después de cada página — bs4 lo proporciona precisamente para romper el ciclo y liberar antes. Los árboles en C de selectolax y lxml no tienen este problema.

Codificación: UnicodeDammit es la ventaja silenciosa de bs4

bs4 incluye un componente que los parsers rápidos no traen: UnicodeDammit, que detecta la codificación de un documento y lo convierte automáticamente a Unicode. Le pasé una matriz de 8 casos de “codificación declarada vs. real”:

BeautifulSoup UnicodeDammit recovers 5 of 8 encoding cases

CasoCodificación realAdivinó UnicodeDammit¿Recuperó?
utf8_no_declutf-8utf-8
utf16_bomutf-16utf-16le
gbk_chinesegbkgb18030Sí (superconjunto)
shiftjisshift_jiscp932Sí (superconjunto)
latin1_declared_utf8latin-1 (declarado utf-8)iso-8859-1Sí (ignoró la mentira)
latin1_no_decllatin-1cp720No
cp1252_no_declcp1252cp862No
utf8_declared_latin1utf-8 (declarado latin-1)iso-8859-1No (siguió la mentira)

Recuperó cinco de ocho. UTF-8, UTF-16 con BOM, GBK, Shift-JIS e incluso latin-1 mal etiquetado salieron bien, y las adivinanzas de superconjunto (GBK→gb18030, Shift-JIS→cp932) también decodifican correctamente. Hay dos modos de fallo que conviene conocer: muestras cortas de bytes latin-1/cp1252 se confunden con páginas de códigos DOS, porque el detector estadístico no es fiable con entradas cortas y los caracteres de dibujo de DOS se solapan con los puntos de código de Latin-1; y cuando una declaración <meta charset> es directamente falsa, UnicodeDammit se la cree. La documentación de bs4 señala ambos casos: una muestra puede ser “so short that Unicode, Dammit can't get a lock on it”, y cuanta más información haya, mejor será la adivinanza.

Frente a selectolax, que corrompe silenciosamente bytes no UTF-8 y espera que tú los decodifiques por tu cuenta, esta es una ventaja real: bs4 al menos intenta detectar la codificación y a menudo acierta. Pero no es una garantía. Si conoces la codificación, no adivines: sé explícito y usa BeautifulSoup(bytes, from_encoding="...").

¿De verdad discrepan los backends en páginas reales?

La matriz de HTML malformado muestra discrepancias entre backends con entradas rotas a propósito. La siguiente pregunta obvia es si eso importa en el mundo real, así que ejecuté los tres backends sobre 11 páginas reales descargadas — BBC, Wikipedia, Craigslist, MDN, old.reddit, la documentación de Python, Hacker News, Books to Scrape, webscraper.io, whitehouse.gov y una página de citas renderizada por JS — comparando recuentos de enlaces, encabezados e imágenes.

Los tres coincidieron en las 11 páginas. Cero divergencia. Eso significa que la discrepancia entre backends de la sección de la trampa aparece solo con HTML deliberadamente malformado; cuando un sitio moderno de producción está suficientemente bien estructurado — incluso si es “desordenado” — la elección del backend no cambia lo que extraes. La lectura práctica: para sitios convencionales y bien formados, html.parser va perfectamente y te ahorra una dependencia. Solo cuando haces scraping de HTML antiguo, hecho a mano o visiblemente no estándar, la elección del backend empieza a cambiar el resultado, y ahí es cuando conviene pasar a lxml o html5lib.

Un apunte de esa ejecución, porque es un caso límite real. La página de MDN contiene un elemento <template>, y todos los backends de bs4 devolvieron 508 enlaces — lo que significa que bs4 aplana el contenido de <template> dentro del árbol principal. Eso coloca a bs4 del mismo lado que lxml, y en el opuesto de selectolax-Lexbor, que sigue estrictamente la especificación HTML5 (un <template> es un DocumentFragment inerte) y devuelve 497, omitiendo silenciosamente los 11 enlaces dentro de la plantilla. Así que bs4 sí captura datos dentro de un <template> — útil, pero también una forma de recoger contenido “fantasma” que un navegador nunca renderizaría. Ningún comportamiento es incorrecto; son interpretaciones distintas de la especificación, y conviene saber cuál estás recibiendo.

Dónde encaja BeautifulSoup — y dónde no

En lugar de convertir todo esto en una puntuación única de 0 a 100 — que ocultaría justo los intercambios que importan — aquí tienes una evaluación por dimensiones, con una advertencia en cada fila:

DimensiónQué encontraron las pruebasMatiz para el lector
Instalación / primera ejecuciónEnvoltorio puro, sin navegador ni setup; html.parser sin dependencias; todas las wheels son precompiladasEl backend lxml requiere una dependencia en C
Velocidad frente a parsers en C12–17x más lento (html.parser) / 10.5–14x (backend lxml), en todos los tamañosUn solo rig; datos de selectolax reutilizados
Rendimiento de consultas CSS~6–7.5x más lento en 100k nodos; el backend lxml no lo rescataReutilizado; paga el peaje del objeto Python Tag
Memoria1.5–1.75x la de selectolax/lxml; la más pesadaReutilizado; medido por RSS
Arranque en frío al importar2.36x más lento (33.4 frente a 14.1 ms)Reutilizado; dato pequeño
Escalado con hilosbs4-lxml ~3.9x más lento con 4 hilos (mantiene el GIL)Una sola observación; usa multiprocessing
Ergonomía de API29/29 pruebas; find con predicado de función + navegación bidireccionalTrampa con atributos booleanos como cadena vacía y límites de palabra con strip
Cobertura CSSsoupsieve es el más completo: 41/41 base + 20/20 extendido; soporta :langSin XPath, sin ::text
Tolerancia entre 3 backendslxml/html5lib 15/15; html.parser 12/15La divergencia solo aparece en HTML malformado
Consistencia en páginas reales3 backends coinciden 11/11; todos aplanan <template> (508)En sitios bien formados, el backend no importa
Gestión de ciclos de referenciaEl árbol forma un ciclo; 300 bucles retuvieron 120,900 objetos, collect() los dejó en ceroLos bucles largos necesitan decompose()
CodificaciónUnicodeDammit recupera 5/8; se equivoca con muestras cortas y sigue declaraciones falsasUna sola observación
MantenimientoActivo (4.15.0, junio de 2026); MITVive en crummy/Launchpad, no en GitHub

Entonces, ¿para quién es BeautifulSoup? Para quien valora una API legible y un parseo tolerante por encima del rendimiento bruto, trabajando con volúmenes moderados: prototipos, scrapers puntuales, herramientas internas, equipos donde el tiempo de desarrollo cuesta más que el tiempo de ejecución. ¿Quién debería mirar otra cosa? Tuberías de millones de páginas donde el impuesto de velocidad se convierte en dinero real, cargas que necesitan paralelismo por hilos y cualquiera que dependa de XPath.

Una nota sobre cómo encaja esto en una pila real de scraping, y dónde entra nuestra propia herramienta. BeautifulSoup asume que ya tienes el HTML. No descarga páginas, no renderiza JavaScript y no hace nada contra defensas anti-bot o CAPTCHAs — eso es otra capa completamente distinta, y en la web actual es un problema de verdad. Aquí es donde una API de scraping con IA se sitúa en otra capa: el stack para desarrolladores de Thunderbit — una API REST, un servidor MCP y una CLI — se encarga de la captura, el renderizado JS y el problema anti-bot, y luego devuelve Markdown limpio (POST /distill) o JSON estructurado que coincide con un esquema (POST /extract) sin que tengas que escribir selectores. No compiten; se complementan. bs4 parsea el HTML que ya tienes; la API, MCP y CLI de Thunderbit te consiguen el HTML al que no puedes llegar fácilmente de entrada. Si tu cuello de botella es el parseo, bs4 es una buena respuesta. Si tu cuello de botella es la obtención, eso pertenece a otra capa.

Prueba Thunderbit para la extracción de datos web

Conclusión

BeautifulSoup te da la API más amigable, la tolerancia más fuerte frente a HTML malformado y el motor CSS más completo de esta comparación — a cambio de un coste de velocidad de aproximadamente un orden de magnitud y la huella de memoria más alta. Ese es todo el intercambio, dicho sin rodeos. El backend por defecto html.parser es la única trampa real: desfigura en silencio tablas y listas sin cerrar, así que pasa "lxml" o "html5lib" siempre que tu entrada pueda venir fea. Los hilos no lo acelerarán — multiprocessing sí. Y en bucles largos, llama a decompose() en cada página para evitar que los ciclos de referencia se acumulen.

Dos limitaciones para cerrar. Todo esto se midió en una sola plataforma (macOS arm64, Python 3.14, wheels precompiladas), y los multiplicadores de tiempo se reutilizan del banco de pruebas de selectolax (misma prueba, estado a 2026-07-13) en lugar de ejecutarse de nuevo — así que heredan esa limitación de una sola plataforma, y una configuración Linux x86_64 o compilada desde código fuente podría mover ligeramente las cifras exactas. Y nada de lo observado aquí es un descubrimiento novedoso: bs4 es una biblioteca con 20 años de historia, así que cada comportamiento probado está documentado o registrado públicamente. El valor no está en destapar algo oculto. Está en poner una cifra real a intercambios que la documentación solo describe de forma cualitativa.

Preguntas frecuentes

¿BeautifulSoup es lento? Sí, y de forma medible. En una tarea de parseo más extracción, va aproximadamente entre 12 y 17 veces más lento que un parser en C como selectolax-Lexbor con el backend html.parser por defecto, y entre 10.5 y 14 veces más lento con el backend lxml, porque construye un objeto Python para cada nodo. Que eso importe o no depende de la escala: en una página de 1 MB son 232 ms frente a 15 ms, algo invisible para unos pocos miles de páginas pero decisivo en una tubería de un millón de páginas.

¿Qué parser de BeautifulSoup debo usar: html.parser, lxml o html5lib? Para sitios convencionales y bien formados, el html.parser por defecto va bien y no añade dependencias. Pero no implementa las etiquetas de cierre opcional de HTML5, así que en tablas o listas sin cerrar mezcla texto vecino sin avisar. Cuando tu entrada pueda venir malformada, escrita a mano o sea antigua, pasa explícitamente "lxml" o "html5lib" — ambos obtuvieron un 15/15 limpio en una matriz de HTML malformado, mientras html.parser se quedó en 12/15.

¿BeautifulSoup puede parsear en paralelo con hilos? No. La construcción del árbol en bs4 es puro Python y mantiene el GIL, así que añadir hilos lo hace más lento, no más rápido — en las pruebas, cuatro hilos procesando una página de 1 MB tardaron unas 3.9 veces más que un solo hilo. Para paralelizar bs4, usa multiprocessing (ProcessPoolExecutor). Las bibliotecas con núcleo en C, como selectolax y lxml, sí son las que aprovechan el paralelismo por hilos.

¿BeautifulSoup maneja bien el HTML roto? En general, sí — en un conjunto de muestras malformadas (etiquetas mal anidadas, ausencia de estructura base, atributos sin comillas y más), los tres backends se recuperaron correctamente. El único punto débil es html.parser por defecto y las etiquetas de cierre opcional: <td>/<li> sin cerrar se anidan en lugar de cerrarse, corrompiendo el texto extraído. Cambia al backend lxml o html5lib y ese problema desaparece.

BeautifulSoup frente a lxml — ¿cuál es mejor? Son herramientas distintas. lxml es mucho más rápido tanto construyendo el árbol como consultándolo, y además soporta XPath. BeautifulSoup envuelve lxml (entre otros) con una API mucho más amigable y, de hecho, tiene una cobertura CSS más amplia gracias a soupsieve. Eso sí: no esperes que el backend lxml convierta a bs4 en lxml-rapidez — el backend solo acelera el parseo, mientras que las consultas y el recorrido siguen pagando el coste del objeto Python por nodo de bs4, dejándolo unas 6–7.5 veces más lento en selecciones por lotes grandes.

Prueba Thunderbit para la extracción de datos web Get Started Free

Ke
Ke
CTO en Thunderbit | Científico de datos sénior y experto en ML Con casi una década de experiencia en aprendizaje automático y ciencia de datos, Ke Shen es exalumno de la Universidad de Columbia y antiguo científico de datos sénior en Walmart Labs. Con una sólida experiencia, reconocida por sus pares, en Python, R, Java y estadística, comparte conocimientos probados en el campo sobre cómo llevar algoritmos complejos de IA desde la teoría hasta una arquitectura lista para producción.
Tabla de contenidos
Thunderbit · Agente de datos web con IA

Extrae datos de cualquier página en 1 clic

Con la confianza de más de 250.000 usuarios
plan gratuito disponible
Extrae Datos Usando IA
Transfiere fácilmente datos a Google Sheets, Airtable o Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week