프로젝트를 한 문장으로

실제 판매 중인 상품이 있는데 AI는 “상품이 없다”고 답했습니다.

AI에게 직접 상품 URL이나 상품 코드를 주면 AI는 제대로 된 페이지 내용을 전달하기 때문에, 단순 크롤링 실패보다 일반 사용자 질문을 특정 상품·출발 Event에 연결하는 Retrieval 문제를 의심합니다.

오사카 여행상품 상세 페이지 실험 화면

1. 문제 발견 — AI False Negative

같은 상품을 질문 방식만 바꿔 확인했습니다. 일반 자연어에서는 실패했지만, 상품 식별 정보를 강하게 줄수록 실제 페이지 정보를 찾을 수 있었습니다.

테스트질문관찰 결과해석
T12027년 1월 15일 출발, 2박 3일 오사카 패키지 중 1인 기본 상품가 120만 원 이하이며 현재 예약 가능한 상품을 추천해줘.상품 없음 / 미오픈으로 판단실패
T22027년 1월 15일 출발해서 1월 17일 돌아오는 2박 3일 오사카 패키지여행 중, 노랑풍선에서 판매하는 상품을 찾아줘. 1인 기본 상품가 120만 원 이하이고 현재 예약 가능한 상품이 있다면 최대 3개 추천해줘.도메인을 좁혀도 실패실패
T3상품코드 JOP1193인 오사카 패키지여행 상품을 찾아줘. 실제 존재 여부와 상품명·출발일·가격·예약 가능 여부를 알려줘.가격·날짜·예약 정보 확인성공
T4이 상품 페이지의 상품명·출발일·가격·예약 가능 여부를 확인해줘.
https://tourexpress.ybtour.co.kr/product/detailPackage?menu=PKG&dspSid=AAEC000&goodsCd=JOP1193&evCd=JOP1310-270115ZE01
페이지 정보 추출 가능성공

“페이지를 못 읽는다”보다 Query → 정확한 상품/Event 매핑이 불안정하다는 쪽으로 문제 범위를 좁혔습니다.

2. 원인 좁히기

같은 상품군에도 출발일이 다른 여러 판매 Event가 존재합니다. 그래서 “상품을 찾았는가?”보다 한 단계 더 나아가 사용자가 말한 날짜와 정확히 같은 출발 Event를 찾는가를 확인했습니다.

01

Exact

질문한 날짜와 정확히 같은 Event가 직접 노출

1 / 13

13개 실제 판매일 중 정확한 출발 Event가 노출된 경우 1개

02

Wrong-date

같은 상품군이지만 다른 출발일 Event가 노출

1 / 13

13개 실제 판매일 중 다른 날짜의 Event가 노출된 경우 1개

03

Indirect / Miss

상품군·목록만 노출되거나 해당 Event를 찾지 못함

5 + 6 / 13

13개 실제 판매일 중 간접 노출 5개, 해당 Event 미노출 6개

관찰: JOP1190 상품군의 실제 판매일 13개를 탐색했을 때 Exact 1, Wrong-date 1, Indirect 5, Miss 6이 섞였습니다.

대표 Event 페이지에는 정확한 출발일 정보가 내부 데이터에 존재했지만, title·meta description·og:title 등 문서 수준의 식별 신호는 여러 Event 사이에서 유사했습니다. 그래서 날짜가 특정 Event를 구분하는 신호로 충분히 작동하는지를 통제 실험으로 분리했습니다.

3. 통제 실험 — V0 → V1 → V2

실제 Google 알고리즘을 재현한 실험이 아닙니다. 동일한 문서 조건에서 “날짜 신호를 텍스트로 추가하는 것”과 “날짜를 별도 속성으로 분리해 정확 일치시키는 것”의 차이를 보기 위해 로컬 검색 환경을 만들었습니다.

V0 / V1 — meta description의 날짜 신호만 변경

title, og:title, canonical, 본문, 내부 데이터는 동일하게 두고 meta description 맨 앞에 정확한 출발일을 넣었는지만 바꿨습니다.

