免責事項: 本記事はBybitの公開市場データエンドポイントへのアクセスに関する技術的解説です。Bybitの利用規約および該当管轄区の法律を遵守してください。スクレイピングを行う前に必ず公式ドキュメントとToSを確認し、許可された範囲でデータを収集してください。
Bybit V5 Market APIのローテーションプロキシによるスクレイピングは、仮想通貨取引ボットや市場データパイプラインを構築する開発者にとって中核的な課題です。BybitはV5統合APIでエンドポイント構造を刷新しましたが、IPベースのレート制限と地理的制限により、高頻度データ収集は容易ではありません。本ガイドでは、api.bybit.com/v5/market/orderbook、/v5/market/tickers、/v5/market/kline、/v5/market/recent-tradeの各エンドポイントを、ProxyHatのローテーションプロキシ(gate.proxyhat.com:8080)経由でスクレイピングする実践的な方法を、Node.js・Pythonのコード例とともに解説します。
Bybit V5 Market APIのエンドポイント構造とレスポンスエンベロープ
Bybit V5 Market APIは、取引所の公開市場データへのアクセスを提供するRESTエンドポイント群です。認証不要でアクセスでき、公式V5ドキュメントに仕様が公開されています。主要なエンドポイントは以下の通りです。
| エンドポイント | メソッド | 必須パラメータ | 用途 |
|---|---|---|---|
/v5/market/orderbook | GET | category, symbol | 板情報(オーダーブック)のスナップショット取得 |
/v5/market/tickers | GET | category | 全銘柄または指定銘柄のティッカー情報(価格、出来高等) |
/v5/market/kline | GET | category, symbol, interval | ローソク足(OHLCV)データの取得 |
/v5/market/recent-trade | GET | category, symbol | 直近の約定履歴取得 |
categoryパラメータの理解
V5 APIの重要な設計は category パラメータです。これは spot、linear、inverse、option のいずれかを指定し、取得する市場のタイプを切り替えます。例えば、/v5/market/orderbook?category=spot&symbol=BTCUSDT は現物のBTCUSDT板情報を返し、category=linear はUSDT無期限先物の板情報を返します。各エンドポイントでサポートされるcategoryは異なるため、ドキュメントで確認が必要です。
retCode / retMsg レスポンスエンベロープ
Bybit V5のすべてのレスポンスは統一されたJSONエンベロープで返されます。
{
"retCode": 0,
"retMsg": "OK",
"result": {
"category": "spot",
"symbol": "BTCUSDT",
"bids": [["42000.50", "1.234"], ...],
"asks": [["42000.60", "0.567"], ...]
},
"retExtInfo": {},
"time": 1700000000000
}
retCode が 0 の場合は成功、それ以外はエラーです。retMsg には人間可読なメッセージが含まれます。スクレイピング時は必ず retCode をチェックし、非ゼロの場合はリトライまたはエラーハンドリングを行う必要があります。HTTPステータスが200でも retCode が非ゼロのケースがある点に注意してください。
BybitのIPベースレート制限と403バンの仕組み
BybitはエンドポイントごとにIPベースのレート制限を設けています。/v5/market/orderbook は高頻度アクセスが想定されるため比較的緩い制限ですが、バースト的なアクセスは制限に到達しやすくなります。制限を超過すると以下が発生します。
- HTTP 403レスポンス: レート制限超過時に即座に返されます。
- IPバンウィンドウ: 制限超過後、約10分間そのIPからのアクセスがブロックされます。
- エンドポイント別カウント:
orderbookへのバーストはtickersの制限には影響しませんが、同一IPでの全体的なリクエスト頻度は監視されます。
この仕組みがローテーションプロキシを必要とする理由です。単一IPで高頻度に api.bybit.com/v5/market/orderbook を叩くと、数分以内に403とIPバンが発生し、データ収集パイプラインが停止します。リクエストごとにIPをローテーションすることで、各IPのレート制限クォータを独立して消費できます。
Bybitの地理的制限と住宅プロキシによる回避
Bybitは規制上の理由から、米国、英国、および一部制限地域からのアクセスをブロックしています。このブロックはAWS CloudFrontのジオフィルタリングレイヤーで実装されており、IPの地理的位置に基づいてHTTP 403を返します。データセンタープロキシを使用すると、そのIPが制限地域のデータセンターに属している場合、CloudFrontレベルでブロックされる可能性があります。
ProxyHatの住宅プロキシを使用することで、許可された国からの実際のISP IPアドレスでアクセスできます。ユーザー名にジオターゲティングフラグを指定するだけで、アクセス元の国を制御できます。
# 日本からのアクセス
http://user-country-JP:pass@gate.proxyhat.com:8080
# ドイツからのアクセス
http://user-country-DE:pass@gate.proxyhat.com:8080
# シンガポールからのアクセス(Bybit本社所在地)
http://user-country-SG:pass@gate.proxyhat.com:8080
ProxyHatの利用可能なロケーションはロケーション一覧で確認できます。価格についてはプランページを参照してください。
実装例1: curlでの基本アクセス(プロキシなし vs プロキシあり)
まずはcurlでの基本比較から始めます。
# プロキシなし(制限地域からの場合、403の可能性)
curl -s "https://api.bybit.com/v5/market/orderbook?category=spot&symbol=BTCUSDT&limit=50"
# ProxyHatローテーションプロキシ経由(日本IP)
curl -s -x "http://user-country-JP:pass@gate.proxyhat.com:8080" \
"https://api.bybit.com/v5/market/orderbook?category=spot&symbol=BTCUSDT&limit=50"
# SOCKS5経由
curl -s -x "socks5://user-country-JP:pass@gate.proxyhat.com:1080" \
"https://api.bybit.com/v5/market/tickers?category=linear"
# kline取得(1分足、200件)
curl -s -x "http://user-country-JP:pass@gate.proxyhat.com:8080" \
"https://api.bybit.com/v5/market/kline?category=spot&symbol=ETHUSDT&interval=1&limit=200"
# 直近の約定履歴
curl -s -x "http://user-country-JP:pass@gate.proxyhat.com:8080" \
"https://api.bybit.com/v5/market/recent-trade?category=spot&symbol=BTCUSDT&limit=50"
実装例2: Node.js (axios) で複数銘柄のIPローテーション
次に、Node.jsで複数銘柄のオーダーブックをローテーションプロキシ経由で取得する例を示します。ProxyHat SDKパターンと生プロキシパターンを並べて示します。
// ===== 生プロキシパターン (axios + http-proxy-agent) =====
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const PROXYHAT_HOST = 'gate.proxyhat.com';
const PROXYHAT_PORT = 8080;
const PROXY_USER = 'user-country-JP';
const PROXY_PASS = 'pass';
const proxyUrl = `http://${PROXY_USER}:${PROXY_PASS}@${PROXYHAT_HOST}:${PROXYHAT_PORT}`;
const agent = new HttpsProxyAgent(proxyUrl);
const symbols = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'XRPUSDT', 'DOGEUSDT'];
async function fetchOrderbook(symbol, retries = 3) {
const url = `https://api.bybit.com/v5/market/orderbook?category=spot&symbol=${symbol}&limit=50`;
for (let attempt = 0; attempt < retries; attempt++) {
try {
const res = await axios.get(url, {
httpsAgent: agent,
timeout: 10000,
headers: {
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (compatible; MarketDataBot/1.0)'
}
});
if (res.data.retCode === 0) {
return res.data.result;
} else {
throw new Error(`retCode=${res.data.retCode}, retMsg=${res.data.retMsg}`);
}
} catch (err) {
const wait = Math.pow(2, attempt) * 500;
console.error(`[${symbol}] attempt ${attempt + 1} failed: ${err.message}, retry in ${wait}ms`);
if (attempt === retries - 1) throw err;
await new Promise(r => setTimeout(r, wait));
}
}
}
// 全銘柄を並列取得(ただしレート制限を考慮して5並列まで)
async function main() {
const results = await Promise.allSettled(
symbols.map(sym => fetchOrderbook(sym))
);
results.forEach((r, i) => {
if (r.status === 'fulfilled') {
const bids = r.value.bids.slice(0, 3);
console.log(`${symbols[i]}: best bid=${bids[0]?.[0]}, asks count=${r.value.asks.length}`);
} else {
console.error(`${symbols[i]}: FAILED - ${r.reason.message}`);
}
});
}
main();
// ===== ProxyHat SDKパターン =====
// ProxyHat SDK はローテーション・リトライ・ジオターゲティングを抽象化
const { ProxyHatClient } = require('@proxyhat/sdk'); // 仮想SDK例
const client = new ProxyHatClient({
gateway: 'gate.proxyhat.com',
port: 8080,
username: 'user',
password: 'pass',
country: 'JP',
rotation: 'per-request', // リクエストごとにIPローテーション
maxRetries: 3,
backoff: 'exponential',
timeout: 10000
});
async function fetchOrderbookSDK(symbol) {
const url = `https://api.bybit.com/v5/market/orderbook?category=spot&symbol=${symbol}&limit=50`;
const data = await client.get(url, {
headers: { 'Accept': 'application/json' }
});
if (data.retCode !== 0) {
throw new Error(`retCode=${data.retCode}, retMsg=${data.retMsg}`);
}
return data.result;
}
// 複数銘柄を順次取得(レート制限を考慮してシーケンシャル)
async function scrapeAll(symbols) {
const results = [];
for (const sym of symbols) {
try {
const ob = await fetchOrderbookSDK(sym);
results.push({ symbol: sym, bestBid: ob.bids[0]?.[0], bestAsk: ob.asks[0]?.[0] });
console.log(`${sym}: bid=${ob.bids[0]?.[0]}, ask=${ob.asks[0]?.[0]}`);
} catch (err) {
console.error(`${sym}: ${err.message}`);
}
// エンドポイント別レート予算: orderbook は 5 req/sec に制限
await new Promise(r => setTimeout(r, 200));
}
return results;
}
scrapeAll(['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'XRPUSDT', 'DOGEUSDT']);
実装例3: Python (httpx) でklineデータのページネーション
klineデータの過去分を取得する場合、start と end パラメータで時間範囲を指定し、limit(最大1000件)でページネーションを行います。この場合、sticky sessionを使用することで、同一IPから継続的にアクセスし、IPローテーションによる一貫性の問題を回避できます。
# ===== 生プロキシパターン (httpx) =====
import httpx
import asyncio
import time
from datetime import datetime, timedelta, timezone
PROXY_URL = "http://user-country-JP-session-kline01:pass@gate.proxyhat.com:8080"
BASE_URL = "https://api.bybit.com/v5/market/kline"
async def fetch_kline_page(symbol, category, interval, start_ts, end_ts, retries=3):
params = {
"category": category,
"symbol": symbol,
"interval": str(interval),
"start": str(start_ts),
"end": str(end_ts),
"limit": "1000"
}
for attempt in range(retries):
try:
async with httpx.AsyncClient(
proxy=PROXY_URL,
timeout=15.0,
headers={
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (compatible; KlineCollector/1.0)"
}
) as client:
resp = await client.get(BASE_URL, params=params)
resp.raise_for_status()
data = resp.json()
if data.get("retCode") == 0:
return data["result"]
else:
raise Exception(f"retCode={data['retCode']}, retMsg={data['retMsg']}")
except Exception as e:
wait = 2 ** attempt * 500
print(f" [retry {attempt+1}/{retries}] {e}, waiting {wait}ms")
if attempt == retries - 1:
raise
await asyncio.sleep(wait / 1000)
async def scrape_kline_history(symbol, category, interval, days_back):
"""過去N日分のklineを1000件ずつページネーションで取得"""
now = int(datetime.now(timezone.utc).timestamp() * 1000)
start = int((datetime.now(timezone.utc) - timedelta(days=days_back)).timestamp() * 1000)
all_candles = []
cursor = start
while cursor < now:
end = min(cursor + (interval * 60_000 * 1000), now) # 1000本分の時間窓
print(f"Fetching {symbol} kline: {datetime.fromtimestamp(cursor/1000, tz=timezone.utc)} -> {datetime.fromtimestamp(end/1000, tz=timezone.utc)}")
result = await fetch_kline_page(symbol, category, interval, cursor, end)
candles = result.get("list", [])
if not candles:
break
all_candles.extend(candles)
print(f" Got {len(candles)} candles (total: {len(all_candles)})")
# 最後のキャンドルのタイムスタンプ + 1ms から次ページ
cursor = int(candles[-1][0]) + 1
# レート予算: kline は 2 req/sec に制限
await asyncio.sleep(0.5)
return all_candles
# 実行例: BTCUSDT現物の1時間足を過去7日分取得
async def main():
candles = await scrape_kline_history("BTCUSDT", "spot", 60, 7)
print(f"\nTotal candles collected: {len(candles)}")
if candles:
print(f"First: ts={candles[0][0]}, open={candles[0][1]}")
print(f"Last: ts={candles[-1][0]}, close={candles[-1][4]}")
asyncio.run(main())
# ===== ProxyHat SDKパターン (Python) =====
from proxyhat import ProxyHatClient # 仮想SDK例
import asyncio
async def fetch_kline_sdk(symbol, category, interval, start_ts, end_ts):
client = ProxyHatClient(
gateway="gate.proxyhat.com",
port=8080,
username="user",
password="pass",
country="JP",
session="kline-pagination-01", # sticky sessionでIP固定
max_retries=3,
backoff="exponential",
timeout=15.0
)
url = f"https://api.bybit.com/v5/market/kline?category={category}&symbol={symbol}&interval={interval}&start={start_ts}&end={end_ts}&limit=1000"
data = await client.get_async(url)
if data.get("retCode") != 0:
raise Exception(f"retCode={data['retCode']}, retMsg={data['retMsg']}")
return data["result"]
async def scrape_with_sdk(symbol, category, interval, days_back):
from datetime import datetime, timedelta, timezone
now = int(datetime.now(timezone.utc).timestamp() * 1000)
start = int((datetime.now(timezone.utc) - timedelta(days=days_back)).timestamp() * 1000)
all_candles = []
cursor = start
while cursor < now:
end = min(cursor + (interval * 60_000 * 1000), now)
result = await fetch_kline_sdk(symbol, category, interval, cursor, end)
candles = result.get("list", [])
if not candles:
break
all_candles.extend(candles)
cursor = int(candles[-1][0]) + 1
await asyncio.sleep(0.5) # レート予算
return all_candles
asyncio.run(scrape_with_sdk("ETHUSDT", "spot", 60, 3))
実装例4: Python (aiohttp) でティッカー情報を高並列スクレイピング
複数銘柄のティッカー情報を高並列で取得する例です。エンドポイント別のレート予算を管理しつつ、ローテーションプロキシでIPを分散させます。
# ===== 生プロキシパターン (aiohttp) =====
import aiohttp
import asyncio
import logging
from aiohttp import ClientTimeout, ClientProxyConnectionError
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger("bybit_scraper")
PROXY = "http://user-country-JP:pass@gate.proxyhat.com:8080"
BASE = "https://api.bybit.com/v5/market/tickers"
# エンドポイント別レート予 budget (req/sec)
RATE_BUDGETS = {
"tickers": 3, # 3 req/sec
"orderbook": 5, # 5 req/sec
"kline": 2, # 2 req/sec
}
class RateLimiter:
"""トークンバケット方式のレート制限"""
def __init__(self, rate_per_sec):
self.rate = rate_per_sec
self.tokens = rate_per_sec
self.last_refill = asyncio.get_event_loop().time()
self.lock = asyncio.Lock()
async def acquire(self):
async with self.lock:
now = asyncio.get_event_loop().time()
elapsed = now - self.last_refill
self.tokens = min(self.rate, self.tokens + elapsed * self.rate)
self.last_refill = now
if self.tokens < 1:
wait = (1 - self.tokens) / self.rate
await asyncio.sleep(wait)
self.tokens = 0
else:
self.tokens -= 1
class CircuitBreaker:
"""連続エラー時のサーキットブレーカー"""
def __init__(self, threshold=5, reset_timeout=60):
self.failures = 0
self.threshold = threshold
self.reset_timeout = reset_timeout
self.opened_at = None
def record_failure(self):
self.failures += 1
if self.failures >= self.threshold:
self.opened_at = asyncio.get_event_loop().time()
logger.warning(f"Circuit breaker OPEN after {self.failures} failures")
def record_success(self):
self.failures = 0
self.opened_at = None
def is_open(self):
if self.opened_at is None:
return False
elapsed = asyncio.get_event_loop().time() - self.opened_at
if elapsed > self.reset_timeout:
logger.info("Circuit breaker resetting")
self.failures = 0
self.opened_at = None
return False
return True
limiter = RateLimiter(RATE_BUDGETS["tickers"])
breaker = CircuitBreaker(threshold=5, reset_timeout=60)
async def fetch_tickers(session, category, retries=3):
if breaker.is_open():
raise Exception("Circuit breaker is open")
url = f"{BASE}?category={category}"
for attempt in range(retries):
await limiter.acquire()
try:
async with session.get(url, timeout=ClientTimeout(total=10)) as resp:
if resp.status == 403:
logger.warning(f"403 on attempt {attempt+1} - rate limited or geo-blocked")
breaker.record_failure()
await asyncio.sleep(2 ** attempt)
continue
resp.raise_for_status()
data = await resp.json()
if data.get("retCode") == 0:
breaker.record_success()
return data["result"]
else:
raise Exception(f"retCode={data['retCode']}, retMsg={data['retMsg']}")
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
logger.error(f"Attempt {attempt+1} failed: {type(e).__name__}: {e}")
breaker.record_failure()
if attempt < retries - 1:
await asyncio.sleep(2 ** attempt)
else:
raise
async def scrape_all_tickers(categories):
connector = aiohttp.TCPConnector(limit=20, limit_per_host=5)
timeout = ClientTimeout(total=30)
async with aiohttp.ClientSession(
connector=connector,
timeout=timeout,
headers={"Accept": "application/json", "User-Agent": "Mozilla/5.0 (compatible; TickerBot/1.0)"}
) as session:
tasks = []
for cat in categories:
tasks.append(fetch_tickers(session, cat))
results = await asyncio.gather(*tasks, return_exceptions=True)
for cat, result in zip(categories, results):
if isinstance(result, Exception):
logger.error(f"{cat}: FAILED - {result}")
else:
tickers = result.get("list", [])
logger.info(f"{cat}: {len(tickers)} tickers retrieved")
if tickers:
first = tickers[0]
logger.info(f" Sample: {first.get('symbol')} lastPrice={first.get('lastPrice')}")
asyncio.run(scrape_all_tickers(["spot", "linear"]))
実装例5: 直近の約定履歴を継続的にポーリング
/v5/market/recent-trade エンドポイントを使用して、直近の約定履歴を定期的にポーリングする例です。リクエストごとにIPをローテーションしつつ、重複データを除外します。
# ===== Python (httpx) + ローテーションプロキシ =====
import httpx
import asyncio
import time
import json
# リクエストごとに異なるセッションID = IPローテーション
def get_proxy_url(request_id):
return f"http://user-country-JP-session-rt{request_id}:pass@gate.proxyhat.com:8080"
BASE = "https://api.bybit.com/v5/market/recent-trade"
seen_trade_ids = set()
request_counter = 0
async def poll_recent_trades(symbol, category, interval_sec=2, max_polls=100):
global request_counter, seen_trade_ids
new_trades = []
for poll_idx in range(max_polls):
request_counter += 1
proxy = get_proxy_url(request_counter)
params = {"category": category, "symbol": symbol, "limit": "50"}
try:
async with httpx.AsyncClient(
proxy=proxy,
timeout=10.0,
headers={"Accept": "application/json"}
) as client:
resp = await client.get(BASE, params=params)
resp.raise_for_status()
data = resp.json()
if data.get("retCode") != 0:
print(f"[poll {poll_idx}] retCode={data['retCode']}, retMsg={data['retMsg']}")
await asyncio.sleep(interval_sec * 2)
continue
trades = data["result"].get("list", [])
fresh = []
for t in trades:
trade_id = t.get("execId", "")
if trade_id and trade_id not in seen_trade_ids:
seen_trade_ids.add(trade_id)
fresh.append(t)
if fresh:
new_trades.extend(fresh)
print(f"[poll {poll_idx}] {len(fresh)} new trades (total new: {len(new_trades)})")
for ft in fresh[:3]:
print(f" {ft.get('time')} price={ft.get('price')} qty={ft.get('size')} side={ft.get('side')}")
else:
print(f"[poll {poll_idx}] no new trades")
except Exception as e:
print(f"[poll {poll_idx}] ERROR: {type(e).__name__}: {e}")
await asyncio.sleep(interval_sec * 3) # バックオフ
continue
await asyncio.sleep(interval_sec)
return new_trades
asyncio.run(poll_recent_trades("BTCUSDT", "spot", interval_sec=2, max_polls=30))
RESTスナップショット vs WebSocket orderbook.50ストリーム
Bybitは公開WebSocketチャンネルも提供しており、orderbook.50.{symbol} や orderbook.200.{symbol} でリアルタイムの板情報ストリームを購読できます。RESTのポーリング方式とWebSocket方式の比較は以下の通りです。
| 項目 | REST (/v5/market/orderbook) | WebSocket (orderbook.50) |
|---|---|---|
| データ形式 | スナップショット(取得時点の完全な板) | スナップショット+差分更新 |
| レイテンシ | ポーリング間隔に依存(通常200ms〜2s) | リアルタイム(通常50ms未満) |
| レート制限 | リクエストごとに消費 | 接続ごと(購読後は追加リクエスト不要) |
| プロキシ要件 | リクエストごとのIPローテーションが可能 | WSS接続を維持(sticky session推奨) |
| 適用ケース | 定期的なスナップショット保存、バッチ分析 | リアルタイムトレーディング、ライブダッシュボード |
WebSocketを使用する場合、ProxyHatのSOCKS5プロキシ(gate.proxyhat.com:1080)とsticky sessionを組み合わせることで、安定したWSS接続を維持できます。
# ===== WebSocket + SOCKS5 プロキシ (Node.js) =====
const WebSocket = require('ws');
const { SocksProxyAgent } = require('socks-proxy-agent');
// sticky session でIPを固定(WSS接続の安定性確保)
const socksAgent = new SocksProxyAgent(
'socks5://user-country-JP-session-ws01:pass@gate.proxyhat.com:1080'
);
const ws = new WebSocket('wss://stream.bybit.com/v5/public/spot', {
agent: socksAgent
});
ws.on('open', () => {
console.log('WebSocket connected via ProxyHat SOCKS5');
// orderbook.50 ストリームを購読
ws.send(JSON.stringify({
op: 'subscribe',
args: ['orderbook.50.BTCUSDT', 'orderbook.50.ETHUSDT']
}));
});
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
if (msg.topic && msg.topic.startsWith('orderbook.50')) {
const symbol = msg.topic.split('.').pop();
if (msg.type === 'snapshot') {
console.log(`[${symbol}] snapshot: ${msg.data.bids.length} bids, ${msg.data.asks.length} asks`);
} else if (msg.type === 'delta') {
console.log(`[${symbol}] delta: delete=${msg.data.delete?.length}, update=${msg.data.update?.length}, insert=${msg.data.insert?.length}`);
}
}
});
ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
});
ws.on('close', () => {
console.log('WebSocket closed');
});
// グレースフルシャットダウン
process.on('SIGINT', () => {
ws.close();
process.exit(0);
});
よくある失敗とエッジケース
1. retCodeのチェック漏れ
HTTPステータス200でも retCode が非ゼロのケースがあります。必ずJSONボディの retCode を検証してください。retMsg には「rate limit exceeded」や「invalid symbol」などのメッセージが含まれます。
2. バーストアクセスによるIPバン
ループ内で await なしに連続リクエストを送ると、数秒で403と10分間のIPバンが発生します。レートリミッター(トークンバケット)を必ず実装してください。エンドポイント別のレート予算を分けることも重要です。
3. 地域制限の見落とし
データセンタープロキシを使用して「アクセスできるのにデータが返ってこない」場合は、CloudFrontレベルでジオブロックされている可能性があります。user-country-JP などのジオターゲティングフラグで許可された国を指定してください。
4. kline ページネーションでの時刻の扱い
Bybitのklineエンドポイントは start と end をミリ秒タイムスタンプで受け取ります。ページネーション時は最後のキャンドルのタイムスタンプ +1ms を次の start に設定してください。intervalパラメータは分単位の整数(1, 5, 15, 30, 60, 240, 720, D, W, M)です。
5. プロキシ認証エラー
ユーザー名にジオターゲティングフラグやセッションフラグを含める場合、コロン(:)で区切る必要があります。形式は user-country-JP-session-abc123 です。パスワードに特殊文字が含まれる場合はURLエンコードしてください。
ProxyHat固有のセットアップとベストプラクティス
ProxyHatを使用したBybit APIスクレイピングのベストプラクティスをまとめます。
- エンドポイント別レート予算:
orderbookは5 req/sec、tickersは3 req/sec、klineは2 req/secを目安に設定。 - per-request ローテーション: 高頻度ポーリングにはリクエストごとにIPを変える
user-session-{unique_id}パターンを使用。 - sticky session: klineページネーションやWebSocket接続では
user-session-abc123でIPを固定。 - 指数バックオフ: 403または
retCode非ゼロ時は2^attempt * 500msでリトライ。 - サーキットブレーカー: 連続5回失敗で60秒間リクエストを停止し、IPバンの延長を防ぐ。
- ジオターゲティング:
user-country-JPまたはuser-country-SGで許可された国からアクセス。 - タイムアウト: プロキシ経由のレイテンシを考慮し、10〜15秒のタイムアウトを設定。
詳細なプロキシ設定についてはProxyHatドキュメントを参照してください。ウェブスクレイピングのユースケースについてはこちらを、SERPトラッキングについてはこちらを参照してください。
Key Takeaways(重要ポイント)
- Bybit V5 Market APIは
categoryパラメータで spot/linear/inverse/option を切り替え、統一された retCode/retMsg エンベロープでレスポンスを返す。- IPベースのレート制限はエンドポイントごとに設定され、超過すると403と約10分間のIPバンが発生する。
- 地理的制限はCloudFrontレベルで実装され、米国・英国・制限地域からのアクセスがブロックされる。住宅プロキシのジオターゲティングで回避可能。
- ローテーションプロキシ(
gate.proxyhat.com:8080)でリクエストごとにIPを変えることで、各IPのレート制限クォータを独立消費できる。- Sticky session(
-session-abc123)はklineページネーションやWebSocket接続の安定性に不可欠。- REST vs WebSocketの選択は用途次第。定期的なスナップショット保存はREST、リアルタイムトレーディングはWebSocket(SOCKS5
:1080)。- 本ガイドのプロキシ設定: HTTPは
gate.proxyhat.com:8080、SOCKS5はgate.proxyhat.com:1080。ユーザー名にcountry-JPやsession-xxxフラグを指定。
Bybit V5 Market APIのスクレイピングは、適切なプロキシインフラとレート管理があれば、安定した市場データパイプラインを構築できます。ProxyHatのローテーションプロキシを活用し、エンドポイント別のレート予算と指数バックオフを組み合わせることで、403バンや地理的制限を回避しながら高品質なデータ収集が可能になります。ProxyHatのプランを確認し、今すぐBybit市場データの収集を始めましょう。






