파이썬 스크립트에서 403 오류, CAPTCHA 또는 IP 차단이 발생하면 대상 웹사이트가 이미 당신을 감지한 것입니다. requests 라이브러리에 프록시를 연결하면 이 문제를 해결할 수 있습니다: IP 주소를 변경하고, 지리적 제한을 우회하며, 여러 주소 간에 부하를 분산시킵니다. 이 가이드에서는 기본 연결부터 고급 IP 회전까지 실제 코드 예제를 통해 설명합니다.
파이썬 스크립트에서 프록시가 필요한 이유
대부분의 웹사이트와 API는 들어오는 요청의 IP 주소를 추적합니다. 하나의 주소가 분당 100개 이상의 요청을 보내면 차단됩니다. 이는 Wildberries, Ozon, Avito, Google, Instagram 및 수백 개의 다른 플랫폼에서 사용하는 표준 봇 방지 방법입니다. 프록시는 요청을 다른 IP 주소를 가진 중간 서버를 통해 전달하여 보호 시스템에 대해 보이지 않게 만듭니다.
다음은 파이썬에서 프록시가 필수적인 주요 작업입니다:
- 마켓플레이스 파싱 — Wildberries, Ozon, Yandex.Market에서 IP 차단 없이 가격 수집
- 경쟁사 모니터링 — 경쟁사 웹사이트에 5~15분마다 정기적으로 요청
- 제한이 있는 API 작업 — 요청을 여러 IP 간에 분산시켜 rate limit 초과 방지
- 지리적 테스트 — 다양한 국가 및 지역에서 웹사이트가 어떻게 보이는지 확인
- 양식 및 등록 자동화 — 여러 IP에서 계정 생성 또는 양식 작성
- SEO 모니터링 — 러시아 및 다른 국가의 다양한 지역에서 순위 확인
프록시 없이 잘 작성된 파서도 몇 시간 내에 차단될 수 있습니다. 올바르게 설정된 IP 회전을 통해 동일한 스크립트는 몇 주 동안 중단 없이 작동합니다.
requests에서 프록시 기본 설정
requests 라이브러리는 프록시를 기본적으로 지원합니다 — 추가 패키지가 필요하지 않습니다. 프록시는 요청 매개변수의 proxies 딕셔너리를 통해 전달됩니다.
가장 간단한 예는 하나의 요청에 대한 HTTP 프록시입니다:
import requests
proxies = {
"http": "http://123.45.67.89:8080",
"https": "http://123.45.67.89:8080",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
# {'origin': '123.45.67.89'}
주의: proxies 딕셔너리에는 두 개의 키 — http와 https를 모두 지정해야 합니다. 하나만 지정하면 두 번째 프로토콜에 대한 요청은 프록시 없이 직접 진행됩니다. 이는 초보자들이 자주 범하는 실수로, 이로 인해 실제 IP가 유출될 수 있습니다.
프록시가 작동하는지 확인하려면 httpbin.org/ip 서비스를 사용하세요 — 이 서비스는 요청이 들어온 IP 주소를 반환합니다. 응답에서 프록시 서버의 IP가 보이고 자신의 IP가 보이지 않으면 모든 설정이 올바른 것입니다.
HTTP, HTTPS 및 SOCKS5 프록시: 차이점 및 코드 예제
프록시는 다양한 유형이 있으며 각 유형은 특정 작업에 적합합니다. 파이썬 requests의 맥락에서 세 가지 주요 프로토콜 간의 차이를 이해하는 것이 중요합니다:
| 유형 | URL의 프로토콜 | 속도 | UDP 지원 | 최고의 시나리오 |
|---|---|---|---|---|
| HTTP | http:// |
높음 | 아니요 | HTTP 사이트 파싱 |
| HTTPS | https:// |
높음 | 아니요 | 보호된 사이트 파싱 |
| SOCKS5 | socks5:// |
중간 | 예 | 완전한 익명성, 모든 프로토콜 |
파이썬에서 SOCKS5를 사용하려면 추가 패키지를 설치해야 합니다:
pip install requests[socks] # 또는 별도로: pip install PySocks
설치 후 SOCKS5 프록시 연결은 다음과 같이 보입니다:
import requests
# SOCKS5 프록시
proxies = {
"http": "socks5://123.45.67.89:1080",
"https": "socks5://123.45.67.89:1080",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
SOCKS5는 익명성이 중요한 작업에 적합한 프로토콜입니다. HTTP 프록시와 달리 SOCKS5는 실제 IP를 노출할 수 있는 X-Forwarded-For 헤더를 추가하지 않습니다.
로그인 및 비밀번호 인증이 있는 프록시
대부분의 유료 프록시 서비스는 로그인 및 비밀번호 인증을 사용합니다. 이는 표준 관행으로, 인증 없이 프록시는 요청을 허용하지 않습니다. requests 라이브러리에서는 인증 정보를 프록시 URL에 직접 전달합니다.
import requests # 형식: 프로토콜://로그인:비밀번호@호스트:포트 proxy_url = "http://myuser:[email protected]:8080" proxies = { "http": proxy_url, "https": proxy_url, } response = requests.get("https://httpbin.org/ip", proxies=proxies) print(response.status_code) print(response.json())
비밀번호나 로그인에 특수 문자가 포함된 경우 (예: @, #, %), URL 인코딩해야 합니다. 이를 위해 urllib.parse 모듈을 사용하세요:
import requests
from urllib.parse import quote
username = "myuser"
password = "p@ss#word!" # 특수 문자 포함
# 로그인 및 비밀번호 URL 인코딩
encoded_user = quote(username, safe="")
encoded_pass = quote(password, safe="")
proxy_url = f"http://{encoded_user}:{encoded_pass}@123.45.67.89:8080"
proxies = {
"http": proxy_url,
"https": proxy_url,
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
💡 보안 팁
스크립트 코드에 로그인 및 비밀번호를 하드코딩하지 마세요. 환경 변수나 .env 파일을 사용하여 python-dotenv 라이브러리와 함께 사용하세요. 이렇게 하면 GitHub에 코드를 게시할 때 우연한 자격 증명 유출을 방지할 수 있습니다.
프록시 회전: 자동 IP 변경
하나의 프록시는 여전히 하나의 IP 주소로, 차단될 수 있습니다. 차단 방지의 진정한 방법은 회전입니다: 각 요청(또는 N 요청마다)이 새로운 IP로 전송됩니다. 아래는 회전을 구현하는 몇 가지 접근 방식입니다.
방법 1: 목록에서 무작위 선택
import requests
import random
# 프록시 목록
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
def get_random_proxy():
proxy = random.choice(proxy_list)
return {"http": proxy, "https": proxy}
# IP 회전으로 10페이지 파싱
urls = [f"https://example.com/page/{i}" for i in range(1, 11)]
for url in urls:
proxies = get_random_proxy()
try:
response = requests.get(url, proxies=proxies, timeout=10)
print(f"URL: {url} | IP: {proxies['http'].split('@')[1]} | 상태: {response.status_code}")
except requests.RequestException as e:
print(f"오류: {e}")
방법 2: itertools를 통한 순환 회전
import requests
import itertools
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
# 프록시 목록에 대한 무한 루프 생성
proxy_cycle = itertools.cycle(proxy_list)
def get_next_proxy():
proxy = next(proxy_cycle)
return {"http": proxy, "https": proxy}
# 각 요청은 순환적으로 다음 프록시를 사용합니다
for i in range(9):
proxies = get_next_proxy()
response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(f"요청 {i+1}: {response.json()['origin']}")
시간당 수천 개의 요청을 처리하는 산업 작업의 경우 주거용 프록시를 사용하는 것이 좋습니다. 이 프록시는 내장된 자동 회전을 제공하여 제공자가 각 요청에 대해 IP를 변경하므로 주소 목록을 수동으로 관리할 필요가 없습니다.
세션을 통한 프록시: 지속적인 연결 및 쿠키
하나의 세션 내에서 여러 요청을 수행해야 할 때(예: 로그인 후 요청 수행) requests.Session() 객체를 사용하세요. 이 객체는 요청 간에 쿠키, 헤더 및 프록시 설정을 저장하므로 각 호출에 프록시를 별도로 전달할 필요가 없습니다.
import requests
# 프록시와 함께 세션 생성
session = requests.Session()
session.proxies = {
"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080",
}
# 브라우저를 모방하기 위한 헤더 추가
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "ru-RU,ru;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
})
# 단계 1: 인증
login_data = {"username": "myuser", "password": "mypass"}
session.post("https://example.com/login", data=login_data)
# 단계 2: 쿠키와 프록시를 통해 요청
response = session.get("https://example.com/dashboard")
print(response.status_code)
# 단계 3: 세션 종료
session.close()
Session을 사용하면 성능 측면에서도 더 효율적입니다: TCP 연결이 재사용되며 각 요청에 대해 새로 열지 않습니다. 1000페이지 이상을 파싱할 때 이는 속도 향상에 큰 도움이 됩니다.
오류, 타임아웃 및 자동 재시도 처리
프록시 서버는 사용할 수 없거나 느리게 응답하거나 연결 오류를 반환할 수 있습니다. 신뢰할 수 있는 파싱 스크립트는 이러한 상황을 처리하고 오류 발생 시 다른 프록시로 자동 전환할 수 있어야 합니다.
import requests
import random
import time
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
def fetch_with_retry(url, max_retries=3, timeout=10):
"""
오류 발생 시 자동으로 프록시를 변경하여 요청을 수행합니다.
Response 객체를 반환하거나 시도 횟수를 초과하면 None을 반환합니다.
"""
available_proxies = proxy_list.copy()
random.shuffle(available_proxies)
for attempt, proxy_url in enumerate(available_proxies[:max_retries], 1):
proxies = {"http": proxy_url, "https": proxy_url}
try:
response = requests.get(
url,
proxies=proxies,
timeout=timeout,
headers={"User-Agent": "Mozilla/5.0"}
)
response.raise_for_status() # 4xx/5xx에서 예외 발생
print(f"✓ 시도 {attempt} 성공")
return response
except requests.exceptions.ProxyError:
print(f"✗ 시도 {attempt}: 프록시 사용 불가 — {proxy_url}")
except requests.exceptions.Timeout:
print(f"✗ 시도 {attempt}: 타임아웃 — {proxy_url}")
except requests.exceptions.HTTPError as e:
print(f"✗ 시도 {attempt}: HTTP 오류 {e.response.status_code}")
if e.response.status_code == 403:
print(" → 차단됨, 다음 프록시 시도...")
except requests.exceptions.RequestException as e:
print(f"✗ 시도 {attempt}: 일반 오류 — {e}")
time.sleep(1) # 시도 간 대기 시간
print(f"✗ {url}에 대해 모든 {max_retries} 시도가 실패했습니다.")
return None
# 사용 예
result = fetch_with_retry("https://httpbin.org/ip")
if result:
print(result.json())
raise_for_status()에 주의하세요 — 이 메서드는 HTTP 상태 코드 4xx 및 5xx에서 자동으로 예외를 발생시킵니다. 이를 사용하지 않으면 스크립트가 403(차단) 또는 429(요청 한도 초과) 응답을 성공으로 간주할 수 있습니다.
환경 변수를 통한 프록시: 데이터 안전한 저장
requests 라이브러리는 자동으로 HTTP_PROXY 및 HTTPS_PROXY 환경 변수를 읽습니다. 이를 통해 코드에 자격 증명을 저장하지 않고도 쉽게 프록시를 전환할 수 있습니다.
터미널에서 환경 변수를 설정하는 방법 (Linux/macOS):
export HTTP_PROXY="http://user:[email protected]:8080" export HTTPS_PROXY="http://user:[email protected]:8080" export NO_PROXY="localhost,127.0.0.1"
또는 .env 파일을 사용하여 python-dotenv 라이브러리와 함께 사용할 수 있습니다:
# .env 파일 (추가하여 .gitignore에 포함!) HTTP_PROXY=http://user:[email protected]:8080 HTTPS_PROXY=http://user:[email protected]:8080
# 파이썬 스크립트
from dotenv import load_dotenv
import requests
import os
load_dotenv() # .env에서 변수 로드
# requests는 자동으로 HTTP_PROXY 및 HTTPS_PROXY를 사용합니다
response = requests.get("https://httpbin.org/ip")
print(response.json())
# 또는 환경 변수에서 명시적으로 가져올 수 있습니다:
proxies = {
"http": os.getenv("HTTP_PROXY"),
"https": os.getenv("HTTPS_PROXY"),
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
print(response.json())
⚠️ 중요: NO_PROXY 변수
NO_PROXY 변수는 특정 주소를 프록시에서 제외할 수 있습니다. 반드시 localhost 및 127.0.0.1을 추가하여 로컬 요청이 프록시를 통과하지 않도록 해야 합니다.
실제 시나리오: 마켓플레이스 파싱, API 작업 및 자동화
개발자들이 자주 직면하는 세 가지 실용적인 시나리오를 살펴보겠습니다.
시나리오 1: 마켓플레이스에서 가격 파싱
Wildberries 또는 Ozon에서 가격을 모니터링할 때는 실제 사용자 행동을 모방하는 것이 중요합니다: 올바른 브라우저 헤더를 전달하고 요청 간에 지연을 추가하며 IP를 회전시킵니다. 이 작업에는 데이터 센터 프록시가 적합합니다 — 대량의 데이터를 처리할 때 빠르고 저렴합니다.
import requests
import time
import random
HEADERS = {
"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": "application/json, text/plain, */*",
"Accept-Language": "ru-RU,ru;q=0.9",
"Referer": "https://www.wildberries.ru/",
}
PROXIES = [
{"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080"},
{"http": "http://user:[email protected]:8080",
"https": "http://user:[email protected]:8080"},
]
def get_product_price(article_id: int) -> dict:
"""Wildberries에서 아티클 ID로 상품 가격을 가져옵니다."""
url = f"https://card.wb.ru/cards/v1/detail?appType=1&curr=rub&nm={article_id}"
proxies = random.choice(PROXIES)
try:
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=15)
resp.raise_for_status()
data = resp.json()
product = data["data"]["products"][0]
return {
"id": product["id"],
"name": product["name"],
"price": product["salePriceU"] / 100, # 가격은 코페이카 단위
}
except (requests.RequestException, KeyError, IndexError) as e:
return {"error": str(e)}
# 지연을 두고 여러 아티클 파싱
articles = [12345678, 87654321, 11223344]
for article in articles:
result = get_product_price(article)
print(result)
time.sleep(random.uniform(1.5, 3.0)) # 1.5-3초의 무작위 지연
시나리오 2: 프록시를 통한 API 작업
일부 API는 하나의 IP에서 요청 수를 제한합니다 (rate limiting). 여러 프록시 간에 요청을 분산시키면 이 제한을 우회할 수 있습니다:
import requests
import itertools
from typing import Optional
class ProxyAPIClient:
"""프록시 회전을 통해 API와 작업하는 클라이언트입니다."""
def __init__(self, api_key: str, proxy_list: list):
self.api_key = api_key
self.proxy_cycle = itertools.cycle(proxy_list)
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
})
def _get_proxy(self) -> dict:
proxy = next(self.proxy_cycle)
return {"http": proxy, "https": proxy}
def get(self, url: str, **kwargs) -> Optional[dict]:
proxies = self._get_proxy()
try:
resp = self.session.get(url, proxies=proxies, timeout=10, **kwargs)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
print(f"API 요청 실패: {e}")
return None
# 사용 예
proxy_list = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
client = ProxyAPIClient(api_key="your_api_key", proxy_list=proxy_list)
data = client.get("https://api.example.com/products")
시나리오 3: 지리적 테스트
마케팅 담당자와 SEO 전문가들은 종종 웹사이트가 다양한 지역에서 어떻게 보이는지 확인합니다. 필요한 위치의 프록시를 사용하여 이 프로세스를 자동화할 수 있습니다:
import requests
# 다양한 지역의 프록시
regional_proxies = {
"모스크바": "http://user:[email protected]:8080",
"상트페테르부르크": "http://user:[email protected]:8080",
"노보시비르스크": "http://user:[email protected]:8080",
"미국": "http://user:[email protected]:8080",
}
url = "https://example.com/prices"
for region, proxy_url in regional_proxies.items():
proxies = {"http": proxy_url, "https": proxy_url}
try:
resp = requests.get(url, proxies=proxies, timeout=15)
print(f"[{region}] 상태: {resp.status_code} | "
f"크기: {len(resp.content)} 바이트")
except requests.RequestException as e:
print(f"[{region}] 오류: {e}")
작업에 적합한 프록시 유형 선택하기
프록시 유형 선택은 프로젝트의 성공에 직접적인 영향을 미칩니다. 저렴한 데이터 센터 프록시는 일부 작업에 매우 잘 작동할 수 있지만 다른 작업에서는 완전히 실패할 수 있습니다. 다음은 선택을 위한 실용적인 가이드입니다:
| 작업 | 프록시 유형 | 이유 |
|---|---|---|
| 마켓플레이스 파싱 (Wildberries, Ozon) | 주거용 | 일반 사용자처럼 보이며 차단될 가능성이 적음 |
| 공개 데이터, 뉴스 파싱 | 데이터 센터 | 빠르고 저렴하며 충분히 익명 |
| Facebook API, Instagram 작업 | 모바일 | 소셜 네트워크는 모바일 IP를 가장 신뢰함 |
| 지리적 테스트 | 지리적 타겟팅이 있는 주거용 | 정확한 지리적 위치, 필요한 지역의 실제 IP |
| 고부하 파싱 (10k+ 요청/시간) | 데이터 센터 (풀) | 대량 처리 시 속도 및 비용 |
| 인증 및 계정 작업 | 주거용 또는 모바일 | 안티 프로드 시스템의 트리거가 적음 |
보호된 웹사이트에서 작업할 때 최대한의 신뢰성과 최소한의 차단 위험이 중요한 경우, 개발자들은 종종 모바일 프록시를 선택합니다 — 이들은 실제 모바일 운영자(MTS, Beeline, MegaFon)의 IP 주소를 사용하여 차단 목록에 올라갈 가능성이 매우 낮습니다.
프록시 사용 전 확인 체크리스트
- ✅
httpbin.org/ip를 통해 IP 확인 — 실제 주소가 보이는가? - ✅ 속도 확인 — 응답 시간은 2-3초를 초과하지 않아야 합니다.
- ✅
blocklist.de또는ipqualityscore.com를 통해 프록시가 차단 목록에 없는지 확인하세요. - ✅
ipinfo.io를 통해 지리적 위치 확인 — 예상 지역과 일치하는가? - ✅ 전체 스크립트를 실행하기 전에 하나의 요청으로 대상 웹사이트에서 테스트하세요.
- ✅ HTTPS 트래픽도 프록시를 통해 전달되는지 확인하세요 (딕셔너리의 두 키 모두 사용).
결론
파이썬 requests에서 프록시 설정은 어렵지 않지만 세부 사항에 주의를 기울여야 합니다. 기억해야 할 주요 원칙은: 항상 프록시 딕셔너리에 두 개의 키(http와 https)를 지정하고, 다단계 시나리오에는 Session을 사용하며, 오류와 타임아웃을 반드시 처리하고, 자격 증명은 코드에 저장하지 않고 환경 변수에 보관하세요.
수천 개의 요청을 처리하는 산업 파싱의 경우 수동 프록시 목록은 충분하지 않습니다 — 회전이 필요합니다. Wildberries 또는 Ozon과 같은 보호된 마켓플레이스를 파싱하거나 소셜 네트워크와 작업하거나 지리적 테스트를 수행하는 경우 주거용 프록시를 사용해 보시기 바랍니다 — 이들은 안티봇 시스템에서 높은 신뢰도를 제공하며, 단일 엔드포인트를 통해 자동 IP 회전을 지원하여 스크립트 코드를 크게 단순화합니다.