Binance REST APIをプロキシでスクレイピングする完全ガイド

Binanceの公開マーケットデータREST APIをプロキシ経由で大規模収集する方法を、エンドポイント別のウェイト制限、IPローテーション、リトライ戦略まで含めて解説します。

How to Scrape the Binance REST API with Proxies: A Developer Guide
この記事の内容

注意(コンプライアンス): 本記事は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〜2limit パラメータで変動(1500まで)
GET /api/v3/depthオーダーブック5〜20limit=5000 でウェイト20、高コスト
GET /api/v3/ticker/24hr24時間ティッカー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環境でも同様のパターンが使えます。axioshttps-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)も提供しており、klinedepth のリアルタイム更新が可能です。

比較項目RESTポーリングWebSocketストリーム
レイテンシ100ms〜500ms10ms〜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_USERPROXYHAT_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ドキュメントで接続方法を確認し、少額プランからテストを始めることをお勧めします。

よくある質問

Binance REST APIをプロキシでスクレイピングする方法とは何ですか?

Binanceの公開マーケットデータエンドポイント(klines、depth、tickerなど)にプロキシ経由でアクセスし、IPごとのウェイト制限を回避しながら大規模にデータを収集する手法です。レジデンシャルプロキシでIPをローテーションすることで、1分あたり約6,000ウェイトの制限を複数IPに分散し、スループットを向上させます。

なぜBinance REST APIのスクレイピングにプロキシが必要なのですか?

BinanceはIPごとにウェイトベースのレート制限を設けており、制限を超えるとHTTP 429(Too Many Requests)、継続するとHTTP 418でIPバンを科します。プロキシでIPを分散することでウェイト予算を複数IPに分割し、429や418を回避しながら高頻度でデータを取得できます。

Binance REST APIのスクレイピングに最適なプロキシタイプは何ですか?

レジデンシャルプロキシが最適です。BinanceはデータセンタIPを検出しやすく制限を厳しくかける傾向がありますが、レジデンシャルプロキシはISP発の本物の住宅IPを使用するため検出リスクが低く、大規模なマーケットデータ収集に適しています。ProxyHatのレジデンシャルプロキシはgate.proxyhat.com:8080(HTTP)または:1080(SOCKS5)でアクセスできます。

Binance REST APIのスクレイピングでブロックを回避するにはどうすればよいですか?

X-MBX-USED-WEIGHT-1Mヘッダーを監視して4,500〜5,000ウェイトでスロットリングし、429時はRetry-Afterヘッダーの秒数を尊重して待機します。リクエストごとにIPをローテーションし、指数バックオフでリトライし、並行数を10〜20に制限することでブロックをほぼ回避できます。

BinanceのWebSocketストリームとRESTポーリングのどちらを使うべきですか?

リアルタイムの価格・オーダーブック監視にはWebSocketストリーム(wss://stream.binance.com:9443)が適しています。レイテンシが10〜50msでウェイト消費もほぼありません。一方、過去データのバックフィルやスナップショット取得にはREST API + プロキシローテーションが適しています。用途に応じて使い分けるのがベストプラクティスです。

始める準備はできましたか?

148か国以上の住宅用・ISP・モバイルプロキシ。無料アカウントを作成。

無料アカウントを作成
← ブログに戻る