Das Sammeln öffentlicher Marktdaten über die Binance REST API ist eine der häufigsten Aufgaben für Quant-Entwickler und Krypto-Dateningenieure. Doch sobald Sie binance api scraping in größerem Maßstab betreiben, stoßen Sie auf das gewichtbasierte Rate-Limit-System von Binance. Dieser Leitfaden zeigt, wie Sie scrape binance market data zuverlässig über rotierende Residential Proxies von ProxyHat betreiben — mit lauffähigen Code-Beispielen für Python und Node.js.
Rechtlicher Hinweis: Dieser Artikel behandelt ausschließlich öffentliche Marktdaten. Beachten Sie die Binance-Nutzungsbedingungen und bevorzugen Sie offiziellen API-Zugang, wo die Bedingungen dies vorschreiben. Respektieren Sie Weight-Limits, robots.txt und lokale Vorschriften. Keine Anleitung zum Umgehen von Authentifizierung oder privater Daten.
Warum Binance-REST-API-Scraping mit Proxies überhaupt nötig ist
Binance betreibt eines der liquidesten Krypto-Orderbuch-Systeme weltweit. Die öffentliche REST-API unter api.binance.com liefert Candlesticks (Klines), Orderbuch-Tiefen, 24h-Ticker und Einzelpreise. Das Problem: Binance wendet ein gewichtbasiertes Rate-Limit pro IP an, nicht einfach eine feste Request-Anzahl. Wer api.binance.com/api/v3/klines oder /api/v3/depth in hoher Frequenz pollt, verbraucht das IP-Kontingent von ca. 6000 Gewichtspunkten pro Minute innerhalb weniger Minuten.
Das führt zu HTTP 429 (Too Many Requests) und bei wiederholten Verstößen zu HTTP 418 (IP-Ban) mit einem Retry-After-Header, der Sperren von mehreren Minuten bis Stunden ankündigt. Genau hier kommt binance rest api rate limit proxy ins Spiel: Durch das Verteilen von Requests über mehrere Residential-IPs verteilt sich auch das Gewicht — und das Limit pro IP wird nicht mehr ausgereizt.
Die wichtigsten öffentlichen Marktdaten-Endpunkte und ihre Gewichte
Vor dem Code müssen Sie verstehen, welche Endpunkte Sie treffen und was sie kosten. Jeder Request verbraucht ein bestimmtes „Weight“, das Binance im Response-Header X-MBX-USED-WEIGHT-1M zurückgibt. Das ist Ihr Kontostand für die laufende Minute.
| Endpunkt | Zweck | Weight (Standard) | Anmerkung |
|---|---|---|---|
GET /api/v3/klines | Candlesticks/OHLCV | 1–2 (je nach limit) | Bis zu 1000 Kerzen pro Request |
GET /api/v3/depth | Orderbuch-Tiefen | 5–20 (je nach limit) | limit=5000 kostet 20, limit=100 kostet 5 |
GET /api/v3/ticker/24hr | 24h-Rolling-Ticker | 1 (single), 40 (all) | Ohne Symbol = 40 Weight! |
GET /api/v3/ticker/price | Aktueller Preis | 1 (single), 2 (all) | Billigster Endpunkt |
Das bedeutet konkret: Wenn Sie /api/v3/depth?symbol=BTCUSDT&limit=1000 10-mal pro Sekunde pollen, verbrauchen Sie ca. 10 × 10 = 100 Weight/Sekunde — also 6000 Weight in 60 Sekunden. Ihr IP-Kontingent ist erschöpft, bevor die erste Minute um ist. Genau das macht binance ip ban 429 proxy zu einem realen Produktionsproblem.
Der X-MBX-USED-WEIGHT-1M-Header: Ihr wichtigstes Telemetrie-Signal
Binance sendet in jeder Response den Header X-MBX-USED-WEIGHT-1M. Das ist das genutzte Gewicht der letzten 60 Sekunden für Ihre IP. Eine professionelle Scraping-Pipeline liest diesen Header aus und passt die Rate dynamisch an — statt blind zu feuern und auf 429 zu warten.
Binance-Rate-Limits verstehen: 429, 418 und Retry-After
Das Limit-System hat drei Eskalationsstufen:
- HTTP 429 — Rate-Limit überschritten. Reduzieren Sie sofort die Rate. Der Header
Retry-Aftergibt an, wie lange Sie warten sollen. - HTTP 418 — IP wurde gebannt. Meist nach wiederholten 429-Verstößen. Dauer variiert von Minuten bis zu mehreren Stunden.
- HTTP 451 — Geo-Restriktion. Tritt auf, wenn Sie aus einer Region zugreifen, die nicht unterstützt wird (z. B. Binance.com aus US-IPs ohne entsprechende Berechtigung).
Eine ausführliche Beschreibung finden Sie in den offiziellen Binance API-Limit-Dokumenten. Für Produktionssysteme gilt: Behandeln Sie 429 als weiche Warnung, 418 als harte Sperre und 451 als Geo-Problem, das Sie mit länderspezifischen Proxies lösen.
Proxy-Strategie: Weight über IPs verteilen mit ProxyHat
Da Binance Weight pro IP zählt, verteilen rotierende Residential Proxies das Gewicht über viele IPs. Wenn Sie 20 IPs rotieren, haben Sie effektiv 20 × 6000 = 120.000 Weight/Minute zur Verfügung — genug für ein massives Orderbuch-Backfill. ProxyHat bietet Residential-, Mobile- und Datacenter-Proxies über das Gateway gate.proxyhat.com an.
Die Verbindungsdetails:
- HTTP:
http://USERNAME:PASSWORD@gate.proxyhat.com:8080 - SOCKS5:
socks5://USERNAME:PASSWORD@gate.proxyhat.com:1080 - Geo-Targeting:
user-country-DE:passoderuser-country-US:pass - Sticky Session:
user-session-abc123:pass
Preise und verfügbare Standorte finden Sie auf der ProxyHat-Preisseite und der Standortübersicht. Für umfassende Scraping-Use-Cases siehe Web-Scraping-Anwendungsfälle und SERP-Tracking.
Geo-Restriktionen mit country-Flag umgehen
Binance betreibt separate Domains: binance.com global und binance.us für US-Nutzer. Wenn Sie aus einer US-IP auf binance.com zugreifen, kann HTTP 451 zurückkommen. Mit dem ProxyHat-Geo-Flag -country-US steuern Sie gezielt, aus welcher Region Ihre Requests kommen — nützlich, wenn Sie US-Marktdaten von binance.us sammeln oder umgekehrt.
Implementierung: Python mit requests und rotierenden IPs
Beginnen wir mit einem einfachen Beispiel: Abruf von BTCUSDT-Klines über einen rotierenden Proxy. Wir verwenden requests und rotieren die IP bei jedem Request, indem wir eine neue Session-ID generieren.
import requests
import random
import string
import time
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
PROXYHAT_USER = "user-country-DE"
PROXYHAT_PASS = "your_password"
BINANCE_BASE = "https://api.binance.com"
def get_proxy_url(session_id=None):
"""Erzeugt eine ProxyHat-URL mit optionaler Sticky Session."""
if session_id:
username = f"{PROXYHAT_USER}-session-{session_id}"
else:
# Zufällige Session = neue IP pro Request
sid = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
username = f"{PROXYHAT_USER}-session-{sid}"
return f"http://{username}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
def build_session():
session = requests.Session()
retry = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET"]
)
adapter = HTTPAdapter(max_retries=retry, pool_connections=10, pool_maxsize=20)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
def fetch_klines(symbol="BTCUSDT", interval="1m", limit=500):
url = f"{BINANCE_BASE}/api/v3/klines"
params = {"symbol": symbol, "interval": interval, "limit": limit}
proxy = get_proxy_url()
proxies = {"http": proxy, "https": proxy}
session = build_session()
try:
resp = session.get(url, params=params, proxies=proxies, timeout=15)
used_weight = resp.headers.get("X-MBX-USED-WEIGHT-1M", "unknown")
print(f"Status: {resp.status_code}, Used Weight 1M: {used_weight}")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 5))
time.sleep(retry_after)
return None
if resp.status_code == 418:
print("IP gebannt! Lange Pause erforderlich.")
return None
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
print(f"Request fehlgeschlagen: {e}")
return None
data = fetch_klines(symbol="BTCUSDT", interval="1m", limit=500)
if data:
print(f"{len(data)} Kerzen abgerufen")
In diesem Beispiel rotieren wir die IP bei jedem Aufruf durch eine zufällige Session-ID. Der X-MBX-USED-WEIGHT-1M-Header wird protokolliert, und 429/418 werden explizit behandelt. Für Produktion sollten Sie den Weight-Wert zentral sammeln und eine dynamische Rate-Anpassung implementieren.
Gewichtbewusstes Throttling mit httpx und Async
Für höhere Durchsatzraten ist httpx mit Async besser geeignet. Hier ein Beispiel mit Gewicht-Tracking, exponentiellem Backoff und concurrency-Limits.
import httpx
import asyncio
import random
import string
from typing import Optional
PROXYHAT_USER = "user-country-DE"
PROXYHAT_PASS = "your_password"
BINANCE_BASE = "https://api.binance.com"
MAX_CONCURRENT = 10
WEIGHT_BUDGET_PER_IP = 5500 # Sicherheitsreserve unter 6000
class WeightTracker:
"""Verfolgt genutztes Gewicht pro Proxy-Session."""
def __init__(self):
self.sessions = {} # session_id -> weight
def update(self, session_id: str, weight: int):
self.sessions[session_id] = weight
def can_request(self, session_id: str, cost: int) -> bool:
current = self.sessions.get(session_id, 0)
return (current + cost) < WEIGHT_BUDGET_PER_IP
tracker = WeightTracker()
semaphore = asyncio.Semaphore(MAX_CONCURRENT)
def make_proxy_url(session_id: str) -> str:
return f"http://{PROXYHAT_USER}-session-{session_id}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
def new_session_id() -> str:
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
async def fetch_with_retry(client: httpx.AsyncClient, url: str, params: dict,
proxy_url: str, session_id: str, cost: int,
max_retries: int = 4) -> Optional[dict]:
for attempt in range(max_retries):
async with semaphore:
if not tracker.can_request(session_id, cost):
# Neue Session = neue IP
session_id = new_session_id()
proxy_url = make_proxy_url(session_id)
try:
resp = await client.get(
url, params=params, proxy=proxy_url, timeout=15
)
weight = int(resp.headers.get("X-MBX-USED-WEIGHT-1M", 0))
tracker.update(session_id, weight)
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
print(f"429 auf {session_id}, warte {wait}s")
await asyncio.sleep(wait)
session_id = new_session_id()
proxy_url = make_proxy_url(session_id)
continue
if resp.status_code == 418:
print(f"418 Ban auf {session_id}, wechsle IP")
session_id = new_session_id()
proxy_url = make_proxy_url(session_id)
await asyncio.sleep(2 ** attempt)
continue
resp.raise_for_status()
return resp.json()
except (httpx.HTTPError, httpx.ProxyError) as e:
print(f"Fehler (Versuch {attempt+1}): {e}")
await asyncio.sleep(2 ** attempt)
return None
async def scrape_multiple_symbols(symbols: list[str]):
async with httpx.AsyncClient(http2=True) as client:
tasks = []
for sym in symbols:
sid = new_session_id()
url = f"{BINANCE_BASE}/api/v3/ticker/price"
params = {"symbol": sym}
proxy = make_proxy_url(sid)
tasks.append(fetch_with_retry(client, url, params, proxy, sid, cost=1))
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
# Beispielaufruf
symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]
results = asyncio.run(scrape_multiple_symbols(symbols))
for sym, r in zip(symbols, results):
if isinstance(r, dict):
print(f"{sym}: {r.get('price')}")
Dieser Ansatz kombiniert drei wichtige Strategien: Weight-Tracking pro IP, IP-Rotation bei drohendem Limit und exponentielles Backoff bei 429/418. Der Semaphore begrenzt die Gleichzeitigkeit auf 10, was eine vernünftige Obergrenze für die meisten Workloads ist.
Sticky Sessions für paginierte Klines-Backfills
Beim Backfillen historischer Klines benötigen Sie oft tausende Requests für ein einzelnes Symbol. Eine rotierende IP pro Request ist hier nicht ideal, weil Binance manchmal Konsistenz über eine Session erwartet. Sticky Sessions halten dieselbe IP für eine logische Aufgabe.
import requests
import time
from datetime import datetime, timedelta
PROXYHAT_USER = "user-country-DE"
PROXYHAT_PASS = "your_password"
# Sticky Session: gleiche IP für gesamten Backfill
BACKFILL_SESSION = "klines-btc-backfill-2025"
PROXY = f"http://{PROXYHAT_USER}-session-{BACKFILL_SESSION}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY, "https": PROXY}
def fetch_klines_paginated(symbol, interval, start_time, end_time, max_limit=1000):
"""Paginierter Klines-Backfill mit Sticky Session."""
url = "https://api.binance.com/api/v3/klines"
all_data = []
current = start_time
while current < end_time:
params = {
"symbol": symbol,
"interval": interval,
"startTime": int(current.timestamp() * 1000),
"endTime": int(end_time.timestamp() * 1000),
"limit": max_limit
}
try:
resp = requests.get(url, params=params, proxies=PROXIES, timeout=20)
weight = resp.headers.get("X-MBX-USED-WEIGHT-1M", "?")
print(f"Weight: {weight}/6000 | Kerzen bisher: {len(all_data)}")
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 10))
print(f"Rate-Limit, pausiere {wait}s")
time.sleep(wait)
continue
if resp.status_code == 418:
print("IP gebannt, wechsle Session")
# Neue Sticky Session = neue IP
new_sid = f"klines-btc-backfill-{int(time.time())}"
global PROXY, PROXIES
PROXY = f"http://{PROXYHAT_USER}-session-{new_sid}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY, "https": PROXY}
time.sleep(60)
continue
resp.raise_for_status()
batch = resp.json()
if not batch:
break
all_data.extend(batch)
# Letzten Timestamp + 1ms für nächste Seite
last_open = batch[-1][0]
current = datetime.fromtimestamp((last_open + 1) / 1000)
# Proaktives Throttling: bei >5000 Weight kurz pausieren
if int(weight) > 5000:
time.sleep(2)
except requests.RequestException as e:
print(f"Fehler: {e}, warte 5s")
time.sleep(5)
return all_data
# Backfill für 7 Tage BTCUSDT 1m-Kerzen
start = datetime(2025, 1, 1)
end = datetime(2025, 1, 8)
data = fetch_klines_paginated("BTCUSDT", "1m", start, end)
print(f"Gesamt: {len(data)} Kerzen")
Hier ist die Sticky Session entscheidend: Binance liefert konsistente paginierte Ergebnisse, solange dieselbe IP verwendet wird. Bei 418 wechseln wir die Session-ID und erhalten eine neue IP — der Backfill wird nahtlos fortgesetzt.
Node.js mit axios: Gleichzeitige Ticker-Abfragen
Für JavaScript/TypeScript-Entwickler hier ein äquivalenter Ansatz mit axios und https-proxy-agent:
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { HttpProxyAgent } = require('http-proxy-agent');
const PROXYHAT_USER = 'user-country-DE';
const PROXYHAT_PASS = 'your_password';
const BINANCE_BASE = 'https://api.binance.com';
const MAX_CONCURRENT = 8;
function makeProxyAgent(sessionId) {
const proxyUrl = `http://${PROXYHAT_USER}-session-${sessionId}:${PROXYHAT_PASS}@gate.proxyhat.com:8080`;
return {
http: new HttpProxyAgent(proxyUrl),
https: new HttpsProxyAgent(proxyUrl)
};
}
function randomSessionId() {
return Math.random().toString(36).substring(2, 14);
}
async function fetchWithRetry(url, params, cost, maxRetries = 4) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const sid = randomSessionId();
const agents = makeProxyAgent(sid);
try {
const resp = await axios.get(url, {
params,
httpAgent: agents.http,
httpsAgent: agents.https,
timeout: 15000
});
const weight = resp.headers['x-mbx-used-weight-1m'] || 'unknown';
console.log(`Status ${resp.status}, Weight 1M: ${weight}, Session: ${sid}`);
return resp.data;
} catch (err) {
if (err.response) {
const status = err.response.status;
if (status === 429) {
const retryAfter = parseInt(err.response.headers['retry-after'] || '5');
console.log(`429, warte ${retryAfter}s`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
if (status === 418) {
console.log('IP gebannt, warte 60s');
await new Promise(r => setTimeout(r, 60000));
continue;
}
}
const backoff = Math.pow(2, attempt) * 1000;
console.log(`Fehler Versuch ${attempt + 1}, warte ${backoff}ms`);
await new Promise(r => setTimeout(r, backoff));
}
}
return null;
}
async function scrapeOrderBooks(symbols) {
const concurrencyLimit = MAX_CONCURRENT;
const results = [];
for (let i = 0; i < symbols.length; i += concurrencyLimit) {
const batch = symbols.slice(i, i + concurrencyLimit);
const promises = batch.map(sym => {
const url = `${BINANCE_BASE}/api/v3/depth`;
const params = { symbol: sym, limit: 100 };
return fetchWithRetry(url, params, 5).then(data => ({ symbol: sym, data }));
});
const batchResults = await Promise.allSettled(promises);
results.push(...batchResults);
}
return results;
}
(async () => {
const symbols = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT',
'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT'];
const results = await scrapeOrderBooks(symbols);
results.forEach(r => {
if (r.status === 'fulfilled' && r.value.data) {
console.log(`${r.value.symbol}: ${r.value.data.bids.length} Bids, ${r.value.data.asks.length} Asks`);
}
});
})();
SOCKS5 für niedrigere Latenz bei Orderbuch-Streaming
Für latenzkritische Anwendungen wie Orderbuch-Polling kann SOCKS5 geringfügig bessere Performance bieten. ProxyHat unterstützt SOCKS5 auf Port 1080:
import requests
from requests.adapters import HTTPAdapter
from urllib3.contrib.socks import SOCKSProxyManager
PROXYHAT_USER = "user-country-US"
PROXYHAT_PASS = "your_password"
# SOCKS5 Proxy für niedrigere Latenz
SOCKS_PROXY = f"socks5://{PROXYHAT_USER}-session-depth-stream:{PROXYHAT_PASS}@gate.proxyhat.com:1080"
def fetch_depth_socks(symbol="BTCUSDT", limit=100):
url = "https://api.binance.com/api/v3/depth"
params = {"symbol": symbol, "limit": limit}
proxies = {"http": SOCKS_PROXY, "https": SOCKS_PROXY}
session = requests.Session()
resp = session.get(url, params=params, proxies=proxies, timeout=10)
weight = resp.headers.get("X-MBX-USED-WEIGHT-1M", "?")
print(f"SOCKS5 | Status: {resp.status_code}, Weight: {weight}")
return resp.json() if resp.ok else None
orderbook = fetch_depth_socks("BTCUSDT", limit=100)
if orderbook:
print(f"Spread: {float(orderbook['asks'][0][0]) - float(orderbook['bids'][0][0])}")
Wann WebSocket besser als REST-Polling ist
REST-Polling ist nicht immer die beste Wahl. Binance bietet öffentliche WebSocket-Streams unter wss://stream.binance.com:9443, die Echtzeit-Updates ohne Weight-Verbrauch liefern. Für kontinuierliche Daten wie Live-Klines oder Orderbuch-Updates sind WebSockets überlegen:
- wss://stream.binance.com/ws/btcusdt@kline_1m — Live-Klines, kein Weight-Verbrauch
- wss://stream.binance.com/ws/btcusdt@depth20@100ms — Orderbuch-Updates alle 100ms
- wss://stream.binance.com/ws/btcusdt@ticker — 24h-Ticker-Updates in Echtzeit
WebSocket-Streams haben ein eigenes Limit (max. 5 Verbindungen pro IP, 200 Streams pro Verbindung), aber kein Weight-System. Für historische Backfills und diskontinuierliche Abfragen bleibt REST jedoch die einzige Option — und hier sind Proxies unverzichtbar.
Ein hybrider Ansatz ist oft optimal: WebSocket für Live-Daten, REST über rotierende Proxies für historische Backfills und Snapshot-Abfragen. Details zu den WebSocket-Limits finden Sie in den Binance WebSocket-Dokumenten.
Häufige Fehler und Edge Cases
1. Blindes Feuern ohne Weight-Header-Auswertung
Der häufigste Fehler: Requests abfeuern, ohne X-MBX-USED-WEIGHT-1M auszuwerten. Sie treffen 429, bevor Sie es bemerken. Lösung: Weight-Tracker implementieren und proaktiv drosseln.
2. /api/v3/ticker/24hr ohne Symbol-Parameter
Ein Aufruf von /api/v3/ticker/24hr ohne symbol kostet 40 Weight — das ist fast 0,7% Ihres Minutenbudgets pro Request. Bei 150 Aufrufen/Minute ist das Budget erschöpft. Lösung: Immer symbol angeben, es sei denn, Sie brauchen wirklich alle Ticker.
3. Depth-Polling mit limit=5000
limit=5000 kostet 20 Weight pro Request. Bei 10 Requests/Sekunde sind das 12.000 Weight/Minute — das Doppelte des Budgets. Lösung: limit=100 (5 Weight) für die meisten Anwendungsfälle verwenden oder auf WebSocket umsteigen.
4. Keine Retry-After-Behandlung
Bei 429 sendet Binance Retry-After. Ignorieren Sie diesen Header, verschlimmern Sie die Situation und riskieren 418. Lösung: Retry-After auslesen und exakt so lange warten.
5. Falsche Geo-Ausrichtung
Wenn Sie versehentlich US-IPs für binance.com verwenden, kann HTTP 451 zurückkommen. Lösung: Geo-Flag bewusst setzen — -country-DE für globale Daten, -country-US für binance.us.
Key Takeaways
- Weight pro IP: Binance limitiert auf ~6000 Weight/Minute pro IP. Rotierende Proxies verteilen dieses Budget über viele IPs.
- X-MBX-USED-WEIGHT-1M auswerten: Dieser Header ist Ihre wichtigste Telemetrie. Ohne ihn fliegen Sie blind.
- 429 vs 418: 429 ist weich (kurze Pause), 418 ist hart (IP-Ban). Behandeln Sie beide explizit mit
Retry-After.- Sticky Sessions für Backfills: Verwenden Sie
-session-abc123für paginierte Abfragen, rotierende IPs für unabhängige Requests.- WebSocket für Live-Daten: Nutzen Sie
wss://stream.binance.comfür kontinuierliche Updates — kein Weight-Verbrauch.- Geo-Targeting:
-country-USoder-country-DEsteuert, aus welcher Region Requests kommen — wichtig für Binance.com vs Binance.US.- Depth-Limits wählen:
limit=100(5 Weight) stattlimit=5000(20 Weight), wo möglich.
Bereit zum Start? Die ProxyHat-Dokumentation unter docs.proxyhat.com enthält weitere Details zur SDK-Verwendung, und die Preisseite zeigt verfügbare Pläne. Für eine Übersicht aller unterstützten Standorte siehe Proxy-Locations.






