스크래핑

깨끗한 세션을 위한 레지덴셜 프록시 지역, 시간대, 로케일 일치시키기

뉴욕 시간대를 보고하는 독일 exit는 실제 방문자라면 발생하지 않는 모순입니다. 모든 로케일 신호를 exit 국가로부터 도출하면 서로 어긋날 수 없습니다.

Chris Collins

Chris Collins

2026년 8월 27일 · 5 분 소요

독일 출구를 설정했고, 주소는 지리적으로 정확하게 위치를 잡는데도, 타겟은 여전히 세션을 의심스럽게 취급하거나 다른 나라를 위한 콘텐츠를 제공한다. IP는 맞았다. 요청에 관한 나머지 모든 것은 여전히 다른 나라의 방문자를 설명하고 있었다.

위치는 단일 신호가 아니다. 그것은 여러 신호들의 집합이며, 실제 방문자는 한 대의 기기와 한 나라에서 오기 때문에 이 모든 신호를 같은 장소에서 만들어낸다. 스크래퍼는 이를 서로 다른 출처에서 조합한다. 국가는 프록시 파라미터에서 나오고, 타임존은 서버의 시계에서 나오며, 로케일은 라이브러리 기본값에서, 언어 헤더는 하드코딩된 무언가에서 나온다. 이것들이 서로 어긋나면 그 모순은 어떤 단일 값보다도 더 탐지하기 쉬우며, 수집하는 데이터 자체를 바꿔놓을 수도 있다.

일치해야 하는 신호들

여섯 가지가 사이트에 당신의 위치를 알려주며, 대부분의 스크래퍼가 그중 한두 개만 통제한다는 점에서 명시적으로 나열할 가치가 있다.

IP 주소는 주된 신호이며 프록시가 설정하는 값이다. Accept-Language는 언어 선호도를 나타내는 HTTP 헤더이며, 많은 사이트가 이 값을 그대로 사용해 콘텐츠를 제공한다. 타임존은 브라우저에서 JavaScript 날짜 API를 통해 관찰 가능하며, 더 정확하게는 Intl API가 해석한 시간대를 통해 드러난다. 로케일navigator.language와 Intl API가 해석하는 형식 규칙을 아우르며, 이는 날짜, 숫자, 통화가 어떻게 표시되는지를 결정한다. 통화 및 단위는 사이트가 클라이언트에게 힌트를 주도록 허용하는 경우에 해당한다. 그리고 DNS 해석은 무관해 보이지만 그렇지 않다. 클라이언트가 호스트명을 로컬에서 해석하면서 원격으로 출구를 통과한다면, 해석은 당신의 실제 위치에서 이루어지며 지역적으로 잘못된 엔드포인트를 넘겨줄 수 있는데, 이것이 DNS 유출 문제다.

일반 HTTP 작업에서는 처음 두 가지와 DNS만 보이며, 그렇기 때문에 헤더 관련 글에서는 Accept-Language를 주요 짝으로 다룬다. 브라우저 자동화에서는 여섯 가지 모두가 관찰 가능하며, 불일치가 대개 여기서 나타난다.

모든 것을 하나의 진실 소스에서 도출하라

해결책은 체크리스트가 아니라 구조적인 것이다. 국가는 한 곳에서 선택하고 타임존은 다른 곳에서 선택한다면, 누군가 시장을 추가하는 순간부터 둘은 어긋나기 시작할 것이다. 각 시장을 한 번만 정의하되 그것이 암시하는 모든 신호를 포함시키고, 전체 세션을 그 레코드로부터 도출하라.

MARKETS = {
    "de": {"lang": "de-DE,de;q=0.9,en;q=0.8", "locale": "de-DE",
           "tz": "Europe/Berlin",   "currency": "EUR"},
    "us": {"lang": "en-US,en;q=0.9", "locale": "en-US",
           "tz": "America/New_York", "currency": "USD"},
    "jp": {"lang": "ja-JP,ja;q=0.9,en;q=0.8", "locale": "ja-JP",
           "tz": "Asia/Tokyo",      "currency": "JPY"},
    "br": {"lang": "pt-BR,pt;q=0.9,en;q=0.8", "locale": "pt-BR",
           "tz": "America/Sao_Paulo", "currency": "BRL"},
}

def proxy_for(country, session=None):
    user = f"customer-USERNAME-country-{country}"
    if session:
        user += f"-sid-{session}-ttl-600"
    url = f"http://{user}:PASSWORD@p.shifter.io:443"
    return {"http": url, "https": url}

이제 시장은 단일 인자가 되며, 국가와 타임존이 서로 어긋날 수 있는 코드 경로는 존재하지 않는다. 누구도 이 둘을 따로 설정하지 않기 때문이다.

브라우저 세션에 적용하기

브라우저 자동화 프레임워크는 타임존과 로케일을 컨텍스트 옵션으로 노출하는데, 이는 이를 설정하기에 올바른 위치다. 페이지 스크립트가 실행되기 전에 적용되므로, 페이지는 기기의 실제 값을 관찰할 수 없다.

# Playwright: 프록시, 타임존, 로케일 모두 하나의 시장 레코드에서 도출
m = MARKETS[country]
context = browser.new_context(
    proxy={"server": "http://p.shifter.io:443",
           "username": f"customer-USERNAME-country-{country}-sid-{sid}",
           "password": "PASSWORD"},
    locale=m["locale"],                  # navigator.language 및 Intl 포맷팅
    timezone_id=m["tz"],                 # Intl이 해석하는 시간대와 Date 오프셋
    extra_http_headers={"Accept-Language": m["lang"]},
)

