Come fare scraping della Binance REST API con proxy: panoramica rapida
Raccogliere dati di mercato pubblici da Binance è apparentemente semplice — gli endpoint REST sono pubblici, ben documentati e non richiedono chiavi API per i dati di prezzo e order book. Il problema nasce quando si tenta di farlo su larga scala: Binance applica un rate limit basato su peso per IP, e un polling aggressivo di /api/v3/depth può esaurire il budget di circa 6.000 pesi al minuto in pochi cicli. La soluzione è distribuire il peso su più IP usando proxy residenziali rotanti come quelli di ProxyHat, che permette di superare sia i limiti di frequenza sia le restrizioni geografiche (HTTP 451) tra Binance.com e Binance.US.
Nota su conformità e ToS: questa guida riguarda esclusivamente dati di mercato pubblici (candlestick, order book top-of-book, ticker 24h, prezzo). Rispetta sempre i pesi documentati, leggi i Termini di servizio di Binance, e preferisci l'accesso API ufficiale quando i termini lo richiedono. Non usare proxy per aggirare restrizioni legali o sanzioni.
Endpoint pubblici chiave e peso per richiesta
Binance pubblica un set di endpoint REST pubblici su api.binance.com (e mirror regionali). Ogni endpoint ha un peso (weight) che conta contro il budget orario e per-minuto dell'IP chiamante. La tabella seguente mappa i quattro endpoint più rilevanti per dataset di prezzo/orderbook.
| Endpoint | Scopo | Peso tipico | Note |
|---|---|---|---|
GET /api/v3/klines | Candlestick OHLCV | 1–10 (dipende da limit) | max 1000 candele per richiesta; peso cresce con limit |
GET /api/v3/depth | Snapshot order book | 5–20 (dipende da limit) | limit=1000 → peso 20; limit=5000 → peso 50 (documentato) |
GET /api/v3/ticker/24hr | Statistiche 24h per simbolo | 1 (singolo) – 80 (tutti i simboli) | Chiamata senza simbolo restituisce tutti i ticker: peso alto |
GET /api/v3/ticker/price | Prezzo spot corrente | 1–4 | Leggerissimo; ideale per heartbeat |
Ogni risposta REST include l'header X-MBX-USED-WEIGHT-1M, che indica il peso consumato nell'ultimo minuto per quell'IP. Monitorare questo header è obbligatorio per evitare ban: la documentazione ufficiale di Binance Spot API descrive il sistema di peso in dettaglio.
Perché il rate limit basato su peso è insidioso
Il budget predefinito per IP è di 6.000 pesi al minuto (può variare; verifica l'header X-MBX-USED-WEIGHT-1M). Se fai polling di /api/v3/depth con limit=1000 (peso 20) ogni secondo, consumi 1.200 pesi al minuto — apparentemente sotto soglia. Ma se aggiungi klines multi-simbolo e ticker 24h per decine di pair, il peso si somma rapidamente e il budget si esaurisce in pochi minuti.
Quando superi il limite, Binance risponde con HTTP 429 Too Many Requests e l'header Retry-After (secondi di attesa). Se continui a forzare, l'IP viene bannato temporaneamente con HTTP 418 (un endpoint "ban" con corpo JSON {"code": -1003, "msg": ...}). I ban possono durare da 2 minuti a 3 giorni a seconda della gravità. Per un data pipeline in produzione, un ban significa gap nei dati e possibili perdite finanziarie per strategie quant.
La geo-restrizione aggiunge un ulteriore livello: da alcuni IP (tipicamente Stati Uniti), api.binance.com può rispondere con HTTP 451 Unavailable For Legal Reasons, reindirizzando verso Binance.US (endpoint diversi, liquidità diversa). I proxy residenziali geolocalizzati risolvono entrambi i problemi: distribuiscono il peso su molti IP e permettono di scegliere il Paese di uscita.
Proxy residenziali ProxyHat: configurazione di base
ProxyHat espone un gateway unico (gate.proxyhat.com) su porta 8080 per HTTP e 1080 per SOCKS5. La rotazione e la geolocalizzazione si controllano tramite il username, con flag separati da trattino:
user-country-US:pass— uscita da Stati Uniti (evita HTTP 451 su Binance.com)user-country-DE-region-state_of_berlin-city-berlin:pass— uscita da Berlinouser-sid-abc123:pass— sessione sticky (stesso IP per più richieste)user-country-DE-sid-abc123:pass— combinazione geo + sticky
Per backfill paginati di klines (dove serve coerenza e continuità di sessione), usa sessioni sticky; per polling distribuito di depth/ticker, ruota per-request senza sessione.
Esempio 1: Python requests con proxy raw e rotazione per-request
Questo snippet mostra l'uso diretto del proxy HTTP ProxyHat con rotazione casuale della sessione (nuovo IP per ogni richiesta). Include lettura dell'header X-MBX-USED-WEIGHT-1M, retry con backoff esponenziale e gestione di 429/418.
import requests
import time
import random
import logging
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
PROXYHAT_USER = "user"
PROXYHAT_PASS = "pass"
GATE = "gate.proxyhat.com:8080"
BASE = "https://api.binance.com"
def build_proxy_url(country=None):
# Rotazione per-request: sessione casuale = nuovo IP ogni volta
session_id = f"sess-{random.randint(0, 10_000_000)}"
user = f"{PROXYHAT_USER}-sid-{session_id}"
if country:
user = f"{PROXYHAT_USER}-country-{country}-sid-{session_id}"
return f"http://{user}:{PROXYHAT_PASS}@{GATE}"
def fetch_klines(symbol="BTCUSDT", interval="1m", limit=500):
url = f"{BASE}/api/v3/klines"
params = {"symbol": symbol, "interval": interval, "limit": limit}
proxies = {"http": build_proxy_url("DE"), "https": build_proxy_url("DE")}
for attempt in range(5):
try:
r = requests.get(url, params=params, proxies=proxies, timeout=10)
used = r.headers.get("X-MBX-USED-WEIGHT-1M", "?")
logging.info("%s weight_used=%s status=%s", symbol, used, r.status_code)
if r.status_code == 429:
retry_after = int(r.headers.get("Retry-After", 5))
logging.warning("429 received, sleeping %ss", retry_after)
time.sleep(retry_after)
continue
if r.status_code == 418:
logging.error("418 ban — IP bloccato, pausa lunga")
time.sleep(60)
continue
r.raise_for_status()
return r.json()
except requests.RequestException as e:
backoff = 2 ** attempt
logging.warning("errore %s, retry in %ss", e, backoff)
time.sleep(backoff)
return None
if __name__ == "__main__":
data = fetch_klines()
print(f"candele ricevute: {len(data) if data else 0}")
Esempio 2: Python httpx asincrono con ProxyHat SDK e throttling basato su peso
Per throughput più alto, httpx con async è preferibile. Qui usiamo il pattern SDK ProxyHat (costruzione dell'URL proxy) con un semaforo per limitare la concorrenza e un throttle che rispetta il peso cumulativo.
import asyncio
import httpx
import random
import logging
import time
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
PROXYHAT_USER = "user"
PROXYHAT_PASS = "pass"
GATE_HTTP = "gate.proxyhat.com:8080"
BASE = "https://api.binance.com"
MAX_CONCURRENCY = 10
WEIGHT_BUDGET_PER_MIN = 5500 # margine sotto 6000
class WeightThrottle:
def __init__(self, budget_per_min):
self.budget = budget_per_min
self.consumed = 0
self.window_start = time.monotonic()
self.lock = asyncio.Lock()
async def acquire(self, weight=1):
async with self.lock:
now = time.monotonic()
if now - self.window_start >= 60:
self.consumed = 0
self.window_start = now
if self.consumed + weight > self.budget:
sleep_for = 60 - (now - self.window_start)
logging.warning("peso %s/%s, attendo %.1fs", self.consumed, self.budget, sleep_for)
await asyncio.sleep(max(sleep_for, 0.5))
self.consumed = 0
self.window_start = time.monotonic()
self.consumed += weight
def proxy_url(country="DE", session=None):
sess = session or f"sess-{random.randint(0, 10_000_000)}"
user = f"{PROXYHAT_USER}-country-{country}-sid-{sess}"
return f"http://{user}:{PROXYHAT_PASS}@{GATE_HTTP}"
async def fetch_depth(client, symbol, throttle, limit=100):
weight = 1 if limit <= 100 else 5 # approssimazione; vedi docs
await throttle.acquire(weight)
url = f"{BASE}/api/v3/depth"
params = {"symbol": symbol, "limit": limit}
proxy = proxy_url()
for attempt in range(4):
try:
r = await client.get(url, params=params, proxy=proxy, timeout=10)
used = r.headers.get("X-MBX-USED-WEIGHT-1M", "?")
logging.info("depth %s weight=%s status=%s", symbol, used, r.status_code)
if r.status_code == 429:
await asyncio.sleep(int(r.headers.get("Retry-After", 5)))
continue
r.raise_for_status()
return r.json()
except httpx.HTTPError as e:
await asyncio.sleep(2 ** attempt)
return None
async def main():
throttle = WeightThrottle(WEIGHT_BUDGET_PER_MIN)
sem = asyncio.Semaphore(MAX_CONCURRENCY)
async with httpx.AsyncClient() as client:
symbols = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT", "XRPUSDT"]
async def worker(sym):
async with sem:
return await fetch_depth(client, sym, throttle)
results = await asyncio.gather(*[worker(s) for s in symbols])
for sym, res in zip(symbols, results):
print(f"{sym}: bids={len(res['bids']) if res else 0}")
if __name__ == "__main__":
asyncio.run(main())
Esempio 3: Sessione sticky per backfill paginato di klines
Per ricostruire serie storiche lunghe, serve paginare con endTime e mantenere lo stesso IP (sticky) per evitare salti di peso tra IP diversi. Il flag -sid-abc123 garantisce lo stesso IP per la durata della sessione.
import requests
import time
PROXYHAT_USER = "user"
PROXYHAT_PASS = "pass"
GATE = "gate.proxyhat.com:8080"
BASE = "https://api.binance.com"
# Sessione sticky: stesso IP per tutto il backfill
STICKY_SESSION = "klines-btc-backfill-001"
PROXY = {
"http": f"http://{PROXYHAT_USER}-country-DE-sid-{STICKY_SESSION}:{PROXYHAT_PASS}@{GATE}",
"https": f"http://{PROXYHAT_USER}-country-DE-sid-{STICKY_SESSION}:{PROXYHAT_PASS}@{GATE}",
}
def backfill_klines(symbol="BTCUSDT", interval="1m", start_ms=None, end_ms=None, max_candles=100_000):
url = f"{BASE}/api/v3/klines"
collected = []
cursor = start_ms
total = 0
while total < max_candles:
params = {"symbol": symbol, "interval": interval, "startTime": cursor, "limit": 1000}
if end_ms:
params["endTime"] = end_ms
r = requests.get(url, params=params, proxies=PROXY, timeout=10)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 5)))
continue
if r.status_code == 418:
print("Ban ricevuto, attesa 120s")
time.sleep(120)
continue
r.raise_for_status()
batch = r.json()
if not batch:
break
collected.extend(batch)
total += len(batch)
cursor = batch[-1][6] + 1 # close time + 1ms
print(f"raccolte {len(batch)} candele, totale {total}, peso {r.headers.get('X-MBX-USED-WEIGHT-1M')}")
time.sleep(0.2) # rispetto peso: klines limit=1000 → peso ~2
return collected
if __name__ == "__main__":
start = int(time.time() * 1000) - 3600_000 # ultima ora
data = backfill_klines(start_ms=start)
print(f"totale candele: {len(data)}")
Esempio 4: Node.js con axios e proxy HTTPS
In Node.js, axios con https-proxy-agent è il pattern standard. Qui mostriamo rotazione per-request e gestione di 429/418 con backoff.
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const PROXYHAT_USER = 'user';
const PROXYHAT_PASS = 'pass';
const GATE = 'gate.proxyhat.com:8080';
const BASE = 'https://api.binance.com';
function buildAgent(country = 'DE') {
const sess = `sess-${Math.floor(Math.random() * 10_000_000)}`;
const user = `${PROXYHAT_USER}-country-${country}-sid-${sess}`;
const proxyUrl = `http://${user}:${PROXYHAT_PASS}@${GATE}`;
return new HttpsProxyAgent(proxyUrl);
}
async function fetchTicker24hr(symbol) {
const url = `${BASE}/api/v3/ticker/24hr`;
const params = { symbol };
for (let attempt = 0; attempt < 5; attempt++) {
try {
const agent = buildAgent('DE');
const res = await axios.get(url, { params, httpsAgent: agent, timeout: 10000 });
const used = res.headers['x-mbx-used-weight-1m'];
console.log(`${symbol} weight=${used} status=${res.status}`);
return res.data;
} catch (err) {
if (err.response) {
const { status, headers } = err.response;
if (status === 429) {
const ra = parseInt(headers['retry-after'] || '5', 10);
console.warn(`429, sleeping ${ra}s`);
await new Promise(r => setTimeout(r, ra * 1000));
continue;
}
if (status === 418) {
console.error('418 ban, sleeping 120s');
await new Promise(r => setTimeout(r, 120_000));
continue;
}
}
const backoff = Math.pow(2, attempt) * 1000;
console.warn(`errore ${err.message}, retry in ${backoff}ms`);
await new Promise(r => setTimeout(r, backoff));
}
}
return null;
}
(async () => {
const data = await fetchTicker24hr('BTCUSDT');
if (data) console.log(`prezzo: ${data.lastPrice}, var 24h: ${data.priceChangePercent}%`);
})();
Esempio 5: curl one-liner con proxy geolocalizzato
Per test rapidi o integrazione in pipeline shell, ecco un comando curl che usa il proxy HTTP ProxyHat con uscita dagli Stati Uniti (utile per verificare la geo-restrizione HTTP 451 o per accedere a Binance.com da IP US).
# Test ticker/price con proxy US
curl -x http://user-country-US-sid-test01:pass@gate.proxyhat.com:8080 \
-s -w "\nHTTP %{http_code} weight=%header{x-mbx-used-weight-1m}\n" \
"https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"
# Backfill klines sticky session DE
curl -x http://user-country-DE-sid-backfill01:pass@gate.proxyhat.com:8080 \
-s "https://api.binance.com/api/v3/klines?symbol=ETHUSDT&interval=1h&limit=100" \
| python -m json.tool | head -20
Quando preferire WebSocket invece del polling REST
Binance offre stream WebSocket pubblici su wss://stream.binance.com:9443 per kline, depth (diff e partial book), trade e ticker. Per dati in tempo reale continuo, i WebSocket sono quasi sempre superiori al polling REST:
- Nessun peso REST: i consumano peso API; il rate limit REST non si applica.
- Latenza minima: push di aggiornamenti in tempo reale invece di polling a intervallo fisso.
- Banda ottimizzata: stream diff depth invia solo le modifiche, non l'intero snapshot.
Il polling REST resta preferibile per snapshot puntuali, backfill storici (klines paginati), o quando serve un singolo punto nel tempo. Una pipeline ibrida (REST per backfill + WebSocket per live) è il pattern più efficiente per dataset quant. Per approfondire, consulta la documentazione WebSocket di Binance.
Errori comuni e edge case
- Ignorare X-MBX-USED-WEIGHT-1M: è l'unica fonte di verità sul consumo. Senza monitorarlo, vai alla cieca verso un ban.
- Usare proxy datacenter per Binance: IP datacenter sono spesso già flaggati o rate-limited più aggressivamente. I proxy residenziali hanno fingerprint più naturale e tasso di successo più alto.
- Rotazione troppo aggressiva senza sticky per paginazione: se cambi IP a metà backfill, i pesi si distribuiscono ma potresti perdere coerenza temporale o ricevere 451 su un IP US mid-stream.
- Non gestire HTTP 451: se l'IP è in US e l'endpoint è Binance.com, ricevi 451. Usa
-country-DEo un Paese supportato, oppure passa aapi.binance.uscon endpoint equivalenti. - Concorrenza illimitata: anche con molti IP, la CPU locale e la banda diventano colli di bottiglia. Limita a 10–50 connessioni simultanee.
Configurazione ProxyHat e risorse
Per iniziare con ProxyHat, configura le credenziali nel pannello delle location e consulta la documentazione ufficiale ProxyHat per dettagli su rotazione, sessioni sticky e geolocalizzazione. Per i prezzi dei piani residenziali, vedi la pagina pricing. Se stai costruendo pipeline di web scraping o SERP tracking, gli stessi pattern di rotazione si applicano.
Punti chiave (Key Takeaways)
- Binance usa rate limit basato su peso per IP (~6.000/min); monitora sempre
X-MBX-USED-WEIGHT-1M./api/v3/depthcon limit alto è il consumer più vorace: peso 20–50 per richiesta.- HTTP 429 = rallenta (leggi
Retry-After); HTTP 418 = ban (minuti/giorni).- Proxy residenziali rotanti distribuiscono il peso su molti IP; geolocalizzazione
-country-USrisolve HTTP 451.- Usa sessioni sticky (
-sid-abc123) per backfill paginati di klines.- Per dati live continui, preferisci WebSocket; per snapshot e storici, REST con proxy.
- Usa solo dati di mercato pubblici e rispetta i ToS di Binance.






