GraphQL API는 점점 더 인기를 얻고 있지만, 그에 따라 제한도 증가하고 있습니다: 속도 제한, IP 차단, 지리적 필터. 대량의 데이터를 GraphQL을 통해 처리하는 경우 — 전자상거래 플랫폼을 파싱하거나, 소셜 미디어에서 분석을 수집하거나, API를 테스트하는 경우 — 프록시 없이는 불가능합니다. 이 기사에서는 GraphQL 요청을 위한 프록시를 올바르게 설정하고, IP 회전을 구현하며, 차단을 피하는 방법을 살펴보겠습니다.
우리는 Python 및 Node.js에서의 실용적인 예제를 보여주고, 일반적인 오류를 분석하며, 다양한 작업에 적합한 프록시 유형 선택에 대한 권장 사항을 제공합니다.
GraphQL 요청을 위한 프록시가 필요한 이유
GraphQL API는 짧은 시간에 대량의 데이터를 얻기 위해 자주 사용됩니다. REST API와 달리 데이터가 여러 엔드포인트에 분산되어 있는 GraphQL은 필요한 모든 것을 하나의 요청으로 요청할 수 있습니다. 이는 편리하지만 문제를 발생시킵니다:
- 속도 제한 — 대부분의 공개 GraphQL API는 하나의 IP에서 요청 수를 제한합니다 (예: GitHub API: 시간당 5000 요청, Shopify: 초당 2 요청)
- IP 차단 — 한도를 초과하거나 의심스러운 활동이 감지되면 IP가 몇 시간 또는 영구적으로 차단될 수 있습니다
- 지리적 제한 — 일부 API는 특정 국가에서만 사용할 수 있습니다 (예: 지역 마켓플레이스 또는 지역 서비스)
- 파싱 방지 — 서버는 요청 패턴을 추적하고 의심스러운 IP를 차단합니다
프록시는 이러한 문제를 해결하여 여러 IP 주소를 통해 요청을 분산하고, 다양한 지역에서 요청을 모방하며, 차단을 우회할 수 있도록 합니다. 이는 다음과 같은 작업을 수행할 때 특히 중요합니다:
- 전자상거래 플랫폼에서 데이터 파싱 (Shopify, WooCommerce GraphQL API)
- 소셜 미디어에서 분석 수집 (Facebook Graph API, Instagram API)
- 가격 및 재고 모니터링
- 다양한 지리적 위치에서 API 테스트
- 분석 및 연구를 위한 데이터 수집 자동화
GraphQL 작업을 위한 프록시 유형 선택
프록시 유형 선택은 작업 및 API 요구 사항에 따라 다릅니다. GraphQL 요청에 대한 세 가지 주요 유형과 그 적용을 살펴보겠습니다:
| 프록시 유형 | 속도 | 익명성 | 언제 사용해야 하는가 |
|---|---|---|---|
| 데이터 센터 프록시 | 매우 높음 (10-50 ms) | 중간 | 공개 API 파싱, 테스트, 높은 속도가 익명성보다 중요할 때 |
| 주거용 프록시 | 중간 (100-300 ms) | 매우 높음 | 보호된 API 작업 (Shopify, Facebook), 엄격한 필터 우회 |
| 모바일 프록시 | 중간 (150-400 ms) | 최대 | Instagram API, TikTok API, GraphQL을 사용하는 모바일 애플리케이션 |
선택에 대한 권장 사항:
- 공개 API의 경우 (GitHub, OpenWeather) — 데이터 센터 프록시로 충분하며, 빠르고 저렴합니다
- 전자상거래의 경우 (Shopify, WooCommerce) — 주거용 프록시가 필요합니다, 이 플랫폼은 데이터 센터를 적극적으로 필터링합니다
- 소셜 미디어의 경우 (Facebook Graph API, Instagram) — 모바일 또는 주거용 프록시가 필수입니다
- 대량 파싱의 경우 — 조합: 기본 트래픽을 위한 데이터 센터 + 차단 시 회전을 위한 주거용
Python에서 GraphQL을 위한 프록시 설정 (requests, httpx, gql)
Python은 API 작업을 위한 가장 인기 있는 언어 중 하나입니다. GraphQL 요청을 위한 프록시 설정의 세 가지 방법을 살펴보겠습니다.
옵션 1: requests 라이브러리 (간단한 HTTP 클라이언트)
가장 간단한 방법은 표준 라이브러리 requests를 사용하는 것입니다. 복잡한 로직 없이 기본 GraphQL 요청에 적합합니다.
import requests
import json
# 프록시 설정
proxies = {
'http': 'http://username:password@proxy.example.com:8080',
'https': 'http://username:password@proxy.example.com:8080'
}
# GraphQL 요청
query = """
query {
products(first: 10) {
edges {
node {
id
title
priceRange {
minVariantPrice {
amount
}
}
}
}
}
}
"""
# 프록시를 통해 요청 전송
url = "https://your-shop.myshopify.com/api/2024-01/graphql.json"
headers = {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': 'your_token_here',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.post(
url,
json={'query': query},
headers=headers,
proxies=proxies,
timeout=30
)
data = response.json()
print(json.dumps(data, indent=2))
옵션 2: httpx 라이브러리 (비동기 요청)
많은 요청을 병렬로 보내야 하는 경우, httpx를 사용하여 async/await를 지원합니다:
import httpx
import asyncio
import json
async def fetch_graphql(query, proxy_url):
url = "https://api.example.com/graphql"
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'
}
# httpx를 위한 프록시 설정
proxies = {
"http://": proxy_url,
"https://": proxy_url
}
async with httpx.AsyncClient(proxies=proxies, timeout=30.0) as client:
response = await client.post(
url,
json={'query': query},
headers=headers
)
return response.json()
# 사용 예
query = """
query {
viewer {
login
repositories(first: 5) {
nodes {
name
stargazerCount
}
}
}
}
"""
proxy = "http://user:pass@proxy.example.com:8080"
result = asyncio.run(fetch_graphql(query, proxy))
print(json.dumps(result, indent=2))
옵션 3: gql 라이브러리 (전문 GraphQL 클라이언트)
GraphQL과의 고급 작업을 위해 gql 라이브러리를 사용하세요 — 이 라이브러리는 스키마 검증, 캐싱 및 요청 작업을 지원합니다:
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
# 프록시와 함께 전송 설정
transport = RequestsHTTPTransport(
url='https://api.example.com/graphql',
headers={
'Authorization': 'Bearer YOUR_TOKEN',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)'
},
proxies={
'http': 'http://user:pass@proxy.example.com:8080',
'https': 'http://user:pass@proxy.example.com:8080'
},
timeout=30
)
# 클라이언트 생성
client = Client(transport=transport, fetch_schema_from_transport=True)
# GraphQL 요청
query = gql("""
query GetProducts($first: Int!) {
products(first: $first) {
edges {
node {
id
title
variants(first: 1) {
edges {
node {
price
}
}
}
}
}
}
}
""")
# 요청 실행
result = client.execute(query, variable_values={"first": 20})
print(result)
Node.js에서 GraphQL을 위한 프록시 설정 (axios, apollo-client)
Node.js는 GraphQL API 작업에도 널리 사용됩니다. 두 가지 주요 접근 방식을 살펴보겠습니다.
옵션 1: 프록시가 있는 Axios
간단하고 유연한 HTTP 클라이언트로 프록시를 지원합니다:
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
// 프록시 설정
const proxyUrl = 'http://username:password@proxy.example.com:8080';
const httpsAgent = new HttpsProxyAgent(proxyUrl);
// GraphQL 요청
const query = `
query {
products(first: 10) {
edges {
node {
id
title
priceRange {
minVariantPrice {
amount
}
}
}
}
}
}
`;
// 요청 전송
axios.post('https://your-shop.myshopify.com/api/2024-01/graphql.json',
{ query },
{
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': 'your_token_here',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
},
httpsAgent: httpsAgent,
timeout: 30000
}
)
.then(response => {
console.log(JSON.stringify(response.data, null, 2));
})
.catch(error => {
console.error('Error:', error.message);
});
옵션 2: 프록시가 있는 Apollo Client
Apollo Client는 Node.js 및 브라우저를 위한 가장 인기 있는 GraphQL 클라이언트입니다. 사용자 정의 fetch를 통한 프록시 설정:
const { ApolloClient, InMemoryCache, HttpLink, gql } = require('@apollo/client');
const fetch = require('cross-fetch');
const HttpsProxyAgent = require('https-proxy-agent');
// 프록시 에이전트
const proxyUrl = 'http://user:pass@proxy.example.com:8080';
const agent = new HttpsProxyAgent(proxyUrl);
// 프록시가 있는 사용자 정의 fetch
const customFetch = (uri, options) => {
return fetch(uri, {
...options,
agent: agent
});
};
// Apollo Client 생성
const client = new ApolloClient({
link: new HttpLink({
uri: 'https://api.example.com/graphql',
fetch: customFetch,
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'
}
}),
cache: new InMemoryCache()
});
// GraphQL 요청
const GET_REPOS = gql`
query GetRepositories($login: String!) {
user(login: $login) {
repositories(first: 5) {
nodes {
name
stargazerCount
}
}
}
}
`;
// 요청 실행
client.query({
query: GET_REPOS,
variables: { login: 'facebook' }
})
.then(result => {
console.log(JSON.stringify(result.data, null, 2));
})
.catch(error => {
console.error('Error:', error);
});
속도 제한 우회를 위한 프록시 회전 구현
프록시 회전은 API 제한을 우회하기 위한 핵심 기술입니다. 모든 요청을 하나의 IP에서 보내는 대신, 여러 프록시 간에 분산합니다. 이를 통해 속도 제한을 우회하고 차단을 피할 수 있습니다.
Python에서의 간단한 회전
프록시를 순환적으로 전환하는 기본 회전 구현:
import requests
import itertools
import time
# 프록시 목록
PROXY_LIST = [
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080',
'http://user:pass@proxy4.example.com:8080',
]
# 무한 반복 생성기 생성
proxy_pool = itertools.cycle(PROXY_LIST)
def make_graphql_request(query):
"""프록시 회전을 통한 GraphQL 요청 전송"""
proxy = next(proxy_pool)
proxies = {'http': proxy, 'https': proxy}
url = "https://api.example.com/graphql"
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
}
try:
response = requests.post(
url,
json={'query': query},
headers=headers,
proxies=proxies,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"프록시 {proxy}에서 오류 발생: {e}")
# 다음 프록시로 전환
return make_graphql_request(query)
# 사용 예
queries = [
'query { products(first: 10) { edges { node { id title } } } }',
'query { collections(first: 5) { edges { node { id title } } } }',
'query { shop { name email } }'
]
for query in queries:
result = make_graphql_request(query)
print(result)
time.sleep(1) # 요청 간 대기 시간
오류 추적을 통한 스마트 회전
작동하지 않는 프록시를 추적하고 자동으로 풀에서 제외하는 더 발전된 버전:
import requests
import random
from collections import defaultdict
import time
class ProxyRotator:
def __init__(self, proxy_list, max_failures=3):
self.proxy_list = proxy_list.copy()
self.max_failures = max_failures
self.failures = defaultdict(int)
self.active_proxies = proxy_list.copy()
def get_proxy(self):
"""무작위 활성 프록시 가져오기"""
if not self.active_proxies:
raise Exception("모든 프록시가 사용할 수 없습니다!")
return random.choice(self.active_proxies)
def mark_failure(self, proxy):
"""실패한 시도 표시"""
self.failures[proxy] += 1
if self.failures[proxy] >= self.max_failures:
print(f"프록시 {proxy}가 풀에서 제외되었습니다 (오류 한도 초과)")
if proxy in self.active_proxies:
self.active_proxies.remove(proxy)
def mark_success(self, proxy):
"""성공 시 오류 카운터 초기화"""
self.failures[proxy] = 0
# 초기화
proxies = [
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080',
]
rotator = ProxyRotator(proxies)
def graphql_request_with_retry(query, max_retries=3):
"""자동 재시도가 있는 GraphQL 요청"""
for attempt in range(max_retries):
proxy = rotator.get_proxy()
proxies_dict = {'http': proxy, 'https': proxy}
try:
response = requests.post(
'https://api.example.com/graphql',
json={'query': query},
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer TOKEN',
'User-Agent': 'Mozilla/5.0'
},
proxies=proxies_dict,
timeout=30
)
response.raise_for_status()
# 성공 — 오류 카운터 초기화
rotator.mark_success(proxy)
return response.json()
except Exception as e:
print(f"{proxy}로 {attempt + 1}/{max_retries} 시도가 실패했습니다: {e}")
rotator.mark_failure(proxy)
time.sleep(2) # 재시도 전 대기 시간
raise Exception("모든 시도 후 요청을 수행할 수 없습니다.")
# 사용 예
query = 'query { products(first: 10) { edges { node { id title } } } }'
result = graphql_request_with_retry(query)
print(result)
GraphQL 요청을 위한 헤더 및 User-Agent 설정
올바른 HTTP 헤더는 프록시를 통해 GraphQL API와 성공적으로 작업하기 위해 매우 중요합니다. 많은 API는 IP뿐만 아니라 요청 헤더도 확인합니다.
GraphQL을 위한 필수 헤더
headers = {
# 콘텐츠 유형 — 항상 GraphQL을 위한 application/json
'Content-Type': 'application/json',
# 인증 (API에 따라 다름)
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
# 또는
'X-Shopify-Storefront-Access-Token': 'token_here',
# User-Agent — 실제 브라우저를 모방
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
# Accept — JSON 수신을 지정
'Accept': 'application/json',
# Accept-Language — 사용자 언어
'Accept-Language': 'en-US,en;q=0.9',
# Accept-Encoding — 압축 지원
'Accept-Encoding': 'gzip, deflate, br',
# Referer — 요청 출처 (선택 사항)
'Referer': 'https://example.com/',
# Origin — CORS 요청을 위한
'Origin': 'https://example.com'
}
User-Agent 회전
더 높은 익명성을 위해 프록시와 함께 User-Agent를 회전하는 것이 좋습니다:
import random
USER_AGENTS = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15'
]
def get_random_headers(token):
"""무작위 헤더 생성"""
return {
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}',
'User-Agent': random.choice(USER_AGENTS),
'Accept': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br'
}
프록시를 통한 오류 처리 및 재시도
프록시 작업 시 오류는 불가피합니다: 타임아웃, 사용할 수 없는 프록시, 차단. 이러한 상황을 올바르게 처리하고 재시도 메커니즘을 구현하는 것이 중요합니다.
프록시를 통한 GraphQL의 일반적인 오류
- 타임아웃 — 프록시가 느리거나 과부하 상태 (timeout을 30-60초로 늘리세요)
- HTTP 407 Proxy Authentication Required — 잘못된 프록시 로그인/비밀번호
- HTTP 429 Too Many Requests — 속도 제한 초과 (프록시 회전 필요)
- HTTP 403 Forbidden — 프록시 IP가 차단됨 (주거용 프록시로 유형 변경)
- Connection refused — 프록시가 사용할 수 없음 (풀에서 제외)
고급 오류 처리
import requests
import time
from requests.exceptions import ProxyError, Timeout, ConnectionError
def graphql_request_robust(query, proxy, max_retries=3, backoff=2):
"""
모든 유형의 오류를 처리하는 신뢰할 수 있는 GraphQL 요청
Args:
query: GraphQL 요청
proxy: 프록시 URL
max_retries: 최대 시도 횟수
backoff: 시도 간 대기 시간 배수
"""
url = "https://api.example.com/graphql"
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer TOKEN',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
}
proxies = {'http': proxy, 'https': proxy}
for attempt in range(max_retries):
try:
response = requests.post(
url,
json={'query': query},
headers=headers,
proxies=proxies,
timeout=30
)
# 속도 제한 확인
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"속도 제한! {retry_after}초 대기 중...")
time.sleep(retry_after)
continue
# IP 차단 확인
if response.status_code == 403:
print(f"IP {proxy}가 차단되었습니다! 다른 프록시가 필요합니다.")
raise Exception("IP 차단됨")
# 프록시 인증 오류 확인
if response.status_code == 407:
print(f"프록시 {proxy} 인증 오류")
raise Exception("프록시 인증 실패")
response.raise_for_status()
# GraphQL 오류 확인
data = response.json()
if 'errors' in data:
print(f"GraphQL 오류: {data['errors']}")
# 일부 오류는 재시도 가능, 일부는 불가능
if is_retryable_graphql_error(data['errors']):
time.sleep(backoff * (attempt + 1))
continue
else:
raise Exception(f"GraphQL 오류: {data['errors']}")
return data
except (ProxyError, ConnectionError) as e:
print(f"{attempt + 1} 시도: 프록시 사용 불가 - {e}")
time.sleep(backoff * (attempt + 1))
except Timeout as e:
print(f"{attempt + 1} 시도: 타임아웃 - {e}")
time.sleep(backoff * (attempt + 1))
except requests.exceptions.HTTPError as e:
print(f"{attempt + 1} 시도: HTTP 오류 - {e}")
if attempt < max_retries - 1:
time.sleep(backoff * (attempt + 1))
else:
raise
raise Exception(f"{max_retries} 시도 후 요청을 수행할 수 없습니다.")
def is_retryable_graphql_error(errors):
"""GraphQL 오류 발생 시 재시도 가능 여부 확인"""
retryable_codes = ['THROTTLED', 'INTERNAL_ERROR', 'TIMEOUT']
for error in errors:
if error.get('extensions', {}).get('code') in retryable_codes:
return True
return False
프록시를 통한 GraphQL 작업의 모범 사례
GraphQL API를 프록시를 통해 효과적으로 작업하기 위한 권장 사항을 정리해 보겠습니다:
✓ 요청 최적화
- 필요한 필드만 요청하세요 — GraphQL은 필요한 내용을 정확하게 지정할 수 있습니다
- 모든 데이터를 한 번에 요청하는 대신 페이지네이션을 사용하세요
- 관련 요청을 하나로 그룹화하세요 (GraphQL은 다중 요청을 지원합니다)
- 클라이언트 측에서 결과를 캐시하여 요청 수를 줄이세요
✓ 프록시 관리
- 회전을 위해 최소 5-10개의 프록시 풀을 사용하세요
- 프록시의 작동 상태를 정기적으로 확인하세요 (health check)
- 작동하지 않는 프록시는 자동으로 풀에서 제외하세요
- 중요한 작업을 위해 다른 유형의 백업 프록시를 유지하세요
✓ 제한 준수
- API 문서를 검토하세요 — 정확한 제한이 명시되어 있습니다
- 요청 간 지연을 추가하세요 (최소 1-2초)
- X-RateLimit-Remaining 및 X-RateLimit-Reset 헤더를 추적하세요
- 429 오류를 받을 경우 지연 시간을 지수적으로 늘리세요
✓ 보안 및 익명성
- 항상 HTTPS 프록시를 사용하여 인증 토큰을 보호하세요
- 프록시와 함께 User-Agent를 회전하세요
- 코드에 토큰을 저장하지 마세요 — 환경 변수를 사용하세요
- 최소한의 정보만 로깅하세요
대규모 프로젝트를 위한 권장 아키텍처
대량의 데이터 작업을 하는 경우 다음 아키텍처를 권장합니다:
- 작업 대기열 (Redis, RabbitMQ) — 요청을 워커 간에 분산
- 워커 풀 — 각 워커는 자신의 프록시를 사용
- 프록시 관리자 — 프록시 상태를 추적하고 워커 간에 분산
- 데이터베이스 — 결과 및 작업 상태 저장
- 모니터링 — 오류, 속도, 프록시 사용 추적
결론
GraphQL API를 프록시를 통해 작업하는 것은 단순히 요청에 proxies 매개변수를 추가하는 것이 아닙니다. 신뢰할 수 있고 효과적으로 작업하기 위해서는 프록시 회전 구현, 올바른 오류 처리, 헤더 설정 및 API 제한 준수가 필요합니다. 우리는 Python 및 Node.js에서 즉시 사용할 수 있는 실용적인 예제를 살펴보았습니다.
주요 결론: 보호된 API (Shopify, Facebook)에 대해서는 주거용 프록시를 사용하고, 공개 API 및 대량 파싱에는 데이터 센터 프록시를 사용하며, 작동하지 않는 프록시는 자동으로 제외하고, 지연 시간을 추가하며 모든 유형의 오류를 처리하세요. 이를 통해 모든 GraphQL API와 안정적으로 작업할 수 있습니다.
GraphQL API를 프로덕션에서 작업할 계획이라면 주거용 프록시를 사용하는 것이 좋습니다 — 이는 최대의 안정성과 최소한의 차단 위험을 보장합니다. 테스트 및 개발에는 데이터 센터 프록시가 적합합니다 — 이는 더 빠르고 저렴합니다.