diff --git a/.gitignore b/.gitignore
index e098bf8..53dc64e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,3 +46,7 @@ logs/
._*
.Spotlight-V100
.Trashes
+
+*.yml
+Dockerfile
+.dockerignore
\ No newline at end of file
diff --git a/app/home/api/routers/v1/home.py b/app/home/api/routers/v1/home.py
index 97e167c..d5bb7c1 100644
--- a/app/home/api/routers/v1/home.py
+++ b/app/home/api/routers/v1/home.py
@@ -16,6 +16,8 @@ from app.user.dependencies.auth import get_current_user
from app.user.models import User
from app.home.schemas.home_schema import (
AutoCompleteRequest,
+ AccommodationSearchItem,
+ AccommodationSearchResponse,
CrawlingRequest,
CrawlingResponse,
ErrorResponse,
@@ -25,6 +27,7 @@ from app.home.schemas.home_schema import (
MarketingAnalysis,
ProcessedInfo,
)
+from app.home.services.naver_search import naver_search_client
from app.utils.upload_blob_as_request import AzureBlobUploader
from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError
from app.utils.common import generate_task_id
@@ -70,6 +73,47 @@ KOREAN_CITIES = [
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:
"""roadAddress에서 시 이름 추출"""
if not road_address:
diff --git a/app/home/schemas/home_schema.py b/app/home/schemas/home_schema.py
index 0985da3..b2d6bbf 100644
--- a/app/home/schemas/home_schema.py
+++ b/app/home/schemas/home_schema.py
@@ -139,6 +139,45 @@ class AutoCompleteRequest(BaseModel):
address: str = Field(..., 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": "스테이,머뭄",
+ "address": "전북특별자치도 군산시 신흥동 63-18",
+ "roadAddress": "전북특별자치도 군산시 절골길 18",
+ },
+ {
+ "title": "머뭄스테이",
+ "address": "전북특별자치도 군산시 비응도동 123",
+ "roadAddress": "전북특별자치도 군산시 비응로 456",
+ },
+ ],
+ }
+ }
+ )
+
+ query: str = Field(..., description="검색어")
+ count: int = Field(..., description="검색 결과 수")
+ items: list[AccommodationSearchItem] = Field(
+ default_factory=list, description="검색 결과 목록"
+ )
+
class ProcessedInfo(BaseModel):
"""가공된 장소 정보 스키마"""
diff --git a/app/home/services/naver_search.py b/app/home/services/naver_search.py
new file mode 100644
index 0000000..343b1c5
--- /dev/null
+++ b/app/home/services/naver_search.py
@@ -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()
diff --git a/config.py b/config.py
index 05b2016..a3be1b1 100644
--- a/config.py
+++ b/config.py
@@ -105,6 +105,19 @@ class CrawlerSettings(BaseSettings):
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):
"""Azure Blob Storage 설정"""
@@ -440,6 +453,7 @@ apikey_settings = APIKeySettings()
db_settings = DatabaseSettings()
cors_settings = CORSSettings()
crawler_settings = CrawlerSettings()
+naver_api_settings = NaverAPISettings()
azure_blob_settings = AzureBlobSettings()
creatomate_settings = CreatomateSettings()
prompt_settings = PromptSettings()
diff --git a/main.py b/main.py
index 516cada..43763a7 100644
--- a/main.py
+++ b/main.py
@@ -51,6 +51,33 @@ tags_metadata = [
# "name": "Home",
# "description": "홈 화면 및 프로젝트 관리 API",
# },
+ {
+ "name": "Search",
+ "description": """숙박/펜션 검색 API - 네이버 지역 검색 기반 자동완성
+
+**인증: 불필요** (공개 API)
+
+## 사용법
+
+`GET /search/accommodation?query=스테이머뭄`
+
+## 응답 예시
+
+```json
+{
+ "query": "스테이머뭄",
+ "count": 1,
+ "items": [
+ {
+ "title": "스테이,머뭄",
+ "address": "전북특별자치도 군산시 신흥동 63-18",
+ "roadAddress": "전북특별자치도 군산시 절골길 18"
+ }
+ ]
+}
+```
+""",
+ },
{
"name": "Crawling",
"description": """네이버 지도 크롤링 API - 장소 정보 및 이미지 수집
@@ -190,6 +217,7 @@ def custom_openapi():
"/auth/test/", # 테스트 엔드포인트
"/crawling",
"/autocomplete",
+ "/search", # 숙박 검색 자동완성
]
# 보안이 필요한 엔드포인트에 security 적용