스크래핑

비용 인식 크롤링 스케줄링: 변화가 있는 곳에 예산을 쓰는 법

대부분의 재크롤링 예산은 아무것도 바뀌지 않았음을 확인하는 데 쓰인다. 비용 대비 기대값으로 스케줄을 세우는 방법과 가장 빨리 변하는 페이지가 최선의 선택이 아닌 이유.

James Meadow

James Meadow

2026년 9월 22일 · 7 분 소요

거의 모든 장기 실행 크롤러의 fetch 로그를 살펴보면 동일한 패턴이 나타난다. 대다수의 요청은 마지막 사본과 동일한 페이지를 반환한다. 이러한 fetch 하나하나는 대역폭이나 크레딧을 소모하고, 동시성 슬롯을 점유하고, 상대방 서버에 부하를 주지만, 결과적으로 아무것도 얻지 못한다.

이는 fetcher의 버그가 아니다. 대개 기본값으로 내려지는 스케줄링 결정, 즉 모든 것을 같은 주기로 재방문하거나 중요한 것을 더 자주 재방문하는 방식의 문제다. 두 방식 모두 합리적으로 느껴지지만, 둘 다 예산의 대부분을 낭비한다. 이 글은 이 결정을 의도적으로 내리는 방법, 즉 각 방문이 가져다줄 가능성이 있는 가치로 방문 비용을 매기는 방법을 다룬다.

올바른 단위를 측정하라

팀들은 보통 fetch된 페이지당 비용을 추적한다. 정작 중요한 수치는 감지된 변경당 비용이다.

하루에 백만 페이지를 fetch하여 만 건의 변경을 감지하는 크롤러는 유의미한 관찰 하나당 100번의 fetch를 소모한 셈이다. 변경 감지 수를 유지하면서 fetch 수를 절반으로 줄이면 데이터셋 비용이 절반이 된다. 변경을 더 많이 찾지 못하면서 fetch 수를 두 배로 늘리면 아무 소득 없이 비용만 두 배가 된다. “변경을 찾지 못한 fetch”를 사이트별, 페이지 유형별로 대시보드에 표시하면 비용이 어디로 새는지 명확해진다.

fetch의 실제 비용을 파악하라

방문 비용은 과금 방식에 따라 달라지며, 흔한 두 가지 모델은 서로 다른 최적화를 보상한다.

과금 모델지불 대상방문 비용을 낮추는 요인
레지덴셜 프록시 대역폭게이트웨이를 통과한 바이트더 작은 응답: 렌더링 없음, 이미지 없음, 압축 전송
스크래핑 API 크레딧성공한 응답더 적은 fetch 수; 각 응답의 크기는 훨씬 덜 중요함

Shifter의 레지덴셜 게이트웨이에서는 헤더를 포함해 송수신되는 모든 바이트가 계산되며, 오류 발생 전에 바이트가 전송되었다면 실패한 요청도 계산에 포함된다. 각 fetch를 줄이면 직접적인 비용 절감으로 이어지며, 이에 대한 구체적 방법은 프록시 대역폭 비용 절감하기에서 다룬다. Web Scraping API에서는 JavaScript 렌더링 활성화 여부와 관계없이 성공한 요청 하나당 1크레딧이 소모되며, 실패한 요청과 대상 오류는 비용이 들지 않는다. 이 경우 렌더링된 페이지와 일반 페이지의 비용이 동일하며, 청구액을 움직이는 유일한 레버는 성공한 fetch를 얼마나 요청하느냐다.

어느 쪽이든 스케줄러야말로 당신이 가진 가장 큰 레버다. 가장 저렴한 fetch는 하지 않기로 결정한 fetch이기 때문이다.

각 페이지는 얼마나 자주 변하는가?

기대값에 따른 스케줄링을 하려면 각 페이지가 얼마나 자주 변하는지에 대한 추정치가 필요하다. 크롤러 연구에서 표준적으로 사용되는 작업 가정은 변경이 페이지당 어떤 평균 비율로 대략 무작위로 발생한다는 것이며, 이는 마지막 방문 이후 페이지가 변경되었을 확률을 다음과 같이 표현한다.

P(changed) = 1 - e^(-rate × time since last visit)

이 비율은 자체 이력에서 추정한다. 각 방문은 이전 방문 이후 페이지가 변경되었는지 아닌지를 알려준다. 관찰된 시간 대비 발견된 변경 수를 페이지별로 나누고, 세 번 방문한 페이지가 극단적인 추정치를 갖지 않도록 유사한 페이지들의 평균 쪽으로 값을 완화(smoothing)한다.

