- WebSearchService 신규: Naver Search webkr.json 우선, DDG 폴백 - searchTabling/searchCatchtable 내부 호출만 교체 (시그니처 유지) - application.yml + k8s secrets에 NAVER_CLIENT_ID/SECRET 추가 Refs: #357 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5.2 KiB
5.2 KiB
설계서: DDG HTML 파싱 → 정식 검색 API 전환 (#357)
상태: Approved 작성: [AI] Architect · 최종수정: 2026-06-15 추적성 — Redmine: #357 · 부모: #348(이름 유사도, 09-Done) · 관련: searchTabling/searchCatchtable · 구현 파일:
backend-java/src/main/java/com/tasteby/service/WebSearchService.java(신규),backend-java/src/main/java/com/tasteby/controller/RestaurantController.java,backend-java/src/main/resources/application.yml,k8s/secrets.yaml.template· 테스트: 본 범위 밖 (수동 — Tabling/Catchtable 매핑 SSE에서 확인)
1. 목적 (Why)
searchDuckDuckGo는 html.duckduckgo.com을 정규식으로 긁어 Tabling/Catchtable URL을 추출. 봇 차단/HTML 구조 변경 시 대량 NONE 마킹 위험. 정식 검색 API로 교체해 안정성/정확도 확보.
2. 범위 (Scope)
- 포함
WebSearchService신규 — Naver Search webkr.json 우선, 미설정/실패 시 DDG 폴백.RestaurantController.searchTabling/searchCatchtable내부 호출을 새 서비스로 교체.application.yml+k8s/secrets.yaml.template에NAVER_CLIENT_ID/SECRET항목 추가.
- 제외 (별도 후속)
- Kakao Local API 검색 (식당 검색 전용이라 사이트 URL 매칭 부적합).
- Google Custom Search Engine (비용 + 무료 100/day 제약).
- 결과 캐시/메트릭 — 후속.
3. 인수조건
NAVER_CLIENT_ID/SECRET환경변수 등록 시 Naver Search webkr.json 호출.- 미설정 또는 5xx/timeout 시 DDG로 자동 폴백 — 회귀 없음.
- 응답 형식: 기존
List<Map<String, Object>>유지 —{title, url}키. - URL 패턴 필터 동일 동작 (예:
tabling.co.kr/restaurant/,tabling.co.kr/place/). - 빌드/배포 회귀 없음.
4. 컨텍스트 & 제약
- Naver Search 무료 한도: 일 25,000건. Tabling/Catchtable 매핑 백필이 1,200 식당 × 2회 ≈ 2,400건 1회성 → 한도 내.
- Naver는
site:연산자 미지원 — 결과 URL 패턴 필터링으로 대체. WebClient또는HttpClient— 기존HttpClient(static) 재사용.- 키 미설정 환경(dev local 일부)에서도 DDG 폴백으로 동작 보장.
5. 아키텍처 개요
RestaurantController.searchTabling(name)
│
▼
WebSearchService.search(query, urlPatterns)
│
├─ NAVER_CLIENT_ID 설정 시
│ └─ Naver webkr.json → URL 패턴 필터 → 결과
│ (실패/0건 시 ▼)
└─ DDG html.duckduckgo.com → 정규식 파싱 → URL 패턴 필터
6. 데이터 모델
응답 (기존 유지)
[ { "title": "스타벅스 강남대로점", "url": "https://app.catchtable.co.kr/dining/12345" } ]
Naver 응답 → 변환
{ "items": [ { "title": "<b>스타벅스</b> 강남점", "link": "https://..." } ] }
title은<b>태그 제거 후 사용.link가 urlPatterns 중 하나에 매칭되면 결과에 포함.
7. 함수 명세
| 함수 | 책임 | 비고 |
|---|---|---|
WebSearchService.search(query, urlPatterns) |
외부 진입점 | Naver 우선 → DDG 폴백 |
WebSearchService.searchNaver(...) |
Naver Search webkr.json 호출 + 필터 | 키 미설정 시 즉시 빈 결과 |
WebSearchService.searchDdg(...) |
기존 DDG 로직 이관 | RestaurantController에서 옮김 |
WebSearchService.stripTags(s) |
<b>...</b> 제거 |
|
WebSearchService.matchesPattern(url, patterns) |
URL 패턴 매칭 | urlPatterns.length == 0 면 모두 통과 |
8. 흐름
정상
searchTabling(name)→webSearchService.search("스타벅스", ["tabling.co.kr/restaurant/", "tabling.co.kr/place/"]).- Naver 호출 → 200, items 30 → URL 패턴 매칭 0~N건 추출.
- 0건이면 DDG 폴백, 그래도 0건이면 빈 리스트.
폴백
- Naver 5xx / IOException / 키 미설정 → DDG.
키 등록 (운영자 작업)
- https://developers.naver.com/apps/#/list 검색 앱 등록.
NAVER_CLIENT_ID,NAVER_CLIENT_SECRET발급.- dev:
.env에 추가, prod:k8s/secrets.yaml에 추가 +kubectl apply.
9. 엣지케이스
- 키 미설정: searchNaver 즉시 빈 리스트 → DDG 폴백 (회귀 없음).
- Naver rate limit (429): DDG 폴백.
- DDG도 막힘: 빈 리스트 반환 → 호출자(매핑 SSE)에서
NONE처리 — 기존 동작 동일. - URL 패턴 빈 배열: 패턴 매칭 스킵, 모든 결과 반환 (API 일반화 대비).
10. 테스트 (수동)
- Dev:
NAVER_CLIENT_ID/SECRET등록 → 어드민bulkTablingSSE 실행 → 매칭율 비교 (이전 DDG 대비 ≥). - 키 일부 제거 → DDG 폴백 동작 확인.
11. 리스크 & 대안
- 선택: Naver primary + DDG fallback. 한국 식당 정확도 + 무료 한도 + 폴백 안정성.
- 대안 A: Kakao Local Search — 식당 정보 직접 검색은 가능하지만 Tabling/Catchtable URL 매핑 부적합.
- 대안 B: Google CSE — 비용/한도 제약.
- 트레이드오프: Naver 응답 정확도가 압도적이지만 키 발급 운영자 작업 필요. 폴백으로 회귀 0 보장.
12. 미해결 질문
- Naver의 인덱스 신선도 vs Tabling/Catchtable 최신 입점 식당 미반영 가능성 — 백필 주기 후속.
- 결과 캐시(같은 식당 재호출) — 후속.