2023년 중반, X(구 트위터)는 무료 API 액세스를 사실상 종단했습니다. 기존의 무료 검색 엔드포인트는 제거되었고, 기본 티어조차 월 100달러부터 시작합니다. 많은 개발팀과 성장 팀이 공개 데이터에 접근하기 위해 웹 스크래핑으로 눈을 돌리고 있습니다. 하지만 X는 데이터센터 IP를 적극적으로 차단하고, 비로그인 세션에 더 엄격한 속도 제한을 적용합니다. 이 가이드에서는 실거주 IP 프록시를 활용해 공개 트위터 데이터를 안정적으로 수집하는 방법을 설명합니다.
중요 고지: 이 글은 공개적으로 접근 가능한 데이터의 합법적 수집만을 다룹니다. 각 플랫폼의 이용약관과 관할 법률(CFAA, GDPR 등)을 준수하세요. 스크래핑이 적절하지 않은 경우 공식 API 사용을 권장합니다.
X API의 변화: 무료 티어 종료와 새로운 비용 구조
2023년 이전까지 트위터는 관대한 무료 API를 제공했습니다. 연구자, 개발자, 소규모 스타트업이 합리적인 양의 데이터에 무료로 접근할 수 있었습니다. 하지만 X 인수 후 API 정책이 급격히 변경되었습니다:
- Free 티어: 트윗 게시만 가능, 읽기 액세스 없음
- Basic 티어: 월 100달러, 월 10,000포스트 읽기 한도
- Pro 티어: 월 5,000달러, 월 1,000,000포스트 읽기 한도
- Enterprise: 맞춤형 가격, 연구 목적 별도 협상
이전에 무료였던 검색 API는 완전히 제거되었습니다. 기본 티어조차 트윗당 약 0.01달러의 비용이 발생합니다. 대규모 감성 분석이나 트렌드 모니터링 프로젝트에는 연간 수만 달러의 비용이 들 수 있습니다.
공개 웹으로 접근 가능한 데이터 vs 로그인 필요 데이터
X의 웹 인터페이스는 SPA(Single Page Application)로 구축되어 있어, GraphQL 엔드포인트를 통해 데이터를 가져옵니다. 로그인 없이도 상당한 양의 공개 데이터에 접근할 수 있습니다.
로그인 없이 접근 가능한 데이터
- 공개 프로필: 사용자 이름, 바이오, 팔로워/팔로잉 수, 가입일
- 공개 트윗: 텍스트, 미디어 URL, 좋아요/리트윗/답글 수, 타임스탬프
- 트윗 스레드: 원본 트윗과 답글 체인
- 트렌딩 토픽: 위치 기반 트렌드 목록
- 검색 결과: 해시태그, 키워드 검색 (최근 트윗 중심)
로그인 필요한 데이터
- 비공개 계정의 트윗
- 팔로워/팔로잉 전체 목록 (일부만 공개)
- 북마크
- 맞춤형 알고리즘 타임라인
- 직접 메시지
- 고급 검색의 일부 필터
핵심 인사이트: 대부분의 공개 감성 분석, 브랜드 모니터링, 트렌드 추적은 로그인 없이 수행 가능합니다. 하지만 X는 비로그인 세션에 훨씬 엄격한 속도 제한을 적용합니다.
왜 실거주 IP 프록시가 필수인가
X는 스크래핑 방지를 위해 다층 보안을 운영합니다:
데이터센터 IP 차단
AWS, Google Cloud, Azure 등 주요 클라우드 공급자의 IP 대역은 자동으로 플래그됩니다. X는 수년간 수집한 IP 평판 데이터베이스를 보유하고 있으며, 데이터센터 IP에서 오는 요청은 즉시 의심스럽게 처리됩니다.
비로그인 세션 제한
로그인하지 않은 방문자는 계정이 있는 사용자보다 훨씬 낮은 속도 제한을 받습니다. 일반적으로 몇 분 내에 수십 개의 요청만 허용된 후 "속도 제한 초과" 오류가 발생합니다.
지문 추적
X는 브라우저 지문, JavaScript 실행 패턴, 마우스 움직임 등을 추적합니다. 단순 HTTP 클라이언트로는 차단이 거의 확실합니다.
실거주 IP 프록시의 장점
| 특성 | 데이터센터 프록시 | 실거주 IP 프록시 |
|---|---|---|
| IP 평판 | 낮음 (자동 플래그) | 높음 (실제 가정용 ISP) |
| 차단 확률 | 매우 높음 | 낮음~보통 |
| 속도 | 빠름 | 보통 |
| 비용 | 낮음 | 높음 |
| 용도 | 테스트, 저위험 사이트 | 소셜 미디어, 전자상거래 |
실거주 IP 프록시는 실제 인터넷 공급자(ISP)를 통해 할당된 IP 주소입니다. X의 관점에서 이 요청은 일반 가정용 사용자와 동일하게 보입니다. 실거주 IP 프록시에 대한 상세 가이드에서 더 자세한 정보를 확인하세요.
Python + Playwright로 X 스크래핑 구현하기
X의 SPA는 GraphQL을 사용해 데이터를 로드합니다. 브라우저 자동화 도구로 네트워크 요청을 가로채거나, 페이지 내에 임베드된 JSON 데이터를 추출할 수 있습니다.
기본 설정
먼저 필요한 패키지를 설치합니다:
pip install playwright asyncio
playwright install chromium
실거주 IP 프록시와 함께 사용하기
import asyncio
from playwright.async_api import async_playwright
import json
PROXY_URL = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"
async def scrape_tweet(tweet_id: str):
async with async_playwright() as p:
browser = await p.chromium.launch(
proxy={"server": PROXY_URL},
headless=True
)
context = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
page = await context.new_page()
# GraphQL 응답 가로채기
graphql_data = []
async def handle_response(response):
if "graphql" in response.url:
try:
data = await response.json()
graphql_data.append(data)
except:
pass
page.on("response", handle_response)
# 트윗 페이지 방문
url = f"https://x.com/i/status/{tweet_id}"
await page.goto(url, wait_until="networkidle")
# 데이터 추출 대기
await page.wait_for_timeout(3000)
await browser.close()
return graphql_data
# 실행
results = asyncio.run(scrape_tweet("1234567890"))
print(json.dumps(results, indent=2))
프록시 풀로 IP 순환하기
단일 IP로는 금방 차단됩니다. 요청마다 다른 실거주 IP를 사용해야 합니다:
import random
import string
def generate_session_id():
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
def get_proxy_url(country: str = "US"):
session = generate_session_id()
return f"http://user-country-{country}-session-{session}:PASSWORD@gate.proxyhat.com:8080"
async def scrape_with_rotation(tweet_ids: list):
results = []
for i, tweet_id in enumerate(tweet_ids):
# 요청마다 새 세션 생성
proxy_url = get_proxy_url(country="US")
async with async_playwright() as p:
browser = await p.chromium.launch(
proxy={"server": proxy_url},
headless=True
)
context = await browser.new_context()
page = await context.new_page()
try:
url = f"https://x.com/i/status/{tweet_id}"
await page.goto(url, wait_until="domcontentloaded")
await page.wait_for_timeout(2000)
# 페이지에서 데이터 추출
content = await page.content()
results.append({"id": tweet_id, "status": "success"})
except Exception as e:
results.append({"id": tweet_id, "error": str(e)})
finally:
await browser.close()
# 요청 간 지연
await asyncio.sleep(random.uniform(2, 5))
return results
Node.js 예제
const { chromium } = require('playwright');
const PROXY_URL = 'http://user-country-US:PASSWORD@gate.proxyhat.com:8080';
async function scrapeProfile(username) {
const browser = await chromium.launch({
proxy: { server: PROXY_URL },
headless: true
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
});
const page = await context.newPage();
try {
await page.goto(`https://x.com/${username}`, {
waitUntil: 'networkidle'
});
await page.waitForTimeout(3000);
// 프로필 데이터 추출
const profileData = await page.evaluate(() => {
const name = document.querySelector('[data-testid="UserName"]')?.textContent;
const bio = document.querySelector('[data-testid="UserDescription"]')?.textContent;
return { name, bio };
});
return profileData;
} finally {
await browser.close();
}
}
scrapeProfile('elonmusk').then(console.log);
속도 제한과 429 오류 처리
X는 다층 속도 제한 시스템을 운영합니다. 이를 이해하고 대응하는 것이 안정적인 스크래핑의 핵심입니다.
IP 수준 제한
단일 IP에서 너무 많은 요청을 보내면 429 Too Many Requests 오류가 발생합니다. 비로그인 세션의 경우 분당 10-20개 요청이 일반적인 한계입니다.
계정 수준 제한
로그인한 계정은 더 높은 한계를 가지지만, 계정별로 추적됩니다. 단일 계정으로 과도한 활동을 하면 계정이 잠길 수 있습니다.
슬라이딩 윈도우 탐지
X는 고정 윈도우가 아닌 슬라이딩 윈도우를 사용합니다. "지난 15분 동안 100개 요청"이 아니라, "최근 15분 동안의 요청 수"를 계산합니다.
대응 전략
import asyncio
import random
from collections import deque
import time
class RateLimiter:
def __init__(self, max_requests: int = 15, window_seconds: int = 900):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.request_times = deque()
async def acquire(self):
now = time.time()
# 윈도우 밖의 요청 제거
while self.request_times and self.request_times[0] < now - self.window_seconds:
self.request_times.popleft()
if len(self.request_times) >= self.max_requests:
# 대기 필요
wait_time = self.request_times[0] + self.window_seconds - now
await asyncio.sleep(wait_time + random.uniform(1, 3))
self.request_times.append(time.time())
limiter = RateLimiter(max_requests=12, window_seconds=900)
async def safe_scrape(url: str, proxy_url: str):
await limiter.acquire()
# 스크래핑 로직...
429 오류 자동 재시도
async def fetch_with_retry(page, url: str, max_retries: int = 3):
for attempt in range(max_retries):
try:
response = await page.goto(url, wait_until='domcontentloaded')
if response.status == 429:
# Rate limit - 새 IP로 재시도
retry_after = int(response.headers.get('retry-after', 60))
await asyncio.sleep(retry_after + random.uniform(5, 15))
raise Exception("Rate limited")
if response.status >= 500:
await asyncio.sleep(2 ** attempt)
continue
return response
except Exception as e:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt)
raise Exception("Max retries exceeded")
법적 프레임워크와 이용약관 고려사항
트위터/X 스크래핑의 법적 환경은 복잡하고 진화하고 있습니다.
HiQ Labs v. LinkedIn 판결 (미국)
미국 9순회항소법원은 공개적으로 접근 가능한 데이터의 스크래핑이 CFAA(Computer Fraud and Abuse Act) 위반이 아니라고 판결했습니다. 이는 공개 웹 데이터 스크래핑에 일정한 합법성을 부여했습니다.
X의 이용약관
X 이용약관은 "라이선스 없이 콘텐츠를 크롤링, 스크래핑, 또는 데이터 마이닝하는 것을 금지"합니다. 하지만 이용약관 위반은 범죄가 아니라 계약 문제입니다. 실무적으로는 계정 정지, IP 차단이 주요 결과입니다.
GDPR 고려사항 (EU)
EU에서는 개인 데이터 처리에 GDPR이 적용됩니다. 공개 트윗이라도 개인 정보를 포함할 수 있으며, 수집 목적, 보관 기간, 데이터 주체 권리를 고려해야 합니다.
스크래핑 vs API 사용: 언제 무엇을 선택할까
| 상황 | 권장 접근법 |
|---|---|
| 소규모 프로토타입 | 스크래핑 (비용 효율적) |
| 상용 서비스 | 공식 API (안정성, 법적 보호) |
| 학술 연구 | 연구용 API 프로그램 또는 스크래핑 |
| 실시간 모니터링 | API (스트리밍 엔드포인트) |
| 역사적 데이터 | API (전체 아카이브 액세스) |
| 경쟁사 분석 | 스크래핑 (공개 데이터만) |
현실적 조언: 스크래핑은 공개 데이터에 대한 "합리적 사용"을 유지할 때 가장 안전합니다. 과도한 요청, 데이터 재판매, 사용자 프라이버시 침해는 법적 위험을 크게 증가시킵니다.
모범 사례와 권장 아키텍처
1. 요청 분산
단일 IP에 집중하지 마세요. ProxyHat과 같은 실거주 IP 풀을 사용해 요청을 여러 IP로 분산합니다:
# 국가별 분산 예시
countries = ['US', 'DE', 'GB', 'CA', 'AU']
proxy_url = f"http://user-country-{random.choice(countries)}-session-{session_id}:PASSWORD@gate.proxyhat.com:8080"
2. 인간 같은 패턴 유지
- 요청 간 무작위 지연 추가
- 일관된 User-Agent 사용
- 필요시 쿠키 저장 및 재사용
- JavaScript 렌더링 사용 (Playwright, Puppeteer)
3. 데이터 최소화
필요한 데이터만 수집하세요. 전체 프로필 대신 사용자 이름과 트윗 텍스트만 저장하는 것이 GDPR 위험을 줄입니다.
4. robots.txt 준수
X의 robots.txt를 확인하고 준수하세요. 이는 법적 요구사항은 아니지만, 좋은 실무 관행입니다.
핵심 요약
- X API 무료 티어 폐지로 많은 팀이 웹 스크래핑으로 전환하고 있습니다.
- 공개 프로필, 트윗, 검색 결과는 로그인 없이 접근 가능하지만, 엄격한 속도 제한이 적용됩니다.
- 데이터센터 IP는 거의 확실히 차단되므로 실거주 IP 프록시가 필수입니다.
- Playwright로 GraphQL 응답을 가로채거나 페이지 데이터를 추출할 수 있습니다.
- 슬라이딩 윈도우 속도 제한을 이해하고 적절한 지연과 재시도 로직을 구현하세요.
- 공개 데이터 스크래핑은 일정한 합법성이 있지만, 이용약관 위반 시 계정 정지가 발생할 수 있습니다.
- 상용 서비스에는 공식 API 사용을 권장합니다.
ProxyHat은 전 세계 190개국 이상의 실거주 IP를 제공합니다. 요금제를 확인하고 무료 평가판을 시작하세요. 소규모 모니터링 프로젝트부터 대규모 데이터 파이프라인까지 유연한 솔루션을 제공합니다.






