Skip to content

Commit 6ce32f9

Browse files
committed
장소명에 대한 지도 스크래퍼 기능 추가 : feat : 구글, 네이버지도 에대한 장소정보 추출 로직 추가 #11
1 parent 6ec8e93 commit 6ce32f9

6 files changed

Lines changed: 1135 additions & 1 deletion

File tree

src/apis/test_router.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
"""
44
import logging
55
from fastapi import APIRouter
6-
from pydantic import BaseModel
6+
from pydantic import BaseModel, Field
77

88
from src.services.scraper.scrape_router import route_and_scrape
9+
from src.services.scraper.platforms.naver_map_scraper import NaverMapScraper
10+
from src.services.scraper.platforms.google_map_scraper import GoogleMapScraper
911

1012
logger = logging.getLogger(__name__)
1113
router = APIRouter(prefix="/api/test", tags=["테스트 API"])
@@ -15,6 +17,16 @@ class ScrapeRequest(BaseModel):
1517
url: str
1618

1719

20+
class NaverMapSearchRequest(BaseModel):
21+
"""네이버 지도 검색 요청"""
22+
query: str = Field(..., description="검색어 (예: 늘푸른목장)", min_length=1)
23+
24+
25+
class GoogleMapSearchRequest(BaseModel):
26+
"""구글 지도 검색 요청"""
27+
query: str = Field(..., description="검색어 (예: 늘푸른목장)", min_length=1)
28+
29+
1830
@router.post("/scrape", status_code=200)
1931
async def scrape_url(request: ScrapeRequest):
2032
"""
@@ -33,3 +45,61 @@ async def scrape_url(request: ScrapeRequest):
3345
async def health_check():
3446
"""스크래핑 테스트 API 상태 확인"""
3547
return {"status": "ok"}
48+
49+
50+
@router.post("/naver-map", status_code=200)
51+
async def scrape_naver_map(request: NaverMapSearchRequest):
52+
"""
53+
네이버 지도에서 장소 정보 스크래핑
54+
55+
장소명을 검색하여 첫 번째 검색 결과의 상세 정보를 추출합니다.
56+
57+
- POST /api/test/naver-map
58+
- Body: {"query": "늘푸른목장"}
59+
- 성공: 200 + NaverPlaceInfo
60+
- 실패: 4xx/5xx + 에러 메시지
61+
62+
추출 정보:
63+
- 장소명, 카테고리
64+
- 별점, 리뷰 수 (방문자/블로그)
65+
- 주소, 지하철 정보
66+
- 영업 상태, 영업 시간
67+
- 전화번호, 편의시설
68+
"""
69+
logger.info(f"네이버 지도 스크래핑 요청: query='{request.query}'")
70+
71+
scraper = NaverMapScraper()
72+
result = await scraper.search_and_scrape(request.query)
73+
74+
logger.info(f"스크래핑 완료: {result.name} ({result.place_id})")
75+
return result
76+
77+
78+
@router.post("/google-map", status_code=200)
79+
async def scrape_google_map(request: GoogleMapSearchRequest):
80+
"""
81+
구글 지도에서 장소 정보 스크래핑
82+
83+
장소명을 검색하여 첫 번째 검색 결과의 상세 정보를 추출합니다.
84+
85+
- POST /api/test/google-map
86+
- Body: {"query": "늘푸른목장"}
87+
- 성공: 200 + GooglePlaceInfo
88+
- 실패: 4xx/5xx + 에러 메시지
89+
90+
추출 정보:
91+
- 장소명, 카테고리
92+
- 별점, 리뷰 수
93+
- 가격대, 주소
94+
- 영업 상태, 요일별 영업 시간
95+
- 전화번호, Plus Code
96+
- 위도/경도, 웹사이트 URL
97+
- 대표 이미지 URL
98+
"""
99+
logger.info(f"구글 지도 스크래핑 요청: query='{request.query}'")
100+
101+
scraper = GoogleMapScraper()
102+
result = await scraper.search_and_scrape(request.query)
103+
104+
logger.info(f"스크래핑 완료: {result.name} ({result.place_id})")
105+
return result

