공공 정책 데이터 파이프라인을 만들면서 가장 큰 산은 서울 25개 자치구의 홈페이지 게시판이었습니다. 구마다 홈페이지 시스템이 다르고 같은 구 안에서도 공지·고시공고·복지소식처럼 게시판이 여러 개입니다. 최종적으로 수집해야 할 게시판이 65개였는데, 이걸 사이트별 크롤러 65벌로 짜면 만드는 것도 문제지만 유지보수가 재앙이 됩니다. 이 글은 그 65개를 크롤러 템플릿 11종과 설정 파일로 흡수한 설계 이야기입니다.
사이트 수만큼 크롤러를 짜면 안 되는 이유
크롤러는 만드는 순간부터 낡기 시작합니다. 대상 사이트가 개편되면 깨지고 깨진 걸 고치는 비용은 크롤러 개수에 비례합니다. 사이트 65개를 코드 65벌로 대응하면 개편 한 번마다 수리 대상을 찾아 헤매야 하고 새 사이트 추가도 매번 개발 건이 됩니다.
다행히 공공기관 홈페이지는 겉모습만 다를 뿐 구조가 수렴합니다. 상당수 자치구가 전자정부 표준프레임워크(eGovFrame) 계열 게시판을 쓰고 portal/bbs 형태나 site/ex/bbs 형태처럼 URL과 마크업 패턴이 같은 부류로 묶입니다. 목록은 표 아니면 리스트고 페이지네이션과 상세 링크 규칙도 몇 가지 패턴 안에 들어옵니다. 그렇다면 코드는 패턴 수만큼만 짜고 사이트별 차이는 데이터로 내리면 됩니다.
크롤러 템플릿 11종 + 설정 25개 구조
설계는 두 층으로 나뉩니다. 템플릿은 한 게시판 유형의 파싱 로직 전체를 담습니다. 목록에서 행을 찾는 법, 제목·날짜·링크를 뽑는 법, 다음 페이지로 넘어가는 법, 상세 페이지 본문을 읽는 법이 템플릿 안에 있습니다. 설정은 구별 차이만 담습니다. 게시판 주소, 게시판 ID, 선택자 몇 개, 인코딩 정도입니다.
// 자치구 설정 파일의 뼈대 (실제 값은 단순화)
export const exampleDistrict = defineDistrict({
name: "○○구",
template: "egov-bbs", // 11종 템플릿 중 선택
boards: [
{ boardId: "notice", label: "공지사항" },
{ boardId: "welfare", label: "복지소식" },
],
baseUrl: "https://www.example.go.kr",
});이 구조에서 새 자치구를 붙이는 일은 설정 파일 하나를 추가하는 일이 됩니다. 팩토리가 설정을 읽어 해당 템플릿의 크롤러 인스턴스를 만들기 때문에, 게시판 65개가 돌아가도 실제 파싱 코드는 11벌만 존재합니다.
템플릿이 다루는 범위는 목록 파싱만이 아닙니다. 게시판 수집은 목록에서 제목·날짜·링크를 얻은 뒤 상세 페이지에 들어가 본문을 읽는 2단 구조인데, 상세 진입과 본문 추출도 템플릿의 일부입니다. 목록만 유형화하고 상세를 사이트별 코드로 남기면 절반만 유형화한 셈이 되기 때문입니다. 페이지네이션 규칙과 문자 인코딩(EUC-KR 계열 포함) 처리도 템플릿 계층의 몫이고 설정에는 어디까지나 값만 남습니다.
| 층 | 담는 것 | 개수 | 바뀌는 빈도 |
|---|---|---|---|
| 템플릿 | 게시판 유형별 파싱 로직 | 11종 | 낮음 |
| 설정 | 구별 주소·선택자·게시판 목록 | 25개 | 개편 때만 |
| 인스턴스 | 실제 수집 단위(게시판) | 65개 | 자동 생성 |
표준 패턴에서 벗어난 몇몇 구청은 커스텀 템플릿으로 대응했습니다. 중요한 건 커스텀도 템플릿 계층 안에 들어온다는 점입니다. 예외를 임기응변 코드로 흩뿌리지 않고 유형 하나로 등록해 두면, 비슷한 사이트가 또 나타났을 때 재사용됩니다.
개편이 나도 수리 범위가 한 곳으로 좁혀진다
이 설계의 진짜 가치는 장애 대응에서 드러납니다. 어떤 구청이 홈페이지를 개편하면 그 구의 수집만 실패하고 실행 이력에 소스 단위로 기록됩니다. 수리는 대부분 해당 구 설정의 주소나 선택자를 고치는 수준이고 같은 템플릿을 쓰는 다른 24개 구는 영향을 받지 않습니다. 반대로 템플릿 계층의 버그를 고치면 그 유형을 쓰는 모든 게시판이 한 번에 좋아집니다.
수집 자체의 안정 장치(재시도, 속도 제한, 차단 대응)는 템플릿 바깥의 공통 계층에 있습니다. 이 부분은 대규모 크롤러 운영 안정성에서 따로 다뤘고 이 템플릿 구조가 들어간 전체 시스템은 공공데이터 크롤링 자동화 사례에서 볼 수 있습니다.
어디까지 유형화할 것인가
유형화에도 선이 있습니다. 우리가 지킨 기준은 두 가지였습니다. 첫째, 템플릿은 실제로 두 곳 이상에서 반복될 때만 만든다. 한 곳뿐인 구조를 미리 일반화하면 추상화 비용만 남습니다. 둘째, 설정에는 로직을 넣지 않는다. 설정 파일에 분기나 예외 처리가 스며들기 시작하면 템플릿과 설정의 경계가 무너지고 결국 사이트별 코드로 되돌아갑니다. 이 두 기준 덕에 게시판이 65개가 되어도 구조가 유지됐습니다.
자주 묻는 질문
새 자치구나 게시판을 추가하는 데 얼마나 걸리나요?
기존 11종 템플릿에 맞는 게시판이라면 설정 파일 작성과 검증 수준의 작업입니다. 게시판 주소와 선택자 몇 줄을 채우고 수집 결과를 확인하면 끝나므로, 신규 개발 건이 아니라 운영 작업에 가깝습니다.
대상 사이트가 개편되면 어떻게 되나요?
해당 소스의 수집만 실패하고 실행 이력에 소스 단위로 남습니다. 수리는 보통 그 구의 설정에서 주소나 선택자를 고치는 정도이며 같은 템플릿을 쓰는 다른 게시판에는 영향이 없습니다.
템플릿으로 안 잡히는 특이한 사이트는 어떻게 하나요?
커스텀 템플릿을 만들어 같은 계층에 등록합니다. 예외를 개별 코드로 흩뿌리지 않고 유형으로 관리하면, 비슷한 구조가 다시 나타났을 때 그대로 재사용할 수 있습니다.
크롤러 템플릿 설계를 처음부터 해야 하나요?
아니요. 사이트가 두세 개일 때는 개별 크롤러로 시작해도 됩니다. 같은 구조가 두 곳 이상에서 반복되는 순간이 템플릿으로 승격할 때입니다. 그때 기존 크롤러를 템플릿과 설정으로 분리하면 됩니다. 유형화는 결과이지 출발점이 아닙니다.
이 방식은 자치구 게시판에만 쓸 수 있나요?
아닙니다. 목록과 상세가 있는 게시판형 사이트라면 협회 공지, 학교 공지, 기관 보도자료처럼 어디든 같은 구조가 적용됩니다. 핵심은 사이트 수가 아니라 유형 수만큼만 코드를 갖는다는 원칙입니다.