스크래핑

타겟 헬스 스코어 구축하기: 사이트가 당신에게 등을 돌리고 있다는 것을 아는 법

사이트는 크롤러를 한 번에 차단하는 경우가 거의 없습니다. 먼저 챌린지를 걸고, 소프트 블록을 하고, 속도를 늦춥니다. 이러한 신호를 사이트별 하나의 점수로 전환하고 이에 대응하는 방법을 알아봅니다.

Matt Brown

Matt Brown

2026년 9월 22일 · 7 분 소요

사이트가 크롤러를 정상적으로 처리하다가 한 번에 완전히 차단하는 경우는 거의 없다. 먼저 일어나는 일은 더 조용하다. 몇 개의 요청이 더 챌린지 페이지에 걸린다. 일부 응답은 200 OK로 돌아오지만 안에 유용한 내용이 없다. 지연 시간이 조금씩 늘어난다. 일부 레코드는 검증에 실패한다. 각각의 신호는 그 자체로는 잡음처럼 보이고, 각각 다른 대시보드에 존재하기 때문에, 데이터셋에 구멍이 생기기 전까지는 아무도 이들을 연결해서 보지 않는다.

타겟 헬스 점수는 이 문제에 대한 해법이다. 사이트별로 하나의 숫자를 두어, 이 사이트가 여전히 자기 자신의 정상 상태처럼 동작하고 있는지를 이유와 함께 알려준다. 이 글은 어떤 신호들이 이 점수에 반영되는지, 스스로를 속이지 않고 이를 결합하는 방법은 무엇인지, 그리고 이 숫자가 떨어졌을 때 크롤러가 무엇을 해야 하는지를 다룬다.

타겟 헬스는 프록시 헬스가 아니다

무엇을 측정하는지 정확히 짚어둘 필요가 있다. 이 둘은 자주 혼동된다.

프록시 헬스는 하나의 경로가 작동하는지를 묻는다: 게이트웨이, 국가, 세션, 출구. 타겟 헬스는 특정 사이트가 여전히 당신에게 서비스를 제공할 의지와 능력이 있는지를 묻는다. 죽은 프록시 경로는 모든 사이트에 대해 실패한다. 당신에게 등을 돌린 사이트는 모든 경로에서 실패를 만든다.

이 차이가 첫 번째 진단 기준이다. 실패율이 오르면, 이를 경로별로 나눠 보라. 만약 특정 국가, 특정 풀, 특정 세션 패턴에 몰려 있다면 이는 경로 문제이며, 대규모로 레지덴셜 프록시 상태 모니터링하기에서 다룬 접근 방식이 적용된다. 만약 해당 사이트로 보내는 모든 경로에서 고르게 실패율이 오른다면, 사이트가 당신에 대한 태도를 바꾼 것이며, 이 점수가 바로 그것을 위한 것이다.

신호들

신호들을 무엇을 드러내는지에 따라 그룹화하라. 일부는 명확하고, 일부는 거의 조용하며, 조용한 신호들을 놓치는 것이 가장 큰 비용을 초래한다.

신호어떻게 보이는가왜 중요한가
하드 블록403, 429, 연결 초기화명시적 거부; 세기 쉬움
챌린지CAPTCHA 또는 인터스티셜 페이지사이트가 자동화를 의심하고 테스트하는 중
소프트 블록차단 페이지, 빈 결과, 또는 축소된 레이아웃을 담은 200 OK성공으로 가려진 거부
지연 시간 드리프트해당 사이트 자체의 정상 대비 p95 응답 시간 상승종종 의도적인 지연이지만, 때로는 단순 부하
추출 실패필수 필드 누락, 스키마 불일치페이지가 바뀌었거나, 다른 것이 제공되고 있음
비용 드리프트유효 레코드당 재시도, 바이트, 크레딧 증가위의 모든 것이 돈으로 표현된 것

소프트 블록은 특별한 주의가 필요하다. 상태 코드는 거짓말을 하기 때문이다. 2023년 쿠바에서의 지오블로킹 측정에서, 32개 도메인이 200 OK 상태로 차단 페이지를 서빙했다. 이는 어느 국가가 가장 많이 지오블로킹되는가에 설명되어 있다. 상태 코드를 신뢰하는 크롤러는 이를 성공으로 기록한다. 소프트 블록은 내용으로 탐지하라: 알려진 차단 페이지 마커, 해당 페이지 유형의 정상 대비 훨씬 작은 응답 크기, 또는 항상 무언가를 반환하던 곳에서 아무것도 반환하지 않는 추출 결과.

