web scraping를 통해 데이터셋을 구축하는 방법을 다루는 대부분의 가이드는 “스크레이퍼를 작성하고 결과를 저장한다”에서 멈춘다. 그것은 쉬운 20%에 불과하다. 어려운 80%, 즉 데이터셋이 실제로 사용 가능한지를 결정하는 부분은 fetch 주변의 모든 것이다. 올바른 행을 수집했는지, 그것들이 완전한지, 최신인지, 그리고 데이터의 공백이 체계적이지 않고 무작위인지를 확인하는 작업이다.
마지막 항목은 조용히 데이터셋을 망치는 요소이며, 거의 아무도 이에 대해 글을 쓰지 않는다. 스크레이프가 페이지의 30%에서 실패하면, 데이터의 무작위 30%를 잃는 것이 아니다. 특정한 30%, 즉 더 접근하기 어렵고, 더 방어가 강하며, 더 지역 제한이 걸린 부분을 잃게 되고, 남는 것은 완전해 보이지만 편향된 표본이다. 이를 기반으로 학습된 모델이나 이로부터 내려진 결정은 아무도 눈치채지 못한 채 그 편향을 그대로 물려받는다.
이 글은 유지될 수 있는 웹 스크레이핑 데이터셋을 구축하는 실용적인 가이드다: 파이프라인 단계, 중요한 품질 차원, 그리고 프록시 계층이 대표성 있는 데이터셋과 왜곡된 데이터셋의 차이를 만드는 지점을 다룬다.
”좋은 데이터셋”이 실제로 의미하는 것
코드를 작성하기 전에, 무엇을 최적화하고 있는지 명확히 하라. 스크레이핑으로 구축한 데이터셋은 다섯 가지 기준으로 평가된다:
완전성(Completeness). 범위 내의 모든 것을 수집했는가, 아니면 저항하지 않은 부분만 수집했는가? 누락된 행은 나쁘다; 체계적으로 누락된 행은 더 나쁘다.
대표성(Representativeness). 표본이 실제 모집단과 일치하는가? 제품 가격을 스크레이핑하는데 스크레이퍼가 트래픽이 많은 소매업체에서는 차단되고 소규모 업체에서는 순조롭게 통과한다면, “평균 가격”은 보이지 않는 방향으로 잘못되어 있는 것이다.
최신성(Freshness). 웹 데이터는 부패한다. 지난달의 가격 데이터셋은 오늘의 데이터셋과 다르다. 각 행이 얼마나 오래되었는지 알아야 하고, 이를 갱신할 계획이 있어야 한다.
일관성(Consistency). 모든 행은 동일한 스키마, 동일한 단위, 형식, 인코딩을 따라야 한다. 스크레이핑은 지저분한 HTML에서 가져오므로, 정규화가 작업의 절반이다.
출처(Provenance). 모든 행에 대해: 어디서 왔는지(소스 URL), 언제 가져왔는지, 어디서 가져왔는지(지역). 출처 정보 없이는 나중에 데이터셋을 디버그하거나, 중복 제거하거나, 갱신하거나, 방어할 수 없다.
이 다섯 가지를 염두에 두어라. 아래의 모든 파이프라인 결정은 이 중 하나를 위한 것이다.
파이프라인, 단계별로
스크레이핑에서 데이터셋으로 이어지는 파이프라인은 여섯 단계다. 하나의 큰 스크립트가 아니라, 각각 고유한 검증을 가진 별개의 단계로 취급하라.
1. 발견(Discover). 사이트맵, 검색/목록 크롤링, API 인덱스, 또는 알려진 ID 범위에서 범위 내의 URL을 열거한다. 이 단계는 의도한 모집단을 정의한다. 이를 명시적으로 기록해두라. 나중에 완전성을 측정하는 기준이 된다.
2. 가져오기(Fetch). 각 URL을 검색한다. 이 단계에서 차단, 지역 리다이렉트, 속도 제한, 타임아웃이 발생하며, 데이터셋 편향이 생겨난다. 이에 대해서는 아래에서 더 다룬다. 품질에 있어 가장 중요한 단계이기 때문이다.
3. 추출(Extract). 응답을 구조화된 필드로 파싱한다. 방어적으로 접근하라: 레이아웃은 변하고, 필드는 누락되며, 취약한 셀렉터는 수천 개의 행에서 조용히 null로 바뀐다.
4. 정규화(Normalize). 추출된 원본 값을 일관된 타입과 단위로 변환한다. 통화는 단일 단위로, 날짜는 ISO 형식으로, 공백은 제거하고, 인코딩은 수정하며, 범주형 값은 통제된 어휘로 매핑한다.
5. 중복 제거(Deduplicate). 동일한 엔티티가 여러 URL에 나타나는 경우가 많다(정규 URL + 변형, 페이지네이션된 중복, 재등록된 항목). URL이 아니라 안정적인 키로 중복을 제거하라.
6. 저장 및 갱신(Store and refresh). 완전한 출처 정보와 함께 저장한 다음, 재크롤링 주기를 결정하여 데이터셋이 일회성 스냅샷으로 부패하지 않고 최신 상태를 유지하도록 하라.
품질을 결정하는 단계: fetch
이것이 이 글 전체의 핵심 주장이다. 데이터셋의 품질은 fetch 단계에서 상한선이 정해진다. 차단된 요청은 데이터를 잃는 것이 아니라, 무작위가 아닌 데이터를 잃기 때문이다.
세 가지 메커니즘이 fetch 실패를 데이터셋 편향으로 바꾼다:
차단 편향(Blocking bias). 봇 방지 시스템(Cloudflare, Akamai, DataDome)은 가치가 높고 트래픽이 많은 타겟을 가장 공격적으로 보호한다. 스크레이퍼가 데이터센터 IP에서 실행되어 그런 곳에서 차단된다면, 데이터셋은 쉬운 행은 유지한 채 가장 중요한 행을 체계적으로 놓치게 된다. 그 결과는 더 작고 방어가 약한 소스로 편향되며, 여전히 수천 개의 행을 얻었기 때문에 완전해 보인다. (메커니즘에 대해서는 스크레이퍼가 차단되는 이유를 참고하라.)
지역 편향(Geo bias). 많은 사이트는 방문자의 위치에 따라 다른 콘텐츠, 가격, 재고를 제공하며, IP를 기반으로 조용히 리다이렉트하거나 지역화한다. 모든 요청이 한 지역에서 발생한다면, 데이터셋의 모든 지역별 변동 필드는 실제로 포착했다고 생각하는 전 세계적 현실이 아니라 그 단일 관측 지점만을 반영한다. “전 세계 제품 재고”를 한 국가에서 스크레이핑하면, 실제로는 한 국가의 관점을 캡처한 것인데 이를 전 세계라고 잘못 표기한 셈이다.
속도 제한 편향(Rate-limit bias). 타겟이 스크레이퍼의 속도를 제한하면, 순진한 대응은 속도를 늦추거나 응답이 느린 페이지, 종종 무겁고 데이터가 풍부한 페이지를 포기하는 것이다. 결국 빠르고 가벼운 페이지를 과대 표집하게 된다.
세 가지 모두에 대한 해결책은 동일하다: 실제 사용자처럼 보이는 IP 풀을 통해, 적절한 위치에서 가져와서, 접근하기 쉬운 것으로 편향되지 않고 완전하고 고르게 커버리지를 확보하는 것이다.
프록시 계층이 단순한 배관이 아니라 데이터 품질 결정인 이유
이것이 레지덴셜 프록시 네트워크가 단순히 “차단되지 않는다”를 넘어 데이터셋 구축에 특히 중요한 이유다:
완전한 커버리지. 레지덴셜 IP는 실제 소비자 연결의 신뢰 프로필을 갖고 있어서, 데이터센터 IP가 도달할 수 없는 방어된 타겟을 통과할 수 있다. 이는 차단 편향의 격차를 메워, 쉬운 행뿐만 아니라 어려운 행도 수집하게 한다.
의도적인 지역 커버리지. 국가, 주, 도시 타겟팅을 통해 각 시장을 의도적으로 표본화하고 각 행에 그것이 나온 관측 지점을 라벨링할 수 있다. 우연한 하나의 관점 대신, 지역이 숨겨진 교란 변수가 아니라 하나의 컬럼이 되는 통제된 다중 지역 데이터셋을 얻는다. 이것이 “가격을 스크레이핑했다”와 “12개의 특정 시장에서 본 것처럼 가격을 스크레이핑하고 행마다 기록했다”의 차이다.
대규모의 고른 표집. 대규모 풀을 통한 로테이팅은 요청을 분산시켜 단일 IP가 속도 제한에 걸리지 않도록 하며, 이는 빠른 페이지를 과대 표집하고 느리고 데이터가 풍부한 페이지를 과소 표집하는 것을 막아준다.
간단히 말하면: 프록시 계층은 데이터셋이 대표성 있는 표본인지 편의 표본인지를 결정하는 지점이다. 데이터셋 작업에 있어 이는 배관의 세부사항이 아니라 방법론적 선택이다. (더 넓은 인프라 관점은 머신러닝을 위한 프록시 인프라를 참고하라.)
최신성: 데이터셋은 명사가 아니라 동사다
일회성 스크레이핑은 스냅샷이며, 스냅샷은 부패한다. 정적 데이터셋(특정 시점 연구에 적합)을 만들 것인지, 아니면 살아있는 데이터셋(가격, 재고, 목록 등 변하는 모든 것에 필요한)을 만들 것인지 미리 결정하라.
살아있는 데이터셋의 경우:
- 데이터 변화 속도에 맞춰 재크롤링 주기를 설정하라. 변동성 있는 가격은 매시간, 카탈로그 메타데이터는 매주, 느리게 변하는 참조 데이터는 매달.
- 전체 재스크레이핑이 아니라 증분 갱신을 하라. 무엇이 변했는지 감지하고(ETag, last-modified, 콘텐츠 해시, 목록 diff) 그것만 다시 가져와라. 더 저렴하고, 더 빠르며, 타겟에 부담이 적다.
- 모든 행에 fetch 타임스탬프를 찍어라. 그래야 다운스트림 소비자가 최신성으로 필터링할 수 있고, 오래됨을 측정할 수 있다.
최신성도 커버리지 문제다: 갱신 크롤링이 매번 같은 방어된 페이지에서 차단된다면, 그 행들은 오래되어가고 쉬운 행들만 최신 상태를 유지하며, 시간이 지남에 따라 편향이 다시 발생한다. 해결책은 동일하다.
중복 제거와 정규화, 데이터셋이 이기거나 지는 지점
원본 스크레이핑 데이터는 지저분하다. 두 단계가 이를 정리한다:
스키마로 정규화하라. 목표 스키마를 먼저 결정한 다음, 모든 소스를 여기에 매핑하라. 통화는 단일 단위로, 날짜는 ISO 8601로, “1,299 units” 같은 문자열에서 숫자를 파싱하고, 텍스트는 다듬고 유니코드로 정규화하며, 범주형 값은 통제된 어휘에 맞춘다. 일관성 없는 정규화는 스크레이핑된 데이터셋이 기술적으로는 완전하지만 분석적으로는 쓸모없게 되는 가장 흔한 이유다.
URL이 아니라 안정적인 키로 중복을 제거하라. 동일한 제품, 사람, 레코드가 여러 URL에 존재하는 경우가 흔하다. 안정적인 정체성(SKU, ISBN, 정규화된 이름 + 위치, 정규 URL)으로 중복 제거 키를 구축하고, 가장 최신이거나 가장 완전한 버전을 유지하며 중복을 병합하라. 원본 URL만으로 중복을 제거하면 부풀려진 개수와 이중 가중치가 부여된 행이 남아 조용히 모든 집계를 왜곡한다.
출처 정보와 함께 저장하기
모든 행에 대해 최소한 다음을 저장하라:
- 출처가 된 소스 URL
- fetch 타임스탬프 (UTC)
- 지역이 데이터에 중요한 경우, 요청에 사용된 지역 관측 지점(국가/도시)
- 재크롤링 시 변화를 감지할 수 있도록 하는 콘텐츠 해시 또는 버전
- 파싱된 필드와 별도로 저장된 원본 페이로드(또는 참조). 이렇게 하면 추출기가 개선될 때 재스크레이핑 없이 다시 파싱할 수 있다
출처 정보는 누군가 “이 숫자는 어디서 나왔나요”라고 처음 물어보거나, 추출기에 버그가 있어서 네트워크를 다시 히트하지 않고 50만 개의 행을 다시 파싱해야 할 때까지는 오버헤드처럼 느껴진다. 첫날부터 저장하라.
신뢰하기 전에 데이터셋 검증하기
누구든 데이터셋을 기반으로 무언가를 만들기 전에, 커버리지와 품질 검사를 실행하라. 이것이 fetch 단계에서 발생할 수 있는 편향을 잡아내는 방법이다:
- 커버리지 감사. 수집된 행을 발견 단계에서의 의도한 모집단과 비교하라. 92%의 완료율은 괜찮다; 문제는 누락된 8%가 무작위인지 여부다. 실패를 표본 조사하라. 만약 이것이 하나의 소스, 하나의 지역, 하나의 사이트 유형에 몰려있다면, 단순히 누락된 데이터가 아니라 고쳐야 할 체계적인 편향이 있는 것이다.
- 필드별 null 비율 확인. 갑자기 40%가 null인 필드는 보통 셀렉터가 깨진 것이지, 데이터가 없는 것이 아니다.
- 분포 타당성 검사. 가격 분포, 카테고리 구성, 지역 분포가 예상과 일치하는가? 편향은 종종 업스트림의 표집 문제를 드러낸다.
- 최신성 검사. 행의 연령 분포는 어떤가? 특정 부분이 항상 오래되어 있다면, 그곳에서 갱신 크롤링이 차단되고 있는 것이다.
이러한 검사는 비용이 저렴하며, 데이터셋을 제대로 만드는 것과 자신만만하게 잘못된 데이터셋을 만드는 것의 차이를 만든다.
책임감 있게 이를 수행하는 것에 대한 참고사항
웹에서 데이터셋을 구축하는 것은 실질적인 의무를 수반한다. 공개 데이터만 수집하고, robots.txt가 부하와 관련이 있는 경우 이를 준수하며, 속도 제한을 존중하고 데이터를 가져오는 사이트를 저하시키지 말며, 합법적인 근거가 없는 한 개인 데이터는 피하고, 각 타겟의 이용약관을 따르라. 프록시는 요청이 어느 IP에서 오는지를 바꾸는 것이지, 그 요청을 해야 하는지 여부를 바꾸는 것이 아니다. 우리의 수용 가능한 사용 정책은 Shifter에서 허용되는 것에 대한 근거 자료이며, 규모를 확장하기 전에 윤리적 데이터 수집을 읽어볼 가치가 있다.
FAQ
웹 스크레이핑으로 데이터셋을 구축할 때 가장 어려운 부분은 무엇인가? 스크레이핑 자체가 아니라 커버리지다. 완전하고 편향 없는 표본을 얻는 것은 페이지를 가져오는 것보다 훨씬 어렵다. 실패한 요청은 데이터의 무작위가 아닌 부분을 제거하지만, 결과 데이터셋은 여전히 완전해 보이기 때문이다. 대부분의 데이터셋 품질 문제는 fetch 단계로 거슬러 올라간다.
차단이 스크레이핑된 데이터셋을 어떻게 편향시키는가? 봇 방지 시스템은 가치가 높은 타겟을 가장 공격적으로 보호하므로, 차단된 스크레이퍼는 쉬운 행은 유지한 채 중요하고 방어가 잘된 행을 잃는다. 데이터셋은 방어가 약한 소스로 편향되며, 이는 그것을 기반으로 한 모든 집계나 모델을 오염시킨다.
데이터셋을 구축하려면 레지덴셜 프록시가 필요한가? 타겟이 데이터센터 IP를 차단하거나 지역에 따라 콘텐츠를 다르게 제공하는 경우에만 필요하며, 대부분의 가치 있는 타겟이 그렇다. 보호되지 않고 지역에 중립적인 소스의 경우, 데이터센터 IP로 충분하다. 방어되거나 지역화된 사이트의 완전하고 대표성 있는 커버리지를 위해서는, 레지덴셜 프록시가 편향의 격차를 메운다.
스크레이핑된 데이터셋을 어떻게 최신 상태로 유지하는가? 데이터 변화 속도에 맞춰 재크롤링 주기를 설정하고, 증분 갱신을 수행하며(전체 재스크레이핑이 아니라 ETag/해시/diff를 통해 변화를 감지), 모든 행에 fetch 타임스탬프를 찍어서 오래됨을 측정하고 필터링할 수 있도록 하라.
스크레이핑된 데이터를 어떻게 중복 제거해야 하는가? 안정적인 정체성 키(SKU, ISBN, 정규 URL, 정규화된 이름+위치)로 하되, 원본 URL로는 하지 마라. 동일한 엔티티가 여러 URL에 나타나기 때문이다. 중복은 가장 최신이거나 가장 완전한 버전으로 병합하라.
추출된 필드 외에 무엇을 저장해야 하는가? 출처 정보다: 소스 URL, fetch 타임스탬프, 지역 관측 지점, 변화 감지를 위한 콘텐츠 해시, 그리고 이상적으로는 추출기가 개선될 때 재스크레이핑 없이 다시 파싱할 수 있도록 원본 페이로드.
결론
웹 스크레이핑으로 데이터셋을 구축하는 것은 스크레이핑의 옷을 입은 데이터 품질 문제다. 누구나 페이지를 가져올 수 있다; 진짜 작업은 올바른 페이지를 완전하게, 최신 상태로, 그리고 어려운 타겟이 있어야 할 자리에 체계적인 구멍이 없도록 가져왔는지 확인하는 것이다. 발견, fetch, 추출, 정규화, 중복 제거, 저장, 갱신으로 이어지는 파이프라인은 간단하다. 조용히 품질의 상한선을 정하는 유일한 단계는 fetch다. 차단과 지역이 누락된 데이터를 편향된 데이터로 바꾸는 곳이 바로 여기이기 때문이다.
fetch 계층을 제대로 갖추면 나머지는 엔지니어링이다. 소스가 방어되어 있거나 지역에 따라 달라진다면, 레지덴셜 프록시 네트워크가 편의 표본을 대표성 있는 표본으로 바꾸는 요소다. 어려운 타겟의 완전한 커버리지, 의도적인 다중 지역 표집, 그리고 대규모의 고른 로테이션이 그것이다. 이를 실제로 구축할 준비가 되었다면, Python 가이드에서 fetch 단계 코드를 확인할 수 있고, 가격 페이지에는 GB당 요금제가 나와 있다. 커버리지를 먼저 구축하면 데이터셋은 저절로 해결된다.