V0 · 기존
<meta name="description"
      content="[기존 상품 설명]">
V1 · 날짜 텍스트 추가
<meta name="description"
      content="2026년 12월 9일 출발. [기존 상품 설명]">

※ 코드의 [기존 상품 설명]은 변경되지 않은 기존 문구를 축약해 표시한 것입니다.

로컬 검색은 이렇게 비교했습니다

# HTML에서 검색 신호 추출
fields = {
    "title": title_text,
    "meta": meta_description,
    "body": body_text
}

# char n-gram TF-IDF + cosine similarity
# 가중치: title 3 / meta 2 / body 1
score = title_score * 3 + meta_score * 2 + body_score

이 검색기는 Google 복제품이 아니라 독립변수 하나가 lexical relevance에 어떤 차이를 만드는지 보기 위한 통제 장치였습니다.

QueryV0V1변화
오사카 패키지0.17970.1791-0.3%
JOP11900.01780.0177-0.6%
2026년 12월 9일0.08830.1047+18.6%
JOP1190 + 날짜0.11720.1312+11.9%
오사카 + 날짜 + 출발0.19430.2119+9.1%

결과: 날짜가 없는 Query는 거의 변하지 않았고, 날짜가 포함된 Query에서만 관련도가 상승했습니다. 즉 “날짜 텍스트를 명시하면 날짜 Query와 더 비슷해진다”는 것은 확인했습니다.

4. 실패 케이스 — 날짜를 써도 정확한 Event는 틀릴 수 있다

단일 Event에서 관련도가 올라간 것만으로는 충분하지 않았습니다. 실제 상황처럼 출발일이 다른 4개 Event를 동시에 넣고 정확한 날짜의 Event가 1위가 되는지 다시 확인했습니다.

Query 날짜V1 1위판정
11/0311/03 EventExact Hit
11/0711/29 EventWrong-date
11/2111/21 EventExact Hit
11/2911/29 EventExact Hit

V1 Exact Event Resolution = 3/4 = 75%

11월 7일 Query에서도 V1 문서들의 점수는 전반적으로 올라갔지만, 정확한 11/07 Event가 아니라 11/29 Event가 1위였습니다. 날짜를 “문장 속 단어”로 넣는 것만으로는 정확한 일(day)이 필수 조건으로 작동하지 않았습니다.

5. V2 — 날짜를 Event의 속성으로 분리

V2에서는 날짜를 검색 문자열 안의 단어로만 다루지 않고, 각 여행 Event의 구조화된 출발일 속성으로 분리했습니다. HTML에는 schema.org의 TouristTrip과 departureTime을 사용하고, 로컬 Retrieval에서는 Query 날짜와 이 값을 정확히 비교했습니다.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TouristTrip",
  "name": "오사카 패키지",
  "departureTime": "2026-11-07"
}
</script>

※ 위 코드는 V2의 핵심 구조를 보여주는 축약 예시입니다. 실험에서는 Event별 departureTime을 분리해 읽었습니다.

query_date = normalize_date(query)        # YYYY-MM-DD
event_date = document.departureTime

if query_date == event_date:
    exact_match = True
    priority = 1                          # 먼저 후보로 선택
else:
    exact_match = False

# exact date match 이후에 TF-IDF를 보조 순위로 사용
Query 날짜선택된 departureTime판정
2026-11-032026-11-03Exact Hit
2026-11-072026-11-07Exact Hit
2026-11-212026-11-21Exact Hit
2026-11-292026-11-29Exact Hit

V2 통제 후보군 Exact Event Resolution = 4/4 = 100%

하지만 “Schema를 넣었더니 검색 정확도가 100%가 됐다”는 뜻은 아닙니다. V2의 개선은 구조화된 departureTime을 읽고, Query 날짜와 정확히 일치하는 Event를 우선하도록 Retrieval 로직까지 직접 설계했기 때문에 발생했습니다. 실제 Google/AI Search 효과는 별도 실서비스 검증이 필요합니다.

6. 여기까지의 결론