이 값들을 나중에 속성을 패치하는 방식이 아니라 컨텍스트 수준에서 설정하는 것이 중요한데, Date 오프셋과 어긋나는 패치된 Intl.DateTimeFormat은 그 자체로 탐지 가능한 불일치이며, 탐지 스크립트는 이 둘을 습관적으로 교차 확인하기 때문이다.

정밀도, 그리고 얼마나 필요한가

대부분의 작업에서는 국가 수준의 세분화로 충분한데, 타임존과 로케일이 대체로 국가 단위이기 때문이다. 더 세심한 주의가 필요한 경우는 세 가지다.

여러 타임존을 가진 국가는 인구의 일부에게 국가 기본값이 틀리게 만든다. 미국 출구는 여러 시간대 중 어디든 그럴듯하므로, 도시 수준 타겟팅으로 작업하고 있다면 국가가 아니라 도시로부터 타임존을 도출해야 하며, 그렇지 않다면 사용자 비중이 가장 큰 시간대를 선택하고 무작위화하지 말고 일관성을 유지해야 한다.

여러 공식 언어를 가진 국가는 의도적인 선택이 필요하다. 스위스나 캐나다 출구는 여러 로케일이 그럴듯하며, 올바른 답은 대개 수집하는 콘텐츠와 일치하는 것을 고정하는 것이다.

지역별 형식 차이는 더 미묘하고 대개 쫓아갈 가치가 없지만, 로케일을 제시한다면 그 로케일이 실제로 만들어내는 것과 맞지 않을 수 있는 날짜와 숫자 형식을 직접 작성하기보다는 Intl API가 형식을 처리하도록 두어라.

세션 수명 동안의 일관성

세션은 하나의 이야기이며, 그 이야기는 중간에 바뀌어서는 안 된다. 고정 세션이 다단계 플로우 동안 하나의 주소를 유지한다면, 그 플로우의 모든 요청은 동일한 언어, 타임존, 로케일을 가져야 한다. 플로우 중간에 이 중 하나라도 바꾸는 것은 검색을 클릭한 후 결과를 보는 사이에 나라를 옮긴 방문자를 묘사하는 셈이며, 이는 어떤 정적인 불일치보다도 더 강한 이상 신호다.

여기서 하나의 시장 레코드로부터 도출하는 방식이 다시 빛을 발한다. 세션 식별자와 로케일 묶음이 같은 곳에서 나오고 같은 기간 동안 지속되기 때문이다. 이 둘을 명시적으로 묶어서, 세션을 해제하면 전체 신원이 해제되고 새 세션은 내부적으로 일관된 새로운 신원으로 시작하도록 하라. 같은 논리가 안티디텍트 브라우저에서 기기 지문과 네트워크 신원을 짝짓는 방식을 지배한다.

제대로 했는지 확인하기

아무것도 가정하지 말고 두 가지 수준에서 확인하라.

첫째, 브라우저가 자기 자신에 대해 보고하는 내용. 해석된 타임존, navigator.language, 형식화된 날짜를 읽는 페이지를 실행해서 이것들이 의도한 시장과 일치하는지 확인하라. 이는 설정 실수를 즉시 잡아낸다.

JSON.stringify({
  tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
  lang: navigator.language,
  langs: navigator.languages,
  offset: new Date().getTimezoneOffset(),
})

둘째, 그리고 더 의미 있는 것은 타겟이 실제로 하는 행동이다. 지리 정보에 민감한 페이지를 가져와서 통화, 언어, 지역 콘텐츠가 현지 방문자가 볼 것과 일치하는지 확인하라. 지리위치 데이터베이스와 타겟의 판단이 항상 일치하지는 않기 때문에 사이트 자체의 동작이 진짜 판정 기준이며, 이것이 바로 위치 정확도 테스트가 존재하는 이유다. 주소는 지리적으로 정확하게 위치를 잡는데 콘텐츠가 틀렸다면, 다른 무엇보다 DNS 해석을 먼저 의심하라.

결론

위치는 신호들의 집합이며, 사이트는 이를 함께 읽는다. 프록시에 국가를 설정하면서 타임존, 로케일, 언어는 서버 기본값 그대로 두면 존재할 수 없는 방문자가 만들어지며, 이는 탐지 신호이자 조용히 잘못된 데이터의 원인이 된다. 각 시장을 그것이 암시하는 모든 신호와 함께 한 번만 정의하고, 프록시 파라미터와 브라우저 컨텍스트를 그 하나의 레코드로부터 도출해서 서로 어긋날 수 없게 하며, 타임존과 로케일은 로드 후에 패치하지 말고 컨텍스트 수준에서 설정하고, 세션 수명 동안 전체 묶음을 일정하게 유지하며, 브라우저가 보고하는 것과 타겟이 실제로 제공하는 것 두 수준 모두에서 확인하라. 그러면 당신의 트래픽이 위치에 대해 말하는 유일한 것은 당신이 선택한 바로 그것이 된다.

지리적 요소 자체는 레지덴셜 프록시에서 나온다. 국가 및 도시 타겟팅이 가능한 실제 가정용 등급 주소로, 세션이 주장하는 위치가 실제로 출구를 통과하는 위치가 되도록 하며, 여러 시장에 걸쳐 동일한 작업을 실행하기에 적합한 GB당 가격을 제공한다.

시작할 준비가 되셨나요?

205M개 이상의 IP, 195개 이상의 국가를 지원하는 Shifter의 레지덴셜 프록시를 $0.75/GB부터 이용해보세요.

시작하기