두 명의 소유자, 두 개의 점수

혼란을 크게 줄여주는 구분이 하나 있다: “사이트가 변했다”와 “사이트가 당신에게 등을 돌렸다”를 분리하라.

리디자인은 파서를 망가뜨린다. 모든 페이지는 정상적으로 로드되지만 필수 필드가 사라진다. 이는 코드 수정으로 해결할 추출 문제이며, 파서를 관리하는 사람이 소유한다. 챌린지와 소프트 블록을 시작하는 사이트는 관계의 문제이며, 해법은 수집 방식, 수집량, 또는 수집 여부의 변화다. 소유자가 다르고, 대응도 다르다.

따라서 블록, 챌린지, 소프트 블록, 지연 시간을 의미하는 적대성(hostility)을 헬스 점수로 계산하고, 추출 유효성은 별도의 파서 헬스 신호로 함께 추적하라. 둘 다 동시에 떨어질 때는 먼저 적대성을 살펴보라. 챌린지 페이지를 서빙하는 사이트는 모든 추출기도 함께 망가뜨릴 것이다.

사이트 자체의 정상 상태 대비 점수화하라

가장 흔한 실수는 전역 임계값을 사용하는 것이다. 5%의 챌린지 비율은 한 번도 챌린지를 받아본 적 없는 사이트에서는 경고 신호지만, 첫 방문부터 모두를 챌린지하는 사이트에서는 완전히 정상이다. 모든 신호는 해당 사이트 자체의 베이스라인과 비교되어야 하며, 안정적일 만큼 충분히 긴 기간, 예를 들어 지난 2주를 기준으로 하되, 가장 최근 하루는 제외하여 나쁜 하루가 새로운 정상으로 자리 잡지 않도록 해야 한다.

그 다음 가중치를 부여하고, 상한을 두고, 합산하라:

from dataclasses import dataclass


@dataclass
class Window:
    requests: int
    hard_blocks: int      # 403, 429, connection resets
    challenges: int       # CAPTCHA or interstitial pages
    soft_blocks: int      # 200 OK carrying a block page or an empty result
    p95_latency_ms: float


MIN_REQUESTS = 50
WEIGHTS = {"hard_block": 35, "challenge": 25, "soft_block": 25, "latency": 15}


def signals(w):
    n = max(1, w.requests)
    return {
        "hard_block": w.hard_blocks / n,
        "challenge": w.challenges / n,
        "soft_block": w.soft_blocks / n,
        "latency": w.p95_latency_ms,
    }


def badness(name, now, base):
    if name == "latency":
        # Full penalty at three times the site's own normal latency.
        return min(1.0, max(0.0, (now / max(base, 1.0) - 1) / 2))
    # Full penalty at 20 percentage points above the site's own normal rate.
    return min(1.0, max(0.0, (now - base) / 0.20))


def health_score(current, baseline):
    if current.requests < MIN_REQUESTS:
        return None, ["not enough requests to judge"]
    now, base = signals(current), signals(baseline)
    penalty = {k: w * badness(k, now[k], base[k]) for k, w in WEIGHTS.items()}
    score = round(100 - sum(penalty.values()))
    reasons = [k for k, p in sorted(penalty.items(), key=lambda kv: -kv[1]) if p >= 1]
    return score, reasons

여기에는 몇 가지 의도적인 설계 선택이 있다.

  • 베이스라인을 초과하는 부분만 계산된다. 항상 요청의 3%를 챌린지해온 사이트는 오늘도 그렇게 한다고 해서 점수를 잃지 않는다.
  • 각 신호에 상한이 있다. 하나의 폭주하는 신호가 점수를 0 이하로 끌어내리거나 다른 신호들을 압도할 수 없으며, 이유(reasons) 목록은 어느 신호가 지배적이었는지를 보여준다.
  • 작은 윈도우는 점수를 반환하지 않는다. 다섯 개의 요청 중 하나가 블록된 것은 20% 블록률이 아니라, 데이터가 부족한 것이다. 점수가 없는 것이 확신에 찬 잘못된 점수보다 더 정직하다.
  • 가중치는 의견이다. 이와 비슷한 값으로 시작한 뒤, 실제 사건 몇 건을 검토한 후 조정하라. 하드 블록과 소프트 블록은 얻지 못한 데이터를 의미하므로 가장 높은 가중치를 받아야 한다.