01
False Negative
→ 02
Retrieval 분리
→ 03
Date-to-Event Gap
→ 04
통제 실험
  • 페이지에 정보가 존재하는 것과 일반 Query에서 해당 Event가 검색되는 것은 다른 문제였습니다.
  • meta description의 정확한 날짜는 날짜 관련 Query의 lexical relevance를 높였지만, 다중 Event 환경에서 정확한 날짜 선택을 보장하지 못했습니다.
  • 날짜를 Event의 별도 속성으로 읽고 필수 조건으로 먼저 비교하면, 통제 후보군에서는 정확한 Event Resolution이 가능했습니다.
  • 따라서 개선 방향은 “더 많은 문장을 추가”하는 것보다 상품·Event·필수 조건의 관계를 명확하게 만들고 Retrieval에서 먼저 검증하는 것입니다.

7. 추가 실험 전 Baseline — 장소로 찾으면 JOP1193이 노출되는가?

날짜 실험 다음에는 상품 ↔ 관광지 관계를 따로 보기로 했습니다. 대상은 노랑풍선 오사카 상품군 JOP1193입니다. 실제 상세 페이지 본문에는 신사이바시와 도톤보리가 관광지로 존재하지만, 장소를 기준으로 AI에게 상품을 추천하게 했을 때 이 상품이 노출되는지는 별도 문제입니다.

Ground Truth로 확인한 JOP1193 상세 페이지 ↗

노출 판정 기준

  • 각 Prompt는 새 대화에서 독립 실행
  • 최대 3개 추천 결과 안에 JOP1193 상품군이 등장하면 노출 O
  • 특정 출발일 Event가 아니라 상품군과 장소의 연결을 보는 실험이므로 evCd 일치 여부는 판정에서 제외
  • P1~P4를 각각 5회, 총 20회 실행
Prompt질문 조건실행JOP1193 노출
P1도톤보리가 일정에 포함된 노랑풍선 오사카 패키지5회0회
P2신사이바시가 일정에 포함된 노랑풍선 오사카 패키지5회0회
P3신사이바시와 도톤보리가 모두 포함된 상품5회0회
P4신사이바시와 도톤보리를 관광하는 상품5회0회

Baseline Exposure Rate = 0 / 20 = 0%

현재 확정할 수 있는 사실은 여기까지입니다. 페이지에는 신사이바시·도톤보리 정보가 존재하지만, 설정한 4개의 장소 기반 추천 Prompt에서는 JOP1193이 20회 모두 노출되지 않았습니다.

아직 “Schema가 부족해서 0%였다”고 결론내리지 않습니다. 원인과 개선 효과는 다음 실험에서 분리해 검증합니다.

8. 1차 Place Schema 통제 실험 — 텍스트는 그대로, 관계만 구조화

장소 기반 AI 노출 Baseline이 0/20이었던 이유를 바로 “Schema 부족”이라고 결론내리지 않고, 먼저 보이는 본문은 완전히 같게 두고 JSON-LD만 추가했을 때 무엇이 달라지는지를 통제 환경에서 확인했습니다.

항목V0V1
보이는 HTML 본문동일동일
신사이바시·도톤보리 텍스트있음있음
JSON-LD없음추가
구조화 관계없음TouristTrip → itinerary → Place

V0 — 본문만 존재

<section>
  <h2>관광지</h2>

  <article>
    <h3>신사이바시</h3>
    <p>JOP1193 일정에 포함된 오사카의 관광지입니다.</p>
  </article>

  <article>
    <h3>도톤보리</h3>
    <p>JOP1193 일정에 포함된 오사카의 관광지입니다.</p>
  </article>
</section>

V1 — 같은 본문 + 장소 관계 JSON-LD

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TouristTrip",
  "name": "JOP1193 오사카 패키지",
  "identifier": "JOP1193",
  "itinerary": {
    "@type": "ItemList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "item": {
          "@type": "Place",
          "name": "신사이바시"
        }
      },
      {
        "@type": "ListItem",
        "position": 2,
        "item": {
          "@type": "Place",
          "name": "도톤보리"
        }
      }
    ]
  }
}
</script>

