Bloqueo de recursos en Playwright: Lo que realmente ahorra
Resumen:
- "Bloquear imágenes para ahorrar ancho de banda" depende completamente de la página. Medido en tres páginas, bloquear imágenes, medios y fuentes redujo la transferencia en un 67% en un catálogo cargado de imágenes, aproximadamente en un 25% en una página de artículo, y casi nada en un listado solo de texto que no tiene imágenes.
- En la página solo de texto, la hoja de estilos era el peso. Agregar
stylesheeta la lista de bloqueo la redujo a 2,121 bytes — solo el documento HTML — porque tres de sus cuatro solicitudes son CSS. - Los ahorros en bytes son confiables; los ahorros en tiempo no. Cada perfil bloqueado transfirió menos, pero el tiempo en reloj de pared varió en ambas direcciones entre ejecuciones, porque interceptar cada solicitud tiene su propio costo.
- Bloquear no cuesta datos. El recuento de elementos extraídos fue idéntico en las nueve mediciones — 20 productos, 10 citas, 36 párrafos — así que no se desechó nada útil.
- Mide bytes absolutos, no porcentajes, y mide más de una vez. Una página aquí tiene un punto de referencia bimodal que hace que su ahorro aparente oscile entre el 1% y el 65% sin que su contenido cambie.
- Empieza gratis. El Scraping Browser tiene un nivel gratuito, y toda la medición en este post son nueve cargas de página.
Playwright se conecta al Scraping Browser de Scrapeless a través de un endpoint CDP de WebSocket, lo que significa que una sesión de navegador en la nube se comporta como una local — y se cobra como una remota. Cada imagen, fuente y hoja de estilos que tira una página es tráfico por el que pagaste.
El consejo estándar es bloquear imágenes. Ese consejo se repite en todas partes sin un número adjunto, así que este post le añade uno: el mismo script, tres páginas, tres perfiles de bloqueo, bytes contados desde el protocolo.
Lo que realmente cuesta una sesión de navegador
Un navegador obtiene todo lo que un humano vería. Para la extracción, la mayor parte de eso es desperdicio — nadie analiza una fuente web. El peso de la página en la web se rastrea públicamente en el informe de peso de la página de HTTP Archive, y cifras agregadas como esas son sobre las que se basa el consejo de bloquear imágenes.
Las agregaciones son donde ese consejo se equivoca. No raspas la página mediana; raspas un objetivo, cuya mezcla de activos puede no parecerse en nada a la mediana.
Prerrequisitos
- Python 3.9 o posterior y
pip install playwright - Una clave API de Scrapeless en la variable de entorno
SCRAPELESS_API_KEY
No se necesita descarga local del navegador. connect_over_cdp se conecta a una sesión remota, por lo que playwright install no es parte de este flujo de trabajo.
Conectar y contar bytes
Playwright se conecta a través de CDP, y una sesión CDP en bruto abierta en la misma página cuenta lo que cruza el cable:
python
browser = await playwright.chromium.connect_over_cdp(ENDPOINT)
page = await browser.new_page()
cdp = await page.context.new_cdp_session(page)
await cdp.send("Network.enable")
transferred = {"bytes": 0}
cdp.on(
"Network.loadingFinished",
lambda event: transferred.__setitem__(
"bytes", transferred["bytes"] + event.get("encodedDataLength", 0)
),
)
encodedDataLength es la medición que importa. El dominio de red del Protocolo de DevTools de Chrome lo define como el número total de bytes recibidos para una solicitud, por lo que cuenta la transferencia comprimida en lugar del tamaño del documento descomprimido. Eso es lo que un proxy mide y lo que refleja una factura de ancho de banda.
Sumándolo sobre Network.loadingFinished da un número por carga de página, sin ninguna estimación en ninguna parte de la cadena.
Añadir la capa de enrutamiento
Bloquear es una decisión de enrutamiento realizada por cada solicitud:
python
BLOCKED = {"imagen", "media", "fuente"}
async def router(route):
if route.request.resource_type in BLOCKED:
await route.abort()
else:
await route.continue_()
await page.route("**/*", router)
Cada solicitud ahora pasa a través de router, que o bien la aborta o le permite proceder — el contrato del manejador en la API de enrutamiento de la página de Playwright. Un manejador que no hace ninguna de las dos detiene la solicitud hasta que la navegación se agote, por lo que cada rama debe terminar en abort o continue_.
resource_type proviene de la propia clasificación del navegador de por qué se realizó una solicitud, el mismo concepto que el Fetch Standard llama destino de solicitud. Hacer coincidir sobre esto es más duradero que hacer coincidir URL por extensión, porque una fuente servida desde /assets/a8f3c2 sin extensión aún se clasifica como fuente.
Esta es la imagen reflejada de leer tráfico en lugar de detenerlo; para extraer datos de las solicitudes que hace una página, consulte interceptar la API JSON oculta de una página.
Medir Tres Páginas
El script completo carga cada objetivo bajo tres perfiles y muestra los bytes, ahorros, tiempo y el número de elementos aún extraíbles. Dos elementos contrastantes son suficientes para hacer el punto; agregue su propio objetivo a TARGETS para medirlo de la misma manera:
python
import asyncio
import os
import time
from playwright.async_api import async_playwright
ENDPOINT = (
"wss://browser.scrapeless.com/api/v2/browser"
f"?token={os.environ['SCRAPELESS_API_KEY']}&session_ttl=300&proxy_country=US"
)
TARGETS = [
("books.toscrape.com", "https://books.toscrape.com/", "article.product_pod"),
("quotes.toscrape.com", "https://quotes.toscrape.com/", "div.quote"),
]
PROFILES = {
"baseline": set(),
"media-only": {"image", "media", "font"},
"media+css": {"image", "media", "font", "stylesheet"},
}
PAUSE_SECONDS = 5
async def measure(playwright, url, selector, blocked):
"""Cargar una página y devolver (bytes sobre la red, segundos, elementos coincidentes).
Cada medición obtiene su propia conexión, por lo que no se comparte caché HTTP entre
perfiles; una caché caliente subestimaría los bytes que realmente cuesta una carga fría.
"""
browser = await playwright.chromium.connect_over_cdp(ENDPOINT)
try:
page = await browser.new_page()
cdp = await page.context.new_cdp_session(page)
await cdp.send("Network.enable")
transferred = {"bytes": 0}
cdp.on(
"Network.loadingFinished",
lambda event: transferred.__setitem__(
"bytes", transferred["bytes"] + event.get("encodedDataLength", 0)
),
)
if blocked:
async def router(route):
if route.request.resource_type in blocked:
await route.abort()
else:
await route.continue_()
await page.route("**/*", router)
started = time.monotonic()
await page.goto(url, wait_until="load", timeout=120_000)
elapsed = time.monotonic() - started
matched = await page.locator(selector).count()
return transferred["bytes"], elapsed, matched
finally:
await browser.close()
async def main():
print(f"{'target':22}{'profile':12}{'bytes':>10}{'saved':>7}{'time':>8}{'matched':>9}")
async with async_playwright() as playwright:
for name, url, selector in TARGETS:
baseline_bytes = None
for profile, blocked in PROFILES.items():
# Espaciar las cargas: esto abre una nueva sesión por medición,
# y nueve cargas de página consecutivas no son educadas para el objetivo.
await asyncio.sleep(PAUSE_SECONDS)
transferred, elapsed, matched = await measure(playwright, url, selector, blocked)
if baseline_bytes is None:
baseline_bytes = transferred
saved = round((1 - transferred / baseline_bytes) * 100)
print(f"{name:22}{profile:12}{transferred:>10,}{saved:>6}%{elapsed:>7.2f}s{matched:>9}")
if __name__ == "__main__":
asyncio.run(main())
Una ejecución de esto:
text
target profile bytes saved time matched
books.toscrape.com baseline 337,691 0% 4.22s 20
books.toscrape.com media-only 111,939 67% 5.62s 20
books.toscrape.com media+css 76,329 77% 2.56s 20
quotes.toscrape.com baseline 26,731 0% 5.86s 10
quotes.toscrape.com media-only 26,739 0% 5.22s 10
quotes.toscrape.com media+css 2,125 92% 3.12s 10
Tenga en cuenta las dos filas bloqueadas de quotes.toscrape.com: bloquear imágenes, medios y fuentes no ahorró nada en absoluto allí, y bloquear hojas de estilo además de eso ahorró casi todo. La siguiente sección explica por qué, y por qué la misma página a veces informa un 65% de ahorro de ese mismo perfil de medios en su lugar.
Lo que Dicen los Números
Los porcentajes son la forma natural de leer esto, y son la cosa menos estable en ello. Los perfiles bloqueados son altamente repetibles; las líneas de base no lo son, y el porcentaje es una relación de los dos.
La página del catálogo es casi perfectamente repetible: sus tres mediciones base se ubican dentro del 0.004% entre sí. El artículo de la enciclopedia varió alrededor del 7% entre ejecuciones. Y el listado de texto resultó tener dos modos: la mayoría de las cargas transfieren aproximadamente 26,700 bytes, pero aproximadamente una carga de cada tres transfiere alrededor de 75,820, porque el navegador a veces recupera los binarios de fuentes a los que hace referencia la hoja de estilo y otras veces no. Su "ahorro" medido de medios bloqueados fluctúa entre el 1% y el 65% solo con esa base, con el contenido real de la página sin cambios.
Así que primero lee las columnas absolutas. Medianas de tres ejecuciones, con un artículo real de enciclopedia medido de la misma manera agregado como un tercer punto de datos:
| Página | Base | Medios bloqueados | Medios bloqueados + CSS | Elementos extraídos |
|---|---|---|---|---|
| books.toscrape.com — catálogo de imágenes | 337,692 B | 111,970 B | 76,237 B | 20 / 20 / 20 |
| quotes.toscrape.com — listado de texto | 27,004 B | 26,725 B | 2,121 B | 10 / 10 / 10 |
| en.wikipedia.org — artículo | 473,437 B | 359,075 B | 358,770 B | 36 / 36 / 36 |
La página del catálogo pierde dos tercios de su peso por una lista de bloque en tres tipos, y diez puntos más por las hojas de estilo: un recorte del 67% y 77% sobre una base lo suficientemente estable como para confiar en esas cifras.
El listado de texto apenas se mueve cuando se bloquean los medios, luego colapsa a 2,121 bytes cuando las hojas de estilo también se bloquean. Ese número es el documento HTML por sí solo, y fue idéntico en cada ejecución de cada perfil que bloqueó CSS. Un desglose por solicitud explica el por qué: la página realiza cuatro solicitudes, y tres de ellas son hojas de estilo que totalizan aproximadamente 24,500 bytes frente a 2,121 bytes de marcado. No hay imágenes en absoluto.
La página del artículo cede un cuarto a la блокировка de medios y luego nada medible a CSS: la mediana se movió 305 bytes mientras que las ejecuciones de media+css variaron hasta 29,000. En esta página, bloquear hojas de estilo no es un ahorro.
La extracción sobrevivió en todas partes. 20 productos, 10 citas y 36 párrafos regresaron bajo cada perfil, así que ninguno de estos ahorros costó un campo.
Los tiempos merecen su propia advertencia. Cada perfil resultó ser más rápido que su base en la ejecución impresa arriba, que es exactamente el resultado que te tentaría a prometer un aumento de velocidad. En el conjunto de medianas no se sostiene: la página del catálogo tomó 4.66s en media+css frente a un base de 3.90s, y Wikipedia tomó 6.57s en media-only contra 5.86s — ambos más lentos mientras transfieren menos. Enrutando cada solicitud a través de un callback de Python añade un viaje de ida y vuelta por solicitud, y ese coste es independiente de cuántos bytes hubieran llevado los activos bloqueados. Trata la reducción de bytes como la victoria confiable y la latencia como un efecto secundario a medir por objetivo.
Empezar no necesita tarjeta: el plan gratuito cubre una medición de nueve cargas como esta.
Elegir un Perfil por Objetivo
La medición toma aproximadamente un minuto por objetivo y reemplaza la conjetura:
- Ejecuta la base y un perfil bloqueado contra una página representativa — un listado de categoría, no la página de inicio, ya que las mezclas de activos difieren en un sitio.
- Bloquea
image,mediayfontpor defecto. Estas nunca son analizadas, y el inconveniente está limitado. - Prueba
stylesheetpor separado en lugar de asumir. Fue la mayor victoria única en una página aquí y irrelevante en otra. La extracción dependiente del diseño es la que hay que comprobar: los selectores clave a clases y estructuras no se ven afectados, pero cualquier cosa que dependa de la geometría calculada o la visibilidad no lo está. - Nunca bloquees
scriptodocumenten una página que necesites renderizar. Una página renderizada por el cliente construye su contenido con los scripts que estarías descartando. - Re-mide cuando un objetivo rediseña. El perfil está ajustado a una mezcla de activos, y las mezclas de activos cambian.
Donde una página necesita renderizado pero no interacción, una llamada de renderizado HTTP evita completamente la sesión del navegador: la página del producto Scraping Browser y la documentación de conexión cubren cuándo una sesión completa justifica su costo.
Solución de Problemas
La navegación se agota tan pronto como se habilita el enrutamiento. Una ruta de código a través del controlador termina sin llamar a abort o continue_. Cada rama, incluyendo la ruta de excepción, tiene que resolver la ruta.
Las solicitudes bloqueadas todavía aparecen en el conteo de bytes. Network.enable se envió después de que se registró page.route, o en una sesión CDP diferente a la de la página que se mide. Crea la sesión desde el propio contexto de la página y habilita el dominio antes de navegar.
La página se muestra en blanco y los selectores no coinciden con nada. script o document están en la lista de bloqueo. Ambos son necesarios en una página renderizada por el cliente.
Los conteos de bytes varían en más de unos pocos por ciento entre ejecuciones. La página no está obteniendo los mismos activos en cada carga. En la lista de texto medida aquí, los binarios de fuentes referenciados por una hoja de estilo se descargaron en algunas cargas y no en otras, moviendo la línea base de aproximadamente 26,700 bytes a aproximadamente 75,820. Toma una mediana a través de varias ejecuciones y compara bytes absolutos en lugar de porcentajes, ya que una línea base móvil distorsiona la relación.
Los ahorros parecen grandes, pero los registros faltan. Compara el conteo extraído con la línea base antes de confiar en un perfil, como hace el script anterior. Un perfil que reduce bytes y registros no es una optimización.
Conclusión
El ancho de banda crece con cada página que recopilas, y a diferencia de la mayoría de los costos, se puede medir en aproximadamente un minuto. Dos líneas de CDP producen un número que justifica o no una lista de bloqueo para un objetivo dado.
Lo que los números aquí argumentan en contra es un predeterminado. Bloquear imágenes fue decisivo en una página, moderado en otra e irrelevante en una tercera que no tiene imágenes, mientras que la mayor ganancia única en el conjunto provino de bloquear hojas de estilo en exactamente esa tercera página. Ejecuta la línea base en tu propio objetivo antes de adoptar la lista de bloqueo de nadie, incluida esta, y ejecútala más de una vez, porque una de estas tres páginas no da la misma respuesta dos veces.
¿Listo para medir tus propios objetivos? Crea una cuenta gratuita de Scrapeless, exporta tu clave y ejecuta el script contra una página que ya coleccionas. Los límites del plan están en la página de precios y la configuración de proxy cubre el lado de salida de la misma factura.
FAQ
P: ¿Qué tipos de recursos son seguros de bloquear al raspar?
image, media y font son seguros en casi cualquier trabajo de extracción, porque ningún parser los lee. stylesheet es seguro siempre que tus selectores dependan de clases y estructura en lugar de diseño calculado, lo que cubre la mayor parte del raspado, y fue el mayor ahorro único en estas mediciones. Deja script, document, xhr y fetch intactos en cualquier página que construya contenido del lado del cliente.
P: ¿Cuánto ancho de banda ahorra bloquear imágenes?
Depende completamente de la página, por lo que una única cifra es engañosa. La misma lista de bloqueo medida aquí ahorró un 67% en un catálogo de imágenes, aproximadamente un cuarto en un artículo de enciclopedia y esencialmente nada en una lista de solo texto que no tiene imágenes. Ejecuta una línea base contra tu propio objetivo y compara bytes absolutos: un minuto de medición supera cualquier porcentaje publicado.
P: ¿Bloquear recursos hace que el raspado sea más rápido?
Menos confiablemente de lo que reduce bytes. Cada perfil aquí transfirió menos que su línea base, pero los tiempos de reloj avanzaron en ambas direcciones, porque enrutar cada solicitud a través de un controlador cuesta un viaje de ida y vuelta. En páginas con pocos activos pesados, ese costo adicional puede superar los ahorros, así que trata la latencia como algo que verificar en lugar de asumir.
P: ¿Debería usar page.route o Network.setBlockedURLs de CDP?
page.route coincide con el tipo de recurso, que es lo que normalmente deseas, y mantiene la lógica en Python, donde es fácil cambiar por objetivo. Los comandos de bloqueo de CDP coinciden con patrones de URL, por lo que son adecuados para bloquear un host conocido específico, digamos un punto final de análisis, en lugar de una clase completa de activos. Los dos se pueden combinar si necesitas ambos.
P: ¿Bloquear recursos hará que una página se comporte de manera diferente a un navegador real?
Sí, en el sentido de que una sesión que nunca solicita imágenes no está haciendo lo que normalmente hace un navegador. Mantén las listas de bloqueo a lo que tu extracción realmente necesita, y si el comportamiento de un objetivo cambia después de habilitar el bloqueo, estrecha la lista y vuelve a medir en lugar de ampliarla.
P: ¿Cómo puedo contar bytes sin la sesión de CDP?
Puedes aproximarlo sumando las longitudes de los cuerpos de respuesta en un manejador de response de Playwright, pero esos son tamaños descomprimidos en lugar de bytes transferidos, y se pierden las solicitudes que fallan o que se sirven desde la caché. Una alternativa en la página es el atributo transferSize definido por la especificación de Resource Timing de W3C, que se lee a través de performance.getEntriesByType("resource"), que está más cerca de la cantidad correcta pero está sujeto a restricciones de origen cruzado que lo anulan para activos de terceros. encodedDataLength de Network.loadingFinished es lo que el propio navegador registró en la red para cada solicitud independientemente del origen, por lo que vale la pena las dos líneas adicionales.
En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.



