블로그로 돌아가기

브라우저에서는 작동하지만 코드에서는 작동하지 않는 프록시: 문제에 대한 완벽 분석

프록시는 브라우저에서 잘 작동하지만 스크립트에서 오류가 발생하나요? 일반적인 원인을 분석하고 Python, Node.js 및 cURL에 대한 즉각적인 해결책을 제공합니다.

📅2025년 12월 11일
```html

프록시가 브라우저에서는 작동하지만 코드에서는 작동하지 않는 이유: 전체 문제 분석

고전적인 상황: 브라우저에서 프록시를 설정하고 사이트를 열면 모든 것이 작동합니다. 동일한 프록시로 스크립트를 실행하면 연결 오류, 시간 초과 또는 차단이 발생합니다. 왜 이런 일이 발생하는지, 그리고 이를 어떻게 수정할 수 있는지 살펴보겠습니다.

브라우저 요청과 코드 요청의 차이점

프록시를 통해 브라우저에서 사이트를 열면 단순한 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

진단 체크리스트

코드에서 프록시가 작동하지 않으면 다음 순서대로 확인하십시오.

  1. 프록시 URL 형식 - 스키마(http://, socks5://)가 있습니까?
  2. 암호의 특수 문자 - URL 인코딩되어 있습니까?
  3. 프록시 유형 - 지정된 프로토콜이 실제 프로토콜과 일치합니까?
  4. 인증 - IP 기반입니까 아니면 로그인/암호 기반입니까? 서버 IP가 화이트리스트에 있습니까?
  5. 헤더 - User-Agent 및 기타 브라우저 헤더가 추가되었습니까?
  6. SSL - 인증서 오류가 발생하지 않았습니까?
  7. DNS - 프록시를 통한 확인을 위해 socks5h://을 사용하고 있습니까?
  8. 시간 초과 - 연결에 충분한 시간이 주어졌습니까? (특히 레지덴셜 프록시의 경우)

결론

브라우저와 코드의 차이점은 헤더, 프로토콜, SSL, DNS와 같은 세부 사항에 있습니다. 브라우저는 이러한 복잡성을 숨기지만, 코드에서는 각 측면을 명시적으로 구성해야 합니다. URL 형식과 인증 확인부터 시작하여 브라우저 헤더를 추가하면 90%의 문제가 해결됩니다.

크롤링 및 자동화 작업에서 안정성과 낮은 차단 비율이 중요한 경우 레지덴셜 프록시가 잘 작동합니다. 자세한 내용은 proxycove.com에서 확인할 수 있습니다.

```