La mayoría de las configuraciones de scraping eligen una configuración de proxy una vez, en el momento del diseño, y la mantienen. Residencial con segmentación por ciudad para este sitio, proxies ISP para aquel otro, datacenter donde funciona. La elección suele hacerse probando unas pocas opciones durante una tarde, y rara vez se revisa hasta que algo falla.
Esa es una decisión tomada bajo incertidumbre, repetida miles de veces al día, con retroalimentación tras cada solicitud. Es exactamente la forma de problema que los estadísticos llaman un multi-armed bandit, y existen algoritmos bien conocidos para resolverlo. Esta guía explica el planteamiento, ofrece una implementación corta y probada, y muestra qué ocurrió en una simulación en la que la mejor opción cambió a mitad de camino.
Puntos clave
- Cada configuración de proxy que podrías usar para un objetivo es un “brazo”; cada solicitud es una tirada; una respuesta buena es una recompensa. El objetivo es el menor coste por respuesta buena, no la mayor tasa de éxito a cualquier precio.
- El muestreo de Thompson gestiona el equilibrio entre usar la mejor opción conocida y probar las demás, con unas pocas líneas de código y sin necesidad de un calendario de ajuste.
- Los objetivos cambian, así que la evidencia antigua debería desvanecerse. Un factor de descuento proporciona al selector una memoria de aproximadamente los últimos mil resultados.
- En nuestra simulación, el muestreo de Thompson con descuento quedó a un 2% de una estrategia que conocía las tasas reales de antemano, mientras que una elección fija “segura” costó un 25% más por respuesta buena y mantener al ganador temprano costó un 57% más.
- Cuenta como éxitos solo las respuestas genuinamente buenas, y limita cuánta exploración ve un objetivo sensible.
El planteamiento
Imagina una fila de máquinas tragaperras, cada una pagando con una probabilidad desconocida. Cada tirada te enseña algo sobre una máquina, y cada tirada dedicada a aprender sobre una máquina mala es una tirada que no se dedica a una buena. Esa tensión entre explotar lo que sabes y explorar lo que no sabes es el problema del multi-armed bandit.
Para la selección de proxies, la correspondencia es directa:
| Término del bandit | En scraping |
|---|---|
| Brazo | Una configuración de proxy para un objetivo: tipo de proxy, segmentación, ajuste de sesión |
| Tirada | Una solicitud |
| Recompensa | Una respuesta que contiene los datos que querías |
| Coste de una tirada | Ancho de banda, reintentos y tiempo dedicados a esa solicitud |
| No estacionariedad | El objetivo cambiando sus defensas, o la reputación de un pool desplazándose |
Dos características hacen que la versión de scraping sea más difícil que la del libro de texto. Los brazos cuestan cantidades distintas, así que el mejor brazo es el que tiene el menor coste por éxito, no la mayor tasa de éxito. Y las tasas cambian: una configuración que funciona hoy puede estar bloqueada mañana, que es el patrón detrás del contagio de subred y la razón para construir una puntuación de salud del objetivo.
El selector
El muestreo de Thompson mantiene una distribución de probabilidad sobre la tasa de éxito de cada brazo, una distribución Beta construida a partir de sus éxitos y fracasos. Antes de cada solicitud, extrae una tasa plausible para cada brazo y elige el brazo que parece más barato por éxito bajo esos valores extraídos. Los brazos con poca evidencia tienen distribuciones amplias, así que ocasionalmente extraen una tasa alta y se prueban. Los brazos con mucha evidencia extraen valores cercanos a su tasa real. La exploración se desvanece por sí sola a medida que se acumula la evidencia.
La versión de abajo añade dos cosas: coste por intento, y un descuento que decae la evidencia antigua hacia el valor previo para que el selector pueda notar el cambio.
import random
class ProxySelector:
"""Pick a proxy configuration per request with Thompson sampling, minimising cost per successful response.
Each configuration keeps a Beta(successes, failures) belief about its success rate. A discount below 1
lets old outcomes fade, so the selector notices when a configuration that used to work starts failing.
"""
def __init__(self, costs, discount=0.999, prior=(1.0, 1.0), rng=None):
self.costs = dict(costs) # configuration -> cost per attempt
self.discount = discount
self.prior = prior
self.wins = {arm: prior[0] for arm in self.costs}
self.losses = {arm: prior[1] for arm in self.costs}
self.rng = rng or random.Random()
def choose(self):
"""Sample a plausible success rate for each configuration and take the cheapest per expected success."""
def sampled_cost_per_success(arm):
rate = self.rng.betavariate(self.wins[arm], self.losses[arm])
return self.costs[arm] / max(rate, 1e-9)
return min(self.costs, key=sampled_cost_per_success)
def update(self, arm, success):
"""Record one outcome. Every belief decays towards the prior first, so recent results count most."""
for a in self.costs:
self.wins[a] = self.prior[0] + self.discount * (self.wins[a] - self.prior[0])
self.losses[a] = self.prior[1] + self.discount * (self.losses[a] - self.prior[1])
if success:
self.wins[arm] += 1
else:
self.losses[arm] += 1
En uso, llama a choose() antes de cada solicitud, envía la solicitud a través de la configuración elegida, y llama a update() indicando si la respuesta fue buena. Un descuento de 0.999 da una memoria efectiva de alrededor de mil resultados; 0.995 alrededor de doscientos.
La simulación
Para ver cómo se comporta el selector cuando la respuesta cambia, simulamos un objetivo con cuatro configuraciones. Las tasas de éxito y los costes de abajo son supuestos elegidos para hacer visibles los compromisos, no mediciones de ningún proveedor o red:
| Configuración | Tasa de éxito supuesta | Coste supuesto por intento | Coste por éxito |
|---|---|---|---|
| Datacenter | 25% | 0.5 | 2.00 |
| ISP | 80%, cayendo a 30% tras la solicitud 5,000 | 0.9 | 1.13, luego 3.00 |
| Residencial, segmentación por país | 92% | 1.3 | 1.41 |
| Residencial, segmentación por ciudad | 95% | 1.6 | 1.68 |
Los costes son unidades relativas que cubren el ancho de banda y la sobrecarga de un intento. Durante las primeras 5,000 solicitudes, ISP es la forma más barata de obtener una respuesta buena; luego el objetivo empieza a bloquearlo, y residencial con segmentación por país se convierte en la mejor opción. Cada estrategia realizó 20,000 solicitudes, y repetimos cada estrategia 100 veces con semillas aleatorias distintas.
| Estrategia | Coste por respuesta buena | Frente a la clarividencia perfecta | Respuestas buenas | Solicitudes para adaptarse tras el cambio |
|---|---|---|---|---|
| Clarividencia perfecta (conoce las tasas reales) | 1.348 | referencia | 17,806 | 0 |
| Muestreo de Thompson, descuento 0.999 | 1.375 | +2.0% | 16,694 | unas 800 |
| Muestreo de Thompson, descuento 0.995 | 1.389 | +3.0% | 15,704 | unas 1,075 |
| Muestreo de Thompson, sin descuento | 1.428 | +5.9% | 15,933 | unas 3,500 |
| Epsilon-greedy, 10% de exploración | 1.441 | +6.9% | 15,848 | unas 2,800 |
| Muestreo de Thompson, descuento 0.98 | 1.443 | +7.0% | 13,834 | nunca se estabilizó |
| Siempre residencial, segmentación por ciudad | 1.684 | +24.9% | 19,000 | no aplicable |
| Round robin entre las cuatro | 1.689 | +25.3% | 12,731 | no aplicable |
| Siempre ISP, el ganador temprano | 2.118 | +57.1% | 8,501 | no aplicable |
Las cifras son promedios de 100 ejecuciones; para cada estrategia, el 90% central de las ejecuciones abarcó menos del 2.5% de su promedio. “Solicitudes para adaptarse” es el número mediano de solicitudes tras el cambio hasta que el 90% de una ventana de 200 solicitudes fue a la nueva mejor configuración.
Lo que dicen los resultados
Las elecciones fijas son caras en ambas direcciones. Usar siempre la configuración más fiable produjo más respuestas buenas, pero pagó un 25% más por cada una. Usar siempre la configuración que ganó pronto fue la peor estrategia de todas una vez que el objetivo cambió, con un 57% por encima de la referencia.
Aprender no basta sin olvidar. El muestreo de Thompson sin descuento necesitó unas 3,500 solicitudes tras el cambio para dejar de confiar en la evidencia que había acumulado sobre el antiguo ganador. Un descuento de 0.999 redujo eso a unas 800.
Olvidar demasiado rápido es su propio problema. Con un descuento de 0.98, el selector solo recordaba unos 50 resultados, seguía reprobando las opciones malas, y nunca se estabilizó. El rango útil depende del tráfico: la memoria debe cubrir suficientes solicitudes para distinguir las configuraciones, y no más.
El objetivo decide la respuesta. El bandit minimizó el coste por respuesta buena. Si lo que importa es la mayor cantidad de datos sin importar el coste, o un plazo, la recompensa tiene que decirlo; de lo contrario, el selector optimizará lo incorrecto de forma muy eficiente.
Usándolo en tráfico real
- Define el éxito estrictamente. Un HTTP 200 con una página de bloqueo o resultados vacíos es un fracaso. Alimenta al selector con las mismas comprobaciones que usas para la tasa de fallo silencioso, o aprenderá a preferir configuraciones que fallan en silencio.
- Ejecuta un selector por objetivo. Las tasas de éxito difieren según el sitio, así que un selector compartido promedia precisamente las diferencias que quieres que aprenda.
- Limita la exploración en objetivos sensibles. Cada solicitud exploratoria a través de una configuración mala es una solicitud que un sitio puede contar en tu contra. Elimina las configuraciones que sean claramente inadecuadas en lugar de dejar que el selector lo redescubra.
- Mantén las sesiones coherentes. Elige la configuración por sesión, no por solicitud, para cualquier cosa que dependa de estado, como inicios de sesión o carritos, como explica sesiones persistentes frente a rotativas.
- Usa costes reales. En proxies facturados por ancho de banda, el coste por intento depende del peso de la página y de los reintentos; las cifras detrás del coste por registro limpio son los insumos correctos.
- Mantén un mínimo de balanceo de carga. Un bandit decide qué configuración usar, no cómo repartir las solicitudes dentro de ella; eso sigue siendo tarea de la arquitectura de rotación, concurrencia y reintentos.
La conclusión
Elegir una configuración de proxy una vez y mantenerla es una apuesta a que ni tus costes ni el objetivo cambiarán. Tratar cada solicitud como un pequeño experimento, con el muestreo de Thompson y una memoria sensata, convierte esa apuesta en una medición que se actualiza continuamente.
En nuestra simulación, eso quedó a un 2% de conocer la respuesta de antemano, se adaptó en unas 800 solicitudes cuando la mejor opción cambió, y evitó la prima del 25% al 57% de las estrategias fijas. Las tasas y los costes eran supuestos; el código es lo bastante corto como para ejecutarlo con los tuyos propios.
Fuentes y referencias
- Daniel Russo y colegas, A Tutorial on Thompson Sampling, 2017.
- Simulación ejecutada por Shifter el 2 de octubre de 2026 usando el código anterior; las tasas de éxito y los costes son supuestos, no mediciones de ninguna red.