주의할 점 두 가지가 있다. 첫째, 방문은 페이지가 변경되었다는 사실만 알려줄 뿐 몇 번 변경되었는지는 알려주지 않으므로, 방문 빈도보다 빠르게 변하는 페이지는 실제보다 느리게 변하는 것처럼 보인다. 둘째, “변경”을 당신이 신경 쓰는 필드를 기준으로 정의해야 한다. 타임스탬프, 광고 슬롯, 세션 토큰이 로드할 때마다 다른 페이지는 끊임없이 변하는 것처럼 보이지만 아무 의미가 없다. HTML이 아니라 추출된 레코드를 해시하라.

직관에 반하는 결과: 가장 빠르게 변하는 페이지를 쫓지 마라

당연해 보이는 정책은 페이지가 변하는 빈도에 비례해 방문하는 것이다. 하지만 이는 틀렸으며, 그 증거는 20년도 더 된 것이다.

Junghoo Cho와 Hector Garcia-Molina의 “Effective Page Refresh Policies for Web Crawlers”(ACM Transactions on Database Systems, 2003)는 고정된 방문 예산 하에서 로컬 사본의 신선도를 유지하기 위한 할당 정책들을 비교했다. 그들의 발견은, 그들의 표현을 그대로 옮기면, “균등 정책(uniform policy)은 어떤 시나리오에서든 항상 비례 정책(proportional policy)보다 효과적이다”라는 것이다. 그들의 최적 정책은 한 걸음 더 나아가며, 다음과 같이 요약한다. “신선도를 개선하려면 너무 자주 변경되는 요소에 페널티를 줘야 한다.”

일단 서술되고 나면 직관은 단순하다. 15분마다 변하는 페이지는 fetch하자마자 거의 다시 낡은 상태가 된다. 이 페이지를 방문해봐야 몇 분간의 신선도만 얻을 뿐이다. 하루에 한 번 정도 변하는 페이지를 매일 방문하면, 방문 후 하루의 대부분 동안 정확한 상태를 유지한다. 예산이 제한적일 때, 후자가 훨씬 나은 구매다.

이를 수치화할 수 있다. 동일한 변경 모델 하에서, 한 번의 방문이 사들이는 신선도는 페이지가 현재 낡았을 확률에 그 이후 정확한 상태를 유지할 것으로 예상되는 기간을 곱한 값이다. 하루에 한 번 정도 재방문할 수 있는 크롤러의 경우:

페이지 변경 빈도방문당 사들이는 신선도(일)
15분마다 정도0.010
시간당 정도0.042
6시간마다 정도0.241
하루에 한 번 정도0.400
일주일에 한 번 정도0.124
한 달에 한 번 정도0.032
일 년에 한 번 정도0.003

가장 좋은 구매 대상은 당신이 감당할 수 있는 재방문 속도와 대략 비슷한 속도로 변하는 페이지다. 훨씬 빠르게 변하는 페이지는 어떤 감당 가능한 비율로도 거의 신선하게 유지할 수 없고, 훨씬 느리게 변하는 페이지는 거의 항상 이미 신선한 상태다.

한 가지 중요한 예외가 있다. 이 결과는 신선도, 즉 당신의 사본이 실시간 페이지와 얼마나 자주 일치하는지에 관한 것이다. 어떤 작업은 대신 이벤트를 포착하는 것, 즉 모든 가격 변동, 모든 재고 소진, 모든 편집을 다룬다. 각 변경이 개별적으로 중요하다면, 빠르게 변하는 페이지에는 더 적은 방문이 아니라 더 많은 방문이 필요하며, 올바른 답은 API, 피드, 또는 전체 fetch 없이 변경 사항을 보여주는 목록 페이지 같은 완전히 다른 소스일 수 있다. 조정을 시작하기 전에 어떤 문제를 풀고 있는지부터 결정하라.

모든 방문에 비용 대비 기대값으로 점수를 매겨라

이 요소들을 종합하면, 각 후보 방문은 우선순위를 갖는다. 페이지가 얼마나 중요한지에, 방문이 사들일 신선도를 곱하고, 방문 비용으로 나눈 값이다.

import math


def freshness_gain(rate, days_since_visit, interval_days):
    """Expected fresh days bought by visiting now, under a Poisson change model."""
    p_stale = 1 - math.exp(-rate * days_since_visit)
    fresh_after = (1 - math.exp(-rate * interval_days)) / rate if rate > 0 else interval_days
    return p_stale * fresh_after


def priority(page, today, interval_days=1.0):
    gain = freshness_gain(page.change_rate, today - page.last_fetched, interval_days)
    return page.value * gain / page.cost

