Files
tasteby/docs/design/357-web-search-api/README.md
joungmin f1164b63c5 docs(design): #357 정식 검색 API 전환 설계서 (Architect)
- 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>
2026-06-15 20:11:52 +09:00

115 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 설계서: 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. 데이터 모델
### 응답 (기존 유지)
```json
[ { "title": "스타벅스 강남대로점", "url": "https://app.catchtable.co.kr/dining/12345" } ]
```
### Naver 응답 → 변환
```json
{ "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. 흐름
### 정상
1. `searchTabling(name)``webSearchService.search("스타벅스", ["tabling.co.kr/restaurant/", "tabling.co.kr/place/"])`.
2. Naver 호출 → 200, items 30 → URL 패턴 매칭 0~N건 추출.
3. 0건이면 DDG 폴백, 그래도 0건이면 빈 리스트.
### 폴백
- Naver 5xx / IOException / 키 미설정 → DDG.
### 키 등록 (운영자 작업)
1. https://developers.naver.com/apps/#/list 검색 앱 등록.
2. `NAVER_CLIENT_ID`, `NAVER_CLIENT_SECRET` 발급.
3. 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` 등록 → 어드민 `bulkTabling` SSE 실행 → 매칭율 비교 (이전 DDG 대비 ≥).
- 키 일부 제거 → DDG 폴백 동작 확인.
## 11. 리스크 & 대안
- **선택**: Naver primary + DDG fallback. 한국 식당 정확도 + 무료 한도 + 폴백 안정성.
- **대안 A**: Kakao Local Search — 식당 정보 직접 검색은 가능하지만 Tabling/Catchtable URL 매핑 부적합.
- **대안 B**: Google CSE — 비용/한도 제약.
- **트레이드오프**: Naver 응답 정확도가 압도적이지만 키 발급 운영자 작업 필요. 폴백으로 회귀 0 보장.
## 12. 미해결 질문
- Naver의 인덱스 신선도 vs Tabling/Catchtable 최신 입점 식당 미반영 가능성 — 백필 주기 후속.
- 결과 캐시(같은 식당 재호출) — 후속.