시간에 따라 점수도 매끄럽게 만들어라. 예를 들어 지수 가중 평균을 사용하면, 단 한 번의 나쁜 순간이 경보를 흔들지 않으면서도, 지속적인 하락은 여전히 한 시간 안에 드러난다.

캐너리: 당신이 통제하는 근거 진실

위의 모든 신호는 추론된 것이다. 캐너리는 진실에 더 가까운 것을 제공한다. 사이트별로 정답을 알고 있는 안정적인 페이지 몇 개를 골라라: 가격을 확인할 수 있는 상품, 항목 수를 알고 있는 목록, 몇 달 동안 구조가 바뀌지 않은 페이지. 이들을 프로덕션 트래픽과 동일한 경로를 통해 정기적으로 가져오라.

캐너리가 정상처럼 보이는 페이지를 반환하지만 잘못된 값을 담고 있을 때, 당신은 탐지하기 가장 어려운 실패를 발견한 것이다: 완벽하게 파싱되지만 실제 방문자가 보는 것과는 전혀 다른 콘텐츠. 어떤 상태 코드나 지연 시간 그래프도 이를 보여주지 않는다. 캐너리는 또한 베이스라인을 신뢰할 수 있게 만들어준다. 크롤러와 독립적으로 그들의 올바른 동작을 알고 있기 때문이다.

행동을 구간에 연결하고, 사람을 루프 안에 유지하라

아무도 행동하지 않는 점수는 그저 대시보드일 뿐이다. 구간을 부여하고, 각 구간에 시스템이 스스로 취할 행동을 부여하라:

구간의미자동 행동
80에서 100자기 자신의 정상처럼 동작 중없음
50에서 79저하 중해당 사이트의 동시성 감소, 재방문 간격 연장, 실패가 사이트 전체적인지 경로별인지 확인
50 미만사이트가 명확히 저항 중사이트 일시 중지, 캐너리만 계속 실행, 사람에게 경보

저하 구간은 크롤러의 나머지 부분으로 바로 이어진다. 동시성을 낮추는 것은 백프레셔와 흐름 제어에서 다룬 호스트별 제한기가 하는 일이며, 점수 하락은 비용 인식 스케줄러에서 해당 사이트의 실효 비용을 높여야 하며, 그렇게 함으로써 예산이 여전히 데이터를 얻을 수 있는 사이트로 이동한다.

가장 낮은 구간은 일시 중지 이상으로 의도적으로 자동화되어 있지 않다. 강하게 저항하는 사이트는 무언가를 말해주고 있으며, 올바른 대응은 사람의 결정이다: 더 느리게 하거나, 당신의 수집이 여전히 사이트의 이용약관에 맞는지 확인하거나, 공식 API나 데이터 피드를 찾거나, 멈추는 것이다. 점수가 떨어질 때마다 더 강력한 수집 방법으로 자동으로 확대하는 것은 신호를 군비 경쟁으로 바꿀 뿐이며, 이는 정확히 헬스 점수가 막아주어야 하는 것이다. 속도 제한과 요청 스로틀링에서는 사이트가 당신에게 요구하는 것을 읽는 방법을 다룬다.

다른 경보처럼 검토하라

이 점수를 오탐률을 가진 경보처럼 취급하고, 검토하라. 각 사건 이후, 점수가 충분히 일찍 움직였는지, 이유가 실제 원인을 가리켰는지, 자동 행동이 도움이 되었는지 물어보라. 대부분의 튜닝은 사전에 가중치를 설계하는 것이 아니라, 실제 사건 두세 건에서 나온다. 이력을 보관하라: 몇 달에 걸친 점수는 각 사이트의 자동화 트래픽에 대한 태도가 어떻게 변하는지에 대한 최선의 기록이다.

결론

사이트는 완전한 차단이 이루어지기 훨씬 전에, 챌린지, 위장된 거부, 느려진 응답을 통해 점진적이고 조용하게 크롤러에게 등을 돌린다. 각 신호는 그 자체로는 모호하다. 사이트 자체의 정상 상태와 비교하고, 가중치를 부여하고, 상한을 두고, 결합하면, 일찍 움직이면서 이유가 함께 붙은 하나의 숫자를 얻게 된다.

이 점수의 가장 큰 가치는 크롤러가 침착하게 할 수 있는 일에 있다: 차단당하기 전에 속도를 줄이고, 예산을 다른 곳으로 옮기고, 어려운 결정을 증거와 함께 사람에게 넘기는 것. 이를 둘러싼 더 넓은 지표들은 웹 스크래핑 파이프라인 모니터링하기에 있다.

시작할 준비가 되셨나요?

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

시작하기