크롤링에 마케팅 분석 추가, 태그 추가, 부대 시설 추가 완료
parent
79ec5daa0d
commit
b5b8e88005
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import re
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -19,8 +20,10 @@ from app.home.schemas.home import (
|
|||
GenerateResponse,
|
||||
GenerateUploadResponse,
|
||||
GenerateUrlsRequest,
|
||||
MarketingAnalysis,
|
||||
ProcessedInfo,
|
||||
)
|
||||
from app.utils.chatgpt_prompt import ChatgptService
|
||||
from app.home.worker.main_task import task_process
|
||||
from app.utils.nvMapScraper import NvMapScraper
|
||||
|
||||
|
|
@ -68,6 +71,32 @@ def _extract_region_from_address(road_address: str | None) -> str:
|
|||
return ""
|
||||
|
||||
|
||||
def _parse_marketing_analysis(raw_response: str) -> MarketingAnalysis:
|
||||
"""ChatGPT 마케팅 분석 응답을 파싱하여 MarketingAnalysis 객체로 변환"""
|
||||
tags: list[str] = []
|
||||
facilities: list[str] = []
|
||||
report = raw_response
|
||||
|
||||
# JSON 블록 추출 시도
|
||||
json_match = re.search(r"```json\s*(\{.*?\})\s*```", raw_response, re.DOTALL)
|
||||
if json_match:
|
||||
try:
|
||||
json_data = json.loads(json_match.group(1))
|
||||
tags = json_data.get("tags", [])
|
||||
facilities = json_data.get("facilities", [])
|
||||
# JSON 블록을 제외한 리포트 부분 추출
|
||||
report = raw_response[: json_match.start()].strip()
|
||||
# --- 구분자 제거
|
||||
if report.startswith("---"):
|
||||
report = report[3:].strip()
|
||||
if report.endswith("---"):
|
||||
report = report[:-3].strip()
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return MarketingAnalysis(report=report, tags=tags, facilities=facilities)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/crawling",
|
||||
summary="네이버 지도 크롤링",
|
||||
|
|
@ -100,18 +129,34 @@ async def crawling(request_body: CrawlingRequest):
|
|||
|
||||
# 가공된 정보 생성
|
||||
processed_info = None
|
||||
marketing_analysis = None
|
||||
|
||||
if scraper.base_info:
|
||||
road_address = scraper.base_info.get("roadAddress", "")
|
||||
customer_name = scraper.base_info.get("name", "")
|
||||
region = _extract_region_from_address(road_address)
|
||||
|
||||
processed_info = ProcessedInfo(
|
||||
customer_name=scraper.base_info.get("name", ""),
|
||||
region=_extract_region_from_address(road_address),
|
||||
customer_name=customer_name,
|
||||
region=region,
|
||||
detail_region_info=road_address or "",
|
||||
)
|
||||
|
||||
# ChatGPT를 이용한 마케팅 분석
|
||||
chatgpt_service = ChatgptService(
|
||||
customer_name=customer_name,
|
||||
region=region,
|
||||
detail_region_info=road_address or "",
|
||||
)
|
||||
prompt = chatgpt_service.build_market_analysis_prompt()
|
||||
raw_response = await chatgpt_service.generate(prompt)
|
||||
marketing_analysis = _parse_marketing_analysis(raw_response)
|
||||
|
||||
return {
|
||||
"image_list": scraper.image_link_list,
|
||||
"image_count": len(scraper.image_link_list) if scraper.image_link_list else 0,
|
||||
"processed_info": processed_info,
|
||||
"marketing_analysis": marketing_analysis,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,14 @@ class ProcessedInfo(BaseModel):
|
|||
detail_region_info: str = Field(..., description="상세 지역 정보 (roadAddress)")
|
||||
|
||||
|
||||
class MarketingAnalysis(BaseModel):
|
||||
"""마케팅 분석 결과 스키마"""
|
||||
|
||||
report: str = Field(..., description="마케팅 분석 리포트")
|
||||
tags: list[str] = Field(default_factory=list, description="추천 태그 목록")
|
||||
facilities: list[str] = Field(default_factory=list, description="추천 부대시설 목록")
|
||||
|
||||
|
||||
class CrawlingResponse(BaseModel):
|
||||
"""크롤링 응답 스키마"""
|
||||
|
||||
|
|
@ -133,6 +141,9 @@ class CrawlingResponse(BaseModel):
|
|||
processed_info: Optional[ProcessedInfo] = Field(
|
||||
None, description="가공된 장소 정보 (customer_name, region, detail_region_info)"
|
||||
)
|
||||
marketing_analysis: Optional[MarketingAnalysis] = Field(
|
||||
None, description="마케팅 분석 결과 (report, tags, facilities)"
|
||||
)
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ async def lyric_task(
|
|||
lyric_id = await _save_lyric(task_id, project_id, lyric_prompt)
|
||||
|
||||
# GPT 호출
|
||||
result = await service.generate_lyrics(prompt=lyric_prompt)
|
||||
result = await service.generate(prompt=lyric_prompt)
|
||||
|
||||
print(f"GPT Response:\n{result}")
|
||||
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ async def make_song_result(request: Request, conn: Connection):
|
|||
print(f"\n[업데이트된 프롬프트]\n{updated_prompt}\n")
|
||||
|
||||
# 7. 모델에게 요청
|
||||
generated_lyrics = await chatgpt_api.generate_lyrics(prompt=updated_prompt)
|
||||
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
|
||||
|
||||
# 글자 수 계산
|
||||
total_chars_with_space = len(generated_lyrics)
|
||||
|
|
@ -733,7 +733,7 @@ async def make_automation(request: Request, conn: Connection):
|
|||
print(f"\n[최종 프롬프트 길이: {len(updated_prompt)} 자]\n")
|
||||
|
||||
# 7. 모델에게 요청
|
||||
generated_lyrics = await chatgpt_api.generate_lyrics(prompt=updated_prompt)
|
||||
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
|
||||
|
||||
# 글자 수 계산
|
||||
total_chars_with_space = len(generated_lyrics)
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ async def make_song_result(request: Request, conn: Connection):
|
|||
print(f"\n[업데이트된 프롬프트]\n{updated_prompt}\n")
|
||||
|
||||
# 7. 모델에게 요청
|
||||
generated_lyrics = await chatgpt_api.generate_lyrics(prompt=updated_prompt)
|
||||
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
|
||||
|
||||
# 글자 수 계산
|
||||
total_chars_with_space = len(generated_lyrics)
|
||||
|
|
@ -733,7 +733,7 @@ async def make_automation(request: Request, conn: Connection):
|
|||
print(f"\n[최종 프롬프트 길이: {len(updated_prompt)} 자]\n")
|
||||
|
||||
# 7. 모델에게 요청
|
||||
generated_lyrics = await chatgpt_api.generate_lyrics(prompt=updated_prompt)
|
||||
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
|
||||
|
||||
# 글자 수 계산
|
||||
total_chars_with_space = len(generated_lyrics)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,99 @@ ERROR: [Brief reason for failure in English]
|
|||
""".strip()
|
||||
# fmt: on
|
||||
|
||||
MARKETING_ANALYSIS_PROMPT_TEMPLATE = """
|
||||
[ROLE]
|
||||
Content marketing expert specializing in pension/accommodation services in Korea
|
||||
|
||||
[INPUT]
|
||||
- Business Name: {customer_name}
|
||||
- Region: {region}
|
||||
- Region Details: {detail_region_info}
|
||||
|
||||
[ANALYSIS REQUIREMENTS]
|
||||
Provide comprehensive marketing analysis including:
|
||||
1. Target Customer Segments
|
||||
- Primary and secondary target personas
|
||||
- Age groups, travel preferences, booking patterns
|
||||
2. Unique Selling Propositions (USPs)
|
||||
- Key differentiators based on location and region details
|
||||
- Competitive advantages
|
||||
3. Regional Characteristics
|
||||
- Nearby attractions and famous places (within 10 min access)
|
||||
- Local food, activities, and experiences
|
||||
- Transportation accessibility
|
||||
4. Seasonal Appeal Points
|
||||
- Best seasons to visit
|
||||
- Seasonal activities and events
|
||||
- Peak/off-peak marketing opportunities
|
||||
5. Marketing Keywords
|
||||
- Recommended hashtags and search keywords
|
||||
- Trending terms relevant to the property
|
||||
|
||||
[ADDITIONAL REQUIREMENTS]
|
||||
1. Recommended Tags
|
||||
- Generate 5 recommended hashtags/tags based on the business characteristics
|
||||
- Tags should be trendy, searchable, and relevant to accommodation marketing
|
||||
- Return as JSON with key "tags"
|
||||
- **MUST be written in Korean (한국어)**
|
||||
|
||||
2. Facilities
|
||||
- Based on the business name and region details, identify 5 likely facilities/amenities
|
||||
- Consider typical facilities for accommodations in the given region
|
||||
- Examples: 바베큐장, 수영장, 주차장, 와이파이, 주방, 테라스, 정원, etc.
|
||||
- Return as JSON with key "facilities"
|
||||
- **MUST be written in Korean (한국어)**
|
||||
|
||||
[CRITICAL LANGUAGE REQUIREMENT - ABSOLUTE RULE]
|
||||
ALL OUTPUT MUST BE WRITTEN IN KOREAN (한국어)
|
||||
- Analysis sections: Korean only
|
||||
- Tags: Korean only
|
||||
- Facilities: Korean only
|
||||
- This is a NON-NEGOTIABLE requirement
|
||||
- Any output in English or other languages is considered a FAILURE
|
||||
- Violation of this rule invalidates the entire response
|
||||
|
||||
[OUTPUT RULES - STRICTLY ENFORCED]
|
||||
- Output analysis ONLY
|
||||
- ALL content MUST be written in Korean (한국어) - NO EXCEPTIONS
|
||||
- NO greetings or closing remarks
|
||||
- NO additional commentary before or after analysis
|
||||
- Follow the exact format below
|
||||
|
||||
[OUTPUT FORMAT - SUCCESS]
|
||||
---
|
||||
## 타겟 고객 분석
|
||||
[한국어로 작성된 타겟 고객 분석]
|
||||
|
||||
## 핵심 차별점 (USP)
|
||||
[한국어로 작성된 USP 분석]
|
||||
|
||||
## 지역 특성
|
||||
[한국어로 작성된 지역 특성 분석]
|
||||
|
||||
## 시즌별 매력 포인트
|
||||
[한국어로 작성된 시즌별 분석]
|
||||
|
||||
## 마케팅 키워드
|
||||
[한국어로 작성된 마케팅 키워드]
|
||||
|
||||
## JSON Data
|
||||
```json
|
||||
{{
|
||||
"tags": ["태그1", "태그2", "태그3", "태그4", "태그5"],
|
||||
"facilities": ["부대시설1", "부대시설2", "부대시설3", "부대시설4", "부대시설5"]
|
||||
}}
|
||||
```
|
||||
---
|
||||
|
||||
[OUTPUT FORMAT - FAILURE]
|
||||
If you cannot generate analysis due to insufficient information, invalid input, or any other reason:
|
||||
---
|
||||
ERROR: [Brief reason for failure in English]
|
||||
---
|
||||
""".strip()
|
||||
# fmt: on
|
||||
|
||||
|
||||
class ChatgptService:
|
||||
def __init__(
|
||||
|
|
@ -105,7 +198,15 @@ class ChatgptService:
|
|||
detail_region_info=self.detail_region_info,
|
||||
)
|
||||
|
||||
async def generate_lyrics(self, prompt: str | None = None) -> str:
|
||||
def build_market_analysis_prompt(self) -> str:
|
||||
"""MARKETING_ANALYSIS_PROMPT_TEMPLATE에 고객 정보를 대입하여 완성된 프롬프트 반환"""
|
||||
return MARKETING_ANALYSIS_PROMPT_TEMPLATE.format(
|
||||
customer_name=self.customer_name,
|
||||
region=self.region,
|
||||
detail_region_info=self.detail_region_info,
|
||||
)
|
||||
|
||||
async def generate(self, prompt: str | None = None) -> str:
|
||||
"""GPT에게 프롬프트를 전달하여 결과를 반환"""
|
||||
if prompt is None:
|
||||
prompt = self.build_lyrics_prompt()
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ query getAccommodation($id: String!, $deviceType: String) {
|
|||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# import asyncio
|
||||
|
||||
# url = "https://map.naver.com/p/search/%EC%8A%A4%ED%85%8C%EC%9D%B4%EB%A8%B8%EB%AD%84/place/1133638931?c=14.70,0,0,0,dh&placePath=/photo?businessCategory=pension&fromPanelNum=2&locale=ko&searchText=%EC%8A%A4%ED%85%8C%EC%9D%B4%EB%A8%B8%EB%AD%84&svcName=map_pcv5×tamp=202512191123&fromPanelNum=2&locale=ko&searchText=%EC%8A%A4%ED%85%8C%EC%9D%B4%EB%A8%B8%EB%AD%84&svcName=map_pcv5×tamp=202512191007&from=map&entry=bmp&filterType=%EC%97%85%EC%B2%B4&businessCategory=pension"
|
||||
# scraper = NvMapScraper(url)
|
||||
# asyncio.run(scraper.scrap())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import requests
|
||||
from pathlib import Path
|
||||
|
||||
SAS_TOKEN = "sp=racwdl&st=2025-12-01T00:13:29Z&se=2026-07-31T08:28:29Z&spr=https&sv=2024-11-04&sr=c&sig=7fE2ozVBPu3Gq43%2FZDxEYdEcPLDXyNVfTf16IBasmVQ%3D"
|
||||
|
||||
def upload_music_to_azure_blob(file_path = "스테이 머뭄_1.mp3", url = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp3"):
|
||||
access_url = f"{url}?{SAS_TOKEN}"
|
||||
headers = {
|
||||
"Content-Type": "audio/mpeg",
|
||||
"x-ms-blob-type": "BlockBlob"
|
||||
}
|
||||
with open(file_path, "rb") as file:
|
||||
response = requests.put(access_url, data=file, headers=headers)
|
||||
if response.status_code in [200, 201]:
|
||||
print(f"Success Status Code: {response.status_code}")
|
||||
else:
|
||||
print(f"Failed Status Code: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
|
||||
def upload_video_to_azure_blob(file_path = "스테이 머뭄.mp4", url = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp4"):
|
||||
access_url = f"{url}?{SAS_TOKEN}"
|
||||
headers = {
|
||||
"Content-Type": "video/mp4",
|
||||
"x-ms-blob-type": "BlockBlob"
|
||||
}
|
||||
with open(file_path, "rb") as file:
|
||||
response = requests.put(access_url, data=file, headers=headers)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
print(f"Success Status Code: {response.status_code}")
|
||||
else:
|
||||
print(f"Failed Status Code: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
|
||||
|
||||
def upload_image_to_azure_blob(file_path = "스테이 머뭄.png", url = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.png"):
|
||||
access_url = f"{url}?{SAS_TOKEN}"
|
||||
extension = Path(file_path).suffix.lower()
|
||||
content_types = {
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".bmp": "image/bmp"
|
||||
}
|
||||
content_type = content_types.get(extension, "image/jpeg")
|
||||
headers = {
|
||||
"Content-Type": content_type,
|
||||
"x-ms-blob-type": "BlockBlob"
|
||||
}
|
||||
with open(file_path, "rb") as file:
|
||||
response = requests.put(access_url, data=file, headers=headers)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
print(f"Success Status Code: {response.status_code}")
|
||||
else:
|
||||
print(f"Failed Status Code: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
|
||||
|
||||
upload_video_to_azure_blob()
|
||||
|
||||
upload_image_to_azure_blob()
|
||||
|
|
@ -332,7 +332,7 @@ async def make_song_result(request: Request, conn: Connection):
|
|||
print(f"\n[업데이트된 프롬프트]\n{updated_prompt}\n")
|
||||
|
||||
# 7. 모델에게 요청
|
||||
generated_lyrics = await chatgpt_api.generate_lyrics(prompt=updated_prompt)
|
||||
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
|
||||
|
||||
# 글자 수 계산
|
||||
total_chars_with_space = len(generated_lyrics)
|
||||
|
|
@ -733,7 +733,7 @@ async def make_automation(request: Request, conn: Connection):
|
|||
print(f"\n[최종 프롬프트 길이: {len(updated_prompt)} 자]\n")
|
||||
|
||||
# 7. 모델에게 요청
|
||||
generated_lyrics = await chatgpt_api.generate_lyrics(prompt=updated_prompt)
|
||||
generated_lyrics = await chatgpt_api.generate(prompt=updated_prompt)
|
||||
|
||||
# 글자 수 계산
|
||||
total_chars_with_space = len(generated_lyrics)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ app/home/tests/home/__init__.py
|
|||
app/home/tests/home/conftest.py
|
||||
app/home/tests/home/test_db.py
|
||||
app/home/worker/__init__.py
|
||||
app/home/worker/main_task.py
|
||||
app/lyric/__init__.py
|
||||
app/lyric/dependencies.py
|
||||
app/lyric/models.py
|
||||
|
|
@ -44,8 +45,9 @@ app/lyric/api/__init__.py
|
|||
app/lyric/api/lyrics_admin.py
|
||||
app/lyric/api/routers/__init__.py
|
||||
app/lyric/api/routers/v1/__init__.py
|
||||
app/lyric/api/routers/v1/router.py
|
||||
app/lyric/api/routers/v1/lyric.py
|
||||
app/lyric/schemas/__init__.py
|
||||
app/lyric/schemas/lyric.py
|
||||
app/lyric/schemas/lyrics_schema.py
|
||||
app/lyric/services/__init__.py
|
||||
app/lyric/services/base.py
|
||||
|
|
@ -77,6 +79,7 @@ app/utils/__init__.py
|
|||
app/utils/chatgpt_prompt.py
|
||||
app/utils/cors.py
|
||||
app/utils/nvMapScraper.py
|
||||
app/utils/upload_blob_as_request.py
|
||||
app/video/__init__.py
|
||||
app/video/dependencies.py
|
||||
app/video/models.py
|
||||
|
|
|
|||
Loading…
Reference in New Issue