Compare commits
No commits in common. "2e27eb5742a0eef2c34a69da73d159b55db37f86" and "fd4d85cf9e199173582282c3dd499a55e1848cee" have entirely different histories.
2e27eb5742
...
fd4d85cf9e
|
|
@ -46,7 +46,3 @@ logs/
|
||||||
._*
|
._*
|
||||||
.Spotlight-V100
|
.Spotlight-V100
|
||||||
.Trashes
|
.Trashes
|
||||||
|
|
||||||
*.yml
|
|
||||||
Dockerfile
|
|
||||||
.dockerignore
|
|
||||||
|
|
@ -16,8 +16,6 @@ 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,
|
||||||
|
|
@ -27,7 +25,6 @@ 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
|
||||||
|
|
@ -73,47 +70,6 @@ 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:
|
||||||
|
|
|
||||||
|
|
@ -139,45 +139,6 @@ 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):
|
||||||
"""가공된 장소 정보 스키마"""
|
"""가공된 장소 정보 스키마"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
"""
|
|
||||||
네이버 지역 검색 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()
|
|
||||||
14
config.py
14
config.py
|
|
@ -105,19 +105,6 @@ 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 설정"""
|
||||||
|
|
||||||
|
|
@ -453,7 +440,6 @@ 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
28
main.py
|
|
@ -51,33 +51,6 @@ 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 - 장소 정보 및 이미지 수집
|
||||||
|
|
@ -217,7 +190,6 @@ def custom_openapi():
|
||||||
"/auth/test/", # 테스트 엔드포인트
|
"/auth/test/", # 테스트 엔드포인트
|
||||||
"/crawling",
|
"/crawling",
|
||||||
"/autocomplete",
|
"/autocomplete",
|
||||||
"/search", # 숙박 검색 자동완성
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# 보안이 필요한 엔드포인트에 security 적용
|
# 보안이 필요한 엔드포인트에 security 적용
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue