자동완성 기능 추가 .

feature-youtube-upload
hbyang 2026-01-29 16:03:25 +09:00
parent 51c4ea7552
commit 1cb698e8ea
5 changed files with 224 additions and 0 deletions

View File

@ -16,6 +16,8 @@ from app.user.dependencies.auth import get_current_user
from app.user.models import User from app.user.models import User
from app.home.schemas.home_schema import ( from app.home.schemas.home_schema import (
AutoCompleteRequest, AutoCompleteRequest,
AccommodationSearchItem,
AccommodationSearchResponse,
CrawlingRequest, CrawlingRequest,
CrawlingResponse, CrawlingResponse,
ErrorResponse, ErrorResponse,
@ -25,6 +27,7 @@ from app.home.schemas.home_schema import (
MarketingAnalysis, MarketingAnalysis,
ProcessedInfo, ProcessedInfo,
) )
from app.home.services.naver_search import naver_search_client
from app.utils.upload_blob_as_request import AzureBlobUploader from app.utils.upload_blob_as_request import AzureBlobUploader
from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError
from app.utils.common import generate_task_id from app.utils.common import generate_task_id
@ -70,6 +73,47 @@ KOREAN_CITIES = [
router = APIRouter() router = APIRouter()
@router.get(
"/search/accommodation",
summary="숙박/펜션 자동완성 검색",
description="""
네이버 지역 검색 API를 이용한 숙박/펜션 자동완성 검색입니다.
## 요청 파라미터
- **query**: 검색어 (필수)
## 반환 정보
- **query**: 검색어
- **count**: 검색 결과 (최대 10)
- **items**: 검색 결과 목록
- **title**: 숙소명 (HTML 태그 포함 가능)
- **address**: 지번 주소
- **roadAddress**: 도로명 주소
""",
response_model=AccommodationSearchResponse,
responses={
200: {"description": "검색 성공", "model": AccommodationSearchResponse},
},
tags=["Search"],
)
async def search_accommodation(
query: str,
) -> AccommodationSearchResponse:
"""숙박/펜션 자동완성 검색"""
results = await naver_search_client.search_accommodation(
query=query,
display=10,
)
items = [AccommodationSearchItem(**item) for item in results]
return AccommodationSearchResponse(
query=query,
count=len(items),
items=items,
)
def _extract_region_from_address(road_address: str | None) -> str: def _extract_region_from_address(road_address: str | None) -> str:
"""roadAddress에서 시 이름 추출""" """roadAddress에서 시 이름 추출"""
if not road_address: if not road_address:

View File

@ -139,6 +139,45 @@ class AutoCompleteRequest(BaseModel):
address: str = Field(..., description="네이버 검색 place API 지번주소") address: str = Field(..., description="네이버 검색 place API 지번주소")
roadAddress: Optional[str] = Field(None, description="네이버 검색 place API 도로명주소") roadAddress: Optional[str] = Field(None, description="네이버 검색 place API 도로명주소")
class AccommodationSearchItem(BaseModel):
"""숙박 검색 결과 아이템"""
title: str = Field(..., description="숙소명 (HTML 태그 포함 가능)")
address: str = Field(..., description="지번 주소")
roadAddress: str = Field(default="", description="도로명 주소")
class AccommodationSearchResponse(BaseModel):
"""숙박 자동완성 검색 응답"""
model_config = ConfigDict(
json_schema_extra={
"example": {
"query": "스테이 머뭄",
"count": 2,
"items": [
{
"title": "<b>스테이</b>,<b>머뭄</b>",
"address": "전북특별자치도 군산시 신흥동 63-18",
"roadAddress": "전북특별자치도 군산시 절골길 18",
},
{
"title": "머뭄<b>스테이</b>",
"address": "전북특별자치도 군산시 비응도동 123",
"roadAddress": "전북특별자치도 군산시 비응로 456",
},
],
}
}
)
query: str = Field(..., description="검색어")
count: int = Field(..., description="검색 결과 수")
items: list[AccommodationSearchItem] = Field(
default_factory=list, description="검색 결과 목록"
)
class ProcessedInfo(BaseModel): class ProcessedInfo(BaseModel):
"""가공된 장소 정보 스키마""" """가공된 장소 정보 스키마"""

View File

@ -0,0 +1,99 @@
"""
네이버 지역 검색 API 클라이언트
숙박/펜션 자동완성 검색 기능을 제공합니다.
"""
import logging
from typing import List
import aiohttp
from config import naver_api_settings
logger = logging.getLogger(__name__)
class NaverSearchClient:
"""
네이버 지역 검색 API 클라이언트
숙박/펜션 카테고리 검색을 위한 클라이언트입니다.
"""
def __init__(self) -> None:
self.client_id = naver_api_settings.NAVER_CLIENT_ID
self.client_secret = naver_api_settings.NAVER_CLIENT_SECRET
self.api_url = naver_api_settings.NAVER_LOCAL_API_URL
async def search_accommodation(
self,
query: str,
display: int = 5,
) -> List[dict]:
"""
숙박/펜션 검색
Args:
query: 검색어
display: 결과 개수 (기본 5)
Returns:
검색 결과 리스트 (address, roadAddress, title)
"""
# 숙박/펜션 카테고리 검색을 위해 쿼리에 키워드 추가
search_query = f"{query} 숙박"
headers = {
"X-Naver-Client-Id": self.client_id,
"X-Naver-Client-Secret": self.client_secret,
}
params = {
"query": search_query,
"display": display,
"sort": "random", # 정확도순
}
logger.info(f"[NAVER] 지역 검색 요청 - query: {search_query}")
try:
async with aiohttp.ClientSession() as session:
async with session.get(
self.api_url,
headers=headers,
params=params,
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(
f"[NAVER] API 오류 - status: {response.status}, error: {error_text}"
)
return []
data = await response.json()
items = data.get("items", [])
# 필요한 필드만 추출
results = [
{
"address": item.get("address", ""),
"roadAddress": item.get("roadAddress", ""),
"title": item.get("title", ""),
}
for item in items
]
logger.info(f"[NAVER] 검색 완료 - 결과 수: {len(results)}")
return results
except aiohttp.ClientError as e:
logger.error(f"[NAVER] 네트워크 오류 - {str(e)}")
return []
except Exception as e:
logger.error(f"[NAVER] 예기치 않은 오류 - {str(e)}")
return []
# 싱글톤 인스턴스
naver_search_client = NaverSearchClient()

View File

@ -105,6 +105,19 @@ class CrawlerSettings(BaseSettings):
model_config = _base_config model_config = _base_config
class NaverAPISettings(BaseSettings):
"""네이버 API 설정"""
NAVER_CLIENT_ID: str = Field(default="", description="네이버 API 클라이언트 ID")
NAVER_CLIENT_SECRET: str = Field(default="", description="네이버 API 클라이언트 시크릿")
NAVER_LOCAL_API_URL: str = Field(
default="https://openapi.naver.com/v1/search/local.json",
description="네이버 지역 검색 API URL",
)
model_config = _base_config
class AzureBlobSettings(BaseSettings): class AzureBlobSettings(BaseSettings):
"""Azure Blob Storage 설정""" """Azure Blob Storage 설정"""
@ -437,6 +450,7 @@ apikey_settings = APIKeySettings()
db_settings = DatabaseSettings() db_settings = DatabaseSettings()
cors_settings = CORSSettings() cors_settings = CORSSettings()
crawler_settings = CrawlerSettings() crawler_settings = CrawlerSettings()
naver_api_settings = NaverAPISettings()
azure_blob_settings = AzureBlobSettings() azure_blob_settings = AzureBlobSettings()
creatomate_settings = CreatomateSettings() creatomate_settings = CreatomateSettings()
prompt_settings = PromptSettings() prompt_settings = PromptSettings()

28
main.py
View File

@ -51,6 +51,33 @@ tags_metadata = [
# "name": "Home", # "name": "Home",
# "description": "홈 화면 및 프로젝트 관리 API", # "description": "홈 화면 및 프로젝트 관리 API",
# }, # },
{
"name": "Search",
"description": """숙박/펜션 검색 API - 네이버 지역 검색 기반 자동완성
**인증: 불필요** (공개 API)
## 사용법
`GET /search/accommodation?query=스테이머뭄`
## 응답 예시
```json
{
"query": "스테이머뭄",
"count": 1,
"items": [
{
"title": "<b>스테이</b>,<b>머뭄</b>",
"address": "전북특별자치도 군산시 신흥동 63-18",
"roadAddress": "전북특별자치도 군산시 절골길 18"
}
]
}
```
""",
},
{ {
"name": "Crawling", "name": "Crawling",
"description": """네이버 지도 크롤링 API - 장소 정보 및 이미지 수집 "description": """네이버 지도 크롤링 API - 장소 정보 및 이미지 수집
@ -190,6 +217,7 @@ def custom_openapi():
"/auth/test/", # 테스트 엔드포인트 "/auth/test/", # 테스트 엔드포인트
"/crawling", "/crawling",
"/autocomplete", "/autocomplete",
"/search", # 숙박 검색 자동완성
] ]
# 보안이 필요한 엔드포인트에 security 적용 # 보안이 필요한 엔드포인트에 security 적용