src/models/google_place_info.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""src.models.google_place_info
2+
구글 지도 장소 정보 스키마
3+
"""
4+
from pydantic import BaseModel, Field
5+
6+
7+
class GooglePlaceInfo(BaseModel):
8+
"""
9+
구글 지도 장소 상세 정보
10+
11+
구글 지도 검색 결과에서 추출한 장소 정보를 담는 스키마입니다.
12+
"""
13+
# 기본 정보
14+
place_id: str = Field(..., description="구글 Place ID (URL에서 추출)")
15+
name: str = Field(..., description="장소명")
16+
category: str | None = Field(default=None, description="카테고리 (예: 숯불구이/바베큐전문점)")
17+
18+
# URL 정보
19+
google_map_url: str | None = Field(default=None, description="구글 지도 상세 페이지 URL")
20+
21+
# 위치 정보
22+
latitude: float | None = Field(default=None, description="위도")
23+
longitude: float | None = Field(default=None, description="경도")
24+
address: str | None = Field(default=None, description="주소")
25+
plus_code: str | None = Field(default=None, description="Plus Code (예: G35M+R3 서울특별시)")
26+
27+
# 평점/리뷰
28+
rating: float | None = Field(default=None, description="별점 (0.0 ~ 5.0)")
29+
review_count: int | None = Field(default=None, description="리뷰 수")
30+
price_level: str | None = Field(default=None, description="가격대 (예: ₩₩₩)")
31+
32+
# 영업 정보
33+
business_status: str | None = Field(default=None, description="영업 상태 (예: 영업 중)")
34+
business_hours: dict[str, str] | None = Field(default=None, description="요일별 영업 시간")
35+
36+
# 연락처/링크
37+
phone_number: str | None = Field(default=None, description="전화번호")
38+
website_url: str | None = Field(default=None, description="웹사이트 URL")
39+
40+
# 부가 정보
41+
description: str | None = Field(default=None, description="장소 설명/소개")
42+
amenities: list[str] = Field(default_factory=list, description="편의시설/서비스 옵션")
43+
popular_times: str | None = Field(default=None, description="인기 시간대 정보")
44+
45+
# 이미지
46+
image_url: str | None = Field(default=None, description="대표 이미지 URL")
47+
image_urls: list[str] = Field(default_factory=list, description="이미지 URL 목록 (최대 10개)")
48+
49+
class Config:
50+
json_schema_extra = {
51+
"example": {
52+
"place_id": "0x357ca44d6e36590b:0xc0019dfb09b9a4e2",
53+
"name": "늘푸른목장 잠실본점",
54+
"category": "숯불구이/바베큐전문점",
55+
"google_map_url": "https://www.google.com/maps/place/...",
56+
"latitude": 37.509573,
57+
"longitude": 127.0826454,
58+
"address": "서울특별시 송파구 백제고분로9길 34",
59+
"plus_code": "G35M+R3 서울특별시",
60+
"rating": 4.2,
61+
"review_count": 753,
62+
"price_level": "₩₩₩",
63+
"business_status": "영업 중",
64+
"business_hours": {
65+
"일요일": "24시간 영업",
66+
"월요일": "PM 12:00~AM 12:00",
67+
"화요일": "PM 12:00~AM 12:00",
68+
"수요일": "PM 12:00~AM 12:00",
69+
"목요일": "PM 12:00~AM 12:00",
70+
"금요일": "PM 12:00~AM 12:00",
71+
"토요일": "PM 12:00~AM 12:00"
72+
},
73+
"phone_number": "02-3431-4520",
74+
"website_url": "https://example.com",
75+
"description": "된장찌개와 냉면으로 완성하는 한상차림",
76+
"amenities": ["매장 내 식사", "테이크아웃", "배달"],
77+
"popular_times": "오후 7시~9시 가장 붐빔",
78+
"image_url": "https://lh5.googleusercontent.com/...",
79+
"image_urls": ["https://...", "https://..."]
80+
}
81+
}

src/models/naver_place_info.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""src.models.naver_place_info
2+
네이버 지도 장소 정보 스키마
3+
"""
4+
from pydantic import BaseModel, Field
5+
6+
7+
class NaverPlaceInfo(BaseModel):
8+
"""
9+
네이버 지도 장소 상세 정보
10+
11+
네이버 지도 검색 결과에서 추출한 장소 정보를 담는 스키마입니다.
12+
"""
13+
# 기본 정보
14+
place_id: str = Field(..., description="네이버 Place ID")
15+
name: str = Field(..., description="장소명")
16+
category: str | None = Field(default=None, description="카테고리 (예: 소고기구이)")
17+
18+
# URL 정보 (라우팅용)
19+
naver_map_url: str | None = Field(default=None, description="네이버 지도 상세 페이지 URL")
20+
21+
# 위치 정보
22+
latitude: float | None = Field(default=None, description="위도")
23+
longitude: float | None = Field(default=None, description="경도")
24+
address: str | None = Field(default=None, description="주소")
25+
road_address: str | None = Field(default=None, description="도로명 주소")
26+
subway_info: str | None = Field(default=None, description="지하철 정보 (예: 잠실새내역 4번 출구에서 412m)")
27+
directions_text: str | None = Field(default=None, description="찾아가는 길 설명")
28+
29+
# 평점/리뷰
30+
rating: float | None = Field(default=None, description="별점 (0.0 ~ 5.0)")
31+
visitor_review_count: int | None = Field(default=None, description="방문자 리뷰 수")
32+
blog_review_count: int | None = Field(default=None, description="블로그 리뷰 수")
33+
34+
# 영업 정보
35+
business_status: str | None = Field(default=None, description="영업 상태 (예: 영업 중)")
36+
business_hours: str | None = Field(default=None, description="영업 시간 요약")
37+
open_hours_detail: list[str] = Field(default_factory=list, description="요일별 상세 영업시간")
38+
holiday_info: str | None = Field(default=None, description="휴무일 정보")
39+
40+
# 연락처/링크
41+
phone_number: str | None = Field(default=None, description="전화번호")
42+
homepage_url: str | None = Field(default=None, description="홈페이지 URL")
43+
reservation_available: bool = Field(default=False, description="예약 가능 여부")
44+
45+
# 부가 정보
46+
description: str | None = Field(default=None, description="한줄 설명")
47+
amenities: list[str] = Field(default_factory=list, description="편의시설 목록")
48+
keywords: list[str] = Field(default_factory=list, description="키워드/태그")
49+
tv_appearances: list[str] = Field(default_factory=list, description="TV 방송 출연 정보")
50+
menu_info: list[str] = Field(default_factory=list, description="대표 메뉴")
51+
52+
# 이미지
53+
image_url: str | None = Field(default=None, description="대표 이미지 URL")
54+
image_urls: list[str] = Field(default_factory=list, description="이미지 URL 목록 (최대 10개)")
55+
56+
class Config:
57+
json_schema_extra = {
58+
"example": {
59+
"place_id": "11679241",
60+
"name": "늘푸른목장 잠실본점",
61+
"category": "소고기구이",
62+
"naver_map_url": "https://map.naver.com/p/search/늘푸른목장/place/11679241",
63+
"latitude": 37.5112,
64+
"longitude": 127.0867,
65+
"address": "서울 송파구 백제고분로9길 34 1F",
66+
"road_address": "서울 송파구 백제고분로9길 34 1F",
67+
"subway_info": "잠실새내역 4번 출구에서 412m",
68+
"directions_text": "잠실새내역 4번 출구에서 맥도널드 골목 끼고...",
69+
"rating": 4.42,
70+
"visitor_review_count": 1510,
71+
"blog_review_count": 1173,
72+
"business_status": "영업 중",
73+
"business_hours": "24:00에 영업 종료",
74+
"open_hours_detail": ["월 11:30 - 24:00", "화 11:30 - 24:00"],
75+
"holiday_info": "연중무휴",
76+
"phone_number": "02-3431-4520",
77+
"homepage_url": "http://example.com",
78+
"reservation_available": True,
79+
"description": "된장찌개와 냉면으로 완성하는 한상차림",
80+
"amenities": ["단체 이용 가능", "주차", "발렛파킹"],
81+
"keywords": ["소고기", "한우", "회식"],
82+
"tv_appearances": ["줄서는식당 14회 (24.05.13)"],
83+
"menu_info": ["경주갈비살", "한우된장밥"],
84+
"image_url": "https://...",
85+
"image_urls": ["https://...", "https://..."]
86+
}
87+
}

0 commit comments

Comments
 (0)