지운 것 — 앞으로의 개발에 쓸 데가 없다. - solution/backend/demo_site.html: 어떤 스크립트도 만들지 않는 고아 산출물이고, 손으로 쓴 HTML 이라 "백엔드는 HTML 을 만들지 않는다" 와도 어긋난다. - solution/README.md: front/ · admin/.env · 사이트당 rooms/index.html·sitemap.xml 처럼 지금은 전부 틀린 서술이었다. 살아 있는 두 가지(빌더 CSR vs 발행물 SSG 대비표, 하이드레이션 블롭에 미검증 값이 샜던 실측)는 ARCHITECTURE 로 옮겼다. - docs/API_USAGE.md 의 Claude 개발비 집계: 2026-08-27 스냅샷과 재집계 스크립트는 일회성 지출 기록이라 제품 원가와 성격이 다르다. 문서를 외부 API 원가 하나로 좁혔다. 고친 것 — 코드를 따라가지 못하던 서술. - 코드 경로가 solution/backend 로 옮겨진 뒤 `backend/...` 로 남아 있던 포인터 전부. 가리키는 자리가 없는 경로는 문서가 아니라 함정이다. - ARCHITECTURE: 트리의 front→frontend, 컨테이너 표에 api-admin(:9801)·admin(:3002) 추가. - ★ ARCHITECTURE·AGENTS 의 "admin 전용 라우터가 0개" 는 사실이 아니었다. /v1/admin/local-content 가 admin 전용인데 :9800 에도 마운트돼 있다 — 포트를 가른 논리에 아직 남은 구멍이라 그렇게 적었다. - DECISIONS: 결론난 것을 미결로 두면 함정이 된다. 작업 큐(2026-08-27 결론), 날씨 캐시 TTL 1시간, jobs 테이블, media 조회 API, 수집 체인을 결론으로 옮기고 네이버 플레이스 대 TourAPI 실측(2026-08-31)을 1-1 에 이었다. - API_USAGE: 어댑터가 다 붙고 TourAPI 키도 나왔다. "실호출 0건" 은 낡은 서술이었다. - backend/README: 16→17 테이블(jobs), 없어진 alters/, MockAdapter 만이라는 서술, cd backend 경로, media·local 라우터 누락.
33 KiB
콘텐츠 소스 리서치 — AEO/SEO/GEO 관점
조사일: 2026-08-28. 모든 HTTP 응답은 이 날짜에 이 개발 머신에서 직접 찍은 실측값이다.
DECISIONS.md 1-1(크롤링 약관·법적 검토), 1-2(이미지 재게시 권리)에 대한 답을 포함한다.
0. 한 줄 결론
네이버·카카오 생태계는 AI 검색엔진에게 통째로 닫혀 있다. 그래서 우리가 네이버 플레이스를 복제할 이유가 없어졌고, 오히려 복제하면 안 된다. AI 인용을 노린다면 소스 전략은 "OTA에서 긁어오기" → "공공데이터(인용 가능한 1차 출처) + 사장님 인증 채널(고유 콘텐츠)" 로 바꿔야 한다.
1. ★ 결정적 발견 — AI 크롤러 차단 지도
각 사이트 robots.txt 실측. 전면차단 = Disallow: /.
| 소스 | GPTBot | OAI-SearchBot | ClaudeBot | PerplexityBot | Google-Extended | Googlebot | Yeti(네이버) |
|---|---|---|---|---|---|---|---|
| m.place.naver.com (현재 어댑터) | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 |
| pcmap.place.naver.com | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 |
| map.naver.com | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 거의차단 | 거의차단 |
| blog.naver.com | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 거의차단 | 전면차단 |
| booking.naver.com | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 |
| smartstore.naver.com | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 부분제한 | 부분제한 |
| place.map.kakao.com | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 거의차단 | 거의차단 |
| app.catchtable.co.kr | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 전면차단 | 허용 | 허용 |
| www.goodchoice.kr (여기어때) | 전면차단 | 허용 | 전면차단 | 허용 | 전면차단 | 부분제한 | 부분제한 |
| www.diningcode.com | 전면차단 | 부분제한 | 허용 | 허용 | 전면차단 | 부분제한 | 부분제한 |
| www.yanolja.com | 별도규칙 없음(*만) |
" | " | " | " | " | " |
이게 왜 사업의 핵심인가
m.place.naver.com/robots.txt 원문 (2026-08-28, 3회 재시도 모두 동일):
User-agent: *
Disallow: /
즉 네이버 플레이스는 예외 없이 모든 크롤러를 금지한다. 그 결과:
- 사용자가 ChatGPT/Claude/Perplexity/Gemini에
"강릉 ○○펜션 반려동물 되나요?"를 물으면 → 인용할 소스가 존재하지 않는다. 네이버 플레이스에 답이 있어도 AI는 그 페이지를 읽을 수 없다. - 네이버 블로그·카페 후기도 똑같이 막혀 있다. 사장님 블로그를 아무리 잘 써도 글로벌 AI 검색에는 안 잡힌다.
- 캐치테이블·카카오맵도 AI 봇 전면 차단.
- 한국 로컬 비즈니스 정보는 AI 검색 입장에서 통째로 빈 구멍이다.
→ 사장님 자체 도메인의 정적 사이트가 그 구멍을 채우면 경쟁자 없이 독점 인용된다. 이 제품의 해자가 데이터로 확인됐다.
채널 이원화가 필요하다
| 목표 | 인용 소스 특성 | 우리 전략 |
|---|---|---|
| 네이버 AI 브리핑 | 네이버 생태계 편중. 인용 272건 분석 시 블로그가 158건으로 1위. 검색순위와 별개(인용의 49.3%가 검색 10위 밖) | 자체 도메인으로는 뚫기 어렵다. 사장님 블로그 채널이 필요 (현재 미구현 — §11) |
| Google AI Overview / ChatGPT / Perplexity | 웹 전체 대상, 자사 도메인 유리. 경쟁 소스(네이버·카카오)가 전부 차단 상태 | 여기가 우리 주전장. 현재 구조(정적 HTML + JSON-LD + llms.txt)가 정확히 맞다 |
2. 현재 상태 실측
| 항목 | 결과 |
|---|---|
m.place.naver.com/{kind}/{id}/home |
HTTP 200, __APOLLO_STATE__ 존재 (restaurant 347KB / accommodation 612KB). 기능적으로는 정상 동작 |
pcmap.place.naver.com/restaurant/{id}/home |
HTTP 200, __APOLLO_STATE__ 존재 |
| robots.txt 준수 여부 | ❌ 위반 중. Disallow: / 를 무시하고 있다 |
네이버 지역검색 API (openapi.naver.com/v1/search/local) |
✅ 정상 (2026-08-28 확인). 단 telephone·description은 전 표본 100% 빈 문자열 |
TourAPI KorService2 |
❌ HTTP 403 — 키는 유효하나 해당 API 미신청 상태 (같은 키로 축제 API는 200) |
KAKAO_REST_API_KEY |
미설정(빈 값) |
기능이 되는 것과 해도 되는 것은 다르다. 지금 네이버 플레이스 어댑터는 200을 받지만 robots.txt를 명시적으로 위반한다.
DECISIONS.md1-1의 "약관·robots.txt 기준 허용 범위" 질문에 대한 답은 "robots.txt 기준으로는 명백히 불허" 다.
3. OTA 크롤링 — 이미 막혀 있다
풀 브라우저 헤더(Chrome 128 UA + sec-ch-ua + Sec-Fetch-* 전체)로 재시도한 결과:
| URL | 결과 |
|---|---|
www.yanolja.com/pension/3013306 |
HTTP 403 + Cloudflare 챌린지 |
nol.yanolja.com/hotels/{id} |
HTTP 404/403 + Cloudflare 챌린지 |
www.goodchoice.kr/product/detail?ano=... |
HTTP 403 + Cloudflare 챌린지 (홈페이지는 200) |
place.map.kakao.com/{id} |
HTTP 200이나 3.6KB SPA 셸 — 내용 없음 |
place-api.map.kakao.com/places/panel3/{id} (내부 API) |
HTTP 406 차단 |
야놀자·여기어때 어댑터는 만들어도 못 쓴다. 뚫으려면 헤드리스 브라우저 + 봇 탐지 우회가 필요한데, 그건 registry.py와 DECISIONS.md가 결론과 무관하게 영구 금지로 못 박은 영역이다.
법적으로도 하면 안 된다 — 야놀자 v 여기어때
같은 행위(OTA 숙박정보 크롤링)에 대한 국내 유일 대형 선례다. 형사·민사 결론이 갈렸다.
| 구분 | 결론 |
|---|---|
| 형사 (대법원 2022-05-12 확정) | 무죄. 정보통신망침해·업무방해·저작권법 위반 모두 무죄. "수집한 정보 대부분이 이용자에게 공개된 것", DB의 통상적 이용을 방해한 정도가 아니라고 판단 |
| 민사 (서울중앙지법 2021-08) | 10억 원 배상 + 사용·복제·저장 금지. 성과물 무단사용(부정경쟁행위) 인정. 야놀자가 영업조직 인건비만 26억 투입한 점을 배상액 산정에 반영 |
"형사는 무죄니까 괜찮다"가 아니라 "민사로 10억 물었다"가 우리에게 유효한 신호다. 우리는 그 정보를 재게시까지 하므로 여기어때보다 노출이 크다.
4. Google Places API — 우리 제품 구조와 약관이 충돌한다
| 항목 | 내용 |
|---|---|
| 캐싱 | place_id만 무기한 저장 가능. 좌표는 최대 30일. 이름·영업시간·사진·리뷰·전화번호는 캐싱 금지 — 실시간 조회 후 표시해야 한다 |
| 표시 조건 | Google 지도 위에 표시 + Google 로고 및 제3자 제공자 귀속 표기 의무 |
우리는 DB를 실시간 조회하지 않는 정적 HTML을 발행한다(§7). 정적 빌드에 값을 굽는 순간 캐싱 금지 조항 위반이다. → Places API는 콘텐츠 소스로 배제. (Place ID를 엔티티 식별자로만 쓰는 건 가능)
5. 업종별 실측: 사장님이 실제로 어디에 콘텐츠를 두는가
네이버 지역검색 API link 필드 충전율. 업종별 5개 쿼리 × 상위 5건 = 표본 25건씩 (작은 표본이니 방향만 참고).
| 업종 | link 있음 | 인스타그램 | 자체 도메인·기타 | 시사점 |
|---|---|---|---|---|
| 카페 | 23/25 (92%) | 17 | 6 (자체 도메인 2, 스마트스토어 2, 캐치테이블 1) | 카페는 사실상 인스타가 공식 홈페이지다 |
| 숙박 | 24/25 (96%) | 5 | 19 (개별 도메인 다수: gangmunstay.kr, offinghouse.com 등) |
숙박은 자체 홈페이지 보유율이 압도적 |
| 음식점 | 16/25 (64%) | 8 | 8 (네이버블로그 4, 자체 도메인 4) | 절반씩 갈린다. 충전율도 가장 낮다 |
이게 어댑터 우선순위를 정해준다:
- 숙박 → 사장님 자체 홈페이지 크롤링(
StaticHtmlAdapter)이 최고 효율. 사장님이 URL을 확정해주므로 법적으로도 가장 깨끗하다. - 카페 → 인스타그램이 유일한 실질 콘텐츠 소스.
- 음식점 → 커버리지가 낮아 공공데이터·사장님 직접입력 비중을 높여야 한다.
5-1. ★ 업종별로 결론이 다르다
세 업종은 경쟁 상황이 서로 다르다. "AI가 인용할 대체 소스가 이미 있는가"가 갈림점이다.
숙박 — 야놀자·여기어때는 답이 아니다
| 소스 | 실측 | 판정 |
|---|---|---|
| 야놀자 | HTTP 403 + Cloudflare 챌린지 (풀 브라우저 헤더로도 동일) | ✗ 기술적 차단 + 민사 10억 선례 |
| 여기어때 | HTTP 403 + Cloudflare 챌린지 | ✗ 동일 |
| TourAPI 숙박(32) | 무료·재게시 자유. 체크인/아웃, 객실 수·유형, 취사, 주차, 픽업, 부대시설 | ◎ 1순위 |
| 사장님 자체 홈페이지 | 표본 보유율 76% (숙박이 3업종 중 최고) | ◎ 2순위 |
| LOCALDATA 숙박업 | 객실 수, 시설면적, 영업상태(인허가 기준) | ○ 신뢰 근거 |
| 스테이폴리오 | SSR + JSON-LD 있으나 감성숙소 한정 | △ 커버리지 좁음 |
덧붙일 중요한 사실: 야놀자·여기어때는 AI 크롤러에게도 닫혀 있다. 즉 긁어봐야 "AI가 못 읽는 정보를 위법하게 옮겨오는" 것에 불과하다. GEO 관점에서 얻는 게 없다.
단, 숙박만 예외가 하나 있다. 해외 OTA는 AI에 열려 있다:
| 소스 | 상세페이지 | GPTBot | ClaudeBot | PerplexityBot |
|---|---|---|---|---|
| 에어비앤비 | HTTP 200 (446KB, SSR) | 허용 | 허용 | 허용 |
| 부킹닷컴 | 접근 가능 | 허용 | 허용 | 허용 |
| 아고다 | 접근 가능 | 허용 | 허용 | 허용 |
→ 호텔·리조트급은 AI 검색 공백이 아니다. 부킹닷컴·아고다 페이지가 이미 인용된다. 우리가 긁을 대상이 아니라 경쟁 소스로 인식해야 한다. → 반대로 펜션·게스트하우스·독채는 해외 OTA 커버리지가 낮고 국내 채널은 전부 차단 → 공백이 그대로 남아 있다. 여기가 숙박의 진짜 기회다.
카페 — AI 검색 공백은 최대. 소스는 생각보다 있다
정정 (2026-08-31). 초판에서 "TourAPI 카페 커버리지 거의 없음"이라고 썼는데 틀렸다. 카페는 별도 콘텐츠 타입이 아니라 음식점(39)의 소분류(
cat3=A05020900카페/전통찻집)로 들어 있고, 전국 1,979건이 등록돼 있다. 실측으로 확인했다.
| 소스 | 실측 | 판정 |
|---|---|---|
| TourAPI (39 / A05020900) | 전국 1,979건. 영업시간·휴무일·대표메뉴·취급메뉴·주차·사진 | ○ 관광지 인근 편중이지만 실재한다 |
| 인스타그램 | 표본의 **68%**가 인스타를 공식 채널로 등록 (3업종 중 최고) | ◎ 그 외 카페의 주 소스. 사장님 OAuth 필요 |
| LOCALDATA 휴게음식점 | 영업상태·시설면적만 | △ 검증용 |
| 네이버 플레이스 | robots 전면 금지 | ✗ |
→ 카페는 AI 검색 공백이 가장 크다(네이버·카카오·인스타 전부 AI 차단). TourAPI에 등록된 카페는 바로 채워지고, 나머지는 인스타 OAuth + 사장님 입력으로 간다.
음식점 — 소스는 가장 많은데 쓸 만한 게 가장 적다
| 소스 | 실측 | 판정 |
|---|---|---|
| 배민 / 요기요 / 쿠팡이츠 | 공식 API 없음, 앱 중심 | ✗ |
| 캐치테이블 | SPA 셸(8KB) + AI봇 전면차단 | ✗ |
| 다이닝코드 | SPA 셸(23KB, JS 필요). 단 ClaudeBot·PerplexityBot 허용 | △ 경쟁 소스 |
| 식신 | SSR + LocalBusiness JSON-LD (상호·전화·주소) |
△ 얕음 |
| 미쉐린가이드 | AI봇 허용 | △ 경쟁 소스(해당 업소 한정) |
| 블루리본서베이 | AI봇 전면차단 | ✗ |
| TourAPI 음식점(39) | 대표메뉴·취급메뉴·영업시간·쉬는날·포장·예약·좌석 수 | ○ 단 관광지 인근 편중 우려 |
| LOCALDATA 일반음식점 | 업태(한식/중식…), 시설면적, 영업상태 | ○ |
| 식품안전나라 위생등급 | 공공 인증 | ○ E-E-A-T 신호로 유용 |
| 사장님 자체 홈페이지 | 표본 보유율 32%, link 충전율 64%로 3업종 최저 |
△ |
→ 음식점은 자체 채널 보유율이 가장 낮아 공공데이터와 사장님 직접 입력 비중을 가장 크게 잡아야 한다.
요약
| 대체 소스 | AI 검색 공백 | 주력 소스 | 난이도 | |
|---|---|---|---|---|
| 숙박(펜션·독채) | 야놀자·여기어때 모두 차단 | 완전 공백 | TourAPI + 자체 홈페이지(76%) | 낮음 ← 먼저 공략 |
| 숙박(호텔) | 해외 OTA가 AI에 노출 중 | 부분 공백 | TourAPI + 자체 홈페이지 | 중간 |
| 카페 | 사실상 없음 | 완전 공백 | 인스타 OAuth + 사장님 입력 | 높음 |
| 음식점 | 다이닝코드·미쉐린 일부 노출 | 대부분 공백 | LOCALDATA + TourAPI + 사장님 입력 | 높음 |
6. 소스 카탈로그
Tier 1 — 공공 API (재게시 자유, GEO에 가장 강함)
| 소스 | 상태 | 숙박 | 카페 | 음식점 | 라이선스 |
|---|---|---|---|---|---|
| TourAPI (한국관광공사 KorService2) | 키는 있으나 활용신청 필요(무료·즉시) | ◎ 체크인/아웃, 객실 수·유형, 취사, 주차, 픽업, 부대시설 | ✕ 커버리지 거의 없음 | ○ 대표메뉴, 취급메뉴, 영업시간, 쉬는날, 포장·예약, 좌석 수 | 이용허락범위 제한없음 / 무료. 개발계정 1,000건/일 → 운영계정 확장 가능 |
| LOCALDATA (지방행정 인허가) | 미도입. 무료 신청 | ○ 숙박업 인허가 — 객실 수, 시설면적, 영업상태 | ○ 휴게음식점 | ○ 일반음식점 — 업태(한식/중식…), 시설면적, 영업상태 | 공공데이터. 208개 업종 전체, 좌표 포함(EPSG:5174) |
| 네이버 지역검색 API | ✅ 사용 중 | △ | △ | △ | 상호·주소·좌표·카테고리·link만. 전화·설명 필드는 빈 값 |
| 카카오 로컬 API | 키 미설정 | △ | △ | △ | 네이버 지역검색과 동급. 콘텐츠 없음 |
TourAPI의 필드 목록은 활용신청 후
detailIntro2로 실측 확정 필요. 현재 403이라 응답 스키마를 직접 확인하지 못했다.
Tier 2 — 사장님 인증 채널 (고유 콘텐츠, 법적으로 가장 안전)
| 소스 | 방식 | 숙박 | 카페 | 음식점 | 비고 |
|---|---|---|---|---|---|
| 사장님 자체 홈페이지 | 사장님이 URL 확정 → StaticHtmlAdapter |
◎ (보유율 76%) | △ | ○ | 가장 권장. 사장님 동의 = 법적 리스크 0. 이미 LinkChannel.OFFICIAL_SITE 존재 |
| 인스타그램 Graph API | 사장님 OAuth (Business/Creator 계정 필요) | △ | ◎ (68%) | ○ | 사진 + 캡션. 저작권자가 사장님이라 1-2 미결이 해소된다 |
| Google Business Profile API | 사장님 OAuth + Google 접근 승인 필요 | ○ | ○ | ○ | 영업시간·속성·사진 전부. 단 국내 소상공인 GBP 관리율이 낮아 커버리지 문제. 신규 프로젝트는 쿼터 0에서 시작, 별도 심사 필요 |
| 사장님 직접 입력 | 관리자 화면 | ◎ | ◎ | ◎ | 커버리지 100%. 이탈률이 유일한 비용 |
Tier 3 — 기술적으로 가능하나 권장하지 않음
| 소스 | 상태 |
|---|---|
식신 (siksinhot.com) |
HTTP 200, SSR, LocalBusiness JSON-LD 제공(상호·전화·주소). 콘텐츠 깊이는 네이버보다 얕고 업소 매칭이 어렵다 |
| 다이닝코드 | HTTP 200이나 SPA 셸(23KB) — JS 렌더링 필요. rid 매칭 문제 |
Tier 4 — 배제
야놀자 / 여기어때 (Cloudflare 403 + 민사 10억 선례) · 카카오맵 (406, AI봇 전면차단) · 캐치테이블 (SPA + AI봇 전면차단) · Google Places API (캐싱 금지) · 네이버 블로그·카페 후기 (이미 정책상 배제, robots 전면차단)
7. GEO 관점에서 소스를 다시 줄 세우면
Princeton·Georgia Tech·IIT Delhi 공동연구(KDD 2024)가 확인한 AI 인용률을 최대 40% 끌어올리는 요인은 키워드가 아니라 이 셋이다:
- 통계·수치 추가 (Statistics Addition)
- 출처 인용 (Cite Sources)
- 인용문 추가 (Quotation Addition)
그리고 ChatGPT·Gemini·Copilot이 인용한 소스의 10% 미만만 해당 질의 구글 상위 10위에 든다. 랭킹 신호와 인용 신호는 다른 게임이다.
이 기준으로 소스를 평가하면 순위가 뒤집힌다:
| 소스 | 수치 밀도 | 출처로 명시 가능? | GEO 가치 |
|---|---|---|---|
| LOCALDATA 인허가 | 높음 (객실 수, 시설면적, 인허가일) | ✅ "○○시 인허가 정보 기준" | 최상 |
| TourAPI | 높음 (체크인 시각, 객실 수, 좌석 수) | ✅ "한국관광공사 TourAPI 기준" | 최상 |
| 사장님 확인 정보 | 중간 | ✅ "사업주 확인, 2026-08-28" | 높음 |
| 네이버 플레이스 크롤링 | 높음 | ❌ 밝힐 수 없다 (robots 위반 + 재게시 근거 없음) | 낮음 |
핵심 역설: 지금 유일한 소스인 네이버 플레이스가 GEO 관점에서 가장 가치가 낮다.
출처를 명시할 수 없는 사실은 AI가 신뢰하지 않고, 우리 AnswerBlock·llms.txt의 "출처와 검증 시각" 신호도 채우지 못한다. 반면 공공데이터는 그 자체가 인용 가능한 1차 출처라 GEO 신호를 직접 만든다.
여기에 하나 더 — 복제 콘텐츠는 GEO에서 마이너스다. 네이버 플레이스와 같은 문장이 실린 페이지는 PUBLISH_NO_UNIQUE_CONTENT 게이트가 이미 경계하는 그 상태다. AI가 굳이 인용할 이유가 없다.
8. 권고 로드맵
| 순위 | 작업 | 근거 | 비용 |
|---|---|---|---|
| 1 | TourAPI KorService2 활용신청 후 tour_api.py 확장 (searchStay2, detailIntro2) |
숙박·음식점 fact를 출처 명시 가능한 형태로 확보. 무료, 신청 즉시 승인 | 1일 |
| 2 | StaticHtmlAdapter 등록 (사장님 확정 URL 전용) |
숙박 76%·음식점 32%가 자체 도메인 보유. 사장님 동의 기반이라 법적 리스크 0. DECISIONS.md 1-1이 "결론 후 등록"으로 남겨둔 바로 그 어댑터 |
3일 |
| 3 | LOCALDATA 연동 | 인허가 기반 영업상태·객실수·시설면적. PLACE_NOT_VERIFIED 검증 근거로도 사용 가능 |
2일 |
| 4 | 인스타그램 Graph API (사장님 OAuth) | 카페 68%의 유일한 콘텐츠·사진 소스. 1-2(이미지 재게시 권리) 미결을 구조적으로 해소 | 5일 |
| 5 | 네이버 플레이스 어댑터 역할 축소 — 사장님 화면의 "대조용 참고값"으로만 사용하고 발행 payload에서 제외 | robots 위반 해소 + 복제 콘텐츠 제거. COLLECT_ADAPTERS 환경변수로 코드 수정 없이 즉시 가능 |
0.5일 |
| 6 | 네이버 블로그 채널 (§11 미구현분) | 네이버 AI 브리핑 인용의 58%가 블로그. 글로벌 AI와 별개 전장 | 별도 검토 |
registry.py가 이미 COLLECT_ADAPTERS 환경변수로 채널별 on/off를 지원하므로, 5번은 배포 없이 즉시 실행 가능하다.
업종 출시 순서 제안
소스 확보 난이도와 AI 검색 공백 크기가 같은 방향을 가리킨다.
- 숙박(펜션·게스트하우스·독채) — TourAPI 32 + 자체 홈페이지(보유율 76%)로 fact가 가장 잘 차고, 경쟁 소스가 전부 차단 상태다. 1·2번 작업만으로 출시 가능.
- 음식점 — LOCALDATA + TourAPI 39 + 위생등급으로 공공 fact를 채우고 나머지는 사장님 입력. 3번 작업 필요.
- 카페 — 인스타 OAuth(4번)가 선행되지 않으면 사장님 입력 100% 의존이라 이탈률이 높다. 가장 마지막.
8-1. 구현됨 (2026-08-28) — 사이트별 파서 대신 LLM 추출
조사 도중 방향을 바꿨다. 사장님 홈페이지마다 파서를 짜는 것은 끝이 없다 — 실측으로 확인됐다:
| 사이트 | 결과 |
|---|---|
mulhoe.co.kr |
본문 2,841자 + JSON-LD 1블록 — 쓸 만함 |
gangmunstay.kr |
8.5KB HTML인데 본문 9자 — SPA, JS 렌더링 필요 |
cheomdangukbap.com |
834바이트, 본문 0자 — SPA |
offinghouse.com |
SSL 자체서명 인증서 오류 |
4곳 중 1곳만 정적 HTML로 내용이 나온다. 그래서 입력이 무엇이든(URL 크롤링 본문 / 사장님 붙여넣기 텍스트) 원문 문자열 하나로 모아 Gemini가 업종 스키마로 추출하는 구조로 만들었다.
URL → StaticHtmlAdapter (robots 준수) ─┐
붙여넣기 ────────────────────────────────────┴→ 원문 텍스트 → Gemini 구조화 추출
→ evidence 원문 대조 → fact 후보(UNVERIFIED)
핵심 안전장치는 프롬프트가 아니라 코드다. 모델이 적어 낸 근거 문장이 원문에 실제로 있는지 대조하고, 없으면 버린다. 실측 검증:
| 공격 케이스 | 결과 |
|---|---|
| 원문에 없는 문장을 근거로 지어냄 | 🚫 차단 |
근거는 "반려동물 불가"인데 pet_allowed=true |
🚫 차단 (부호 뒤집기) |
| 원문 "오후 3시"를 "15:00"으로 환산 | 🚫 차단 (표기 변경) |
| 업종 스키마에 없는 key | 🚫 차단 |
unit 스코프인데 unit_name 없음 |
🚫 차단 |
실제 결과 — 파서 한 줄 없이:
mulhoe.co.kr(음식점): 영업시간·주차·포장·예약채널 + 메뉴 5종의 이름/가격/설명 = fact 19건- 붙여넣기 238자 (펜션): 체크인/아웃·주차·와이파이·반려동물(false)·바비큐요금 + 객실 2종의 인원/침대/주중·주말요금 = fact 15건
| 파일 | 역할 |
|---|---|
solution/backend/services/collector/static_html_adapter.py |
robots.txt 준수 fetch + JSON-LD/OG 추출. _DENY_HOSTS 로 플랫폼 차단 |
solution/backend/services/prompts/extract.py |
추출 프롬프트·응답 스키마 (업종 스키마에서 자동 생성) |
solution/backend/services/grounding/extract.py |
evidence 원문 대조 게이트 |
solution/backend/services/external/gemini_extract.py |
엮는 자리 |
solution/backend/tests/test_extract_grounding.py |
게이트 회귀 테스트 17건 |
비용: 호출당 약 $0.006 (gemini-3.7-flash, temperature 0.0).
남은 작업: collect_service 에서 이 추출 경로를 호출하도록 배선 + 관리자 화면에 "텍스트 붙여넣기" 입력창. 컨테이너는 소스 마운트가 없으므로 docker compose up -d --build 로 이미지를 다시 빌드해야 반영된다.
8-2. 지금 실제로 가져올 수 있는 정보 (2026-08-31 실측)
TourAPI 활용신청이 승인돼 어댑터를 붙였다. 아래는 표본 8곳씩 실제로 돌려서 센 것이지 문서 기준 추정이 아니다.
숙박 — 객실 단위까지 채워진다
| 필드 | TourAPI 충전율 | 비고 |
|---|---|---|
| ★ 체크인 / 체크아웃 | 8/8곳 | critical |
| 숙소 소개 | 8/8곳 | |
| 바비큐 이용 | 8/8곳 | |
| 주차 | 6/8곳 | |
| ★ 취사 가능 | 4/8곳 | critical |
| ★ 취소·환불 규정 | 3/8곳 | critical |
| 픽업 서비스 | 3/8곳 | |
| 객실 단위 — 타입·★기준인원·★최대인원·★주중요금·★주말요금·에어컨 | 객실 20개분 | 표본 8곳에서 객실 20개 |
| 객실 면적(㎡) | 객실 17개분 | |
| ★ 성수기 요금 | 객실 14개분 | |
| ★ 주방 여부 | 객실 13개분 | |
| 사진 | 4/8곳 | 객실별로 붙는다 |
TourAPI가 못 채우는 13개: pet_allowed smoking extra_person_fee reception_hours parking_capacity wifi bbq_fee breakfast baby_amenities bed_type bathroom_count view room_intro → 자체 홈페이지 추출 또는 사장님 입력.
음식점 · 카페 — 영업정보와 메뉴 이름까지
| 필드 | TourAPI 충전율 |
|---|---|
| 가게 소개 · ★영업시간 · ★휴무일 · 대표메뉴 · ★주차 | 8/8곳 |
| 메뉴 이름(단위) | 메뉴 25건 |
| 포장 | 4/8곳 |
| ★ 예약 필수 | 2/8곳 |
| 사진 | 8/8곳 |
못 채우는 16개: break_time last_order reservation_channel pet_allowed kids_allowed corkage room_available group_seat_max parking_capacity delivery payment_methods price_range wheelchair_accessible menu_price menu_min_order menu_intro
→ 메뉴 가격이 없는 것이 가장 아프다. 자체 홈페이지 추출(속초항아리물회에서 메뉴 5종 가격 확보)이나 사장님 입력으로 메운다.
네이버 플레이스와 비교 (같은 날 실측)
| 네이버 플레이스 | TourAPI | |
|---|---|---|
| 숙박 사업장 fact | 2건 (intro, wifi) |
8종 (체크인/아웃·취사·주차·픽업·바비큐·환불규정·소개) |
| 숙박 객실 정보 | 객실명(room_type)만 18개 |
객실 20개의 인원·요금·면적·주방·에어컨 |
| 음식점 | 수집 실패 (PlaceDetailBase 없음) |
영업시간·휴무일·대표메뉴·주차·메뉴명·사진 |
| 출처 표기 | 불가 (robots 위반) | 가능 — 대한민국구석구석 공개 URL |
네이버 플레이스를 유지할 이유가 사실상 사라졌다. 정보량도 적고 출처도 못 밝힌다.
이미지 — 저작권까지 확정
TourAPI 숙박 사진 167장 표본의 공공누리 유형: Type3 161장 · Type1 6장 → 100% 상업적 이용 가능. 다만 Type3가 96%이고 제3유형은 '변경금지' 다.
⚠️ 렌더러 제약: Type3 사진은 크롭·리사이즈하면 조건 위반이다. 썸네일 생성 경로가 이 사진들을 건드리면 안 된다.
CollectedMedia.license에 유형을 담아 내보내므로 발행 단계에서 판정할 수 있다. 상업적 이용이 막힌 Type2·Type4와 유형 미상은 수집 단계에서 버린다.
8-3. API별 수집 카탈로그 — 어디서 무엇을 가져오는가
코드에서 뽑은 것이다(2026-08-31). → 왼쪽이 API 원본 필드, 오른쪽이 우리 fact key.
A. 한국관광공사 TourAPI · apis.data.go.kr/B551011/KorService2
무료 · 이용허락범위 제한없음 · 개발계정 1,000건/일. fact 의 주력 공급원.
| 오퍼레이션 | 원본 → fact key |
|---|---|
detailCommon2 |
overview → intro · firstimage+cpyrhtDivCd → 대표사진참고용으로만 읽음: title addr1 mapx/mapy homepage cat1/2/3 modifiedtime |
detailIntro2 숙박(32) |
checkintime → check_in_time · checkouttime → check_out_timerefundregulation → cancel_policy · chkcooking → cooking_allowedparkinglodging → parking · pickup → pickup_service · barbecue → bbq_available |
detailIntro2 음식점·카페(39) |
opentimefood → business_hours · restdatefood → closed_daysfirstmenu → signature_menu · treatmenu → menu_name(단위, /로 분리)parkingfood → parking · packing → takeout · reservationfood → reservation_required |
detailInfo2 숙박(32) = 객실 행 |
roomtitle → room_type(+ unit_name) · roombasecount → standard_capacityroommaxcount → max_capacity · roomsize2 → room_size(㎡)roomoffseasonminfee1/2 → weekday_price / weekend_priceroompeakseasonminfee1 → peak_price · roomcook → has_kitchen · roomaircondition → has_airconroomimg1~5 + cpyrhtDivCd1~5 → 객실 사진 |
detailImage2 |
originimgurl imgname cpyrhtDivCd → 추가 사진 |
일부러 안 쓰는 필드 — 스키마에 자리가 없거나 뜻이 다르다:
roomcount scalelodging accomcountlodging foodplace reservationlodging infocenter* subfacility
seminar/sports/sauna/beauty/beverage/karaoke/campfire/bicycle/fitness/publicpc/publicbath
kidsfacility(놀이방 유무 ≠ 아동 입장) · chkcreditcardfood(가능/없음 ≠ 결제수단 목록) · smoking · lcnsno
roomsize1(평 — ㎡와 섞으면 3배 틀린다) · roombathfacility/tv/cable/internet/refrigerator/toiletries/sofa/table/hairdryer
B. 네이버 지역검색 API · openapi.naver.com/v1/search/local.json
fact 를 만들지 않는다. 사업장 후보 검색과 채널 URL 발견에만 쓴다.
title→상호 · roadAddress/address→주소 · mapx/mapy→좌표(1e7 스케일) · category→업종명 · link→공식 채널 URL 후보
telephone·description → 실측 100% 빈 문자열. 있는 셈 치면 안 된다.
C. 카카오 로컬 API · dapi.kakao.com (키 미설정 — 현재 미사용)
id→kakao_place_id · place_name · road_address_name · address_name · phone · x/y→좌표 · category_name · place_url
행정구역: code · region_1~3depth_name · region_type → 지역 콘텐츠 캐시 키
D. 네이버 플레이스 크롤링 · m.place.naver.com __APOLLO_STATE__
⚠️ robots.txt
Disallow: /위반 상태. 출처를 밝힐 수 없어 GEO 가치가 없다. 대조용으로만 남긴다.
description/microReviews → intro · Naverhotel3HotelData.checkInStr/checkOutStr → check_in_time/check_out_time
BusinessHour* → business_hours · operating_hours · reception_hours
conveniences + InformationFacilities* → parking wifi bbq_available cooking_allowed breakfast pet_allowed pickup_service baby_amenities has_aircon terrace takeout delivery power_outlet wheelchair_accessible
Menu:* → room_type(단위) · PlaceDetailTopPhotoItem* → 사진
E. 사장님 자체 홈페이지 · static_html 어댑터 (사장님 확정 URL 한정, robots 준수)
① JSON-LD(schema.org) / OpenGraph 직접 매핑
description/og:description → intro · openingHours(Specification) → business_hours 등
checkinTime/checkoutTime → check_in_time/check_out_time · priceRange → price_range · paymentAccepted → payment_methods
petsAllowed → pet_allowed · smokingAllowed → smoking
hasMenu.hasMenuItem → menu_name · menu_price · menu_intro(단위)
amenityFeature → 편의시설 bool 15종 · image/og:image → 사진
② Gemini 추출 — ①로 안 잡히면 본문 텍스트에서 업종 스키마 전 필드를 뽑는다(근거 원문 대조 통과분만)
F. 사장님 붙여넣기 텍스트 · gemini_extract
원문 문자열 하나 → 업종 스키마 전 필드. E-②와 같은 엔진, 입력만 다르다.
G. Perplexity Sonar
채널 URL 후보만 (title, url). ★ 응답 본문의 영업시간·가격 같은 정보는 쓰지 않는다.
H·I. Gemini
| 산출 | |
|---|---|
| Vision | ref · label(업종별 사진 라벨) · alt_text · confidence |
| Text | intro · meta_description · FAQ(question/answer/fact_keys) — 확보된 fact 로만 |
J·K. 지역 콘텐츠 (사업장 fact 아님 — 주변 정보 블록용)
| API | 산출 |
|---|---|
Open-Meteo api.open-meteo.com/v1/forecast |
temperature weather_code wind_speed observed_at timezone |
공공데이터포털 축제 tn_pubr_public_cltur_fstvl_api |
title eventstartdate eventenddate mapx/mapy tel homepage organizer overview |
9. DECISIONS.md 미결 항목에 대한 답
1-1. OTA·플레이스 크롤링의 약관·법적 검토 → 결론 가능
| 대상 | 판정 | 근거 |
|---|---|---|
| 야놀자·여기어때 | 불가 | Cloudflare 403 실측 + 민사 10억 배상 선례. 기술적으로도 법적으로도 막혔다 |
| 네이버 플레이스 | robots.txt 기준 불허 | Disallow: / 실측. 기능은 되지만 준수 상태가 아니다 |
| 카카오맵 | 불가 | 내부 API 406 차단 |
| 사장님 확정 URL(자체 홈페이지) | 가능 | 사장님 동의 기반. StaticHtmlAdapter 등록 권고 |
→ 폴백 3단계(① 공식 API → ② 사장님 직접 붙여넣기 → ③ 최소 정보 생성 + 보완 요청)로 간다는 기존 설계가 맞았다. 이번 조사는 ①의 실체를 TourAPI·LOCALDATA로 특정한 것이다.
1-2. 크롤링 이미지 재게시 권리 → 회피 경로 확보
권리 관계를 다투는 대신 입력원을 바꾸면 문제가 사라진다.
- 인스타그램 Graph API (사장님 OAuth) → 저작권자 = 사장님
- 사장님 직접 업로드 → 동일
- TourAPI 이미지 → 공공누리
media.source_type(owner/crawl) 컬럼이 이미 있으므로, 발행 시 CRAWL 제외 전환은 준비되어 있다.
10. 확인하지 못한 것
- TourAPI
detailIntro2실제 응답 필드 — 403이라 미확인. 활용신청 후 실측 필요. §6의 필드 목록은 공개 문서 기준 추정치다. - 업종별 link 충전율 표본이 25건씩이라 통계적 신뢰구간이 넓다. 방향성 판단용으로만 사용할 것.
- GBP 국내 소상공인 실제 관리율 — 정량 자료를 찾지 못했다. 도입 전 표본 조사 필요.
- TourAPI 음식점(39) 커버리지 — 관광지 인근 위주라 도심 일반 음식점 커버리지가 낮을 것으로 보이나 미측정.