대학 축제 정보(축제 상세 + 라인업)를 웹에서 수집·추출해 사람이 검토할 CSV로 만드는 로컬 배치
크롤러다. 출처는 손으로 넣은 URL을 우선하고, 없거나 실패한 대학은 검색으로 후보를 찾는다.
설계 근거는 2026-08-04-crawler-pipeline-design.md 참고.
크롤러는 초안 생성기다. 최종 신뢰는 사람이 담보한다 — CSV를 검토하는 단계가 곧 검수
큐다. 크롤러를 돌리고 그 결과를 검수하는 조작판이 관리자 페이지(./admin.sh)이고, 검토를 마친
CSV는 백엔드 어드민에 첨부해 가공·발행한다.
색이 곧 담당이다. 파란 상자는 파이썬이 하는 일, 보라 상자는 구독 claude CLI를 부르는
지점이다. LLM은 세 곳에만 들어간다 — 탐색·추출·정규화.
flowchart TD
seed["universities-2026.csv<br/>대학 29곳 시드"] --> has{"시드에 URL이<br/>적혀 있나?"}
has -->|"있음"| manual["수동 URL 1건 시도"]
has -->|"없음"| sm
manual --> mok{"성공?"}
mok -->|"ok"| adopted["출처 확정<br/>discovery = manual"]
mok -->|"실패"| sm["사이트맵 후보<br/>정식 표기 + 연도 매칭<br/>(문자열 매칭, LLM 없음)"]
sm --> smok{"verify 통과한<br/>후보가 있나?"}
smok -->|"있음"| adoptedM["출처 확정<br/>discovery = sitemap"]
smok -->|"없음"| search["discover.py<br/>웹 검색으로 후보 수집<br/>🟣 LLM 1콜 (도구 ON)"]
search --> cand["후보를 순서대로<br/>최대 3개 시도"]
cand --> cok{"verify 통과한<br/>후보가 있나?"}
cok -->|"있음"| adoptedS["출처 확정<br/>discovery = search"]
cok -->|"없음"| failed["no_candidate<br/>(수동 URL이 있었다면<br/>그 실패 사유 유지)"]
adopted --> csv["festivals.csv<br/>lineup.csv"]
adoptedM --> csv
adoptedS --> csv
failed --> csv
csv --> enrich["enrich.py<br/>아티스트 표기 정규화 + 장르<br/>🟣 LLM 1콜 (전체 배치)"]
enrich --> artists["artists.csv<br/>artist_mapping.json"]
csv --> review["admin.sh<br/>관리자 페이지 (로컬)"]
artists --> review
review --> admin["백엔드 번들 업로드 API에<br/>CSV 3종 첨부 → 가공·발행"]
manual -.-> u
cand -.-> u
u["URL 1건 처리<br/>🟣 추출 LLM 1콜 포함<br/>(아래 그림)"]
classDef llm fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef py fill:#eff6ff,stroke:#2563eb,color:#1e3a8a
class search,enrich,u llm
class seed,manual,sm,cand,adopted,adoptedM,adoptedS,failed,csv,artists,review py
크롤러의 책임은 검토를 마친 CSV까지다. 그 뒤 적재·가공·발행은 백엔드 어드민 소관이다.
산출물 3종의 컬럼·값 형식은 백엔드 크롤링 번들 업로드 API 명세의 CSV 스펙에 맞춰져
있어, 검토를 마친 파일을 그대로 업로드하면 된다. 전환 설계는
2026-08-09-bundle-spec-migration-design.md 참고.
URL 1건을 처리하는 과정은 출처가 수동이든 사이트맵이든 검색이든 동일하다. 여기서도 LLM은 가운데 한 칸뿐이고, 그 앞뒤는 전부 파이썬이다.
flowchart LR
url["URL"] --> robots["robots.txt 확인<br/>동일 호스트 3초 간격"]
robots --> body["본문 추출 + 이미지 수집<br/>셀렉터 → trafilatura 폴백"]
body --> llm["claude -p<br/>구조화 추출 (JSON)<br/>🟣 LLM 1콜 (도구 OFF)"]
llm --> valid{"스키마 검증<br/>pydantic"}
valid -->|"실패"| retry["1회 재시도<br/>→ extract_failed"]
valid -->|"통과"| ver{"verify<br/>이 글이 정말<br/>그 대학·그 연도인가?"}
ver -->|"통과"| ok["ok"]
ver -->|"불일치"| mis["mismatch"]
classDef llm fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef py fill:#eff6ff,stroke:#2563eb,color:#1e3a8a
class llm llm
class url,robots,body,retry,ok,mis py
LLM은 판정하지 않는다. 스키마 검증(pydantic)도 verify도 파이썬이 한다 — LLM은 본문을
JSON으로 바꾸는 일만 하고, 그 결과가 쓸 만한지는 코드가 정한다. 이 마지막 verify가 검색
탐색의 안전장치다. LLM이 URL을 지어내도 열리지 않으면 탈락하고, 엉뚱한 대학 글이면
mismatch로 걸러진다.
| 파이썬 | 구독 claude CLI |
|
|---|---|---|
| 출처 찾기 | 시드 읽기, 사이트맵 문자열 매칭, 차단 도메인 제외, 후보 순서 결정 | 웹 검색으로 후보 URL 수집 (discover) |
| 수집 | robots 확인, 요청 간격, 재시도, 본문·이미지 추출 | — |
| 구조화 | 스키마 검증, 재시도 판정, verify, flag 부여 |
본문 → JSON (extract) |
| 정규화 | 매핑 누적, 이미 매핑된 이름 제외, CSV 반영 | 표기 통합 + 장르 분류 (enrich) |
| 산출 | 컬럼 변환, 열거값 대문자화, 원자적 쓰기, 캐시 판정 | — |
LLM에게 맡기는 것은 "글을 읽는 일"뿐이다. 무엇을 채택할지, 무엇을 실패로 볼지, 무엇을 저장할지는 전부 파이썬이 정한다. 그래서 LLM이 이상한 답을 줘도 파이프라인이 그것을 그대로 산출물에 싣지 않는다.
호출은 extract.call_claude 하나를 지난다. 도구는 탐색에서만 켜고(WebSearch), 신뢰할 수
없는 블로그 본문을 읽는 추출·정규화에서는 전부 차단한다. 각 호출의 비용은
시간 비용 참고.
산출물은 CSV 3종이다. output/에 생성되며 git에 커밋하지 않는다.
erDiagram
festivals ||--o{ lineup : "import_key"
lineup }o--|| artists : "artist_canonical"
festivals {
string import_key "축제 식별자 (주최명-연도)"
string host_name "주최명 (대학명)"
string name "축제명"
date start_date "시작일"
date end_date "종료일"
string venue_name "장소"
string poster_url "포스터 이미지 URL"
string image_urls "이미지 URL (|로 연결)"
string description "축제 설명"
string hashtags "해시태그 (|로 연결)"
string external_visitor_policy "외부인 입장 (ALLOWED/CONDITIONAL/DENIED)"
string verification_method "본인 확인 방법"
string ticket_type "FREE / PAID"
string ticket_open_at "예매 오픈 시각"
string admission_raw "입장 조건 원문"
string source_url "출처 URL"
string discovery "MANUAL | SITEMAP | SEARCH"
string flag "처리 결과"
string instagram_url "인스타그램 URL"
}
lineup {
string import_key "축제 식별자"
int day "n일차 (원문 없으면 빈 값)"
int order "그 날 안에서의 공연 순서"
string artist_raw "원문 표기 (시크릿 게스트는 빈 값)"
string artist_canonical "정식 표기 (시크릿 게스트는 빈 값)"
bool revealed "공개 여부 (시크릿 게스트는 false)"
}
artists {
string name "정식 활동명"
string other_names "다른 표기 (|로 연결)"
string genre "HIPHOP / BALLAD_RNB / DANCE / BAND"
string image_url "이미지 URL"
bool needs_review "확신 낮음"
}
artist_raw는 원문 표기를 그대로 보존하고 artist_canonical이 정규화된 이름을 담는다
(십센치 → 10CM). 원문을 남겨두는 이유는 검수할 때 출처와 대조할 수 있어야 하기 때문이다.
flowchart TD
crawl["crawl.py<br/>오케스트레이션 · 캐시 · flag · CSV"]
crawl --> discover["discover.py<br/>후보 URL 수집"]
crawl --> fetch["fetch.py<br/>본문 · og:image · 인스타 후보"]
crawl --> extract["extract.py<br/>추출 · 검증 · verify"]
enrich["enrich.py<br/>아티스트 정규화"] --> extract
discover --> extract
extract --> claude(["claude CLI<br/>유일한 LLM 접점"])
schema["schema.py<br/>pydantic 스키마"]
시드(대학 29곳)를 순회하며 출처를 수동 URL → 사이트맵 → 검색 순으로 시도하고, verify를
통과한 첫 후보를 채택한다.
캐시를 schema_version으로 판정해 스키마가 바뀐 대학만 재수집하고, 재수집이 실패하면
이전 성공 결과를 지킨다.
결과에 flag 7종을 붙여 festivals.csv·lineup.csv로 쓴다.
블로그 사이트맵 5곳을 받아 대학 정식 표기 + 연도로 문자열 매칭한다 — LLM을 안 쓰므로 싸다. 사이트맵이 실패할 때만 웹 검색 1콜(실측 63초)로 후보를 모으고, 결과는 디스크에 캐시한다. 읽을 수 없는 도메인(나무위키·인스타그램 등)은 제외하되, 어느 후보가 맞는지는 판단하지 않는다.
robots.txt를 확인하고 동일 호스트에 3초 간격을 지켜 요청한다 (실패 시 1회 재시도).
스킨별 셀렉터로 본문을 뽑고, 못 잡으면 trafilatura로 폴백한다 (100자 미만이면 empty_body).
함께 채집하는 것: og:image 1장, 본문 이미지 최대 5장, 인스타그램 핸들 후보.
call_claude가 이 프로젝트 유일의 LLM 접점이다 — API로 옮겨도 이 함수 안만 바꾸면 된다.
본문을 넣어 JSON을 받고, pydantic 검증에 실패하면 오류 내용을 붙여 1회 재시도한다.
verify가 역방향으로 "이 글이 정말 그 대학·그 연도인가"를 판정한다.
아티스트 표기를 배치 1콜로 정규화한다 (십센치 → 10CM). 대학 수와 무관하게 한 번이다.
결과를 artist_mapping.json에 누적해, crawl을 다시 돌려도 정규화가 원문으로 되돌아가지 않는다.
장르를 4종 열거값으로 분류하고, 매핑에 이미 있는 이름은 다시 묻지 않는다.
ExtractionResult·LineupItem·ArtistMaster를 pydantic 모델로 정의한다.
열거값은 Literal이라, LLM이 규격 밖 값을 주면 검증에서 걸려 재시도 경로를 탄다.
검증 코드를 따로 쓰지 않아도 되는 게 요점 — 스키마 자체가 LLM 응답의 가드다.
CSV 3종을 읽어 보여주고 재실행 버튼을 제공하는 로컬 화면이다. 자세한 것은 관리자 페이지 참고.
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt수집·추출·탐색 모두 로컬 Claude Code CLI(claude)를 헤드리스로 호출한다 — 실행 전 claude
로그인이 되어 있어야 한다. 별도의 API 키는 필요 없다.
./admin.sh --year 2026 # 관리자 페이지 — venv가 없으면 만들고 띄운다이거 하나면 된다. 수집·정규화·검수가 전부 그 화면 안에서 끝난다 — crawl·enrich 전체
실행도, 특정 대학만 다시 돌리는 것도 버튼이다 (관리자 페이지 참고).
어느 디렉터리에서 실행해도 되고, 인자는 그대로 넘어간다 (./admin.sh --year 2026 --port 8790).
터미널에서 직접 부를 수도 있다. 화면 없이 배치로 돌리거나 --limit으로 스모크할 때 쓴다.
.venv/bin/python crawl.py --year 2026 [--limit N] # --limit: 앞에서 N행만 처리 (스모크용)
.venv/bin/python enrich.py # crawl 완료 후 실행
.venv/bin/python serve.py --year 2026 # admin.sh 없이 서버만 직접 띄울 때crawl.py가 output/<연도>/festivals.csv·output/<연도>/lineup.csv를, enrich.py가
output/artists.csv를 만든다. artists.csv는 연도 공통이다 — 시즌이 바뀌어도 새로 만들지
않고 계속 누적된다. 화면의 버튼이 실행하는 것도 정확히 이 두 명령이다.
아티스트 정규화 매핑. enrich.py가 만드는 output/artist_mapping.json은 artist_raw →
artist_canonical 대응을 연도 공통으로 누적한다. crawl.py는 실행할 때마다 이 매핑을 읽어
lineup.csv의 artist_canonical을 채우므로, crawl.py를 다시 돌려도 이미 정규화된 표기가
원문으로 되돌아가지 않는다.
한 번 매핑된 이름은 다시 LLM에 가지 않는다. enrich.py는 매핑에 키로 없는 이름만
정규화 대상으로 보낸다. needs_review=true로 확정된 아티스트(LLM이 확신하지 못해 원문
표기를 그대로 쓴 경우)도 매핑에는 이미 들어가 있으므로, enrich.py를 몇 번 더 돌려도
그 이름들은 재시도되지 않는다 — 같은 답을 578초 주고 다시 받지 않기 위해서다.
고치는 방법은 손으로 고치는 것이다. output/artist_mapping.json에서 해당 값을
정식 표기로 바꾸고 enrich.py를 다시 실행하면, 새 이름이 없어도 모든 연도의
lineup.csv가 그 값으로 갱신된다 (LLM 호출 없음). output/artists.csv의
needs_review 컬럼이 손볼 대상 목록이다.
스키마 변경 시 재생성. 캐시 레코드는 schema_version을 담고 있다. 버전이 다른
output/<연도>/raw/*.json 캐시는 crawl.py 실행 시 자동으로 재수집된다. 재수집이
실패하면 이전 성공 결과가 그대로 유지된다 — 캐시 파일은 지우지 않아도 되고, 다음 실행에서
다시 재시도한다.
소요 시간. 캐시가 빈 상태에서 29곳 전체를 돌면 30~50분 걸린다. 중간에 끊겨도 캐시가 남으니 같은 명령으로 다시 실행하면 이어서 처리한다. 무엇이 그 시간을 쓰는지는 시간 비용 참고.
./admin.sh로 띄우는 로컬 화면(serve.py + review.html)이다. 크롤러를 돌리고 그 결과를
검수하는 조작판이며, 크롤러를 쓰는 정상 경로다.
값을 고치는 기능은 없다. 화면이 하는 일은 "무엇이 잘못됐는지 찾아내고 다시 돌리는 것" 까지고, 값 교정은 백엔드 어드민에 CSV를 첨부하는 단계에서 처리한다.
┌────────────────────────────────────────────────────────┐
│ [전체 크롤] [enrich] ● 대기 중 로그 ▾ │
├──────────────────────┬─────────────────────────────────┤
│ [문제만] [검색출처] │ 건국대학교-2026 │
│ [전체] [아티스트] │ [다시 돌리기] [탐색부터] │
│ │ 축제명 / 기간 / 장소 / 외부인 │
│ ● NO_CANDIDATE 숙명 │ 티켓 / 인스타 / 포스터 / 출처 │
│ ● NO_CANDIDATE 동덕 ├─────────────────────────────────┤
│ ○ OK 건국 │ 라인업 8건 │
│ ○ OK 고려 │ 1일차 · 1번 10CM ← 십센치 │
│ ... ├─────────────────────────────────┤
│ │ 원본 (iframe) [새 탭 ↗] │
└──────────────────────┴─────────────────────────────────┘
할 수 있는 것
필터 문제만 |
flag != OK 행만. 진입 시 기본값 — 사람이 볼 것부터 보여준다 |
필터 검색출처 |
discovery가 SITEMAP 또는 SEARCH인 행. 손으로 고른 출처가 아니라 우선 확인 대상이다 |
필터 전체 |
축제 29곳 전부 |
탭 아티스트 |
artists.csv를 needs_review 우선으로 정렬해 표시 (정렬만, 필터 아님) |
| 상세 보기 | 축제 필드 전체 + 그 축제의 라인업. artist_canonical ← artist_raw로 정규화 전후를 나란히 |
| 원본 대조 | source_url을 iframe으로 임베드. 막는 사이트가 많아 새 탭 ↗이 실질적 주경로다 |
버튼 전체 크롤 |
crawl.py 전체 실행 (캐시 비면 30~50분) |
버튼 enrich |
enrich.py 실행 |
버튼 다시 돌리기 |
그 대학의 raw/ 캐시만 지우고 재실행. MISMATCH·EMPTY_BODY처럼 출처는 맞고 추출이 틀린 행에 |
버튼 탐색부터 |
raw/+discovered/를 지우고 재실행. NO_CANDIDATE처럼 후보 목록 자체가 쓸모없던 행에 |
| 로그 | 실행 중인 잡의 stdout을 1초 간격으로 흘려보낸다. 끝나면 상태가 바뀌고 목록이 자동 갱신된다 |
| 키보드 | j/k로 목록 이동 |
행 단위 재실행이 전체 실행인 이유. crawl.py에 "한 곳만" 옵션이 없다. 대신 그 대학의
캐시 파일을 지우고 crawl.py를 통째로 돌린다 — 나머지 28곳은 캐시 히트로 즉시 지나가고,
CSV 재생성까지 따라온다.
제약
127.0.0.1에만 바인드한다. 외부에 열지 않는다- 잡은 한 번에 하나만 돈다. 실행 중에 또 누르면 "이미 실행 중입니다" —
crawl과enrich가 같은 Claude 구독 세션 한도를 쓰기 때문에 둘을 동시에 띄우면 양쪽이 깨진다 - 재실행 대상 대학명은 그 해의 시드(
universities-<연도>.csv)에 실재하는 값만 받는다 - 원본 임베드·링크는
http(s)URL만 연다
| 컬럼 | 내용 |
|---|---|
university campus region |
대학 정보. CSV에만 있고 크롤링하지 않는다 |
year |
수집 대상 연도 |
url |
알고 있는 출처 URL. 비워도 된다 — 비면 검색으로 찾는다 |
나무위키는 라이선스(비영리) 문제로, 검색 엔진 결과 페이지는 robots.txt 차단 때문에 검색
후보에서 제외한다.
festivals.csv의 flag 컬럼:
| flag | 의미 |
|---|---|
OK |
수집·추출·검증 모두 성공 |
FETCH_FAILED |
본문 수집 실패 (네트워크 오류, robots.txt 차단 등) |
EMPTY_BODY |
본문이 100자 미만 (포스터 이미지만 있는 글 등) |
EXTRACT_FAILED |
LLM 추출이 2회 시도 후에도 유효한 JSON을 내지 못함 |
MISMATCH |
추출은 됐지만 결과가 요청한 대학·연도 글이 아닌 것으로 판정 |
NO_CANDIDATE |
검색까지 했으나 쓸 만한 후보를 찾지 못함 |
NO_SOURCE |
출처를 확보하지 못한 그 밖의 경우 |
손으로 넣은 URL이 실패하고 검색도 실패하면 NO_CANDIDATE가 아니라 그 URL의 실패 사유가
남는다 (FETCH_FAILED 등). URL이 깨졌다는 정보가 더 쓸모 있기 때문이다.
flag != OK 행은 운영자가 source_url을 직접 열어 확인한다.
캐시는 두 종류다. 이 절의 fetch_failed 같은 소문자 값은 캐시·내부 상태 표기다 —
CSV로 쓸 때만 대문자(FETCH_FAILED)로 변환된다. 오타가 아니다.
수집·추출 결과 — output/<연도>/raw/<대학명>.json. 시드의 url과 year가
그대로면 캐시를 반환하고 재수집하지 않는다. 둘 중 하나라도 바뀌면 캐시를 무시하고 다시
처리한다. 검색으로 찾은 URL이 아니라 시드에 적힌 URL이 판정 기준이라, 검색으로 채운
대학도 시드가 그대로면 재실행 때 검색을 반복하지 않는다.
검색 후보 목록 — output/<연도>/discovered/<대학명>.json. 연도가 같으면 재사용한다. 검색
1건이 60초 이상 걸리기 때문에 이 캐시가 없으면 재실행 비용이 크다.
- 강제 재수집하려면 해당 대학의
output/<연도>/raw/<대학명>.json을 지우고 다시 실행한다. 검색까지 다시 하려면output/<연도>/discovered/<대학명>.json도 함께 지운다. fetch_failed와extract_failed는 캐시에 저장되지 않는다 — 네트워크 오류나 LLM 호출 실패 같은 일시적 문제를 영구히 굳히지 않기 위함이다.empty_body·mismatch·성공 결과는 캐시된다.- 새 시즌을 시작할 때 — 지난 시즌 시드는 그대로 두고
universities-<연도>.csv를 새로 만든다. 이때url컬럼은 비워 둔다. 지난 시즌 URL을 그대로 채워 넣으면 죽은 링크라도 그 URL은 verify에서 걸러지기 전에 추출 LLM 호출을 1건 쓴다 — 시드 URL 21건이 전부 이전 연도 글이라면 21건의 회피 가능한 LLM 호출이다.url컬럼을 비워 두면 그 호출 없이 곧장 사이트맵 단계로 넘어간다.
전체 시간은 LLM 호출 횟수가 결정한다. 수집·파싱·검증은 다 합쳐도 몇 초 수준이고, 나머지는
전부 claude 응답을 기다리는 시간이다. 게다가 대학은 순차로 처리한다 — 29곳을 동시에 돌리지
않는다. 그래서 비용 모델은 단순하다: 총 시간 ≈ (LLM 호출 수) × (호출당 대기).
| 단계 | 1회 비용 | 대학 1곳당 횟수 |
|---|---|---|
본문 수집 fetch |
동일 호스트 3초 간격 + 응답 대기(타임아웃 10초) | 시도한 URL 수만큼 |
| 사이트맵 적재 | 사이트맵 5곳 수집 | 처음 필요할 때 실행당 1회 (전 대학 공유) |
검색 탐색 discover |
LLM 1콜, 실측 63초 (타임아웃 300초) | 0 또는 1 |
구조화 추출 extract |
LLM 1콜, 타임아웃 120초 · 검증 실패 시 재시도 1회 | 1 ~ 최대 7 |
역방향 검증 verify |
0 — 순수 함수다 | 시도마다 |
정규화 enrich |
LLM 1콜, 실측 578초 (타임아웃 900초) | 전체 실행당 1회 |
대학 1곳의 비용은 출처를 몇 번 만에 찾느냐로 갈린다.
- 시드 URL이 맞으면 추출 1콜로 끝난다 — 가장 싼 경로다
- 시드가 실패하면 사이트맵 후보를 최대 3개 시도(추출 3콜), 그래도 안 되면 검색 1콜을 더 쓰고 검색 후보를 최대 3개 시도(추출 3콜)
- 그래서 가장 비싼 행이
NO_CANDIDATE다. 다 시도하고 실패한 행이므로, 성과 없이 시간을 제일 많이 쓴다. 한 곳에서 탐색 1콜 + 추출 7콜까지 갈 수 있다
시드 URL을 채워 두는 것이 가장 큰 절약이다. 29곳 전체 시간은 시드가 얼마나 맞느냐에 비례해 줄어든다.
정규화(enrich)는 대학 수에 비례하지 않는다. 아티스트 이름을 전부 모아 한 번에 보내는
배치 호출 1건이라, 이름이 100개든 300개든 호출은 한 번이다. 첫 실행이 578초로 가장 길고,
그 뒤로는 매핑에 없는 새 이름만 보내므로 짧아진다.
재실행은 거의 공짜다. 캐시가 유효한 대학은 LLM을 한 번도 부르지 않고 지나가므로 29곳
전체 재실행이 수 초에 끝난다. 다시 비용을 내는 것은 schema_version이 바뀌었거나 시드
url·year가 바뀐 대학뿐이다 (→ 캐시 동작).
왜 병렬로 돌리지 않나. 호출을 여러 개 동시에 띄우면 Claude 구독 세션 한도에 더 빨리 걸리고, 중단됐을 때 어디까지 처리됐는지가 흐려진다. 지금은 느리더라도 한도에 걸린 지점에서 캐시로 이어 붙이는 쪽을 택했다.
- Claude 구독 세션 한도에 걸리면 크롤러가 중단된다. 한도가 리셋된 뒤 같은 명령으로 재실행하면 이미 처리된 대학은 캐시로 건너뛰고 나머지만 이어서 처리한다.
- 자동으로 채워진 행(
discovery=SITEMAP또는SEARCH)은 손으로 고른 출처가 아니므로 검수 때 우선 확인한다. 대학 공식 홈페이지나 학보사가 잡히면 신뢰도가 높고, 커뮤니티·티켓 플랫폼이면 한 번 더 본다. discovery=SITEMAP은 특정 블로그 몇 곳에서만 나온다. 시즌 종료 후discovery분포를 세어 단일 출처 편중이 심해지지 않았는지 확인하고, 심해졌으면 탐색 순서를 재검토한다.- 크롤러 산출물은 초안이다.
./admin.sh로 검토한 뒤 백엔드 어드민에 CSV를 첨부한다 — 최종 신뢰는 검수 단계가 담보한다.