Si estás construyendo un pipeline de datos de criptomonedas, cómo scrapear la API REST de Binance con proxies es probablemente una de las primeras preguntas que te haces. Binance expone endpoints públicos de mercado que no requieren autenticación, pero aplica un sistema de rate limiting basado en pesos por IP que puede bloquear tu scraper en minutos si no gestionas correctamente la rotación de IPs.
Aviso de cumplimiento: Esta guía cubre únicamente datos públicos de mercado (precios, velas, order book). No aborda endpoints autenticados ni trading. Respeta los pesos de la API, los términos de servicio de Binance y las leyes locales aplicables. Donde los términos de Binance requieran acceso oficial via API, úsalo. Consulta los Términos de Uso de Binance antes de implementar cualquier recolección de datos.
Endpoints públicos de mercado y sistema de pesos de Binance
La API REST de Binance publica datos de mercado bajo /api/v3/. Los cuatro endpoints clave para scraping de datos públicos son:
| Endpoint | Descripción | Peso (request weight) | Parámetros clave |
|---|---|---|---|
GET /api/v3/klines | Velas OHLCV (candlesticks) | 1–2 (según limit) | symbol, interval, limit (max 1000) |
GET /api/v3/depth | Order book (libro de órdenes) | 5–20 (según limit) | symbol, limit (5, 10, 20, 50, 100, 500, 1000) |
GET /api/v3/ticker/24hr | Estadísticas 24h por símbolo | 1–80 (según parámetros) | symbol (opcional) |
GET /api/v3/ticker/price | Precio actual de un símbolo | 1–2 | symbol (opcional) |
Cada respuesta incluye el header X-MBX-USED-WEIGHT-1M, que indica cuánto peso has consumido en el último minuto desde esa IP. Este header es tu principal mecanismo de feedback para ajustar la velocidad de scraping. Puedes consultar los detalles oficiales en la documentación oficial de la API de Binance.
¿Por qué el polling de depth agota el presupuesto rápidamente?
Binance asigna un presupuesto de aproximadamente 6000 pesos por minuto por IP. Si haces polling de /api/v3/depth con limit=100 (peso 10) cada segundo, consumes 600 pesos/minuto — solo 10% del presupuesto. Pero si necesitas limit=1000 (peso 20) a 5 requests/segundo, consumes 6000 pesos/minuto en un solo símbolo. Con varios símbolos en paralelo, agotas el presupuesto en segundos.
Rate limiting: pesos, HTTP 429, Retry-After y baneos IP
El sistema de rate limiting de Binance funciona en tres niveles:
- HTTP 429 (Too Many Requests): Tu IP superó el límite de peso. Debes respetar el header
Retry-Afterantes de reintentar. Según MDN Web Docs, este header indica los segundos que el cliente debe esperar. - HTTP 418 (I'm a teapot): Binance usa este código cuando una IP recibe múltiples 429 sin respetar
Retry-After. Implica un baneo temporal, típicamente de 2 a 5 minutos, que puede escalar a baneos permanentes si el patrón persiste. - HTTP 451 (Unavailable For Legal Reasons): Binance.com bloquea accesos desde EE.UU. por restricciones regulatorias. Esto se explica en el RFC 7725 que define este código de estado.
La estrategia para evitar estos bloqueos es distribuir el peso de los requests a través de múltiples IPs usando proxies residenciales rotativos. Cada IP recibe su propio presupuesto de 6000 pesos/minuto, por lo que con 10 IPs efectivamente tienes 60,000 pesos/minuto de capacidad.
Rotación de proxies con ProxyHat para Binance
ProxyHat ofrece proxies residenciales, móviles y datacenter accesibles vía gate.proxyhat.com. Para scraping de Binance, los proxies residenciales son ideales porque cada IP parece un usuario legítimo de ISP, reduciendo el riesgo de bloqueos heurísticos.
Configuración básica con curl
Empecemos con un ejemplo simple en curl usando el proxy HTTP de ProxyHat:
# Request básico a /api/v3/ticker/price vía ProxyHat (HTTP, puerto 8080)
curl -x http://user-country-US:pass@gate.proxyhat.com:8080 \
"https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT" \
-H "Accept: application/json" \
-D -
# El header X-MBX-USED-WEIGHT-1M aparecerá en la respuesta
# Para SOCKS5, usa el puerto 1080:
curl -x socks5://user-country-US:pass@gate.proxyhat.com:1080 \
"https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"
El flag -country-US en el username geo-orienta la IP de salida a Estados Unidos. Si necesitas acceder a Binance.com desde una región restringida, puedes cambiar a otro país: -country-DE, -country-JP, etc. Consulta las ubicaciones disponibles de ProxyHat para ver todos los países soportados.
Ejemplo 1: Python con requests y proxy raw
Aquí tienes un scraper básico que rota IPs por request usando el proxy HTTP de ProxyHat directamente:
import requests
import time
import random
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
logger = logging.getLogger(__name__)
PROXY_BASE = "http://user:pass@gate.proxyhat.com:8080"
BINANCE_BASE = "https://api.binance.com"
# Lista de símbolos a scrapear
SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT", "XRPUSDT"]
def get_proxy_url(country=None):
"""Genera URL de proxy con geo-targeting opcional."""
if country:
username = f"user-country-{country}"
else:
username = "user"
return f"http://{username}:pass@gate.proxyhat.com:8080"
def fetch_ticker_price(symbol, max_retries=3):
"""Obtiene el precio actual de un símbolo con reintentos."""
url = f"{BINANCE_BASE}/api/v3/ticker/price"
params = {"symbol": symbol}
for attempt in range(max_retries):
proxy_url = get_proxy_url()
proxies = {"http": proxy_url, "https": proxy_url}
try:
resp = requests.get(url, params=params, proxies=proxies, timeout=10)
# Verificar peso consumido
used_weight = resp.headers.get("X-MBX-USED-WEIGHT-1M", "0")
logger.info(f"{symbol} - Status: {resp.status_code}, Weight used: {used_weight}")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 5))
logger.warning(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
if resp.status_code == 418:
logger.error("IP banned (418). Backing off 120s.")
time.sleep(120)
continue
resp.raise_for_status()
return resp.json()
except requests.exceptions.RequestException as e:
wait = (2 ** attempt) + random.uniform(0, 1)
logger.warning(f"Attempt {attempt+1} failed: {e}. Retrying in {wait:.1f}s")
time.sleep(wait)
return None
# Ejecutar scraping de precios
for symbol in SYMBOLS:
data = fetch_ticker_price(symbol)
if data:
print(f"{data['symbol']}: ${float(data['price']):,.2f}")
time.sleep(0.1) # Throttle entre requests
Ejemplo 2: Python con ProxyHat SDK y rotación automática
El SDK de ProxyHat simplifica la gestión de rotación de IPs, sesiones sticky y geo-targeting. Aquí tienes el mismo scraper usando el SDK:
from proxyhat import ProxyHatClient
import requests
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Inicializar cliente del SDK
client = ProxyHatClient(
gateway="gate.proxyhat.com",
http_port=8080,
socks5_port=1080,
username="user",
password="pass"
)
BINANCE_BASE = "https://api.binance.com"
SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT", "XRPUSDT"]
def fetch_with_sdk(symbol, max_retries=3):
"""Usa el SDK de ProxyHat para rotar IP por request."""
url = f"{BINANCE_BASE}/api/v3/ticker/price"
params = {"symbol": symbol}
for attempt in range(max_retries):
# El SDK genera automáticamente una nueva IP por request
proxy_dict = client.get_http_proxy(country="US")
proxies = {
"http": proxy_dict["url"],
"https": proxy_dict["url"]
}
try:
resp = requests.get(
url, params=params, proxies=proxies, timeout=10,
headers={"Accept": "application/json"}
)
used_weight = resp.headers.get("X-MBX-USED-WEIGHT-1M", "0")
logger.info(f"{symbol} via IP {proxy_dict['ip']} - Weight: {used_weight}")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 5))
logger.warning(f"429 on {proxy_dict['ip']}. Rotating IP, waiting {retry_after}s")
time.sleep(retry_after)
continue
if resp.status_code == 418:
logger.error(f"IP {proxy_dict['ip']} banned. Rotating immediately.")
client.rotate()
time.sleep(60)
continue
resp.raise_for_status()
return resp.json()
except Exception as e:
wait = (2 ** attempt) * 0.5
logger.warning(f"Error: {e}. Retry in {wait}s")
time.sleep(wait)
return None
for symbol in SYMBOLS:
result = fetch_with_sdk(symbol)
if result:
print(f"{result['symbol']}: ${float(result['price']):,.2f}")
time.sleep(0.05)
Ejemplo 3: Backfill de klines con sesión sticky (httpx async)
Para backfills históricos de klines necesitas paginar con endTime. Una sesión sticky mantiene la misma IP durante toda la sesión, evitando que Binance vea saltos de IP sospechosos en medio de una secuencia de paginación:
import httpx
import asyncio
import logging
from datetime import datetime, timezone
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
BINANCE_BASE = "https://api.binance.com"
SESSION_ID = "klines-backfill-btc-001"
# Proxy con sesión sticky: misma IP durante toda la sesión
PROXY_URL = f"http://user-session-{SESSION_ID}-country-DE:pass@gate.proxyhat.com:8080"
async def fetch_klines_page(symbol, interval, end_time, limit=1000):
"""Obtiene una página de klines usando sesión sticky."""
url = f"{BINANCE_BASE}/api/v3/klines"
params = {
"symbol": symbol,
"interval": interval,
"endTime": end_time,
"limit": limit
}
async with httpx.AsyncClient(
proxy=PROXY_URL,
timeout=15.0,
headers={"Accept": "application/json"}
) as client:
for attempt in range(4):
try:
resp = await client.get(url, params=params)
weight = resp.headers.get("X-MBX-USED-WEIGHT-1M", "0")
logger.info(f"Klines {symbol} - Status: {resp.status_code}, Weight: {weight}")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 5))
logger.warning(f"429. Waiting {retry_after}s (sticky session preserved)")
await asyncio.sleep(retry_after)
continue
resp.raise_for_status()
return resp.json()
except (httpx.HTTPError, httpx.HTTPStatusError) as e:
wait = (2 ** attempt) * 0.5
logger.warning(f"Attempt {attempt+1}: {e}. Retry in {wait}s")
await asyncio.sleep(wait)
return []
async def backfill_klines(symbol, interval, start_ts, end_ts):
"""Backfill completo de klines con paginación."""
all_klines = []
current_end = end_ts
while current_end > start_ts:
klines = await fetch_klines_page(symbol, interval, current_end)
if not klines:
logger.warning("No data returned, stopping.")
break
all_klines.extend(klines)
# El endTime de la próxima página es el openTime del primer kline - 1
current_end = klines[0][0] - 1
logger.info(f"Fetched {len(klines)} klines. Total: {len(all_klines)}")
# Weight-aware throttle: si weight > 4000, ralentizamos
# (el header se loguea dentro de fetch_klines_page)
await asyncio.sleep(0.2)
return all_klines
# Ejecutar backfill de BTCUSDT 1h desde 2024-01-01
start = int(datetime(2024, 1, 1, tzinfo=timezone.utc).timestamp() * 1000)
end = int(datetime(2024, 6, 1, tzinfo=timezone.utc).timestamp() * 1000)
data = asyncio.run(backfill_klines("BTCUSDT", "1h", start, end))
print(f"Total klines descargados: {len(data)}")
La sesión sticky (-session-klines-backfill-btc-001) garantiza que Binance vea todos los requests de paginación desde la misma IP, lo que es más natural y reduce el riesgo de bloqueos. Para más casos de uso de scraping con proxies, visita nuestra guía de web scraping con ProxyHat.
Ejemplo 4: Node.js con axios y rotación de IPs
Para equipos de JavaScript/TypeScript, aquí tienes un ejemplo equivalente con axios:
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
const BINANCE_BASE = 'https://api.binance.com';
const PROXY_HOST = 'gate.proxyhat.com';
const PROXY_PORT = 8080;
const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT'];
// Pool de proxies con geo-targeting
const COUNTRIES = ['US', 'DE', 'JP', 'GB', 'SG'];
let countryIndex = 0;
function getProxyAgent() {
const country = COUNTRIES[countryIndex % COUNTRIES.length];
countryIndex++;
const proxyUrl = `http://user-country-${country}:pass@${PROXY_HOST}:${PROXY_PORT}`;
return {
agent: new HttpsProxyAgent(proxyUrl),
country
};
}
async function fetchTicker24hr(symbol, maxRetries = 3) {
const url = `${BINANCE_BASE}/api/v3/ticker/24hr`;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const { agent, country } = getProxyAgent();
try {
const resp = await axios.get(url, {
params: { symbol },
httpsAgent: agent,
timeout: 10000,
headers: { 'Accept': 'application/json' }
});
const weight = resp.headers['x-mbx-used-weight-1m'] || '0';
console.log(`${symbol} [${country}] - Weight: ${weight}`);
return resp.data;
} catch (err) {
if (err.response) {
const status = err.response.status;
const retryAfter = err.response.headers['retry-after'];
if (status === 429) {
const wait = parseInt(retryAfter || '5');
console.warn(`429 [${country}] - Waiting ${wait}s`);
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
if (status === 418) {
console.error(`418 IP ban [${country}] - Backing off 120s`);
await new Promise(r => setTimeout(r, 120000));
continue;
}
}
const wait = Math.pow(2, attempt) * 500;
console.warn(`Attempt ${attempt + 1} failed: ${err.message}. Retry in ${wait}ms`);
await new Promise(r => setTimeout(r, wait));
}
}
return null;
}
// Ejecutar con concurrencia limitada
async function main() {
const CONCURRENCY = 3;
const results = [];
for (let i = 0; i < SYMBOLS.length; i += CONCURRENCY) {
const batch = SYMBOLS.slice(i, i + CONCURRENCY);
const batchResults = await Promise.all(
batch.map(s => fetchTicker24hr(s))
);
results.push(...batchResults);
// Throttle entre batches
await new Promise(r => setTimeout(r, 200));
}
results.filter(Boolean).forEach(d => {
console.log(`${d.symbol}: ${parseFloat(d.lastPrice).toFixed(2)} USDT`);
});
}
main();
Ejemplo 5: Throttle consciente del peso con circuit breaker
Un patrón de producción combina el monitoreo de X-MBX-USED-WEIGHT-1M con un circuit breaker que pausa el scraping cuando el peso se acerca al límite:
import httpx
import asyncio
import logging
import random
from dataclasses import dataclass
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class WeightTracker:
current_weight: int = 0
max_weight: int = 5500 # Margen de seguridad bajo 6000
cooldown_until: float = 0.0
class BinanceScraper:
def __init__(self, proxy_url, max_concurrency=5):
self.proxy_url = proxy_url
self.tracker = WeightTracker()
self.semaphore = asyncio.Semaphore(max_concurrency)
self.base_url = "https://api.binance.com"
async def _request(self, endpoint, params):
async with self.semaphore:
# Circuit breaker: si estamos en cooldown, esperar
now = asyncio.get_event_loop().time()
if now < self.tracker.cooldown_until:
wait = self.tracker.cooldown_until - now
logger.info(f"Circuit breaker active. Waiting {wait:.1f}s")
await asyncio.sleep(wait)
url = f"{self.base_url}{endpoint}"
async with httpx.AsyncClient(
proxy=self.proxy_url, timeout=10.0
) as client:
for attempt in range(4):
try:
resp = await client.get(url, params=params)
# Actualizar peso consumido
weight_header = resp.headers.get("X-MBX-USED-WEIGHT-1M")
if weight_header:
self.tracker.current_weight = int(weight_header)
# Si weight > 80% del max, activar cooldown
if self.tracker.current_weight > self.tracker.max_weight * 0.8:
cooldown = 10 + random.uniform(0, 5)
self.tracker.cooldown_until = now + cooldown
logger.warning(
f"Weight {self.tracker.current_weight}/6000. "
f"Cooldown {cooldown:.1f}s"
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 5))
self.tracker.cooldown_until = now + retry_after
await asyncio.sleep(retry_after)
continue
if resp.status_code == 418:
self.tracker.cooldown_until = now + 120
await asyncio.sleep(120)
continue
resp.raise_for_status()
return resp.json()
except httpx.HTTPError as e:
wait = (2 ** attempt) * 0.5 + random.uniform(0, 0.5)
logger.warning(f"Retry {attempt+1}: {e}. Wait {wait:.1f}s")
await asyncio.sleep(wait)
return None
async def fetch_depth(self, symbol, limit=100):
return await self._request(
"/api/v3/depth",
{"symbol": symbol, "limit": limit}
)
async def fetch_klines(self, symbol, interval="1m", limit=500):
return await self._request(
"/api/v3/klines",
{"symbol": symbol, "interval": interval, "limit": limit}
)
# Uso con sesión sticky para datos consistentes
proxy = "http://user-session-scraping-001-country-DE:pass@gate.proxyhat.com:8080"
scraper = BinanceScraper(proxy, max_concurrency=3)
async def main():
symbols = ["BTCUSDT", "ETHUSDT", "BNBUSDT"]
for symbol in symbols:
depth = await scraper.fetch_depth(symbol, limit=100)
klines = await scraper.fetch_klines(symbol, interval="1m", limit=500)
if depth and klines:
print(f"{symbol}: {len(depth['bids'])} bids, {len(klines)} klines")
await asyncio.sleep(0.3)
asyncio.run(main())
¿Cuándo usar WebSocket en lugar de REST polling?
Para datos en tiempo real, los WebSocket streams de Binance (wss://stream.binance.com:9443) son superiores al polling REST en la mayoría de los casos:
| Criterio | REST Polling | WebSocket Streams |
|---|---|---|
| Latencia | 50–200ms por request | <50ms (push en tiempo real) |
| Consumo de peso | 1–20 por request | 0 (no aplica rate limit de peso) |
| Datos históricos | Ideal para backfills | No soporta históricos |
| Order book depth | Snapshot puntual | Actualizaciones incrementales (diff) |
| Complejidad | Baja (HTTP estándar) | Media (gestión de reconexión, ping/pong) |
| Con proxies | Funciona con HTTP/SOCKS5 proxy | Requiere proxy que soporte WSS |
La regla general: usa REST con proxies para backfills históricos y snapshots puntuales. Usa WebSocket para streaming en tiempo real donde necesitas baja latencia. Si necesitas tracking de SERPs u otros datos además de Binance, consulta nuestra guía de SERP tracking con proxies.
Errores comunes y edge cases
- Ignorar X-MBX-USED-WEIGHT-1M: Sin monitorear este header, no sabes cuándo estás cerca del límite. Léalo en cada response.
- No respetar Retry-After en 429: Reintentar inmediatamente después de un 429 casi garantiza un 418 (baneo de IP).
- Usar proxies datacenter para Binance: Binance detecta IPs de datacenter conocidas. Los proxies residenciales tienen mayor tasa de éxito.
- Concurrencia sin límite: Aunque rotes IPs, demasiados requests concurrentes desde la misma sesión pueden disparar heurísticas anti-bot.
- No manejar HTTP 451: Si accedes a Binance.com desde EE.UU. sin geo-targeting apropiado, recibirás 451. Usa
-country-DEo-country-JPpara evitarlo.
Configuración de ProxyHat para Binance
Para empezar a scrapear Binance con ProxyHat:
- Crea una cuenta en ProxyHat y obtén tus credenciales.
- Configura el gateway:
gate.proxyhat.com:8080(HTTP) o:1080(SOCKS5). - Usa geo-targeting para evitar el bloqueo regional:
user-country-DE:pass@gate.proxyhat.com:8080. - Para backfills, usa sesiones sticky:
user-session-{id}-country-DE:pass@.... - Para scraping en tiempo real, rota IPs por request omitiendo el flag de sesión.
Consulta la documentación oficial de ProxyHat para más detalles sobre el SDK y configuración avanzada.
Puntos clave
- Monitorea X-MBX-USED-WEIGHT-1M en cada response para ajustar tu velocidad de scraping dinámicamente.
- Usa proxies residenciales rotativos para distribuir el peso de rate limiting entre múltiples IPs (6000 pesos/min por IP).
- Sesiones sticky (
-session-{id}) para backfills paginados de klines; rotación por request para snapshots.- Respeta 429 y Retry-After para evitar escalar a baneos 418 que pueden durar de 2 a 5 minutos.
- Geo-targeting con
-country-{code}para evitar HTTP 451 en regiones restringidas.- Prefiere WebSocket para streaming en tiempo real; usa REST con proxies para históricos.
Preguntas frecuentes
¿Qué es scrapear la API REST de Binance con proxies?
Es la práctica de recolectar datos públicos de mercado (precios, velas, order book) de los endpoints REST de Binance usando proxies para distribuir el rate limit basado en peso entre múltiples IPs. Binance asigna aproximadamente 6000 pesos por minuto por IP, por lo que rotar proxies permite escalar el volumen de datos sin superar el límite individual de cada IP.
¿Por qué importa scrapear la API REST de Binance con proxies para usuarios de proxies?
Porque Binance usa un sistema de rate limiting basado en peso por IP, no por cuenta. Sin rotación de proxies, un scraper que hace polling intensivo de order book o klines agota los 6000 pesos/minuto en segundos y recibe HTTP 429. Los proxies residenciales rotativos permiten distribuir el consumo entre muchas IPs, multiplicando la capacidad efectiva y evitando baneos.
¿Qué tipo de proxy funciona mejor para scrapear la API REST de Binance?
Los proxies residenciales son los más efectivos para Binance porque cada IP de salida pertenece a un ISP real, lo que reduce la probabilidad de detección heurística. Los proxies datacenter son más rápidos pero Binance puede identificarlos como tráfico automatizado. Para datos en tiempo real donde la latencia es crítica, los proxies datacenter pueden ser aceptables si se rotan agresivamente, pero los residenciales ofrecen mayor fiabilidad a largo plazo.
¿Cómo evitas bloqueos al scrapear la API REST de Binance con proxies?
Para evitar bloqueos: (1) monitorea el header X-MBX-USED-WEIGHT-1M y reduce la velocidad cuando se acerque a 5500; (2) respeta el header Retry-After en cada HTTP 429 antes de reintentar; (3) rota IPs por request para distribuir el peso; (4) usa sesiones sticky para secuencias de paginación que se vean naturales; (5) implementa backoff exponencial con jitter; (6) limita la concurrencia por sesión a 3–5 requests paralelos.
¿Cuándo debo usar WebSocket en lugar de REST polling para Binance?
Usa WebSocket streams cuando necesitas datos en tiempo real con baja latencia (menos de 50ms) y no estás haciendo backfills históricos. Los WebSockets no consumen peso de rate limiting, pero requieren gestionar reconexiones y mensajes ping/pong. Para backfills históricos o snapshots puntuales, el REST polling con proxies sigue siendo la mejor opción.






