| import os |
| import random |
| import re |
| import requests |
| import logging |
| from bs4 import BeautifulSoup |
| import html |
| import markdown2 |
| from dotenv import load_dotenv |
|
|
| |
| load_dotenv() |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
| ) |
|
|
| |
| TARGET_CHAR_LENGTH = 4000 |
| MIN_SECTION_LENGTH = 600 |
| MAX_TOKENS = 15000 |
| TEMPERATURE = 0.85 |
| TOP_P = 0.9 |
|
|
| |
| API_BASE_URL = os.getenv("API_BASE_URL", "").rstrip('/') |
| API_KEY = os.getenv("API_KEY", "") |
|
|
| API_HEADERS = { |
| "x-api-key": API_KEY, |
| "content-type": "application/json" |
| } |
|
|
| |
| def load_gemini_api_keys(): |
| |
| api_keys = [ |
| os.getenv("GEMINI_API_KEY_1", ""), |
| os.getenv("GEMINI_API_KEY_2", ""), |
| os.getenv("GEMINI_API_KEY_3", ""), |
| os.getenv("GEMINI_API_KEY_4", ""), |
| os.getenv("GEMINI_API_KEY_5", "") |
| ] |
| |
| |
| api_keys = [key for key in api_keys if key] |
| |
| |
| if not api_keys: |
| default_key = os.getenv("GEMINI_API_KEY") |
| if default_key: |
| api_keys.append(default_key) |
| |
| if not api_keys: |
| raise ValueError("API ํค๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค. .env ํ์ผ์ GEMINI_API_KEY ๋๋ GEMINI_API_KEY_1~5๋ฅผ ์ถ๊ฐํ์ธ์.") |
| |
| logging.info(f"์ด {len(api_keys)}๊ฐ์ API ํค๊ฐ ๋ก๋๋์์ต๋๋ค.") |
| return api_keys |
|
|
| |
| def get_random_gemini_client(): |
| """๋๋ค API ํค๋ก Gemini ํด๋ผ์ด์ธํธ ์ด๊ธฐํ ๋ฐ ๋ฐํ""" |
| from google import genai |
| from google.genai import types |
| |
| api_keys = load_gemini_api_keys() |
| if not api_keys: |
| raise ValueError("์ฌ์ฉ ๊ฐ๋ฅํ API ํค๊ฐ ์์ต๋๋ค.") |
| |
| |
| random_index = random.randint(0, len(api_keys) - 1) |
| selected_key = api_keys[random_index] |
| |
| logging.info(f"๋๋ค API ํค ์ ํ: ์ธ๋ฑ์ค {random_index + 1}") |
| |
| |
| return genai.Client(api_key=selected_key) |
|
|
| |
| |
| |
| |
| |
|
|
| |
| def fetch_references(topic): |
| """API๋ฅผ ํตํด ์ฐธ๊ณ ๋ธ๋ก๊ทธ ๊ธ ๊ฐ์ ธ์ค๊ธฐ""" |
| try: |
| if not topic or not topic.strip(): |
| return ["๊ฒ์ ํค์๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์."] * 3 |
|
|
| encoded_keyword = requests.utils.quote(topic.strip()) |
| url = f"{API_BASE_URL}/search/{encoded_keyword}" |
| |
| logging.info(f"API ํธ์ถ URL: {url}") |
| logging.info(f"API ํค๋: {API_HEADERS}") |
| |
| response = requests.get(url, headers=API_HEADERS) |
| logging.info(f"API ์๋ต ์ํ: {response.status_code}") |
| logging.info(f"API ์๋ต ๋ด์ฉ: {response.text}") |
| |
| if response.ok: |
| result = response.json() |
| return [ |
| result.get("reference1", "์ฐธ๊ณ ๊ธ1์ ์ฐพ์ ์ ์์ต๋๋ค."), |
| result.get("reference2", "์ฐธ๊ณ ๊ธ2์ ์ฐพ์ ์ ์์ต๋๋ค."), |
| result.get("reference3", "์ฐธ๊ณ ๊ธ3์ ์ฐพ์ ์ ์์ต๋๋ค.") |
| ] |
| else: |
| return [f"API ์ค๋ฅ: {response.text}"] * 3 |
| |
| except Exception as e: |
| return [f"์ฐธ๊ณ ๊ธ ์์ง ์ค ์ค๋ฅ ๋ฐ์: {str(e)}"] * 3 |
|
|
| def fetch_crawl_results(query): |
| """API๋ฅผ ํตํด ๋ธ๋ก๊ทธ ๊ฒ์ ๊ฒฐ๊ณผ ๊ฐ์ ธ์ค๊ธฐ (์ฐธ๊ณ ๊ธ 3๊ฐ)""" |
| references = fetch_references(query) |
| return references[0], references[1], references[2] |
|
|
| def get_style_prompt(style="์น๊ทผํ"): |
| prompts = { |
| "์น๊ทผํ": """ |
| [์น๊ทผํ ์ ๋ณด์ฑ ๋ธ๋ก๊ทธ ์คํ์ผ ๊ฐ์ด๋] |
| 1. ํค๊ณผ ์ด์กฐ |
| - ๋ํํ๋ฏ ํธ์ํ๊ณ ์น๊ทผํ ๋งํฌ ์ฌ์ฉ |
| - ์ฃผ์ ์ ๋ํ ๊ด์ฌ๊ณผ ํธ๊ธฐ์ฌ์ ๋ด์ ํํ ์ฌ์ฉ |
| 2. ๋ฌธ์ฅ ๋ฐ ์ดํฌ |
| - ๋ฐ๋์ 'ํด์์ฒด'๋ก ์์ฑ, ์ ๋ '์ต๋๋ค'์ฒด๋ฅผ ์ฌ์ฉํ์ง ๋ง ๊ฒ |
| - '~์'๋ก ๋๋๋๋ก ์์ฑ, '~๋ค'๋ก ๋๋์ง ์๊ฒ ํ๋ผ |
| - ๊ตฌ์ด์ฒด ํํ ์ฌ์ฉ (์: "~ํ์ด์", "~์ธ ๊ฒ ๊ฐ์์") |
| - ์ด๋ชจํฐ์ฝ์ ์ฌ์ฉํ์ง ๋ง์ธ์ |
| 3. ์ฉ์ด ๋ฐ ์ค๋ช
๋ฐฉ์ |
| - ์ ๋ฌธ ์ฉ์ด๋ ์ฌ์ด ๋จ์ด๋ก ํ์ด์ ์ค๋ช
|
| - ๋น์ ๋ ์์ ๋ฅผ ํ์ฉํ์ฌ ๋ณต์กํ ๊ฐ๋
์ค๋ช
|
| - ์์ฌ์๋ฌธ๋ฌธ ํ์ฉํ์ฌ ๋
์์ ์ํตํ๋ ๋๋ ์ฃผ๊ธฐ (์: "์ฌ๋ฌ๋ถ๋ ์ด๋ฐ ์๊ฐ ํด๋ณด์
จ๋์?") |
| - ๊ตฌ์ฒด์ ์ฌ๋ก์ ์์๋ฅผ ํตํ ๊ฐ๋
์ค๋ช
|
| 4. ์ ๋ณด ์ ๋ฌ ๋ฐฉ์ |
| - ๊ฐ์ธ์ ์ธ ๊ด์ ์ ๋
น์ฌ ์์ฐ์ค๋ฝ๊ฒ ์ ๋ณด ์ ๋ฌ |
| - ๋ณต์กํ ๊ฐ๋
์ ๋จ๊ณ์ ์ผ๋ก ์ค๋ช
|
| - ๋
์๊ฐ ์ค์ ๋ก ํ์ฉํ ์ ์๋ ์ค์ฉ์ ์ ๋ณด ์ ๊ณต |
| 5. ๋
์์์ ์ํธ์์ฉ |
| - ๋
์์ ์๊ฒฌ์ ๋ฌผ์ด๋ณด๋ ์ง๋ฌธ ํฌํจ |
| - ์ค์ํ์ ์ ์ฉํ ์ ์๋ ํ์ด๋ ์กฐ์ธ ์ ๊ณต |
| ์ฃผ์์ฌํญ: ๋๋ฌด ๊ฐ๋ฒผ์ด ํค์ ์ง์ํ๊ณ , ์ฃผ์ ์ ์ ๋ฌธ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ํด์น์ง ์๋ ์ ์์ ์น๊ทผํจ ์ ์ง |
| """, |
| "์ผ๋ฐ์ ์ธ": """ |
| #์ผ๋ฐ์ ์ธ ์ ๋ณด์ฑ ๋ธ๋ก๊ทธ ์คํ์ผ ๊ฐ์ด๋ |
| 1. ํค๊ณผ ์ด์กฐ |
| - ์ค๋ฆฝ์ ์ด๊ณ ๊ฐ๊ด์ ์ธ ํค ์ ์ง |
| - ์ ์ ํ ์กด๋๋ง ์ฌ์ฉ (์: "~ํฉ๋๋ค", "~์
๋๋ค") |
| - ์ ๋ณด ์ ๋ฌ ์ค์ฌ์ ๋ช
ํํ ์ดํฌ |
| 2. ๋ด์ฉ ๊ตฌ์กฐ ๋ฐ ์ ๊ฐ |
| - ๋ช
ํํ ์ฃผ์ ์๊ฐ๋ก ์์ |
| - ๋
ผ๋ฆฌ์ ์ธ ์์๋ก ์ ๋ณด ์ ๊ฐ (๋ฐฐ๊ฒฝ โ ์ฃผ์ ๊ฐ๋
โ ๋ถ์ โ ์ ์ฉ ๋ฑ) |
| - ํต์ฌ ํฌ์ธํธ๋ฅผ ๊ฐ์กฐํ๋ ์์ ๋ชฉ ํ์ฉ |
| - ์ ์ ํ ๊ธธ์ด์ ๋จ๋ฝ์ผ๋ก ๊ตฌ์ฑ |
| 3. ์ฉ์ด ๋ฐ ์ค๋ช
๋ฐฉ์ |
| - ์ผ๋ฐ์ ์ผ๋ก ์ดํดํ๊ธฐ ์ฌ์ด ์ฉ์ด ์ ํ |
| - ํ์์ ์ ๋ฌธ ์ฉ์ด์ ๊ฐ๋จํ ์ค๋ช
์ถ๊ฐ |
| - ๊ฐ๊ด์ ์ธ ์ ๋ณด ์ ๊ณต์ ์ค์ |
| - ๊ท ํ ์กํ ์๊ฐ์์ ๋ค์ํ ๊ด์ ์ ์ |
| 4. ์ ๋ณด ์ ๋ฌ ๋ฐฉ์ |
| - ์ฃผ์ ์ ๊ธฐ๋ณธ ๊ฐ๋
๊ณผ ์๋ฆฌ ๋ช
ํํ๊ฒ ์ ๊ณต |
| - ๊ตฌ์ฒด์ ์ธ ์์์ ์ฌ๋ก ํฌํจ |
| - ์๊ฐ์ ์์(๊ทธ๋ํ, ํ ๋ฑ) ์ค๋ช
๋ฐฉ์ |
| - ์ต์ ์ฐ๊ตฌ๋ ๋ํฅ ์ฐธ๊ณ |
| 5. ๋
์ ์ํธ์์ฉ |
| - ์ ์ ํ ๋
์์ ์๊ฐ์ ๋ฌป๋ ์ง๋ฌธ ํฌํจ |
| - ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์๋ ํค์๋ ์ ์ |
| - ์ค์ฉ์ ์ธ ์ ์ฉ ๋ฐฉ์ ์ ๊ณต |
| 6. ๋ง๋ฌด๋ฆฌ |
| - ์ฃผ์ ๋ด์ฉ ๊ฐ๋จํ ์์ฝ |
| - ์ฃผ์ ์ ์ค์์ฑ์ด๋ ์์ ๊ฐ์กฐ |
| - ํฅํ ์ ๋ง์ด๋ ์ถ๊ฐ ๊ณ ๋ ค์ฌํญ ์ ์ |
| ์ฃผ์์ฌํญ: ๋๋ฌด ๋ฑ๋ฑํ๊ฑฐ๋ ์ง๋ฃจํ์ง ์๋๋ก ๋ค์ํ ์์์ ๊ฐ๊ด์ ์ ๋ณด ์ฌ์ด์ ๊ท ํ ์ ์ง |
| """, |
| "์ ๋ฌธ์ ์ธ": """ |
| #์ ๋ฌธ์ ์ธ ์ ๋ณด์ฑ ๋ธ๋ก๊ทธ ์คํ์ผ ๊ฐ์ด๋ |
| 1. ํค๊ณผ ๊ตฌ์กฐ |
| - ๊ณต์์ ์ด๊ณ ์ ๋ฌธ์ ์ธ ํค ์ฌ์ฉ |
| - ๊ฐ๊ด์ ์ด๊ณ ๋ถ์์ ์ธ ์ ๊ทผ ์ ์ง |
| - ๋ช
ํํ ์๋ก (๊ฐ์), ๋ณธ๋ก (์์ธ ๋ถ์), ๊ฒฐ๋ก (์ข
ํฉ ํ๊ฐ) ๊ตฌ์กฐ |
| - ์ฒด๊ณ์ ์ธ ์ ๋ณด ์ ๊ฐ |
| - ์ธ๋ถ ์น์
์ ์ํ ๋ช
ํํ ์์ ๋ชฉ ์ฌ์ฉ |
| 2. ๋ด์ฉ ๊ตฌ์ฑ ๋ฐ ์ ๊ฐ |
| - ์ฃผ์ ์ ์ญ์ฌ์ ๋ฐฐ๊ฒฝ, ์ด๋ก ์ ๊ธฐ๋ฐ, ํ์ฌ ๋ํฅ ๋ฑ ์ฌ์ธต์ ์ ๋ณด ํฌํจ |
| - ๋
ผ๋ฆฌ์ ์ฐ๊ฒฐ์ ์ํ ์ ํ์ด ํ์ฉ |
| - ์ ๋ฌธ ์ฉ์ด ์ ์ ํ ํ์ฉ (ํ์์ ๊ฐ๋ตํ ์ค๋ช
์ ๊ณต) |
| - ์ฌ์ธต์ ์ธ ๋ถ์๊ณผ ๋นํ์ ํ๊ฐ ์ ๊ณต |
| - ๋ค์ํ ๊ด์ ๊ณผ ์ด๋ก ์ ํ๋ ์์ํฌ ์ ์ |
| 3. ๋ฐ์ดํฐ ๋ฐ ๊ทผ๊ฑฐ ํ์ฉ |
| - ํต๊ณ, ์ฐ๊ตฌ ๊ฒฐ๊ณผ, ์ฌ๋ก ์ฐ๊ตฌ ๋ฑ ๊ฐ๊ด์ ๋ฐ์ดํฐ ํ์ฉ |
| - ์ฃผ์ ๋ถ์์ ์ํ ์ฒด๊ณ์ ์ธ ํ๋ ์์ํฌ ์ ์ |
| - ์์น ๋ฐ์ดํฐ๋ ๋ช
ํํ ์ค๋ช
(์ถ์ธ, ์๊ด๊ด๊ณ, ์ธ๊ณผ๊ด๊ณ ๋ฑ) |
| - ํ์ ์ ๊ทผ๊ฑฐ์ ํ์ค ์ ์ฉ์ ๊ท ํ |
| 4. ์ ๋ฌธ์ ์ ๋ณด ์ ๊ณต |
| - ์ต์ ์ฐ๊ตฌ ๋ํฅ ๋ฐ ๋ฐ์ ๋ฐฉํฅ ๋ถ์ |
| - ์ด๋ก ๊ณผ ์ค์ ์ ์ฉ ์ฌ์ด์ ๊ฐ๊ทน ๋ถ์ |
| - ์ฃผ์ ๊ด๋ จ ์์ ๊ณผ ๋
ผ์์ ์๊ฐ |
| - ์ฒด๊ณ์ ์ธ ๋ฌธ์ ํด๊ฒฐ ์ ๊ทผ๋ฒ ์ ์ |
| 5. ๋ง๋ฌด๋ฆฌ |
| - ํต์ฌ ์ ๋ณด ์์ฝ ๋ฐ ์ข
ํฉ ํ๊ฐ |
| - ์ฃผ์ ์ ํ๋ฌธ์ , ์ค์ฉ์ ์์ ๋ถ์ |
| - ํฅํ ์ฐ๊ตฌ ๋ฐฉํฅ์ด๋ ๋ฐ์ ๊ฐ๋ฅ์ฑ ์ ์ |
| ์ฃผ์์ฌํญ: ์ ๋ฌธ์ฑ์ ์ ์งํ๋, ๋ถํ์ํ๊ฒ ์ด๋ ค์ด ์ฉ์ด๋ ์ง์ํ๊ณ ๋ช
ํํ ์ค๋ช
์ค์ฌ์ผ๋ก ๊ตฌ์ฑ |
| """ |
| } |
| return prompts.get(style, prompts["์น๊ทผํ"]) |
|
|
| def get_informational_blog_prompt(): |
| prompts = [ |
| """ |
| [์ค์: ์ ๋ณด์ฑ ๋ธ๋ก๊ทธ ๊ธ ์์ฑ ํ์ ๊ท์น] |
| ์ด ๊ท์น์ ๋ฐ๋์ ๋ฐ๋ฅด์ธ์. ์ด๋ค ์ํฉ์์๋ ์์ธ๋ ์์ต๋๋ค: |
| 1. ๋งํฌ๋ค์ด ๋ฌธ๋ฒ(**, *, #, -, 1., 2., 3.)์ ์ ๋ ์ฌ์ฉํ์ง ๋ง์ธ์. |
| 2. ๋ชจ๋ ์์ ๋ชฉ์ ๋ฒํธ ์์ด ์ผ๋ฐ ๋ฌธ์ฅ์ผ๋ก ์์ฑํ์ธ์. |
| 3. ๋ชจ๋ ๋ชฉ๋ก์ ๋ถ๋ฆฟ์ด๋ ๋ฒํธ ์์ด ์์ฐ์ค๋ฌ์ด ๋ฌธ์ฅ์ผ๋ก ์์ ํ์ธ์. |
| 4. "์ฐธ๊ณ ๊ธ", "์ฐธ๊ณ ๊ธ์ ๋ฐ๋ฅด๋ฉด" ๋ฑ์ ํํ์ ์ ๋ ์ฌ์ฉํ์ง ๋ง์ธ์. |
| 5. ์ฃผ์ ์ ๊ดํ ์ ํํ๊ณ ์ ๋ขฐํ ์ ์๋ ์ ๋ณด๋ง ์์ฑํ์ธ์. |
| 6. ๊ธ์ ์ฃผ์ ๋ ๋ฐ๋์ ์ฃผ์ด์ง ์ฐธ๊ณ ๊ธ์ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์์ฑํ์ธ์. |
| 7. ๊ธ์ ๊ตฌ์ฑ์ ๋ค์์ ํฌํจํด์ผ ํฉ๋๋ค: |
| - ์ฃผ์ ์ ๋ํ ๋ช
ํํ ์๊ฐ์ ๋ฐฐ๊ฒฝ |
| - ์ฃผ์ ์ ์ญ์ฌ์ /์ด๋ก ์ ๋งฅ๋ฝ |
| - ํ์ฌ ๋ํฅ์ด๋ ์ต์ ์ ๋ณด |
| - ์ฃผ์ ๊ฐ๋
์ด๋ ์๋ฆฌ์ ๋ํ ์ค๋ช
|
| - ์ค์ฉ์ ์ธ ์ ์ฉ ๋ฐฉ๋ฒ์ด๋ ์ฌ๋ก |
| - ๋ค์ํ ๊ด์ ์ด๋ ๋น๊ต ๋ถ์ |
| - ํฅํ ์ ๋ง์ด๋ ๊ฒฐ๋ก |
| """ |
| ] |
| return random.choice(prompts) |
|
|
| def remove_unwanted_phrases(text): |
| unwanted_phrases = [ |
| '์ฌ๋ฌ๋ถ', '์ต๊ทผ', '๋ง์ง๋ง์ผ๋ก', '๊ฒฐ๋ก ์ ์ผ๋ก', '๊ฒฐ๊ตญ', |
| '์ข
ํฉ์ ์ผ๋ก', '๋ฐ๋ผ์', '๋ง๋ฌด๋ฆฌ', '๋์ผ๋ก', '์์ฝ', |
| 'ํ ์ค ์์ฝ', '์ ๋ฆฌํ์๋ฉด', '์ด์ ๋ฆฌ', '๊ธ์ ๋ง์น๋ฉฐ', |
| '์ด์์ผ๋ก', '์ถ์ฒ๋๋ฆฝ๋๋ค', '์ฐธ๊ณ ํ์ธ์', '๋์์ด ๋์
จ๊ธธ', |
| '์ข์ ํ๋ฃจ ๋์ธ์', '๋ค์ ๊ธ์์', '๋์์ด ๋์๊ธธ', |
| '์ฆ๊ฑฐ์ด ํ๋ฃจ ๋์ธ์', '๊ฐ์ฌํฉ๋๋ค' |
| ] |
| words = re.findall(r'\S+|\n', text) |
| result_words = [word for word in words if not any(phrase in word for phrase in unwanted_phrases)] |
| return ' '.join(result_words).replace(' \n ', '\n').replace(' \n', '\n').replace('\n ', '\n') |
|
|
| def post_process_blog(blog_content, style="์น๊ทผํ"): |
| """์์ฑ๋ ๋ธ๋ก๊ทธ ๊ธ์ ํ์ฒ๋ฆฌ: ๋งํฌ๋ค์ด, ๋ฒํธ/๋ถ๋ฆฟ ์ ๊ฑฐ ๋ฐ ์คํ์ผ ์์ """ |
| try: |
| |
| blog_content = re.sub(r'^\d+\.\s+', '', blog_content, flags=re.MULTILINE) |
| blog_content = re.sub(r'^[\*\-\โข]\s+', '', blog_content, flags=re.MULTILINE) |
| blog_content = re.sub(r'^#+\s+', '', blog_content, flags=re.MULTILINE) |
|
|
| if style == "์น๊ทผํ": |
| blog_content = re.sub(r'([๊ฐ-ํฃ]+)๊ณ ์', r'\1๊ตฌ์', blog_content) |
| blog_content = re.sub(r'๋ต๋๋ค', '์ด์', blog_content) |
| blog_content = re.sub(r'์๋ต๋๋ค', '์์ด์', blog_content) |
| blog_content = re.sub(r'ํ๋ต๋๋ค', 'ํ์ด์', blog_content) |
| blog_content = re.sub(r'์ต๋๋ค', '์', blog_content) |
| blog_content = re.sub(r'ํฉ๋๋ค', 'ํด์', blog_content) |
| blog_content = re.sub(r'๋ฉ๋๋ค', '๋ผ์', blog_content) |
| blog_content = re.sub(r'์
๋๋ค', '์ด์์', blog_content) |
|
|
| |
| exaggerated_expressions = [ |
| (r'ํ์์ ์ธ', r'์ค์ํ'), |
| (r'ํ๋ช
์ ์ธ', r'์ค์ํ'), |
| (r'๋๋ผ์ด', r'์ฃผ๋ชฉํ ๋งํ'), |
| (r'๊ธฐ์ ์', r'ํจ๊ณผ์ ์ธ'), |
| (r'์ต๊ณ ์', r'์ข์'), |
| (r'์ธ๊ณ์ ์ธ', r'์ ๋ช
ํ'), |
| (r'์๋ฒฝํ', r'์ฐ์ํ'), |
| (r'๊ทน์ ์ธ', r'์๋นํ'), |
| (r'๋ฌดํํ', r'๋ง์'), |
| (r'์ ๋์ ์ธ', r'์๋นํ'), |
| (r'ํ์ ์ ์ธ', r'์๋ก์ด'), |
| (r'ํ์์ ์ธ', r'์ข์'), |
| (r'๊ทผ๋ณธ์ ์ธ', r'๊ธฐ๋ณธ์ ์ธ'), |
| (r'ํ๊ธฐ์ ์ธ', r'์ค์ํ'), |
| (r'์ ๋ก์๋', r'ํน๋ณํ'), |
| (r'์๋์ ์ธ', r'์ฃผ๋ชฉํ ๋งํ'), |
| (r'ํฉํํ', r'์ข์'), |
| (r'์ฒ์์', r'์ฐ์ํ'), |
| (r'๊ธฐ๊ฐ ๋งํ', r'ํจ๊ณผ์ ์ธ'), |
| (r'๋ํ์', r'์ต์์'), |
| (r'๊ทธ ์์ฒด', r''), |
| (r'์ด (.{1,10}) ๊ทธ ์์ฒด์์ด์', r'์ด \1์์ด์'), |
| (r'๊ฐ (.{1,10}) ๊ทธ ์์ฒด์์ด์', r'๊ฐ \1์์ด์'), |
| (r'์๋์ ์ธ', r'์ค์ํ'), |
| (r'์ฒ๊ตญ', r'์ข์ ๊ณณ'), |
| (r'ํฉํํ์ด์', r'์ข์์ด์'), |
| (r'ํ์์', r'์ข์') |
| ] |
| |
| for pattern, replacement in exaggerated_expressions: |
| blog_content = re.sub(pattern, replacement, blog_content, flags=re.IGNORECASE) |
| |
| blog_content = re.sub(r'์ฐธ๊ณ ๊ธ์ ๋ฐ๋ฅด๋ฉด', r'์๋ ค์ง ๋ฐ๋ก๋', blog_content) |
| blog_content = re.sub(r'์ฐธ๊ณ ๊ธ', r'๊ด๋ จ ์ ๋ณด', blog_content) |
| |
| |
| prompt = f""" |
| ๋ค์ ์ ๋ณด์ฑ ๋ธ๋ก๊ทธ ๊ธ์ ๋ ์์ฐ์ค๋ฌ์ด ํํ๋ก ๋ณ๊ฒฝํด์ฃผ์ธ์: |
| |
| ์๋ณธ ๊ธ: |
| {blog_content} |
| |
| ๋ณ๊ฒฝ ์๊ตฌ์ฌํญ: |
| 1. ๋งํฌ๋ค์ด ํ์ ๋ฐ ๋ฒํธ ๋ชฉ๋ก, ๋ถ๋ฆฟ ํํ ์ ๊ฑฐ |
| 2. ์์ ๋ชฉ์ 5๊ฐ ์ดํ๋ก, ๊ฐ ์์ ๋ชฉ ์๋ ๋ด์ฉ์ ์ต์ {MIN_SECTION_LENGTH}์ ์ด์์ผ๋ก ์์ธํ๊ฒ ์์ |
| 3. "์ฐธ๊ณ ๊ธ" ๊ด๋ จ ํํ ์ ๊ฑฐ |
| 4. ๊ตฌ์ฒด์ ์ธ ์์์ ์ค์ฉ์ ์ธ ์ ๋ณด ํฌํจ |
| 5. ์คํ์ผ: {style} (์์ฐ์ค๋ฌ์ด ๋ฌธ์ฒด ์ฌ์ฉ) |
| """ |
| |
| |
| from google.genai import types |
| client = get_random_gemini_client() |
| |
| response = client.models.generate_content( |
| model="gemini-2.0-flash", |
| contents=[prompt], |
| config=types.GenerateContentConfig( |
| max_output_tokens=MAX_TOKENS, |
| temperature=0.7, |
| top_p=0.9 |
| ) |
| ) |
| return response.text.strip() |
| except Exception as e: |
| logging.error(f"๋ธ๋ก๊ทธ ๊ธ ํ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") |
| return blog_content |
|
|
| def format_blog_post(blog_post, query=""): |
| blog_post = re.sub(r'^#+\s+', '', blog_post, flags=re.MULTILINE) |
| blog_post = re.sub(r'^\d+\.\s+', '', blog_post, flags=re.MULTILINE) |
| blog_post = re.sub(r'^[\*\-]\s+', '', blog_post, flags=re.MULTILINE) |
| |
| lines = blog_post.split('\n') |
| formatted_lines = [] |
| in_paragraph = False |
| first_line = True |
| title_found = False |
| first_non_empty_line = "" |
| for line in lines: |
| if line.strip(): |
| first_non_empty_line = line.strip() |
| break |
| |
| subtitle_patterns = [ |
| r'^.{10,100}\?$', |
| r'^.{10,100}:$', |
| r'^.{5,50}์ [๊ฐ-ํฃ\s]+$', |
| r'^[๊ฐ-ํฃ\s]{5,50} [๊ฐ-ํฃ\s]+$' |
| ] |
| |
| previous_line_empty = True |
| |
| def optimize_title(title, max_length=60): |
| if ': ' in title and len(title) > max_length: |
| title = title.split(': ')[0] |
| if len(title) > max_length: |
| title = re.sub(r'\s*\([^)]*\)', '', title) |
| if len(title) > max_length and ',' in title: |
| title = title.split(',')[0] |
| if len(title) > max_length: |
| words = title.split() |
| shortened_title = [] |
| current_length = 0 |
| for word in words: |
| if current_length + len(word) + 1 <= max_length: |
| shortened_title.append(word) |
| current_length += len(word) + 1 |
| else: |
| break |
| title = ' '.join(shortened_title) |
| endings_to_remove = ['๊ทธ', '์ด', '์', '๋', '์ด๋', '์', '๊ณผ', '๋๋', '๊ทธ๋ฆฌ๊ณ '] |
| for ending in endings_to_remove: |
| if title.endswith(f" {ending}"): |
| title = title[:-len(ending)-1] |
| if len(title) < 20 and query: |
| title = f"{query} ์ ๋ณด ๊ฐ์ด๋" |
| return title |
| |
| for i, line in enumerate(lines): |
| line = line.strip() |
| next_line_empty = (i+1 >= len(lines)) or not lines[i+1].strip() |
| |
| if not line: |
| if in_paragraph: |
| formatted_lines.append("</p>") |
| in_paragraph = False |
| formatted_lines.append("<br>") |
| previous_line_empty = True |
| else: |
| if first_line and len(line) > 5: |
| optimized_title = optimize_title(line) |
| formatted_lines.append(f'<h1 style="font-size: 1.8em; margin-bottom: 20px; font-weight: bold; color: #222;">{html.escape(optimized_title)}</h1>') |
| first_line = False |
| title_found = True |
| previous_line_empty = False |
| else: |
| is_subtitle = False |
| if any(re.match(pattern, line) for pattern in subtitle_patterns): |
| is_subtitle = True |
| elif previous_line_empty and next_line_empty and len(line) < 80: |
| is_subtitle = True |
| if is_subtitle: |
| if in_paragraph: |
| formatted_lines.append("</p>") |
| in_paragraph = False |
| 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>') |
| else: |
| if not in_paragraph: |
| formatted_lines.append("<p>") |
| in_paragraph = True |
| content = html.escape(line) |
| bold_content = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', content) |
| formatted_lines.append(bold_content) |
| previous_line_empty = False |
| if in_paragraph: |
| formatted_lines.append("</p>") |
| if not title_found: |
| default_title = f"{query} ์ ๋ณด ๊ฐ์ด๋" if query else "์ ๋ณด์ฑ ๋ธ๋ก๊ทธ" |
| if first_non_empty_line: |
| default_title = optimize_title(first_non_empty_line) |
| formatted_lines.insert(0, f'<h1 style="font-size: 1.8em; margin-bottom: 20px; font-weight: bold; color: #222;">{html.escape(default_title)}</h1>') |
| return '\n'.join(formatted_lines) |
|
|
| |
| def call_gemini_api(prompt): |
| |
| from google.genai import types |
| client = get_random_gemini_client() |
| |
| response = client.models.generate_content( |
| model="gemini-2.0-flash", |
| contents=[prompt], |
| config=types.GenerateContentConfig( |
| max_output_tokens=MAX_TOKENS, |
| temperature=TEMPERATURE, |
| top_p=TOP_P |
| ) |
| ) |
| return response.text.strip() |
|
|
| def generate_blog_post(query, style="์น๊ทผํ"): |
| try: |
| |
| ref1, ref2, ref3 = fetch_crawl_results(query) |
| style_prompt = get_style_prompt(style) |
| format_prompt = get_informational_blog_prompt() |
| |
| |
| style_specific_instructions = "" |
| if style == "์น๊ทผํ": |
| style_specific_instructions = """ |
| ์ด ๋ธ๋ก๊ทธ๋ ๋ฐ๋์ ์น๊ทผํ ๋ํ์ฒด๋ก ์์ฑํด์ผ ํฉ๋๋ค. |
| - 'ํด์์ฒด' ์ฌ์ฉ: "~ํ์ด์", "~์ธ ๊ฒ ๊ฐ์์", "~ํ๋ค์" |
| - ๊ฒฉ์์ฒด(์: "~ํฉ๋๋ค", "~์
๋๋ค") ์ฌ์ฉ ๊ธ์ง |
| - ๋ํํ๋ฏ ํธ์ํ๊ฒ ์์ฑ |
| - ์งง๊ณ ๊ฐ๊ฒฐํ ๋ฌธ์ฅ ์ฌ์ฉ |
| - ๋ณต์กํ ๊ฐ๋
์ ์ผ์์ ์ธ ๋น์ ๋ก ์ค๋ช
|
| - ์ค์ ์ฌ๋ก์ ์์๋ฅผ ํตํ ์ค๋ช
|
| - ์์ฌ์๋ฌธ๋ฌธ์ ํ์ฉํ ๋
์์์ ์ํต |
| """ |
| elif style == "์ผ๋ฐ์ ์ธ": |
| style_specific_instructions = """ |
| ์ด ๋ธ๋ก๊ทธ๋ ๋ฐ๋์ ์ผ๋ฐ์ ์ธ ์กด๋๋ง์ฒด๋ก ์์ฑํด์ผ ํฉ๋๋ค. |
| - '์ต๋๋ค์ฒด' ์ฌ์ฉ: "~ํ์ต๋๋ค", "~์
๋๋ค", "~ํ์์ต๋๋ค" |
| - ๊ฒฉ์์ ์ด์ง ์์ ๋ช
ํํ ํํ ์ฌ์ฉ |
| - ๊ฐ๊ฒฐํ๊ณ ๋ช
๋ฃํ ๋ฌธ์ฅ ์ฌ์ฉ |
| - ๊ฐ๊ด์ ์ธ ์ฌ์ค๊ณผ ๋ถ์ ์ค์ฌ์ผ๋ก ์์ |
| - ๊ท ํ ์กํ ์๊ฐ์์ ๋ค์ํ ๊ด์ ์ ์ |
| """ |
| elif style == "์ ๋ฌธ์ ์ธ": |
| style_specific_instructions = """ |
| ์ด ๋ธ๋ก๊ทธ๋ ๋ฐ๋์ ์ ๋ฌธ์ ์ด๊ณ ๋ถ์์ ์ธ ์ดํฌ๋ก ์์ฑํด์ผ ํฉ๋๋ค. |
| - '์ต๋๋ค์ฒด' ์ฌ์ฉ: "~ํ์ต๋๋ค", "~์
๋๋ค", "~ํ์์ต๋๋ค" |
| - ์ญ์ฌ, ์ด๋ก , ์ฐ๊ตฌ ๊ฒฐ๊ณผ ๋ฑ ์ฌ์ธต ์ ๋ณด ํฌํจ |
| - ๊ฐ๊ด์ ์ด๊ณ ๋
ผ๋ฆฌ์ ์ธ ๋ถ์ ์ค์ฌ ์์ฑ |
| - ๊ตฌ์ฒด์ ์ธ ๋ฐ์ดํฐ ๋ฐ ์์น ํฌํจํ์ฌ ์ค๋ช
|
| - ๋ค์ํ ๊ด์ ๊ณผ ์ด๋ก ์ ํ๋ ์์ํฌ ์ ์ |
| """ |
| |
| |
| initial_prompt = f""" |
| ์ฃผ์ : {query} ์ ๋ณด์ฑ ๋ธ๋ก๊ทธ |
| ์ฐธ๊ณ ๊ธ 1: {ref1} |
| ์ฐธ๊ณ ๊ธ 2: {ref2} |
| ์ฐธ๊ณ ๊ธ 3: {ref3} |
| ๋ชฉํ ๊ธ์์: {TARGET_CHAR_LENGTH} |
| {format_prompt} |
| ์คํ์ผ ๊ฐ์ด๋: |
| {style_prompt} |
| ์คํ์ผ ์ธ๋ถ ์ง์์ฌํญ: |
| {style_specific_instructions} |
| ํน๋ณ ์ง์์ฌํญ: |
| 1. ๋ฐ๋์ ๊ธ์ ์ฒ์์ ๋งค๋ ฅ์ ์ด๊ณ ๋ช
ํํ ์ ๋ชฉ ํฌํจ (์ฃผ์ ์ ํต์ฌ ํฌ์ธํธ๋ฅผ ๋ด์ ์ ๋ชฉ). |
| 2. ๋งํฌ๋ค์ด ๋ฌธ๋ฒ(#, *, -, 1., 2., ๋ฑ) ์ฌ์ฉ ๊ธ์ง. |
| 3. ์์ ๋ชฉ์ ๋ฒํธ ์์ด ์์ฑํ๊ณ , 5๊ฐ ์ดํ๋ก ์ ํ. |
| 4. ๋ชจ๋ ๋ชฉ๋ก์ ๋ถ๋ฆฟ์ด๋ ๋ฒํธ ์์ด ์์ฐ์ค๋ฝ๊ฒ ์์ . |
| 5. "์ฐธ๊ณ ๊ธ" ๊ด๋ จ ํํ ์ฌ์ฉ ๊ธ์ง. |
| 6. ์ฃผ์ ์ ๊ดํ ์ ํํ๊ณ ์ ๋ขฐํ ์ ์๋ ์ ๋ณด๋ง ์์ฑ. |
| 7. ๊ตฌ์ฒด์ ์ธ ์ ๋ณด(์ญ์ฌ์ ๋ฐฐ๊ฒฝ, ์ฃผ์ ๊ฐ๋
, ์ด๋ก , ์ค์ฉ์ ์ ์ฉ, ์ต์ ๋ํฅ ๋ฑ) ์์ฐ์ค๋ฝ๊ฒ ํฌํจ. |
| 8. ๊ธ์์๊ฐ ์ต์ {TARGET_CHAR_LENGTH}์ ์ด์์ด์ด์ผ ํจ. |
| 9. ๊ฐ ์์ ๋ชฉ ์๋ ๋ด์ฉ์ ์ต์ {MIN_SECTION_LENGTH}์ ์ด์ ์์ . |
| 10. ๋ฐ๋์ ๋ค์ ๊ตฌ์ฑ ์์๋ฅผ ํฌํจํ ๊ฒ: |
| - ์ฃผ์ ์ ๋ํ ๋ช
ํํ ์๊ฐ์ ๋ฐฐ๊ฒฝ |
| - ์ฃผ์ ์ ์ญ์ฌ์ /์ด๋ก ์ ๋งฅ๋ฝ |
| - ํ์ฌ ๋ํฅ์ด๋ ์ต์ ์ ๋ณด |
| - ์ฃผ์ ๊ฐ๋
์ด๋ ์๋ฆฌ์ ๋ํ ์ค๋ช
|
| - ์ค์ฉ์ ์ธ ์ ์ฉ ๋ฐฉ๋ฒ์ด๋ ์ฌ๋ก |
| - ๋ค์ํ ๊ด์ ์ด๋ ๋น๊ต ๋ถ์ |
| - ํฅํ ์ ๋ง์ด๋ ๊ฒฐ๋ก |
| """ |
| |
| first_attempt = call_gemini_api(initial_prompt) |
| first_attempt_cleaned = remove_unwanted_phrases(first_attempt) |
| first_attempt_length = len(first_attempt_cleaned) |
| |
| if first_attempt_length >= TARGET_CHAR_LENGTH: |
| final_post = post_process_blog(first_attempt_cleaned, style) |
| final_html = format_blog_post(final_post, query) |
| return final_html, ref1, ref2, ref3, first_attempt_length |
| |
| |
| longest_ref = max([ref1, ref2, ref3], key=len) |
| revision_prompt = f""" |
| ์ด์ ๊ธ: |
| {first_attempt_cleaned} |
| ์ฐธ๊ณ ๊ธ: {longest_ref} |
| ํฌ์คํ
์คํ์ผ: |
| {style_prompt} |
| ์คํ์ผ ์ธ๋ถ ์ง์์ฌํญ: |
| {style_specific_instructions} |
| ๋ฌธ์ ์ : |
| ์ด์ ์ ์์ฑ๋ ๊ธ์ ๋ชฉํ ๊ธ์์์ธ {TARGET_CHAR_LENGTH}์์ ๋ฏธ์น์ง ๋ชปํฉ๋๋ค. ํ์ฌ ๊ธ์์๋ ์ฝ {first_attempt_length}์์
๋๋ค. |
| ๋ํ, ๊ฐ ์์ ๋ชฉ ์๋ ๋ด์ฉ์ด ๋๋ฌด ์งง๊ณ ๋ถ์คํฉ๋๋ค. |
| ์ค์ ์๊ตฌ์ฌํญ: |
| 1. ๊ธ์ ์ฒ์์ ๋งค๋ ฅ์ ์ธ ์ ๋ชฉ(์ฃผ์ ์ ํต์ฌ ํฌ์ธํธ ํฌํจ) ์ถ๊ฐ. |
| 2. ๊ธ์์๋ฅผ ์ต์ {TARGET_CHAR_LENGTH}์ ์ด์์ผ๋ก ๋๋ฆฌ๊ณ , ๊ฐ ์น์
์ ์์ธํ ์์ . |
| 3. ๋งํฌ๋ค์ด ํ์(#, *, -, 1., 2., ๋ฑ) ์ฌ์ฉ ๊ธ์ง. |
| 4. ์์ ๋ชฉ์ ๋ฒํธ ์์ด ์์ฑ. |
| 5. ๋ชจ๋ ๋ชฉ๋ก์ ๋ถ๋ฆฟ์ด๋ ๋ฒํธ ์์ด ์์ . |
| 6. "์ฐธ๊ณ ๊ธ" ๊ด๋ จ ํํ ์ฌ์ฉ ๊ธ์ง. |
| 7. ์ฃผ์ ์ ๊ดํ ์ ํํ๊ณ ์ ๋ขฐํ ์ ์๋ ์ ๋ณด๋ง ์์ฑ. |
| 8. ๊ฐ ์์ ๋ชฉ ์๋ ๋ด์ฉ์ ์ต์ {MIN_SECTION_LENGTH}์ ์ด์ ์์ . |
| 9. ์์ ๋ชฉ ์๋ 5๊ฐ ์ดํ๋ก ์ ํ. |
| ์์ธ ๋ณด์: |
| - ์ฃผ์ ์ ๋ฐฐ๊ฒฝ ๋ฐ ๋งฅ๋ฝ์ ๋ํ ๋ ๊น์ ์ค๋ช
์ถ๊ฐ. |
| - ๊ตฌ์ฒด์ ์ธ ์ฌ๋ก, ์ฐ๊ตฌ ๊ฒฐ๊ณผ, ํต๊ณ ๋ฑ ์ค์ฆ์ ๋ฐ์ดํฐ ์ถ๊ฐ. |
| - ์ญ์ฌ์ ๋ฐ์ ๊ณผ์ , ์ฃผ์ ์ด๋ก ์ ํ๋ ์์ํฌ ๋ฑ ์ฌ์ธต ์ ๋ณด ์ถ๊ฐ. |
| - ์ค์ฉ์ ์ธ ์ ์ฉ ๋ฐฉ๋ฒ, ํ, ๊ถ์ฅ์ฌํญ ๋ฑ ์คํ ๊ฐ๋ฅํ ์ ๋ณด ์ถ๊ฐ. |
| - ๋ค์ํ ๊ด์ , ์์ , ๋
ผ์์ ๋ฑ ๊ท ํ ์กํ ์๊ฐ ์ ๊ณต. |
| """ |
| revised_attempt = call_gemini_api(revision_prompt) |
| revised_cleaned = remove_unwanted_phrases(revised_attempt) |
| final_post = post_process_blog(revised_cleaned, style) |
| final_html = format_blog_post(final_post, query) |
| |
| soup = BeautifulSoup(final_html, 'html.parser') |
| actual_char_length = len(soup.get_text()) |
| |
| |
| if actual_char_length < TARGET_CHAR_LENGTH * 0.8: |
| expansion_prompt = f""" |
| ๋ค์ ์ ๋ณด์ฑ ๋ธ๋ก๊ทธ ๊ธ์ ๋ด์ฉ์ ํฌ๊ฒ ํ์ฅํด์ฃผ์ธ์: |
| ์๋ณธ ๊ธ: |
| {final_post} |
| ๋ฌธ์ ์ : |
| ์ด ๊ธ์ ๋ชฉํ ๊ธ์์์ธ {TARGET_CHAR_LENGTH}์์ ๋ฏธ์น์ง ๋ชปํฉ๋๋ค. ํ์ฌ ๊ธ์์๋ ์ฝ {actual_char_length}์์ด๋ฉฐ, ๋ด์ฉ์ด ๋ถ์คํฉ๋๋ค. |
| ์คํ์ผ ๊ฐ์ด๋: |
| {style_prompt} |
| ์คํ์ผ ์ธ๋ถ ์ง์์ฌํญ: |
| {style_specific_instructions} |
| ์๊ตฌ์ฌํญ: |
| 1. ๊ฐ ์์ ๋ชฉ ์๋์ ๋ด์ฉ์ ์ต์ {MIN_SECTION_LENGTH}์ ์ด์ ๋ํญ ํ์ฅ. |
| 2. ๊ตฌ์ฒด์ ์ธ ์ฌ๋ก, ์ฐ๊ตฌ ๊ฒฐ๊ณผ, ํต๊ณ, ์์ ๋ฑ์ ์ถ๊ฐ. |
| 3. ๋งํฌ๋ค์ด ํ์(#, *, -, 1., 2., ๋ฑ) ์ฌ์ฉ ๊ธ์ง. |
| 4. {style} ์คํ์ผ์ ๋ง์ถฐ ์ผ๊ด๋๊ฒ ์์ฑ. |
| 5. ์์ ๋ชฉ ๊ตฌ์กฐ๋ ์ ์งํ๋, ๊ฐ ์น์
๋ด์ฉ์ 3๋ฐฐ ์ด์ ํ๋ถํ๊ฒ ํ์ฅ. |
| 6. ์ฃผ์ ์ ์ญ์ฌ์ ๋ฐ์ , ์ด๋ก ์ ๋ฐฐ๊ฒฝ, ์ฃผ์ ๊ฐ๋
๋ฑ์ ๋ ์์ธํ ์์ . |
| 7. ์ ์ฒด ๊ธ์์๋ฅผ ์ต์ {TARGET_CHAR_LENGTH}์ ์ด์ ๋ฌ์ฑ. |
| 8. ์์ ๋ชฉ ์๋ ์ต๋ 5๊ฐ๋ก ์ ํ. |
| 9. ์ค์ฉ์ ์ ๋ณด(์ ์ฉ ๋ฐฉ๋ฒ, ํ, ์ฃผ์์ฌํญ ๋ฑ) ์ถ๊ฐ. |
| 10. ๋ค์ํ ๊ด์ ๊ณผ ์๊ฐ์์์ ๋ถ์ ์ถ๊ฐ. |
| 11. ์ฃผ์ ์ ๊ด๋ จ๋ ์ต์ ๋ํฅ์ด๋ ๋ฐ์ ๋ฐฉํฅ ์ถ๊ฐ. |
| 12. ๊ฒฐ๋ก ๋ถ๋ถ์์ ์ฃผ์ ์ ์์์ ์ค์์ฑ ๊ฐ์กฐ. |
| """ |
| expanded_attempt = call_gemini_api(expansion_prompt) |
| final_post = post_process_blog(expanded_attempt, style) |
| final_html = format_blog_post(final_post, query) |
| soup = BeautifulSoup(final_html, 'html.parser') |
| actual_char_length = len(soup.get_text()) |
| |
| return final_html, ref1, ref2, ref3, actual_char_length |
| |
| except Exception as e: |
| logging.error(f"๋ธ๋ก๊ทธ ๊ธ ์์ฑ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") |
| return f"<p>๋ธ๋ก๊ทธ ๊ธ ์์ฑ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}</p>", "", "", "", 0 |