ssboost commited on
Commit
af8c277
Β·
verified Β·
1 Parent(s): b6d7a1b

Update product_blog.py

Browse files
Files changed (1) hide show
  1. product_blog.py +556 -1
product_blog.py CHANGED
@@ -1,2 +1,557 @@
1
  import os
2
- exec(os.environ.get('PRODUCT'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import random
3
+ import re
4
+ import requests
5
+ import logging
6
+ from bs4 import BeautifulSoup
7
+ import html
8
+ import markdown2
9
+ from dotenv import load_dotenv # μΆ”κ°€λœ λΆ€λΆ„
10
+
11
+ # ν™˜κ²½ λ³€μˆ˜ λ‘œλ“œ
12
+ load_dotenv() # μΆ”κ°€λœ λΆ€λΆ„
13
+
14
+ # λ‘œκΉ… μ„€μ •
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
18
+ )
19
+
20
+ # μƒμˆ˜ μ •μ˜
21
+ TARGET_CHAR_LENGTH = 4000 # μ΅œμ’… κΈ€μžμˆ˜ λͺ©ν‘œ (문자)
22
+ MIN_SECTION_LENGTH = 300 # 각 μ†Œμ œλͺ© μ•„λž˜ μ΅œμ†Œ κΈ€μžμˆ˜ (μ œν’ˆ ν›„κΈ°λŠ” 300자둜 μ„€μ •)
23
+ MAX_TOKENS = 15000 # Gemini API μ΅œλŒ€ 토큰 수
24
+ TEMPERATURE = 0.85 # Gemini API μ˜¨λ„ κ°’
25
+ TOP_P = 0.9 # Gemini API top_p κ°’
26
+
27
+ # API μ„€μ •
28
+ API_BASE_URL = os.getenv("API_BASE_URL", "").rstrip('/')
29
+ API_KEY = os.getenv("API_KEY", "")
30
+ API_HEADERS = {
31
+ "x-api-key": API_KEY,
32
+ "content-type": "application/json"
33
+ }
34
+
35
+ # API ν‚€ μ„€μ •
36
+ def load_gemini_api_keys():
37
+ # μ—¬λŸ¬ 개의 API ν‚€ λ‘œλ“œ
38
+ api_keys = [
39
+ os.getenv("GEMINI_API_KEY_1", ""),
40
+ os.getenv("GEMINI_API_KEY_2", ""),
41
+ os.getenv("GEMINI_API_KEY_3", ""),
42
+ os.getenv("GEMINI_API_KEY_4", ""),
43
+ os.getenv("GEMINI_API_KEY_5", "")
44
+ ]
45
+
46
+ # 빈 ν‚€ 제거
47
+ api_keys = [key for key in api_keys if key]
48
+
49
+ # κΈ°λ³Έ ν‚€κ°€ μ—†μœΌλ©΄ GEMINI_API_KEY ν™˜κ²½λ³€μˆ˜ μ‚¬μš©
50
+ if not api_keys:
51
+ default_key = os.getenv("GEMINI_API_KEY")
52
+ if default_key:
53
+ api_keys.append(default_key)
54
+
55
+ if not api_keys:
56
+ raise ValueError("API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. .env νŒŒμΌμ— GEMINI_API_KEY λ˜λŠ” GEMINI_API_KEY_1~5λ₯Ό μΆ”κ°€ν•˜μ„Έμš”.")
57
+
58
+ logging.info(f"총 {len(api_keys)}개의 API ν‚€κ°€ λ‘œλ“œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
59
+ return api_keys
60
+
61
+ # 맀번 λžœλ€ν•˜κ²Œ API ν‚€ μ„ νƒν•˜λŠ” ν•¨μˆ˜
62
+ def get_random_gemini_client():
63
+ """랜덀 API ν‚€λ‘œ Gemini ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” 및 λ°˜ν™˜"""
64
+ from google import genai
65
+ from google.genai import types
66
+
67
+ api_keys = load_gemini_api_keys()
68
+ if not api_keys:
69
+ raise ValueError("μ‚¬μš© κ°€λŠ₯ν•œ API ν‚€κ°€ μ—†μŠ΅λ‹ˆλ‹€.")
70
+
71
+ # λžœλ€ν•˜κ²Œ API ν‚€ 인덱슀 선택
72
+ random_index = random.randint(0, len(api_keys) - 1)
73
+ selected_key = api_keys[random_index]
74
+
75
+ logging.info(f"랜덀 API ν‚€ 선택: 인덱슀 {random_index + 1}")
76
+
77
+ # Gemini ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” 및 λ°˜ν™˜
78
+ return genai.Client(api_key=selected_key)
79
+
80
+ # --- Google Gemini SDK μ΄ˆκΈ°ν™” μ½”λ“œ 제거 ---
81
+ # κΈ°μ‘΄ μ½”λ“œ 주석 처리:
82
+ # from google import genai
83
+ # from google.genai import types
84
+ # client = genai.Client(api_key=gemini_api_key)
85
+
86
+ # ν•„μš”ν•œ λͺ¨λ“ˆμ€ μ—¬μ „νžˆ μž„ν¬νŠΈ
87
+ from google import genai
88
+ from google.genai import types
89
+
90
+ def fetch_references(product):
91
+ """APIλ₯Ό 톡해 μ°Έκ³  μƒν’ˆ 리뷰 κ°€μ Έμ˜€κΈ°"""
92
+ try:
93
+ if not product or not product.strip():
94
+ return ["검색 ν‚€μ›Œλ“œλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."] * 3
95
+
96
+ encoded_keyword = requests.utils.quote(product.strip())
97
+ url = f"{API_BASE_URL}/search/{encoded_keyword}"
98
+
99
+ logging.info(f"API 호좜 URL: {url}")
100
+ logging.info(f"API 헀더: {API_HEADERS}")
101
+
102
+ response = requests.get(url, headers=API_HEADERS)
103
+ logging.info(f"API 응닡 μƒνƒœ: {response.status_code}")
104
+ logging.info(f"API 응닡 λ‚΄μš©: {response.text}")
105
+
106
+ if response.ok:
107
+ result = response.json()
108
+ return [
109
+ result.get("reference1", "μ°Έκ³ κΈ€1을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),
110
+ result.get("reference2", "μ°Έκ³ κΈ€2을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),
111
+ result.get("reference3", "μ°Έκ³ κΈ€3을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")
112
+ ]
113
+ else:
114
+ return [f"API 였λ₯˜: {response.text}"] * 3
115
+ except Exception as e:
116
+ return [f"μ°Έκ³ κΈ€ μˆ˜μ§‘ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"] * 3
117
+
118
+ def fetch_crawl_results(query):
119
+ """APIλ₯Ό 톡해 μƒν’ˆ 검색 κ²°κ³Ό κ°€μ Έμ˜€κΈ° (μ°Έκ³ κΈ€ 3개)"""
120
+ references = fetch_references(query)
121
+ return references[0], references[1], references[2]
122
+
123
+ def get_style_prompt(style="μΉœκ·Όν•œ"):
124
+ prompts = {
125
+ "μΉœκ·Όν•œ": """
126
+ [μΉœκ·Όν•œ ν¬μŠ€νŒ… μŠ€νƒ€μΌ κ°€μ΄λ“œ]
127
+ 1. 톀과 μ–΄μ‘°
128
+ - λŒ€ν™”ν•˜λ“― νŽΈμ•ˆν•˜κ³  μΉœκ·Όν•œ 말투 μ‚¬μš©
129
+ 2. λ¬Έμž₯ 및 μ–΄νˆ¬
130
+ - λ°˜λ“œμ‹œ 'ν•΄μš”μ²΄'둜 μž‘μ„±, μ ˆλŒ€ 'μŠ΅λ‹ˆλ‹€'체 μ‚¬μš©ν•˜μ§€ 말 것
131
+ - '~μš”'둜 λλ‚˜λ„λ‘ μž‘μ„±, '~λ‹€'둜 λλ‚˜μ§€ μ•Šκ²Œ ν•  것
132
+ - ꡬ어체 ν‘œν˜„ μ‚¬μš© (예: "~ν–ˆμ–΄μš”", "~인 것 κ°™μ•„μš”")
133
+ - 이λͺ¨ν‹°μ½˜μ€ μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”
134
+ 3. μš©μ–΄ 및 μ„€λͺ… 방식
135
+ - μ „λ¬Έ μš©μ–΄ λŒ€μ‹  μ‰¬μš΄ 단어 μ‚¬μš©
136
+ - λΉ„μœ λ‚˜ μ€μœ λ₯Ό ν™œμš©ν•΄ λ³΅μž‘ν•œ κ°œλ… μ„€λͺ…
137
+ 4. λ…μžμ™€μ˜ μƒν˜Έμž‘μš©
138
+ - λ…μž μ˜κ²¬μ„ λ¬»κ±°λ‚˜ λŒ“κΈ€ 독렀 문ꡬ μ‚¬μš©
139
+ μ£Όμ˜μ‚¬ν•­: λ„ˆλ¬΄ κ°€λ²Όμš΄ 톀은 μ§€μ–‘ν•˜κ³ , 주제의 μ€‘μš”μ„±μ„ μœ μ§€ν•  것
140
+ """,
141
+ "일반적인": """
142
+ #일반적인 λΈ”λ‘œκ·Έ ν¬μŠ€νŒ… μŠ€νƒ€μΌ κ°€μ΄λ“œ
143
+ 1. 톀과 μ–΄μ‘°
144
+ - 쀑립적이고 객관적인 톀 μœ μ§€
145
+ - μ μ ˆν•œ μ‘΄λŒ“λ§ μ‚¬μš© (예: "~ν•©λ‹ˆλ‹€", "~μž…λ‹ˆλ‹€")
146
+ 2. λ‚΄μš© ꡬ쑰 및 μ „κ°œ
147
+ - λͺ…ν™•ν•œ 주제 μ œμ‹œλ‘œ μ‹œμž‘
148
+ - 논리적인 μˆœμ„œλ‘œ 정보 μ „κ°œ
149
+ - μ†Œμ œλͺ© ν™œμš©ν•˜μ—¬ μ£Όμš” 포인트 κ°•μ‘°
150
+ - μ μ ˆν•œ 길이의 단락 ꡬ성
151
+ 3. μš©μ–΄ 및 μ„€λͺ… 방식
152
+ - μ‰½κ²Œ 이해할 수 μžˆλŠ” μš©μ–΄ μ‚¬μš©
153
+ - ν•„μš”ν•˜λ©΄ κ°„λ‹¨ν•œ μ„€λͺ… μΆ”κ°€
154
+ 4. λ…μž μƒν˜Έμž‘μš©
155
+ - λ…μž μ˜κ²¬μ΄λ‚˜ μΆ”κ°€ 정보λ₯Ό μœ„ν•œ ν‚€μ›Œλ“œ μ œμ‹œ
156
+ 5. 마무리
157
+ - μ£Όμš” λ‚΄μš© μš”μ•½ 및 μΆ”κ°€ 정보 μ•ˆλ‚΄
158
+ μ£Όμ˜μ‚¬ν•­: λ„ˆλ¬΄ λ”±λ”±ν•˜μ§€ μ•Šκ³  κ· ν˜• μœ μ§€
159
+ """,
160
+ "전문적인": """
161
+ #전문적인 λΈ”λ‘œκ·Έ ν¬μŠ€νŒ… μŠ€νƒ€μΌ κ°€μ΄λ“œ
162
+ 1. 톀과 ꡬ쑰
163
+ - 곡식적이고 ν•™μˆ μ μΈ 톀 μ‚¬μš©
164
+ - 객관적이고 뢄석적인 μ ‘κ·Ό μœ μ§€
165
+ - λͺ…ν™•ν•œ μ„œλ‘ , λ³Έλ‘ , κ²°λ‘  ꡬ쑰
166
+ - 체계적인 논점 μ „κ°œ
167
+ 2. λ‚΄μš© ꡬ성 및 μ „κ°œ
168
+ - λ³΅μž‘ν•œ κ°œλ…μ„ μ •ν™•νžˆ μ „λ‹¬ν•˜λŠ” λ¬Έμž₯ ꡬ쑰 μ‚¬μš©
169
+ - μ „λ¬Έ μš©μ–΄ 적극 ν™œμš© (κ°„λ‹¨ν•œ μ„€λͺ… 포함)
170
+ - 심측적인 뢄석과 비ꡐ, 평가 μ „κ°œ
171
+ 3. 데이터 및 κ·Όκ±° ν™œμš©
172
+ - 톡계, 연ꡬ κ²°κ³Ό, μ „λ¬Έκ°€ 의견 λ“± μ‹ λ’°μ„± μžˆλŠ” 좜처 인용
173
+ - 수치 λ°μ΄ν„°λŠ” ν…μŠ€νŠΈλ‘œ λͺ…ν™•νžˆ μ„€λͺ…
174
+ 4. 마무리
175
+ - 핡심 논점 μž¬κ°•μ‘° 및 ν–₯ν›„ 전망 μ œμ‹œ
176
+ μ£Όμ˜μ‚¬ν•­: 전문성을 μœ μ§€ν•˜λ˜ μ§€λ‚˜μΉ˜κ²Œ μ–΄λ ΅μ§€ μ•Šκ²Œ μž‘μ„±
177
+ """
178
+ }
179
+ return prompts.get(style, prompts["μΉœκ·Όν•œ"])
180
+
181
+ def get_product_review_prompt():
182
+ prompts = [
183
+ """
184
+ [μ€‘μš”: μƒν’ˆ ν›„κΈ° λΈ”λ‘œκ·Έ κΈ€ μž‘μ„± ν•„μˆ˜ κ·œμΉ™]
185
+ 이 κ·œμΉ™μ„ λ°˜λ“œμ‹œ λ”°λ₯΄μ„Έμš”. μ–΄λ–€ μƒν™©μ—μ„œλ„ μ˜ˆμ™ΈλŠ” μ—†μŠ΅λ‹ˆλ‹€:
186
+ 1. λ§ˆν¬λ‹€μš΄ 문법(**, *, #, -, 1., 2., 3.)을 μ ˆλŒ€ μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”.
187
+ 2. λͺ¨λ“  μ†Œμ œλͺ©μ€ 번호 없이 일반 λ¬Έμž₯으둜 μž‘μ„±ν•˜μ„Έμš”.
188
+ 3. λͺ¨λ“  λͺ©λ‘μ€ λΆˆλ¦Ώμ΄λ‚˜ 번호 없이 μžμ—°μŠ€λŸ¬μš΄ λ¬Έμž₯으둜 μ„œμˆ ν•˜μ„Έμš”.
189
+ 4. "μ°Έκ³ κΈ€", "참고글에 λ”°λ₯΄λ©΄" λ“±μ˜ ν‘œν˜„μ€ μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”.
190
+ 5. λΈ”λ‘œκ·Έ μž‘μ„±μžκ°€ 직접 κ²½ν—˜ν•˜κ³  λŠλ‚€ λ‚΄μš©λ§Œ μž‘μ„±ν•˜μ„Έμš”.
191
+ 6. κΈ€μ˜ μ£Όμ œλŠ” λ°˜λ“œμ‹œ μ£Όμ–΄μ§„ μ°Έκ³ κΈ€μ—μ„œ μƒν’ˆ 정보λ₯Ό μ„ μ •ν•˜μ—¬ μž‘μ„±ν•  것.
192
+ [λΈ”λ‘œκ·Έ μž‘μ„± μŠ€νƒ€μΌ]
193
+ - μΉœκ·Όν•˜κ³  λŒ€ν™”ν•˜λŠ” λ“―ν•œ μ–΄νˆ¬ μ‚¬μš© ("~ν•΄μš”", "~λ„€μš”")
194
+ - 개인 κ²½ν—˜μ„ μ€‘μ‹¬μœΌλ‘œ ν•œ μŠ€ν† λ¦¬ν…”λ§ 방식
195
+ - 각 단락이 μžμ—°μŠ€λŸ½κ²Œ μ—°κ²°λ˜λŠ” 흐름
196
+ - μƒμƒν•œ 감각적 λ¬˜μ‚¬μ™€ μ†”μ§ν•œ 감정 ν‘œν˜„
197
+ - μƒν’ˆ 정보(가격, ꡬ성, κΈ°λŠ₯, μž₯단점 λ“±)와 μ‚¬μš© κ²½ν—˜μ„ μ†”μ§ν•˜κ²Œ 포함
198
+ """
199
+ ]
200
+ return random.choice(prompts)
201
+
202
+ def remove_unwanted_phrases(text):
203
+ unwanted_phrases = [
204
+ 'μ—¬λŸ¬λΆ„', '졜근', 'λ§ˆμ§€λ§‰μœΌλ‘œ', '결둠적으둜', 'κ²°κ΅­',
205
+ 'μ’…ν•©μ μœΌλ‘œ', 'λ”°λΌμ„œ', '마무리', '끝으둜', 'μš”μ•½'
206
+ ]
207
+ words = re.findall(r'\S+|\n', text)
208
+ result_words = [word for word in words if not any(phrase in word for phrase in unwanted_phrases)]
209
+ return ' '.join(result_words).replace(' \n ', '\n').replace(' \n', '\n').replace('\n ', '\n')
210
+
211
+ def post_process_blog(blog_content, style="μΉœκ·Όν•œ"):
212
+ """μƒμ„±λœ λΈ”λ‘œκ·Έ 글을 μŠ€νƒ€μΌμ— 맞게 ν›„μ²˜λ¦¬ν•©λ‹ˆλ‹€."""
213
+ try:
214
+ # λ§ˆν¬λ‹€μš΄ ν˜•μ‹, 번호 및 뢈릿 λͺ©λ‘, ν—€λ”© 제거
215
+ blog_content = re.sub(r'^\d+\.\s+', '', blog_content, flags=re.MULTILINE)
216
+ blog_content = re.sub(r'^[\*\-\β€’]\s+', '', blog_content, flags=re.MULTILINE)
217
+ blog_content = re.sub(r'^#+\s+', '', blog_content, flags=re.MULTILINE)
218
+
219
+ if style == "μΉœκ·Όν•œ":
220
+ blog_content = re.sub(r'([κ°€-힣]+)κ³ μš”', r'\1κ΅¬μš”', blog_content)
221
+ blog_content = re.sub(r'λ‹΅λ‹ˆλ‹€', 'μ–΄μš”', blog_content)
222
+ blog_content = re.sub(r'μ˜€λ‹΅λ‹ˆλ‹€', 'μ˜€μ–΄μš”', blog_content)
223
+ blog_content = re.sub(r'ν–ˆλ‹΅λ‹ˆλ‹€', 'ν–ˆμ–΄μš”', blog_content)
224
+ blog_content = re.sub(r'μŠ΅λ‹ˆλ‹€', 'μš”', blog_content)
225
+ blog_content = re.sub(r'ν•©λ‹ˆλ‹€', 'ν•΄μš”', blog_content)
226
+ blog_content = re.sub(r'λ©λ‹ˆλ‹€', 'λΌμš”', blog_content)
227
+ blog_content = re.sub(r'μž…λ‹ˆλ‹€', 'μ΄μ—μš”', blog_content)
228
+
229
+ # κ³Όμž₯된 ν‘œν˜„ 정리 (μƒν’ˆ, λ§›, κ²½ν—˜ λ“±)
230
+ exaggerated_expressions = [
231
+ (r'μ™„λ²½ν•œ λ””μžμΈ', r'쒋은 λ””μžμΈ'),
232
+ (r'λŒ€λ‹¨ν•œ 효과', r'쒋은 효과'),
233
+ (r'획기적인', r'μœ μš©ν•œ'),
234
+ (r'ν˜μ‹ μ μΈ', r'μƒˆλ‘œμš΄'),
235
+ (r'μ΅œμƒμ˜', r'쒋은'),
236
+ (r'λ―Έμ‹μ˜ ν–₯μ—°', r'λ§›μžˆλŠ”'),
237
+ (r'μž…λ§›μ„ μ‚¬λ‘œμž‘λŠ”', r'λ§›μžˆλŠ”'),
238
+ (r'ν™©ν™€ν•œ λ§›', r'λ§›μžˆλŠ”'),
239
+ (r'λΉ„ν•  데 μ—†λŠ” λ§›', r'νŠΉλ³„ν•œ λ§›'),
240
+ (r'μ²œμƒμ˜ λ§›', r'λ§›μžˆλŠ”'),
241
+ (r'ν˜€κ°€ κΈ°μ–΅ν•˜λŠ”', r'기얡에 λ‚¨λŠ”'),
242
+ (r'μ ˆλŒ€ μžŠμ„ 수 μ—†λŠ” λ§›', r'기얡에 λ‚¨λŠ” λ§›'),
243
+ (r'κΈ°κ°€ λ§‰νžŒ λ§›', r'λ§›μžˆλŠ”'),
244
+ (r'황홀경', r'쒋은 κ²½ν—˜'),
245
+ (r'κ°€μŠ΄μ΄ λ²…μ°¨μ˜€λ₯΄λŠ”', r'κΈ°λΆ„ 쒋은'),
246
+ (r'κΏˆκ°™μ€ μ‹œκ°„', r'쒋은 μ‹œκ°„'),
247
+ (r'μžŠμ„ 수 μ—†λŠ” κ²½ν—˜', r'기얡에 λ‚¨λŠ” κ²½ν—˜'),
248
+ (r'감동적인', r'인상적인'),
249
+ (r'λ§ˆμŒμ„ μ‚¬λ‘œμž‘λŠ”', r'인상적인'),
250
+ (r'μ˜ν˜Όμ„ μΉ˜μœ ν•˜λŠ”', r'νŽΈμ•ˆν•œ'),
251
+ (r'졜고의 μˆœκ°„', r'쒋은 μ‹œκ°„'),
252
+ (r'평생 기얡에 남을', r'기얡에 λ‚¨λŠ”'),
253
+ (r'μ™„λ²½ν•œ', r'쒋은'),
254
+ (r'ν™˜μƒμ μΈ', r'쒋은'),
255
+ (r'μ΅œμƒμ˜', r'쒋은'),
256
+ (r'경이둜운', r'λ†€λΌμš΄'),
257
+ (r'맀혹적인', r'쒋은'),
258
+ (r'κ·Όμ‚¬ν•œ', r'쒋은'),
259
+ (r'정말이지', r''),
260
+ (r'그 자체', r''),
261
+ (r'μ™„λ²½ν•˜κ²Œ', r'잘'),
262
+ (r'λ©‹μ§€κ²Œ', r'잘'),
263
+ (r'말할 것도 없이', r''),
264
+ (r'κΈ°λŒ€ μ΄μƒμœΌλ‘œ', r'μ˜ˆμƒλ³΄λ‹€'),
265
+ (r'비ꡐ할 수 없이', r''),
266
+ (r'상상을 μ΄ˆμ›”ν•˜λŠ”', r'μ˜ˆμƒ λ°–μ˜')
267
+ ]
268
+ for pattern, replacement in exaggerated_expressions:
269
+ blog_content = re.sub(pattern, replacement, blog_content, flags=re.IGNORECASE)
270
+
271
+ blog_content = re.sub(r'참고글에 λ”°λ₯΄λ©΄', r'μ œκ°€ μ•Œκ²Œ 된 λ°”λ‘œλŠ”', blog_content)
272
+ blog_content = re.sub(r'μ°Έκ³ κΈ€', r'κ΄€λ ¨ 정보', blog_content)
273
+
274
+ # μŠ€νƒ€μΌλ³„ 문체 μ§€μ‹œλ₯Ό ν¬ν•¨ν•œ Gemini API ν”„λ‘¬ν”„νŠΈ 생성 (ν›„μ²˜λ¦¬ μš©λ„)
275
+ prompt = f"""
276
+ λ‹€μŒ μƒν’ˆ ν›„κΈ° λΈ”λ‘œκ·Έ 글을 더 μžμ—°μŠ€λŸ½κ²Œ λ³€κ²½ν•΄μ£Όμ„Έμš”:
277
+
278
+ 원본 κΈ€:
279
+ {blog_content}
280
+
281
+ λ³€κ²½ μš”κ΅¬μ‚¬ν•­:
282
+ 1. λ§ˆν¬λ‹€μš΄ ν˜•μ‹, 번호, 뢈릿 ν‘œν˜„ λͺ¨λ‘ μ œκ±°ν•˜κ³  μžμ—°μŠ€λŸ¬μš΄ λ¬Έμž₯으둜 λ³€κ²½
283
+ 2. μ†Œμ œλͺ©μ€ 5개 μ΄ν•˜λ‘œ μž‘μ„±ν•˜λ©°, 각 μ†Œμ œλͺ© μ•„λž˜ λ‚΄μš©μ€ μ΅œμ†Œ {MIN_SECTION_LENGTH}자 μ΄μƒμœΌλ‘œ μƒμ„Έν•˜κ²Œ μ„œμˆ 
284
+ 3. "μ°Έκ³ κΈ€", "참고글에 λ”°λ₯΄λ©΄" λ“±μ˜ ν‘œν˜„ 제거
285
+ 4. μƒν’ˆ μ‚¬μš© κ²½ν—˜, 감상, 팁 등을 μΆ”κ°€ν•˜μ—¬ λΆ€μ‘±ν•œ λΆ€λΆ„ 보완
286
+ 5. μƒν’ˆμ˜ ꡬ체적인 정보(가격, ꡬ성, μ‚¬μš©λ²•, μž₯단점 λ“±)λ₯Ό μžμ—°μŠ€λŸ½κ²Œ 포함
287
+ 6. μŠ€νƒ€μΌ: {style} (μžμ—°μŠ€λŸ¬μš΄ 문체 μ‚¬μš©)
288
+ """
289
+ # 랜덀 ν΄λΌμ΄μ–ΈνŠΈ μ‚¬μš©μœΌλ‘œ λ³€κ²½
290
+ client = get_random_gemini_client()
291
+
292
+ response = client.models.generate_content(
293
+ model="gemini-2.0-flash",
294
+ contents=[prompt],
295
+ config=types.GenerateContentConfig(
296
+ max_output_tokens=MAX_TOKENS,
297
+ temperature=0.7,
298
+ top_p=0.9
299
+ )
300
+ )
301
+ return response.text.strip()
302
+ except Exception as e:
303
+ logging.error(f"λΈ”λ‘œκ·Έ κΈ€ ν›„μ²˜λ¦¬ 쀑 였λ₯˜ λ°œμƒ: {str(e)}")
304
+ return blog_content
305
+
306
+ def format_blog_post(blog_post, query=""):
307
+ # λ‚΄μš© κ·ΈλŒ€λ‘œ μœ μ§€
308
+ blog_post = re.sub(r'^#+\s+', '', blog_post, flags=re.MULTILINE)
309
+ blog_post = re.sub(r'^\d+\.\s+', '', blog_post, flags=re.MULTILINE)
310
+ blog_post = re.sub(r'^[\*\-]\s+', '', blog_post, flags=re.MULTILINE)
311
+
312
+ lines = blog_post.split('\n')
313
+ formatted_lines = []
314
+ in_paragraph = False
315
+ first_line = True
316
+ title_found = False
317
+
318
+ first_non_empty_line = ""
319
+ for line in lines:
320
+ if line.strip():
321
+ first_non_empty_line = line.strip()
322
+ break
323
+
324
+ subtitle_patterns = [
325
+ r'^.{10,100}\?$',
326
+ r'^.{10,100}:$',
327
+ r'^.{5,50}의 [κ°€-힣\s]+$',
328
+ r'^[κ°€-힣\s]{5,50} [κ°€-힣\s]+$'
329
+ ]
330
+ previous_line_empty = True
331
+
332
+ def optimize_title(title, max_length=60):
333
+ if ': ' in title and len(title) > max_length:
334
+ title = title.split(': ')[0]
335
+ if len(title) > max_length:
336
+ title = re.sub(r'\s*\([^)]*\)', '', title)
337
+ if len(title) > max_length and ',' in title:
338
+ title = title.split(',')[0]
339
+ if len(title) > max_length:
340
+ words = title.split()
341
+ shortened_title = []
342
+ current_length = 0
343
+ for word in words:
344
+ if current_length + len(word) + 1 <= max_length:
345
+ shortened_title.append(word)
346
+ current_length += len(word) + 1
347
+ else:
348
+ break
349
+ title = ' '.join(shortened_title)
350
+ endings_to_remove = ['κ·Έ', '이', '은', 'λŠ”', 'μ΄λ‚˜', '와', 'κ³Ό', 'λ˜λŠ”', '그리고']
351
+ for ending in endings_to_remove:
352
+ if title.endswith(f" {ending}"):
353
+ title = title[:-len(ending)-1]
354
+ if len(title) < 20 and query:
355
+ title = f"{query} μ‚¬μš© ν›„κΈ°"
356
+ return title
357
+
358
+ for i, line in enumerate(lines):
359
+ line = line.strip()
360
+ next_line_empty = (i + 1 >= len(lines)) or not lines[i+1].strip()
361
+
362
+ if not line:
363
+ if in_paragraph:
364
+ formatted_lines.append("</p>")
365
+ in_paragraph = False
366
+ formatted_lines.append("<br>")
367
+ previous_line_empty = True
368
+ else:
369
+ if first_line and len(line) > 5:
370
+ optimized_title = optimize_title(line)
371
+ formatted_lines.append(f'<h1 style="font-size: 1.8em; margin-bottom: 20px; font-weight: bold; color: #222;">{html.escape(optimized_title)}</h1>')
372
+ first_line = False
373
+ title_found = True
374
+ previous_line_empty = False
375
+ else:
376
+ is_subtitle = False
377
+ if any(re.match(pattern, line) for pattern in subtitle_patterns):
378
+ is_subtitle = True
379
+ elif previous_line_empty and next_line_empty and len(line) < 80:
380
+ is_subtitle = True
381
+ if is_subtitle:
382
+ if in_paragraph:
383
+ formatted_lines.append("</p>")
384
+ in_paragraph = False
385
+ formatted_lines.append(f'<h2 style="font-size: 1.3em; margin-top: 25px; margin-bottom: 15px; font-weight: bold; color: #333;">{html.escape(line)}</h2>')
386
+ else:
387
+ if not in_paragraph:
388
+ formatted_lines.append("<p>")
389
+ in_paragraph = True
390
+ content = html.escape(line)
391
+ bold_content = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', content)
392
+ formatted_lines.append(bold_content)
393
+ previous_line_empty = False
394
+
395
+ if in_paragraph:
396
+ formatted_lines.append("</p>")
397
+
398
+ if not title_found:
399
+ default_title = f"{query} μ‚¬μš© ν›„κΈ°" if query else "μƒν’ˆ ν›„κΈ°"
400
+ if first_non_empty_line:
401
+ default_title = optimize_title(first_non_empty_line)
402
+ formatted_lines.insert(0, f'<h1 style="font-size: 1.8em; margin-bottom: 20px; font-weight: bold; color: #222;">{html.escape(default_title)}</h1>')
403
+
404
+ return '\n'.join(formatted_lines)
405
+
406
+ # Gemini API 호좜 헬퍼 ν•¨μˆ˜ μˆ˜μ •
407
+ def call_gemini_api(prompt):
408
+ # 맀번 μƒˆλ‘œμš΄ 랜덀 ν΄λΌμ΄μ–ΈνŠΈ κ°€μ Έμ˜€κΈ°
409
+ client = get_random_gemini_client()
410
+
411
+ response = client.models.generate_content(
412
+ model="gemini-2.0-flash",
413
+ contents=[prompt],
414
+ config=types.GenerateContentConfig(
415
+ max_output_tokens=MAX_TOKENS,
416
+ temperature=TEMPERATURE,
417
+ top_p=TOP_P
418
+ )
419
+ )
420
+ return response.text.strip()
421
+
422
+ def generate_blog_post(query, style="μΉœκ·Όν•œ"):
423
+ try:
424
+ # λͺ©ν‘œ κΈ€μžμˆ˜ μ„€μ •
425
+ target_char_length = TARGET_CHAR_LENGTH
426
+
427
+ # μ°Έκ³ κΈ€ κ°€μ Έμ˜€κΈ° (API μ‚¬μš©)
428
+ ref1, ref2, ref3 = fetch_crawl_results(query)
429
+ style_prompt = get_style_prompt(style)
430
+ format_prompt = get_product_review_prompt()
431
+
432
+ # μŠ€νƒ€μΌ μ„ΈλΆ€ μ§€μ‹œμ‚¬ν•­ κ°•ν™”
433
+ style_specific_instructions = ""
434
+ if style == "μΉœκ·Όν•œ":
435
+ style_specific_instructions = """
436
+ 이 λΈ”λ‘œκ·ΈλŠ” λ°˜λ“œμ‹œ μΉœκ·Όν•œ λŒ€ν™”μ²΄λ‘œ μž‘μ„±λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
437
+ - 'ν•΄μš”μ²΄' μ‚¬μš©: "~ν–ˆμ–΄μš”", "~인 것 κ°™μ•„μš”", "~ν•˜λ„€μš”"
438
+ - 격식체(예: "~ν•©λ‹ˆλ‹€", "~μž…λ‹ˆλ‹€") μ‚¬μš© κΈˆμ§€
439
+ - λŒ€ν™”ν•˜λ“― νŽΈμ•ˆν•˜κ²Œ μž‘μ„±
440
+ """
441
+ elif style == "일반적인":
442
+ style_specific_instructions = """
443
+ 이 λΈ”λ‘œκ·ΈλŠ” λ°˜λ“œμ‹œ 일반적인 μ‘΄λŒ“λ§μ²΄λ‘œ μž‘μ„±λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
444
+ - 'μŠ΅λ‹ˆλ‹€μ²΄' μ‚¬μš©: "~ν–ˆμŠ΅λ‹ˆλ‹€", "~μž…λ‹ˆλ‹€", "~ν•˜μ˜€μŠ΅λ‹ˆλ‹€"
445
+ - 객관적이고 λͺ…ν™•ν•˜κ²Œ μ„œμˆ ν•  것
446
+ """
447
+ elif style == "전문적인":
448
+ style_specific_instructions = """
449
+ 이 λΈ”λ‘œκ·ΈλŠ” λ°˜λ“œμ‹œ 전문적이고 뢄석적인 μ–΄νˆ¬λ‘œ μž‘μ„±λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
450
+ - 'μŠ΅λ‹ˆλ‹€μ²΄' μ‚¬μš©: "~ν–ˆμŠ΅λ‹ˆλ‹€", "~μž…λ‹ˆλ‹€", "~ν•˜μ˜€μŠ΅λ‹ˆλ‹€"
451
+ - 데이터와 ꡬ체적인 정보 포함
452
+ """
453
+
454
+ # Phase 1: 초기 생성
455
+ initial_prompt = f"""
456
+ 주제: {query} μƒν’ˆ ν›„κΈ°
457
+ μ°Έκ³ κΈ€ 1: {ref1}
458
+ μ°Έκ³ κΈ€ 2: {ref2}
459
+ μ°Έκ³ κΈ€ 3: {ref3}
460
+ λͺ©ν‘œ κΈ€μžμˆ˜: {target_char_length}
461
+ {format_prompt}
462
+ μŠ€νƒ€μΌ κ°€μ΄λ“œ:
463
+ {style_prompt}
464
+ μŠ€νƒ€μΌ μ„ΈλΆ€ μ§€μ‹œμ‚¬ν•­:
465
+ {style_specific_instructions}
466
+ νŠΉλ³„ μ§€μ‹œμ‚¬ν•­:
467
+ 1. κΈ€μ˜ μ²˜μŒμ— λͺ…ν™•ν•˜κ³  λ§€λ ₯적인 제λͺ©μ„ 포함할 것 (μƒν’ˆλͺ…κ³Ό 핡심 νŠΉμ§• 포함).
468
+ 2. 제λͺ© μ˜ˆμ‹œ: "{query} μ‚¬μš© ν›„κΈ°: [핡심 νŠΉμ§•/μž₯점]", "{query}, [νŠΉλ³„ν•œ 점] 솔직 ν›„κΈ°"
469
+ 3. λ§ˆν¬λ‹€μš΄ 문법(#, *, -, 1., 2. λ“±)은 μ‚¬μš©ν•˜μ§€ μ•Šμ„ 것.
470
+ 4. μ†Œμ œλͺ©μ€ 번호 없이 일반 λ¬Έμž₯으둜 μž‘μ„±ν•˜κ³ , μ†Œμ œλͺ© 수λ₯Ό 5개 μ΄ν•˜λ‘œ μ œν•œ.
471
+ 5. λͺ¨λ“  λͺ©λ‘μ€ λΆˆλ¦Ώμ΄λ‚˜ 번호 없이 μžμ—°μŠ€λŸ¬μš΄ λ¬Έμž₯으둜 μ„œμˆ .
472
+ 6. "μ°Έκ³ κΈ€" κ΄€λ ¨ ν‘œν˜„μ€ μ‚¬μš©ν•˜μ§€ μ•Šμ„ 것.
473
+ 7. μž‘μ„±μžμ˜ μ‹€οΏ½οΏ½ κ²½ν—˜κ³Ό 감상을 λ°”νƒ•μœΌλ‘œ μž‘μ„±.
474
+ 8. μƒν’ˆμ˜ 정보(가격, ꡬ성, κΈ°λŠ₯, μž₯단점, μ‚¬μš©λ²• λ“±)λ₯Ό 포함할 것.
475
+ 9. κΈ€μžμˆ˜λŠ” μ΅œμ†Œ {target_char_length}자 이상이어야 함.
476
+ """
477
+ first_attempt = call_gemini_api(initial_prompt)
478
+ first_attempt_cleaned = remove_unwanted_phrases(first_attempt)
479
+ first_attempt_length = len(first_attempt_cleaned)
480
+
481
+ if first_attempt_length >= target_char_length:
482
+ final_post = post_process_blog(first_attempt_cleaned, style)
483
+ final_html = format_blog_post(final_post, query)
484
+ return final_html, ref1, ref2, ref3, first_attempt_length
485
+
486
+ # Phase 2: 퇴고 (Revision) μ‹œλ„
487
+ longest_ref = max([ref1, ref2, ref3], key=len)
488
+ revision_prompt = f"""
489
+ 이전 κΈ€:
490
+ {first_attempt_cleaned}
491
+ μ°Έκ³ κΈ€: {longest_ref}
492
+ ν¬μŠ€νŒ… μŠ€νƒ€μΌ:
493
+ {style_prompt}
494
+ μŠ€νƒ€μΌ μ„ΈλΆ€ μ§€μ‹œμ‚¬ν•­:
495
+ {style_specific_instructions}
496
+ 문제점:
497
+ μž‘μ„±λœ 글이 λͺ©ν‘œ κΈ€μžμˆ˜ {target_char_length}μžμ— λ―ΈμΉ˜μ§€ λͺ»ν•˜λ©°, 각 μ†Œμ œλͺ© μ•„λž˜ λ‚΄μš©μ΄ λΆ€μ‹€ν•©λ‹ˆλ‹€.
498
+ μ€‘μš” μš”κ΅¬μ‚¬ν•­:
499
+ 1. μƒν’ˆλͺ…({query})κ³Ό 핡심 νŠΉμ§•μ΄ ν¬ν•¨λœ λ§€λ ₯적인 제λͺ© μΆ”κ°€.
500
+ 2. κΈ€μžμˆ˜λ₯Ό μ΅œμ†Œ {target_char_length}자 μ΄μƒμœΌλ‘œ 늘리고, 각 μ„Ήμ…˜μ„ μƒμ„Έν•˜κ²Œ μ„œμˆ ν•  것.
501
+ 3. λ§ˆν¬λ‹€μš΄ 문법(#, *, -, 1., 2. λ“±)을 μ‚¬μš©ν•˜μ§€ μ•Šμ„ 것.
502
+ 4. μ†Œμ œλͺ©μ€ 번호 없이 μž‘μ„±.
503
+ 5. λͺ¨λ“  λͺ©λ‘μ€ λΆˆλ¦Ώμ΄λ‚˜ 번호 없이 μžμ—°μŠ€λŸ½κ²Œ μ„œμˆ .
504
+ 6. "μ°Έκ³ κΈ€" κ΄€λ ¨ ν‘œν˜„ μ‚¬μš© κΈˆμ§€.
505
+ 7. μž‘μ„±μžμ˜ μ‹€μ œ μ‚¬μš© κ²½ν—˜κ³Ό 감상을 포함할 것.
506
+ 8. 각 μ†Œμ œλͺ© μ•„λž˜ λ‚΄μš©μ€ μ΅œμ†Œ {MIN_SECTION_LENGTH}자 이상 μ„œμˆ ν•  것.
507
+ 9. μ†Œμ œλͺ© μˆ˜λŠ” 5개 μ΄ν•˜λ‘œ μ œν•œ.
508
+ 상세 보완:
509
+ - μƒν’ˆ λ””μžμΈ, 재질, κ΅¬μ„±ν’ˆ λ“± 외관에 λŒ€ν•œ μžμ„Έν•œ λ¬˜μ‚¬.
510
+ - ꡬ맀 κ³Όμ •, μ–Έλ°•μ‹±, μ‚¬μš© κ³Όμ • λ“± ꡬ체적인 μ—ν”Όμ†Œλ“œ μΆ”κ°€.
511
+ - κΈ°λŠ₯κ³Ό μ‚¬μš©λ²•, μž₯단점에 λŒ€ν•œ κ· ν˜• 작힌 평가.
512
+ - 가격 λŒ€λΉ„ κ°€μΉ˜μ— λŒ€ν•œ μ†”μ§ν•œ 의견.
513
+ """
514
+ revised_attempt = call_gemini_api(revision_prompt)
515
+ revised_cleaned = remove_unwanted_phrases(revised_attempt)
516
+ final_post = post_process_blog(revised_cleaned, style)
517
+ final_html = format_blog_post(final_post, query)
518
+
519
+ soup = BeautifulSoup(final_html, 'html.parser')
520
+ actual_char_length = len(soup.get_text())
521
+
522
+ # Phase 3: ν™•μž₯ (Expansion) μ‹œλ„ (κΈ€μžμˆ˜κ°€ λΆ€μ‘±ν•  경우)
523
+ if actual_char_length < target_char_length * 0.8:
524
+ expansion_prompt = f"""
525
+ λ‹€μŒ μƒν’ˆ ν›„κΈ° λΈ”λ‘œκ·Έ κΈ€μ˜ λ‚΄μš©μ„ 크게 ν™•μž₯ν•΄μ£Όμ„Έμš”:
526
+ 원본 κΈ€:
527
+ {final_post}
528
+ 문제점:
529
+ λͺ©ν‘œ κΈ€μžμˆ˜ {target_char_length}μžμ— λ―ΈμΉ˜μ§€ λͺ»ν•˜λ©°, λ‚΄μš©μ΄ λΆ€μ‹€ν•©λ‹ˆλ‹€. ν˜„μž¬ κΈ€μžμˆ˜λŠ” μ•½ {actual_char_length}μžμž…λ‹ˆλ‹€.
530
+ μŠ€νƒ€μΌ κ°€μ΄λ“œ:
531
+ {style_prompt}
532
+ μŠ€νƒ€μΌ μ„ΈλΆ€ μ§€μ‹œμ‚¬ν•­:
533
+ {style_specific_instructions}
534
+ μš”κ΅¬μ‚¬ν•­:
535
+ 1. λ°˜λ“œμ‹œ 제λͺ©μ΄ μœ μ§€λ˜κ±°λ‚˜ μ—†λ‹€λ©΄ "{query} μ‚¬μš© ν›„κΈ°: [핡심 νŠΉμ§•/μž₯점]" ν˜•νƒœλ‘œ 제λͺ© μΆ”κ°€.
536
+ 2. 각 μ†Œμ œλͺ© μ•„λž˜μ˜ λ‚΄μš©μ„ μ΅œμ†Œ {MIN_SECTION_LENGTH}자 μ΄μƒμœΌλ‘œ ν™•μž₯ν•  것.
537
+ 3. μƒν’ˆ μ‚¬μš© 쀑 ꡬ체적 μ—ν”Όμ†Œλ“œ, 상황, 감정 등을 μƒμ„Έν•˜κ²Œ μΆ”κ°€ν•  것.
538
+ 4. λ§ˆν¬λ‹€μš΄ 문법은 μ‚¬μš©ν•˜μ§€ μ•Šμ„ 것.
539
+ 5. μ†Œμ œλͺ© κ΅¬μ‘°λŠ” κ·ΈλŒ€λ‘œ μœ μ§€ν•˜λ˜, 각 μ„Ήμ…˜ λ‚΄μš©μ„ 3λ°° 이상 ν’λΆ€ν•˜κ²Œ μž‘μ„±ν•  것.
540
+ 6. μƒν’ˆμ˜ λͺ¨μŠ΅, μ„±λŠ₯, μ‚¬μš©κ°, μž₯단점 등을 μžμ„Ένžˆ λ¬˜μ‚¬ν•  것.
541
+ 7. 전체 κΈ€μžμˆ˜λ₯Ό μ΅œμ†Œ {target_char_length}자 이상 달성할 것.
542
+ 8. μ†Œμ œλͺ© μˆ˜λŠ” μ΅œλŒ€ 5개둜 μ œν•œ.
543
+ μ˜ˆμ‹œ:
544
+ - ꡬ맀 λ°°κ²½ 및 μ–Έλ°•μ‹±, μ‚¬μš© 과정에 λŒ€ν•΄ ꡬ체적으둜 μ„œμˆ .
545
+ - μƒν’ˆ μ‚¬μš© ν›„ λŠλ‚€ 점 및 가격 λŒ€λΉ„ κ°€μΉ˜ 평가.
546
+ """
547
+ expanded_attempt = call_gemini_api(expansion_prompt)
548
+ final_post = post_process_blog(expanded_attempt, style)
549
+ final_html = format_blog_post(final_post, query)
550
+ soup = BeautifulSoup(final_html, 'html.parser')
551
+ actual_char_length = len(soup.get_text())
552
+
553
+ return final_html, ref1, ref2, ref3, actual_char_length
554
+
555
+ except Exception as e:
556
+ logging.error(f"λΈ”λ‘œκ·Έ κΈ€ 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}")
557
+ return f"<p>λΈ”λ‘œκ·Έ κΈ€ 생성 쀑 였λ₯˜ λ°œμƒ: {str(e)}</p>", "", "", "", 0