그러면 스케줄러는 우선순위 큐를 기반으로 동작한다. 매 사이클마다 예산을 점수가 가장 높은 방문에 사용하고 예산이 소진되면 멈춘다. 세 가지 입력값은 신중을 요한다.

  • 가치는 기술적 판단이 아니라 비즈니스 판단이다. 잘 팔리는 상품, 중요한 경쟁사, 고객이 자주 실행하는 쿼리. 이 값은 대략적으로 유지하라. 보통 3~4단계면 충분하다.
  • 비용은 그 방문의 실제 비용, 즉 해당 페이지 유형의 바이트, 크레딧, 렌더링 필요 여부를 반영해야 한다. 대역폭 과금 플랜에서 헤드리스 브라우저가 필요한 페이지는 일반 fetch보다 몇 배나 비쌀 수 있다.
  • 변경 비율은 자체 이력에서 나오며, 방문할 때마다 갱신된다.

비싼 fetch 전에 저렴한 신호를 활용하라

종종 페이지를 fetch하는 비용보다 훨씬 적은 비용으로 페이지가 변경되었는지 알아낼 수 있다.

  • 조건부 요청. 사이트가 ETagLast-Modified를 준수하는 경우, 304 Not Modified 응답은 전체 전송량의 일부만 소모한다. 검증기(validator)가 신뢰할 수 있는지를 호스트별로 추적하라. 일부 사이트는 이를 보내면서도 무시한다.
  • 변경 감지기로서의 목록 페이지. 카테고리나 검색 페이지는 종종 수십 개 항목의 가격과 재고 상태를 보여준다. 목록을 fetch하여 비교한 뒤, 요약 정보가 변경된 항목만 fetch하라. 이것이 대부분의 실시간 가격 피드재고 모니터가 저렴하게 유지되는 방식이다.
  • 사이트맵과 피드. 신뢰할 수 있는 수정 날짜를 담고 있는 경우, 다른 어떤 것도 방문하지 않고도 무엇이 변경되었는지 알려준다.
  • 구조화된 엔드포인트. 페이지 뒤에 있는 JSON 응답은 보통 페이지 자체보다 더 작고 더 안정적이다.

스케줄러가 볼 수 없는 것을 위한 예산을 확보하라

알려진 페이지만 최적화하는 스케줄러는 서서히 눈이 먼다. 매 사이클마다 예산의 일부를 다음 세 가지를 위해 남겨둬라.

  1. 탐색(Discovery). 새로운 URL은 이력이 없어 기존 페이지보다 절대 높은 순위를 얻지 못한다. 이들에게 별도의 할당분을 주라.
  2. 재추정(Re-estimation). 몇 달간 변하지 않는다고 점수가 매겨진 페이지도 가끔은 방문해야 한다. 페이지는 행동 패턴을 바꾸기 때문이다. 이것이 없으면 잘못된 추정치는 결코 수정되지 않는다.
  3. 검증(Verification). 점수와 무관하게 fetch되는 작은 무작위 표본은 모델의 가정이 여전히 유효한지 알려준다.

비용과 상태가 스케줄에 피드백을 주도록 하라

스케줄은 계획일 뿐 보장이 아니다. 어떤 사이트가 스로틀링을 시작하면, 스케줄러는 이를 인지하고 재순위화해야 하며, fetch 단계가 실행할 수 없는 방문을 계속 큐에 넣어서는 안 된다. fetcher에서 프론티어로 이어지는 이러한 피드백 경로는 분산 크롤러의 백프레셔와 흐름 제어에서 다루며, 사이트의 전반적인 상태는 타겟 상태 점수 구축하기에서 다룬다. 하락하는 상태 점수는 해당 사이트를 방문하는 실효 비용도 높여야 하며, 이것이 바로 비용을 인지하는 스케줄러에 필요한 신호다.

결론

균등하게, 또는 각 페이지가 얼마나 바쁜지에 비례해 소비되는 크롤 예산은 대부분 아무 일도 일어나지 않았음을 확인하는 데 쓰인다. 자체 이력으로부터 각 페이지가 얼마나 자주 변하는지 추정하고, 각 방문이 무엇을 가져다줄지와 무엇을 비용으로 치를지에 따라 가격을 매기고, 최고의 구매 대상에 예산을 먼저 배분하라. 이 최고의 구매 대상은 가장 빠르게 변하는 페이지가 아니라 감당할 수 있는 추적 속도와 대략 비슷한 속도로 변하는 페이지일 것이다.

이로 인한 이득은 청구액 절감뿐만이 아니다. 더 적게 fetch하고, 더 신중하게 fetch하는 크롤러는 의존하는 사이트에도 부담을 덜 준다.

출처 및 참고자료

시작할 준비가 되셨나요?

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

시작하기