注意(コンプライアンス): 本記事はBinanceの公開マーケットデータの収集のみを対象とします。API利用規約(Binance Terms of Service)を遵守し、ウェイト制限を尊重し、公式APIアクセスが義務付けられる場合はそちらを優先してください。認証が必要なエンドポイントやユーザーデータには触れません。
仮想通貨の価格・オーダーブック・ティッカーデータを大規模に収集するquant developerやcrypto data engineerにとって、Binance REST APIのプロキシ経由スクレイピング方法の理解は必須です。Binanceは世界最大級の取引所であり、高品質な公開マーケットデータをREST APIで提供していますが、IPごとのウェイトベースレート制限が厳しく、対策なしでは数分でIPバンを受ける可能性があります。本記事では、Binance API Documentationに基づき、エンドポイント別のウェイト、プロキシローテーション戦略、本番運用向けのコード例を解説します。
Binance REST APIのプロキシ経由スクレイピング方法:基本概念
Binance REST API(https://api.binance.com)は、認証不要で公開マーケットデータにアクセスできる複数のエンドポイントを提供しています。ただし、すべてのリクエストには「ウェイト」という概念が割り当てられており、IPごとに1分あたり約6,000ウェイトまでという制限があります。この制限を超えるとHTTP 429(Too Many Requests)が返り、継続するとHTTP 418でIPが一時的にBANされます。
プロキシを使ってリクエスト元IPを分散させることで、ウェイト予算を複数IPに分割し、実質的なスループットを向上させることができます。ProxyHatのレジデンシャルプロキシ(gate.proxyhat.com:8080)を使えば、リクエストごとにIPをローテーションし、Binance.comとBinance.USの地理的分割(HTTP 451)もバイパスできます。
主要エンドポイントとウェイト
Binanceの公開マーケットデータエンドポイントのうち、本記事で扱う4つを以下の表にまとめます。ウェイト値はBinance Spot API Documentationに基づきます。
| エンドポイント | 用途 | ウェイト | 備考 |
|---|---|---|---|
GET /api/v3/klines | ローソク足(OHLCV) | 1〜2 | limit パラメータで変動(1500まで) |
GET /api/v3/depth | オーダーブック | 5〜20 | limit=5000 でウェイト20、高コスト |
GET /api/v3/ticker/24hr | 24時間ティッカー | 1〜80 | 全シンボル取得でウェイト80 |
GET /api/v3/ticker/price | 現在価格 | 1〜2 | 全シンボルでウェイト2 |
すべてのレスポンスには X-MBX-USED-WEIGHT-1M ヘッダーが含まれており、直近1分間に消費した累計ウェイトを確認できます。この値を監視することで、429に到達する前にスロットリングできます。
ウェイトベースレート制限の仕組みとIPバンの危険性
Binanceのレート制限は単純な「リクエスト数」ではなく「ウェイトの合計」で管理されています。IPごとに1分あたり約6,000ウェイトが上限です。
- HTTP 429: ウェイト上限超過時。
Retry-Afterヘッダーで待機秒数が指定される。 - HTTP 418: 429後も継続リクエストを続けた場合のIP自動BAN。通常2分〜数日。
- HTTP 451: 地理的制限(Binance.USエリアからのBinance.comアクセスなど)。
たとえば /api/v3/depth?limit=5000 はウェイト20を消費します。1IPで連続ポーリングすると、6,000 ÷ 20 = 300リクエスト/分で上限に達します。5秒間隔でも1分あたり12リクエストでウェイト240ですが、複数シンボルを同時にポーリングすればあっという間に上限に到達します。これがプロキシによるIP分散が不可欠な理由です。
プロキシローテーション戦略:レジデンシャル vs データセンター
Binanceはデータセン�ーIPを検出しやすく、制限を厳しくかける傾向があります。レジデンシャルプロキシはISP発の本物の住宅IPを使用するため、検出リスクが低く、大規模収集に適しています。
| プロキシタイプ | 検出リスク | スループット | 用途 |
|---|---|---|---|
| レジデンシャル | 低 | 中〜高 | 大規模マーケットデータ収集 |
| モバイル | 極低 | 中 | 最高のステルス性が必要な場合 |
| データセンター | 高 | 高 | 軽量・低ウェイトのエンドポイント |
ProxyHatのレジデンシャルプロキシは gate.proxyhat.com:8080(HTTP)または :1080(SOCKS5)でアクセスでき、ユーザー名に国・都市・セッションフラグを指定できます。詳細はProxyHat公式ドキュメントを参照してください。
実装例1:Python requests + プロキシローテーション(生プロキシ)
まずはProxyHat SDKを使わず、生のプロキシURLを直接指定する例です。リクエストごとにIPをローテーションし、ウェイトヘッダーを監視します。
import requests
import time
import random
import logging
from urllib.parse import urlencode
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
BINANCE_BASE = "https://api.binance.com"
PROXYHAT_USER = "user-country-US"
PROXYHAT_PASS = "your_password"
def get_proxy_url():
"""リクエストごとに新しいIPを割り当てる(ローテーション)"""
session_id = f"req-{random.randint(100000, 999999)}"
return f"http://{PROXYHAT_USER}-session-{session_id}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
def fetch_klines(symbol="BTCUSDT", interval="1m", limit=500, max_retries=5):
params = {"symbol": symbol, "interval": interval, "limit": limit}
url = f"{BINANCE_BASE}/api/v3/klines?{urlencode(params)}"
for attempt in range(max_retries):
proxy_url = get_proxy_url()
proxies = {"http": proxy_url, "https": proxy_url}
try:
resp = requests.get(url, proxies=proxies, timeout=15)
used_weight = int(resp.headers.get("X-MBX-USED-WEIGHT-1M", 0))
logger.info(f"Attempt {attempt+1} | Status: {resp.status_code} | Used weight: {used_weight}")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 10))
logger.warning(f"429 received. Sleeping {retry_after}s")
time.sleep(retry_after)
continue
if resp.status_code == 418:
logger.error("IP banned (418). Rotating to new IP.")
continue
if resp.status_code == 451:
logger.error("Geo-blocked (451). Using US geo-targeting.")
continue
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
backoff = (2 ** attempt) + random.uniform(0, 1)
logger.warning(f"Error: {e}. Backing off {backoff:.1f}s")
time.sleep(backoff)
raise RuntimeError(f"Failed after {max_retries} retries")
# 実行
data = fetch_klines(symbol="ETHUSDT", interval="5m", limit=1000)
print(f"Retrieved {len(data)} candles")
実装例2:ProxyHat SDK + IPローテーション(Python httpx)
ProxyHat SDKを使うと、プロキシ管理がよりシンプルになります。以下は httpx と組み合わせた例です。
import httpx
import random
import asyncio
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
BINANCE_BASE = "https://api.binance.com"
PROXYHAT_USER = "user-country-US"
PROXYHAT_PASS = "your_password"
def proxyhat_proxy(session_id=None):
"""ProxyHat SDK風プロキシ生成ヘルパー"""
sid = session_id or f"req-{random.randint(100000, 999999)}"
username = f"{PROXYHAT_USER}-session-{sid}"
return f"http://{username}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
async def fetch_ticker_price(symbol="BTCUSDT"):
url = f"{BINANCE_BASE}/api/v3/ticker/price?symbol={symbol}"
proxy = proxyhat_proxy()
async with httpx.AsyncClient(proxy=proxy, timeout=15) as client:
for attempt in range(5):
try:
resp = await client.get(url)
weight = resp.headers.get("X-MBX-USED-WEIGHT-1M", "0")
logger.info(f"Status: {resp.status_code} | Weight: {weight}")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 10))
await asyncio.sleep(retry_after)
continue
resp.raise_for_status()
return resp.json()
except httpx.HTTPError as e:
backoff = (2 ** attempt) + random.uniform(0, 1)
logger.warning(f"Retry {attempt+1}: {e}, backoff {backoff:.1f}s")
await asyncio.sleep(backoff)
raise RuntimeError("Max retries exceeded")
# 実行
result = asyncio.run(fetch_ticker_price("BNBUSDT"))
print(result)
実装例3:スティッキーセッションでklinesバックフィル
過去データのバックフィル(ページネーション)では、同じIPを維持して連続リクエストを行う方が効率的です。-session-abc123 フラグでスティッキーセッションを作成します。
import requests
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
BINANCE_BASE = "https://api.binance.com"
PROXYHAT_USER = "user-country-US"
PROXYHAT_PASS = "your_password"
def fetch_klines_backfill(symbol="BTCUSDT", interval="1m", start_time=None, end_time=None, batch_size=1000):
"""スティッキーセッションでklinesをバックフィル"""
session_id = f"backfill-{symbol}-{int(time.time())}"
proxy_url = f"http://{PROXYHAT_USER}-session-{session_id}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
proxies = {"http": proxy_url, "https": proxy_url}
all_candles = []
current_start = start_time
weight_budget = 5000 # 安全マージン
while True:
params = {
"symbol": symbol,
"interval": interval,
"limit": batch_size,
}
if current_start:
params["startTime"] = current_start
if end_time:
params["endTime"] = end_time
url = f"{BINANCE_BASE}/api/v3/klines"
try:
resp = requests.get(url, params=params, proxies=proxies, timeout=15)
used_weight = int(resp.headers.get("X-MBX-USED-WEIGHT-1M", 0))
logger.info(f"Weight used: {used_weight}/{weight_budget}")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 10))
logger.warning(f"429: sleeping {retry_after}s")
time.sleep(retry_after)
continue
resp.raise_for_status()
data = resp.json()
if not data:
break
all_candles.extend(data)
last_open_time = data[-1][0]
current_start = last_open_time + 1
# ウェイトが閾値に近づいたらスロットリング
if used_weight > 4500:
sleep_time = max(0, (6000 - used_weight) / 100)
logger.info(f"Throttling: sleeping {sleep_time:.1f}s")
time.sleep(sleep_time)
if len(data) < batch_size:
break
except requests.RequestException as e:
logger.error(f"Error: {e}")
time.sleep(5)
return all_candles
# 過去24時間の1分足を取得(ミリ秒タイムスタンプ)
now_ms = int(time.time() * 1000)
start_ms = now_ms - (24 * 60 * 60 * 1000)
candles = fetch_klines_backfill("BTCUSDT", "1m", start_ms, now_ms)
print(f"Total candles: {len(candles)}")
実装例4:Node.js (axios) でプロキシローテーション
Node.js環境でも同様のパターンが使えます。axios と https-proxy-agent を組み合わせます。
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const logger = require('./logger'); // 任意のロガー
const BINANCE_BASE = 'https://api.binance.com';
const PROXYHAT_USER = 'user-country-US';
const PROXYHAT_PASS = 'your_password';
function getProxyUrl() {
const sessionId = `req-${Math.floor(Math.random() * 900000) + 100000}`;
return `http://${PROXYHAT_USER}-session-${sessionId}:${PROXYHAT_PASS}@gate.proxyhat.com:8080`;
}
async function fetchDepth(symbol = 'BTCUSDT', limit = 100, maxRetries = 5) {
const url = `${BINANCE_BASE}/api/v3/depth?symbol=${symbol}&limit=${limit}`;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const proxyUrl = getProxyUrl();
const agent = new HttpsProxyAgent(proxyUrl);
try {
const resp = await axios.get(url, {
httpsAgent: agent,
timeout: 15000,
});
const usedWeight = parseInt(resp.headers['x-mbx-used-weight-1m'] || '0', 10);
logger.info(`Attempt ${attempt + 1} | Status: ${resp.status} | Weight: ${usedWeight}`);
if (resp.status === 429) {
const retryAfter = parseInt(resp.headers['retry-after'] || '10', 10);
logger.warn(`429: waiting ${retryAfter}s`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
return resp.data;
} catch (err) {
const backoff = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
logger.warn(`Error: ${err.message}, backing off ${backoff}ms`);
await new Promise(r => setTimeout(r, backoff));
}
}
throw new Error(`Failed after ${maxRetries} retries`);
}
// 実行
fetchDepth('ETHUSDT', 100).then(data => {
console.log(`Bids: ${data.bids.length}, Asks: ${data.asks.length}`);
});
実装例5:並行ポーリング + ウェイトアウェアスロットリング
複数シンボルを並行ポーリングする場合は、全体のウェイト消費を追跡し、サーキットブレーカーを導入します。
import httpx
import asyncio
import random
import logging
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
BINANCE_BASE = "https://api.binance.com"
PROXYHAT_USER = "user-country-US"
PROXYHAT_PASS = "your_password"
# ウェイト追跡
class WeightTracker:
def __init__(self, max_weight=5000):
self.current_weight = 0
self.max_weight = max_weight
self.last_reset = time.time()
def update(self, weight_header):
if weight_header:
self.current_weight = int(weight_header)
# 1分経過でリセット
if time.time() - self.last_reset > 60:
self.current_weight = 0
self.last_reset = time.time()
def can_request(self, expected_weight=1):
return (self.current_weight + expected_weight) < self.max_weight
async def wait_for_budget(self, expected_weight=1):
while not self.can_request(expected_weight):
sleep_time = max(0.1, (self.max_weight - self.current_weight) / 200)
logger.info(f"Waiting for weight budget: {self.current_weight}/{self.max_weight}")
await asyncio.sleep(sleep_time)
def proxyhat_proxy():
sid = f"req-{random.randint(100000, 999999)}"
return f"http://{PROXYHAT_USER}-session-{sid}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"
async def fetch_ticker_24hr(symbol, tracker, client):
await tracker.wait_for_budget(expected_weight=1)
url = f"{BINANCE_BASE}/api/v3/ticker/24hr?symbol={symbol}"
proxy = proxyhat_proxy()
try:
async with httpx.AsyncClient(proxy=proxy, timeout=15) as c:
resp = await c.get(url)
tracker.update(resp.headers.get("X-MBX-USED-WEIGHT-1M"))
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 10))
await asyncio.sleep(retry_after)
return None
resp.raise_for_status()
return resp.json()
except Exception as e:
logger.error(f"Error fetching {symbol}: {e}")
return None
async def poll_multiple_symbols(symbols, interval_sec=5):
tracker = WeightTracker(max_weight=5000)
while True:
tasks = [fetch_ticker_24hr(s, tracker, None) for s in symbols]
results = await asyncio.gather(*tasks, return_exceptions=True)
valid = [r for r in results if r and not isinstance(r, Exception)]
logger.info(f"Fetched {len(valid)}/{len(symbols)} tickers | Weight: {tracker.current_weight}")
await asyncio.sleep(interval_sec)
# 実行
symbols = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT", "XRPUSDT"]
asyncio.run(poll_multiple_symbols(symbols, interval_sec=10))
WebSocket vs RESTポーリング:いつどちらを使うか
Binanceは公開WebSocketストリーム(wss://stream.binance.com:9443)も提供しており、kline や depth のリアルタイム更新が可能です。
| 比較項目 | RESTポーリング | WebSocketストリーム |
|---|---|---|
| レイテンシ | 100ms〜500ms | 10ms〜50ms |
| ウェイト消費 | リクエストごと | 接続時のみ |
| プロキシの必要性 | 高(IP分散) | 低〜中 |
| 過去データ取得 | 可能(klines) | 不可(リアルタイムのみ) |
| 実装の複雑さ | 低 | 中(再接続管理) |
原則として、リアルタイム監視にはWebSocket、過去データのバックフィルやスナップショット取得にはREST + プロキシローテーションが適しています。
よくある間違いとエッジケース
- ウェイトヘッダーを無視:
X-MBX-USED-WEIGHT-1Mを読まないと429が頻発します。 - 固定IPで連続リクエスト: 1IPで6,000ウェイトを使い切ると429→418の連鎖が始まります。
- geo-blockの無視: Binance.comは米国IPを451で拒否する場合があります。
-country-USでBinance.US向けIPを使うか、別の国を指定します。 - Retry-Afterの無視: 429時は必ず
Retry-Afterヘッダーの秒数待機してください。 - 並行しすぎ: 100並行でリクエストすると、プロキシ側またはBinance側でコネクションが飽和します。10〜20並行が安全な目安です。
ProxyHatのセットアップ
ProxyHatのアカウント作成後、ダッシュボード(ProxyHat 料金プラン)から認証情報を取得し、上記コードの PROXYHAT_USER と PROXYHAT_PASS を置き換えます。利用可能なロケーションはプロキシロケーション一覧で確認できます。
より一般的なWebスクレイピングのユースケースについてはWebスクレイピングのユースケースを、SERP追跡についてはSERP追跡を参照してください。
Key Takeaways(重要ポイント)
- Binance REST APIはIPごとに1分あたり約6,000ウェイトの制限。429→418の連鎖に注意。
X-MBX-USED-WEIGHT-1Mヘッダーを必ず監視し、4,500〜5,000ウェイトでスロットリング。- レジデンシャルプロキシでIPをローテーションし、ウェイト予算を分散。
gate.proxyhat.com:8080(HTTP)または:1080(SOCKS5)を使用。- 過去データのバックフィルにはスティッキーセッション(
-session-xxx)、リアルタイム監視にはWebSocketが適する。- 指数バックオフ + Retry-After尊重 + 10〜20並行が本番運用の基本。
ProxyHatのレジデンシャルプロキシを使えば、Binanceの公開マーケットデータを大規模かつ安全に収集できます。まずはProxyHatドキュメントで接続方法を確認し、少額プランからテストを始めることをお勧めします。






