# 콘텐츠 소스 리서치 — 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.md` 1-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% 끌어올리는 요인**은 키워드가 아니라 이 셋이다: 1. **통계·수치 추가 (Statistics Addition)** 2. **출처 인용 (Cite Sources)** 3. **인용문 추가 (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 검색 공백 크기가 같은 방향을 가리킨다. 1. **숙박(펜션·게스트하우스·독채)** — TourAPI 32 + 자체 홈페이지(보유율 76%)로 fact가 가장 잘 차고, 경쟁 소스가 전부 차단 상태다. **1·2번 작업만으로 출시 가능.** 2. **음식점** — LOCALDATA + TourAPI 39 + 위생등급으로 공공 fact를 채우고 나머지는 사장님 입력. 3번 작업 필요. 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가 업종 스키마로 추출**하는 구조로 만들었다. ```text 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_time**
`refundregulation` → **cancel_policy** · `chkcooking` → **cooking_allowed**
`parkinglodging` → **parking** · `pickup` → **pickup_service** · `barbecue` → **bbq_available** | | `detailIntro2` **음식점·카페(39)** | `opentimefood` → **business_hours** · `restdatefood` → **closed_days**
`firstmenu` → **signature_menu** · `treatmenu` → **menu_name**(단위, `/`로 분리)
`parkingfood` → **parking** · `packing` → **takeout** · `reservationfood` → **reservation_required** | | `detailInfo2` **숙박(32)** = 객실 행 | `roomtitle` → **room_type**(+ unit_name) · `roombasecount` → **standard_capacity**
`roommaxcount` → **max_capacity** · `roomsize2` → **room_size**(㎡)
`roomoffseasonminfee1/2` → **weekday_price / weekend_price**
`roompeakseasonminfee1` → **peak_price** · `roomcook` → **has_kitchen** · `roomaircondition` → **has_aircon**
`roomimg1~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) 커버리지** — 관광지 인근 위주라 도심 일반 음식점 커버리지가 낮을 것으로 보이나 미측정.