크롤러가 죽었다 — 데이터 수집 잔혹사
월드컵 RAG 개발기 2편 — sofascore 403, Cloudflare IP 차단, 브라우저 저장 파이프라인, RTF 지옥.
목차
연재 · 월드컵 RAG 프로젝트 개발기 (2/5)
- 월드컵을 통째로 서비스에 담아보기로 했다
- 크롤러가 죽었다 — 데이터 수집 잔혹사 ← 지금 글
- 배포했더니 데이터가 사라졌다 — Railway 운영의 함정들
- LLM은 숫자를 지어낸다 — 환각과 싸운 기록
- 손흥민 리포트 제작기 — 이벤트 데이터로 기사급 리포트 만들기
했다: sofascore 크롤러
경기 통계의 원천은 sofascore였다. 팀 통계·슛맵(xG 포함)·선수 기록을 selenium 기반 크롤러로 받아 CSV 3종(Team/Shotmap/Player)으로 저장하는 파이프라인을 만들었다. 처음엔 잘 돌았다.
막혔다 1: 403의 시작
어느 날부터 모든 요청이 403으로 돌아왔다. 의심 순서는 이랬다:
- 셀레니움 스텔스 설정 — undetected-chromedriver(uc) 옵션을 켜니 일시적으로 뚫렸다. 헤드리스 모드, 재시도 루프도 손봤다.
- 그런데 코드를 아무리 고쳐도 다시 막혔다. 여기서 중요한 교훈: 코드를 고쳐도 403이 계속되면, 코드가 아니라 IP를 의심해야 한다.
curl로 최소 요청을 날려보니 — 크롬이든 컬이든 뭐든 403. 기관망(학교) IP 대역 자체가 Cloudflare에서 차단돼 있었다.
IP 차단은 코드로 못 푼다. 그래서 발상을 바꿨다.
풀었다: “크롤링하지 말고, 브라우저가 받은 걸 쓰자”
sofascore 웹페이지는 어차피 내 브라우저에 JSON을 내려준다. 개발자 도구 네트워크 탭에서 statistics, shotmap, lineups 세 API 응답을 그대로 저장하면, 크롤러가 하던 일과 똑같은 원본을 얻는다.
그래서 “브라우저 저장 JSON → 기존 크롤러 파서 재사용 → CSV 변환” 모듈을 만들었다. fetch 코드는 한 줄도 없고 파싱만 한다. IP 차단과 무관하고, 사이트에 부하도 안 준다. 크롤링은 이후 전부 로컬 수동 수집으로 전환했고, 배포 서버는 절대 크롤링하지 않는 원칙을 세웠다.
막혔다 2: RTF 지옥
맥에서 JSON을 텍스트 편집기로 저장하면 .rtf가 되기 일쑤다. RTF는 제어 문자를 벗겨내면 되는데, 진짜 함정은 따로 있었다.
어느 경기 파일이 char 824에서 JSON 파싱 실패를 냈다. 원인은 — macOS 텍스트 편집기가 JSON의 곧은따옴표(")를 스마트따옴표로 바꿔치기 한 것. RTF 안에서는 \'93, \'94 같은 cp1252 이스케이프로 숨어 있어서 눈으로는 안 보인다. RTF 스트리퍼에 \'XX 시퀀스를 cp1252로 디코드하고 스마트따옴표를 곧은따옴표로 되돌리는 처리를 넣어서 해결했다.
\uXXXX 유니코드 이스케이프가 RTF에서 \\uXXXX로 이중 이스케이프되는 것도 보호 처리가 필요했다.
막혔다 3: 파일명은 매일 달랐다
매일 수집을 하다 보면 파일명이 통일되지 않는다. 실제로 겪은 변형들:
statistics_team_*/Statistics_*/Team_*/ 그냥statistics_*lineup_players_*/lineup_player_*(단수!) /Player_*/line up_*(공백)shotmap_*/Shotmap_*/ 심지어shop map_*(오타)
“파일이 없다”는 에러의 절반은 파일이 있는데 패턴이 안 맞는 거였다. 다중 패턴 글롭으로 전부 흡수하게 했고, 그래도 누락이면 즉시 경고를 내게 했다. 한번은 “라인업 있는데 왜 없다고 해?”라는 사용자 제보로 lineup_player_(단수) 케이스를 찾았다 — 수집자가 사람인 이상 파일명 방어는 계속 넓혀야 한다.
막혔다 4: 팀 이름도 통일되지 않는다
USA vs United States, Czechia vs Czech Republic, Cabo Verde vs Cape Verde, DR Congo vs Congo DR, 오타 Cote d'lvoire(I가 소문자 L), Brazi… 소스마다, 수집 폴더마다 달랐다. 표준 표기를 sofascore 기준으로 정하고, 양방향 별칭 테이블로 비교하는 헬퍼를 만들어 전 시스템(일정 매칭, 스코어 조회, 리뷰 대상 탐색)이 그걸 쓰게 했다.
배운 것
- 403 디버깅 순서: 코드 → 헤더/스텔스 → 그래도 안 되면 IP. curl 한 줄이 며칠을 아껴준다.
- 크롤링이 막히면 데이터의 다른 합법적 경로(브라우저가 이미 받은 응답)를 찾아라. 파서만 있으면 된다.
- 사람 손을 타는 수집 파이프라인은 파일명·인코딩·표기 방어가 본체다. 변환 로직보다 방어 코드가 더 길어진다.