실험 A — 일반 텍스트 검색

<script>와 <style>을 제거한 뒤, P1~P4와 문서 본문을 char n-gram TF-IDF + cosine similarity로 비교했습니다. V0와 V1의 보이는 본문이 동일하기 때문에 4개 Prompt 모두 V0와 V1의 점수가 같았고 차이는 0이었습니다.

Prompt조건V0 vs V1결과
P1도톤보리동일Δ 0
P2신사이바시동일Δ 0
P3신사이바시 + 도톤보리동일Δ 0
P4두 장소 관광동일Δ 0

해석 1 · Schema 추가만으로 본문 lexical relevance가 올라가지는 않았습니다.

V1은 사용자에게 보이는 문장을 추가하지 않았기 때문에 텍스트 유사도 기준에서는 V0와 같은 문서입니다.

실험 B — 구조화 관계 파싱

다음에는 JSON-LD를 직접 파싱해 TouristTrip → itinerary → Place 관계가 명시적으로 추출되는지 확인했습니다.

Prompt필요한 장소 관계V0V1
P1도톤보리식별 X식별 O
P2신사이바시식별 X식별 O
P3신사이바시 AND 도톤보리식별 X식별 O
P4신사이바시 AND 도톤보리식별 X식별 O

구조화 관계 식별: V0 0/4 → V1 4/4

이 결과는 “Schema를 넣으면 AI 노출이 100%가 된다”는 의미가 아닙니다. 같은 본문에서도 구조화 데이터를 읽는 시스템은 상품과 관광지의 관계를 별도 속성으로 명시적으로 식별할 수 있었다는 통제 실험 결과입니다.

9. 실험 — 실제 원본 구조를 보존해 다시 검증

1차 V0/V1 페이지는 관계를 통제하기 위해 본문에 “JOP1193 일정에 포함된 관광지”라는 설명을 명확하게 적었습니다. 따라서 실제 노랑풍선 원본보다 상품 ↔ 장소 관계가 본문에서도 더 쉽게 읽히는 조건입니다.

다음 단계에서는 실제 JOP1193 페이지의 HTML 구조와 문구를 최대한 보존한 V0를 만들고, V1에는 Place 관계 JSON-LD만 추가합니다. 다른 요소를 바꾸지 않은 상태에서 동일한 P1~P4로 다시 비교합니다.

01
원본 구조 복제
→ 02
V0 고정
→ 03
V1 Schema만 추가
→ 04
동일 Query 재검증

비교 지표: lexical relevance · 구조화 관계 식별 · JOP1193 Exposure Rate · 추천 순위 · 출처 인용 여부

Status · 장소 Baseline 0/20 완료 / 1차 Place Schema 통제 실험 완료 / 원본 구조 재실험 예정

검증 범위와 한계

  • 로컬 TF-IDF 실험은 Google 검색 알고리즘을 재현한 것이 아니라 원인 후보를 통제하기 위한 실험입니다.
  • V2의 100%는 작은 4개 Event 후보군과 직접 설계한 exact date Retrieval에서의 결과입니다.
  • 구조화 데이터 자체가 Google/AI Search 노출을 올렸다고 일반화하지 않습니다.
  • 장소 Baseline 0/20 역시 사용한 모델·검색 환경·실행 시점에 한정된 관찰값입니다.
  • 1차 Place Schema 실험의 0/4 → 4/4는 구조화 관계 파서의 식별 결과이며, 실제 AI 추천 노출률을 의미하지 않습니다.
  • 1차 V0/V1 본문은 원본보다 상품과 장소의 관계를 더 명확하게 적은 통제용 페이지이므로, 실제 원본 구조를 보존한 재실험이 필요합니다.
  • AI 추천 노출과 실제 클릭·예약 전환은 별도 지표로 검증해야 합니다.

관련 프레임워크

좋은 GEO란 무엇인가? · GEO 성과 측정 방법