프록시가 브라우저에서는 작동하지만 코드에서는 작동하지 않는 이유: 전체 문제 분석
고전적인 상황: 브라우저에서 프록시를 설정하고 사이트를 열면 모든 것이 작동합니다. 동일한 프록시로 스크립트를 실행하면 연결 오류, 시간 초과 또는 차단이 발생합니다. 왜 이런 일이 발생하는지, 그리고 이를 어떻게 수정할 수 있는지 살펴보겠습니다.
브라우저 요청과 코드 요청의 차이점
프록시를 통해 브라우저에서 사이트를 열면 단순한 HTTP 요청 이상의 훨씬 많은 작업이 수행됩니다. 브라우저는 자동으로 다음을 수행합니다.
- 전체 헤더 세트(User-Agent, Accept, Accept-Language, Accept-Encoding) 전송
- 올바른 암호화 스위트(Cipher Suite)를 사용하여 TLS 핸드셰이크 수행
- 리디렉션 및 쿠키 처리
- JavaScript 실행 및 종속 리소스 로드
- DNS 응답 및 인증서 캐싱
코드에서 발생하는 최소한의 요청은 서버에 사람의 요청이 아닌 로봇의 요청처럼 보입니다. 프록시가 올바르게 작동하더라도 대상 사이트가 귀하의 스크립트를 차단할 수 있습니다.
프록시 인증 문제
가장 흔한 이유는 로그인 및 암호가 잘못 전달되는 경우입니다. 브라우저는 자격 증명(credentials) 입력을 위한 팝업 창을 표시하지만, 코드에서는 이를 명시적으로 처리해야 합니다.
잘못된 URL 형식
흔한 실수는 스키마를 생략하거나 특수 문자를 잘못 이스케이프(escaping)하는 것입니다.
# 잘못된 예
proxy = "user:pass@proxy.example.com:8080"
# 올바른 예
proxy = "http://user:pass@proxy.example.com:8080"
# 암호에 특수 문자(@, :, /)가 포함된 경우
from urllib.parse import quote
password = quote("p@ss:word/123", safe="")
proxy = f"http://user:{password}@proxy.example.com:8080"
IP 기반 인증 vs 로그인/암호 인증
일부 프록시 제공업체는 IP 주소 기반 화이트리스트를 사용합니다. 귀하의 컴퓨터에 있는 브라우저는 IP가 화이트리스트에 추가되어 있으므로 작동하지만, 서버의 스크립트는 다른 IP를 사용하므로 작동하지 않을 수 있습니다.
프록시 제공업체의 패널에서 어떤 인증 방법이 사용되는지, 그리고 어떤 IP가 화이트리스트에 추가되었는지 확인하십시오.
HTTP/HTTPS/SOCKS 프로토콜 불일치
브라우저는 종종 프록시 유형을 자동으로 감지하지만, 코드에서는 명시적으로 지정해야 하며 프로토콜 오류는 조용한 실패로 이어질 수 있습니다.
| 프록시 유형 | URL 스키마 | 특징 |
|---|---|---|
| HTTP 프록시 | http:// |
CONNECT를 통한 HTTP 및 HTTPS 지원 |
| HTTPS 프록시 | https:// |
프록시와의 암호화된 연결 |
| SOCKS4 | socks4:// |
인증 없음, IPv4만 지원 |
| SOCKS5 | socks5:// |
인증 지원, UDP, IPv6 지원 |
| SOCKS5h | socks5h:// |
프록시를 통한 DNS 확인(Resolving) |
중요: SOCKS5 프록시를 사용하는데 http://를 지정하면 연결이 설정되지 않습니다. 라이브러리는 SOCKS 서버와 HTTP 프로토콜로 통신하려고 시도할 것입니다.
누락된 헤더 및 핑거프린트
프록시가 올바르게 작동하더라도 대상 사이트는 의심스러운 헤더로 인해 요청을 차단할 수 있습니다. 비교해 보세요.
브라우저 요청
GET /api/data HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
기본 requests 요청
GET /api/data HTTP/1.1
Host: example.com
User-Agent: python-requests/2.28.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
차이가 명확합니다. 봇 방지 기능이 있는 사이트는 요청이 브라우저에서 오지 않았음을 즉시 감지합니다.
위장을 위한 최소 헤더 세트
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": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Cache-Control": "max-age=0"
}
SSL 인증서 및 검증
브라우저는 루트 인증서의 내장 저장소를 가지고 있으며 다양한 SSL 구성을 처리할 수 있습니다. 코드에서는 다음과 같은 문제가 발생할 수 있습니다.
SSL 오류: CERTIFICATE_VERIFY_FAILED
일부 프록시는 트래픽 검사를 위해 자체 인증서를 사용합니다. 브라우저는 이 인증서를 신뢰할 수 있는 것으로 가질 수 있지만, 귀하의 스크립트는 아닐 수 있습니다.
# 디버깅을 위한 임시 해결책 (운영 환경에서는 사용 금지!)
import requests
response = requests.get(url, proxies=proxies, verify=False)
# 올바른 해결책 - 인증서 경로 지정
response = requests.get(url, proxies=proxies, verify="/path/to/proxy-ca.crt")
중요: SSL 검증 비활성화(
verify=False)는 연결을 MITM 공격에 취약하게 만듭니다. 안전한 환경에서 디버깅할 때만 사용하십시오.
TLS 핑거프린트
고급 봇 방지 시스템은 연결 설정 시 순서와 암호 세트를 분석하는 TLS 핑거프린트를 분석합니다. Python requests는 브라우저와 다른 표준 세트를 사용합니다.
우회를 위해 사용자 지정 TLS 핑거프린트를 사용하는 라이브러리를 사용하십시오.
# 설치: pip install curl-cffi
from curl_cffi import requests
response = requests.get(
url,
proxies={"https": proxy},
impersonate="chrome120" # Chrome 120의 TLS 핑거프린트 모방
)
DNS 유출 및 확인(Resolving)
또 다른 눈에 띄지 않는 문제는 DNS 확인입니다. HTTP 프록시를 사용할 때 DNS 요청이 프록시를 우회하여 컴퓨터에서 직접 나갈 수 있습니다.
작동 방식에 미치는 영향
- 사이트가 프록시가 아닌 실제 DNS 확인자를 확인합니다.
- 지리적 위치가 잘못 결정됩니다.
- 일부 사이트는 IP와 DNS 지역 불일치를 차단합니다.
SOCKS5에 대한 해결책
프록시를 통해 확인하려면 socks5:// 대신 socks5h:// 스키마를 사용하십시오. "h"는 DNS 확인이 프록시 측에서 수행됨을 의미합니다.
# DNS가 로컬에서 확인됨 (유출 발생!)
proxy = "socks5://user:pass@proxy.example.com:1080"
# DNS가 프록시를 통해 확인됨 (올바름)
proxy = "socks5h://user:pass@proxy.example.com:1080"
Python, Node.js 및 cURL을 위한 작동 예제
requests를 사용한 Python
import requests
from urllib.parse import quote
# 프록시 데이터
proxy_host = "proxy.example.com"
proxy_port = "8080"
proxy_user = "username"
proxy_pass = quote("p@ssword!", safe="") # 특수 문자 이스케이프 처리
# 프록시 URL 구성
proxy_url = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"
proxies = {
"http": proxy_url,
"https": proxy_url
}
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": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
}
try:
response = requests.get(
"https://httpbin.org/ip",
proxies=proxies,
headers=headers,
timeout=30
)
print(f"상태 코드: {response.status_code}")
print(f"IP: {response.json()}")
except requests.exceptions.ProxyError as e:
print(f"프록시 오류: {e}")
except requests.exceptions.ConnectTimeout:
print("프록시 연결 시간 초과")
aiohttp를 사용한 Python (비동기)
import aiohttp
import asyncio
async def fetch_with_proxy():
proxy_url = "http://user:pass@proxy.example.com:8080"
async with aiohttp.ClientSession() as session:
async with session.get(
"https://httpbin.org/ip",
proxy=proxy_url,
headers={"User-Agent": "Mozilla/5.0..."}
) as response:
return await response.json()
result = asyncio.run(fetch_with_proxy())
print(result)
axios를 사용한 Node.js
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
const proxyUrl = 'http://user:pass@proxy.example.com:8080';
const agent = new HttpsProxyAgent(proxyUrl);
axios.get('https://httpbin.org/ip', {
httpsAgent: agent,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'
}
})
.then(response => console.log(response.data))
.catch(error => console.error('오류:', error.message));
node-fetch 및 SOCKS를 사용한 Node.js
const fetch = require('node-fetch');
const { SocksProxyAgent } = require('socks-proxy-agent');
const agent = new SocksProxyAgent('socks5://user:pass@proxy.example.com:1080');
fetch('https://httpbin.org/ip', { agent })
.then(res => res.json())
.then(data => console.log(data));
cURL
# HTTP 프록시
curl -x "http://user:pass@proxy.example.com:8080" \
-H "User-Agent: Mozilla/5.0..." \
https://httpbin.org/ip
# 프록시를 통한 SOCKS5 프록시 및 DNS 확인
curl --socks5-hostname "proxy.example.com:1080" \
--proxy-user "user:pass" \
https://httpbin.org/ip
# 디버깅 - 전체 연결 프로세스 표시
curl -v -x "http://user:pass@proxy.example.com:8080" \
https://httpbin.org/ip
진단 체크리스트
코드에서 프록시가 작동하지 않으면 다음 순서대로 확인하십시오.
- 프록시 URL 형식 - 스키마(http://, socks5://)가 있습니까?
- 암호의 특수 문자 - URL 인코딩되어 있습니까?
- 프록시 유형 - 지정된 프로토콜이 실제 프로토콜과 일치합니까?
- 인증 - IP 기반입니까 아니면 로그인/암호 기반입니까? 서버 IP가 화이트리스트에 있습니까?
- 헤더 - User-Agent 및 기타 브라우저 헤더가 추가되었습니까?
- SSL - 인증서 오류가 발생하지 않았습니까?
- DNS - 프록시를 통한 확인을 위해 socks5h://을 사용하고 있습니까?
- 시간 초과 - 연결에 충분한 시간이 주어졌습니까? (특히 레지덴셜 프록시의 경우)
결론
브라우저와 코드의 차이점은 헤더, 프로토콜, SSL, DNS와 같은 세부 사항에 있습니다. 브라우저는 이러한 복잡성을 숨기지만, 코드에서는 각 측면을 명시적으로 구성해야 합니다. URL 형식과 인증 확인부터 시작하여 브라우저 헤더를 추가하면 90%의 문제가 해결됩니다.
크롤링 및 자동화 작업에서 안정성과 낮은 차단 비율이 중요한 경우 레지덴셜 프록시가 잘 작동합니다. 자세한 내용은 proxycove.com에서 확인할 수 있습니다.