블로그로 돌아가기

프록시 잘못된 데이터 반환: 원인 및 해결 방법

프록시가 잘못된 데이터를 반환하는 일반적인 원인(캐싱부터 지리적 위치 문제까지)을 분석하고 각 사례에 대한 실용적인 해결책을 제시합니다.

📅2025년 12월 12일
```html

프록시가 잘못된 데이터를 반환하는 경우: 원인 및 해결 방법

파서를 설정하고 데이터 수집을 시작했는데, 결과는 다른 지역의 가격, 오래된 콘텐츠 또는 완전히 다른 페이지입니다. 프록시가 잘못된 데이터를 반환하는 이유와 이를 수정하는 방법을 분석해 보겠습니다.

1. 프록시 측 캐싱

오래된 데이터의 가장 흔한 원인은 캐싱입니다. 일부 프록시 서버는 부하를 줄이고 속도를 높이기 위해 웹사이트 응답을 저장합니다. 결과적으로 최신 데이터 대신 일주일 전 데이터를 받게 됩니다.

문제 식별 방법

  • 반복 요청 시에도 데이터가 변경되지 않음
  • 가격이나 재고 현황이 실제와 일치하지 않음
  • 응답 헤더의 Age 값이 큰 값을 표시함

해결 방법

캐싱을 금지하는 헤더를 추가하세요:

import requests

headers = {
    'Cache-Control': 'no-cache, no-store, must-revalidate',
    'Pragma': 'no-cache',
    'Expires': '0'
}

response = requests.get(
    'https://example.com/prices',
    proxies={'http': proxy, 'https': proxy},
    headers=headers
)

공급업체가 계속 캐싱하는 경우 URL에 임의의 매개변수를 추가하세요:

import time

url = f'https://example.com/prices?_nocache={int(time.time())}'

2. 지리적 위치 불일치

독일 프록시를 요청했는데 루블 가격이 표시됩니다. 또는 러시아 데이터가 필요한데 웹사이트가 미국 콘텐츠를 보여줍니다. 이는 몇 가지 이유로 발생합니다.

지리적 위치가 일치하지 않는 이유

원인 설명
오래된 GeoIP 데이터베이스 IP가 최근에 다른 지역으로 이전되었지만 데이터베이스가 아직 업데이트되지 않음
웹사이트 자체 데이터베이스 사용 대상 웹사이트가 프록시 공급업체와 다르게 지리적 위치를 결정함
이전 세션의 쿠키 웹사이트가 이전 방문 기록을 통해 사용자의 지역을 기억함
Accept-Language 헤더 언어 헤더가 프록시의 지리적 위치와 일치하지 않음

해결 방법

요청의 모든 매개변수를 원하는 지리적 위치와 동기화하세요:

# 독일 웹사이트 크롤링용
headers = {
    'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'
}

# 쿠키가 없는 깨끗한 세션
session = requests.Session()
session.cookies.clear()

response = session.get(
    'https://example.de/preise',
    proxies={'http': german_proxy, 'https': german_proxy},
    headers=headers
)

크롤링 전에 프록시의 실제 지리적 위치를 확인하세요:

def check_proxy_geo(proxy):
    response = requests.get(
        'http://ip-api.com/json/',
        proxies={'http': proxy, 'https': proxy},
        timeout=10
    )
    data = response.json()
    return data.get('country'), data.get('city')

3. IP 로테이션 문제

자동 IP 로테이션이 있는 레지덴셜 프록시를 사용할 때, IP는 요청마다 변경됩니다. 이는 제한을 우회하는 데 유용하지만, 데이터의 일관성이 필요할 때는 문제가 발생합니다.

일반적인 증상

  • 페이지네이션 시 중복 항목이 반환되거나 항목이 건너뜀
  • 요청 사이에 장바구니가 비워짐
  • 세션 중간에 인증이 해제됨
  • A/B 테스트에서 다른 버전의 페이지가 표시됨

해결 방법: 고정 세션 (Sticky Sessions)

대부분의 프록시 공급업체는 특정 시간 동안 IP를 유지하는 '고정 세션'을 지원합니다. 이는 일반적으로 연결 문자열의 매개변수를 통해 설정됩니다.

# 세션 ID가 포함된 형식 (구문은 공급업체에 따라 다름)
proxy = 'http://user-session-abc123:pass@gate.provider.com:7777'

# 동일한 세션 ID를 사용하는 모든 요청은 동일한 IP를 통해 전송됨
for page in range(1, 10):
    response = requests.get(
        f'https://example.com/catalog?page={page}',
        proxies={'http': proxy, 'https': proxy}
    )

중요: 고정 세션은 보통 1~30분 동안 지속됩니다. 관련 요청들이 이 시간 내에 완료되도록 데이터 수집 계획을 세워야 합니다.

4. 세션 및 쿠키 무결성 문제

현대 웹사이트는 개인화를 위해 쿠키를 적극적으로 사용합니다. 파서가 쿠키를 올바르게 처리하지 못하면 잘못된 데이터를 받거나 아예 차단될 수 있습니다.

자주 발생하는 오류

  1. Set-Cookie 무시 — 웹사이트가 세션을 추적할 수 없음
  2. 다른 IP와 쿠키 재사용 — 의심스러운 동작으로 간주됨
  3. 초기 요청 부재 — 메인 페이지를 통해 '로그인'하지 않고 내부 페이지로 바로 이동

올바른 접근 방식

import requests

def create_browser_session(proxy):
    session = requests.Session()
    session.proxies = {'http': proxy, 'https': proxy}
    
    # 첫 번째 방문을 시뮬레이션하여 쿠키 획득
    session.get('https://example.com/', headers={
        'User-Agent': 'Mozilla/5.0...',
        'Accept': 'text/html,application/xhtml+xml...',
        'Accept-Language': 'en-US,en;q=0.9'
    })
    
    # 이제 유효한 세션으로 크롤링 가능
    return session

session = create_browser_session(proxy)
data = session.get('https://example.com/api/prices').json()

5. 인코딩 및 압축 오류

데이터 자체는 올바르지만 인코딩 또는 압축 문제로 인해 잘못 표시되는 경우가 있습니다. 이는 특히 키릴 문자나 아시아 언어를 다룰 때 중요합니다.

증상

  • 텍스트 대신 깨진 문자 표시: Цена (러시아어 '가격' 대신)
  • gzip이 활성화된 상태에서 빈 응답 수신
  • HTML 대신 바이너리 쓰레기 데이터

해결 방법

import requests

response = requests.get(url, proxies=proxies)

# 방법 1: 인코딩 자동 감지
response.encoding = response.apparent_encoding
text = response.text

# 방법 2: 인코딩 강제 지정
text = response.content.decode('utf-8')

# 방법 3: 압축 비활성화 (프록시가 gzip을 손상시키는 경우)
headers = {'Accept-Encoding': 'identity'}
response = requests.get(url, proxies=proxies, headers=headers)

6. 숨겨진 차단 및 캡차

모든 차단이 명확한 것은 아닙니다. 웹사이트는 HTTP 200을 반환하지만 실제 데이터 대신 자리 표시자, 오래된 캐시 또는 일반 HTML 내부에 캡차를 삽입할 수 있습니다.

숨겨진 차단 징후

  • 응답 크기가 의심스러울 정도로 작거나 여러 페이지에서 동일함
  • HTML 내부에 captcha, challenge, blocked, access denied와 같은 단어가 포함되어 있음
  • 예상되는 요소(가격, 설명, 버튼)가 없음
  • JavaScript 리디렉션으로 다른 페이지로 이동

응답 유효성 검사

def is_valid_response(response, expected_markers):
    """응답에 실제 데이터가 포함되어 있는지 확인"""
    
    text = response.text.lower()
    
    # 차단 확인
    block_signals = ['captcha', 'blocked', 'access denied', 
                     'rate limit', 'try again later']
    for signal in block_signals:
        if signal in text:
            return False, f'Blocked: {signal}'
    
    # 예상 데이터 포함 여부 확인
    for marker in expected_markers:
        if marker.lower() not in text:
            return False, f'Missing: {marker}'
    
    # 크기 확인 (너무 작으면 자리 표시자)
    if len(response.content) < 5000:
        return False, 'Response too small'
    
    return True, 'OK'

# 사용 예시
valid, reason = is_valid_response(response, ['price', 'add to cart'])
if not valid:
    print(f'Invalid response: {reason}')
    # 프록시 변경, 대기 후 재시도

심각한 봇 방지 기능이 있는 웹사이트의 경우, 데이터센터 프록시보다 고정 세션을 지원하는 모바일 프록시가 더 높은 신뢰도를 보여줍니다.

7. 단계별 진단

프록시가 잘못된 데이터를 반환할 때, 원인을 찾기 위해 다음 알고리즘을 사용하세요.

1단계: 문제 격리

# 프록시 없음 vs 프록시 사용 시 응답 비교
def compare_responses(url, proxy):
    direct = requests.get(url)
    proxied = requests.get(url, proxies={'http': proxy, 'https': proxy})
    
    print(f'Direct:  {len(direct.content)} bytes, status {direct.status_code}')
    print(f'Proxied: {len(proxied.content)} bytes, status {proxied.status_code}')
    
    # 비교를 위해 두 응답 모두 저장
    with open('direct.html', 'w') as f:
        f.write(direct.text)
    with open('proxied.html', 'w') as f:
        f.write(proxied.text)

2단계: 응답 헤더 확인

response = requests.get(url, proxies=proxies)

# 진단에 중요한 헤더
important_headers = ['content-type', 'content-encoding', 
                     'cache-control', 'age', 'x-cache', 
                     'cf-ray', 'server']

for header in important_headers:
    value = response.headers.get(header, 'not set')
    print(f'{header}: {value}')

3단계: 점검 목록

점검 항목 명령/방법
프록시의 실제 IP curl -x proxy:port ifconfig.me
IP 지리적 위치 ip-api.com/json
캐싱 여부 Age, X-Cache 헤더
차단 여부 HTML에서 'captcha', 'blocked' 검색
인코딩 Content-Type charset

4단계: 전체 진단 스크립트

import requests
import json

def diagnose_proxy(proxy, target_url):
    report = {}
    
    # 1. 작동 여부 확인
    try:
        r = requests.get('http://httpbin.org/ip', 
                        proxies={'http': proxy, 'https': proxy},
                        timeout=15)
        report['proxy_ip'] = r.json().get('origin')
        report['proxy_works'] = True
    except Exception as e:
        report['proxy_works'] = False
        report['error'] = str(e)
        return report
    
    # 2. 지리적 위치 확인
    r = requests.get('http://ip-api.com/json/',
                    proxies={'http': proxy, 'https': proxy})
    geo = r.json()
    report['country'] = geo.get('country')
    report['city'] = geo.get('city')
    
    # 3. 대상 사이트 요청
    r = requests.get(target_url,
                    proxies={'http': proxy, 'https': proxy},
                    timeout=30)
    report['status_code'] = r.status_code
    report['content_length'] = len(r.content)
    report['cached'] = 'age' in r.headers or 'x-cache' in r.headers
    
    # 4. 차단 여부 확인
    block_words = ['captcha', 'blocked', 'denied', 'cloudflare']
    report['possibly_blocked'] = any(w in r.text.lower() for w in block_words)
    
    return report

# 사용 예시
result = diagnose_proxy('http://user:pass@proxy:port', 'https://target-site.com')
print(json.dumps(result, indent=2))

결론

프록시에서 잘못된 데이터를 반환하는 문제는 거의 항상 해결 가능합니다. 대부분의 경우 원인은 캐싱, 지리적 위치 불일치 또는 세션 처리 오류입니다. 이 문서의 진단 스크립트를 사용하여 문제의 원인을 신속하게 찾아보세요.

지리적 위치의 정확성과 낮은 차단 비율이 중요한 작업의 경우, 고정 세션을 지원하는 레지덴셜 프록시가 최적입니다. 자세한 내용은 proxycove.com에서 확인하세요.

```