ssboost commited on
Commit
f4227af
ยท
verified ยท
1 Parent(s): 0e8ded5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -867
app.py CHANGED
@@ -1,848 +1,71 @@
1
  import gradio as gr
2
- import requests
3
- from urllib.parse import quote_plus, urlparse
4
- import re
5
- import logging
6
- import tempfile
7
- import pandas as pd
8
- import mecab # pythonโ€‘mecabโ€‘ko ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ
9
  import os
10
- import time
11
- import hmac
12
- import hashlib
13
- import base64
14
- import random
15
- from concurrent.futures import ThreadPoolExecutor, as_completed
16
- import json
 
 
 
 
 
 
 
 
 
17
 
18
- # API ์„ค์ •
19
- API_BASE_URL = os.getenv("API_BASE_URL", "")
20
- API_KEY = os.getenv("API_KEY", "")
21
- HEADERS = {
22
- "x-api-key": API_KEY,
23
- "content-type": "application/json"
24
- }
25
-
26
- # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋„ค์ด๋ฒ„ API ํ‚ค๋“ค์„ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜
27
- def load_naver_api_keys():
28
- """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค."""
29
- try:
30
- # ํ™˜๊ฒฝ๋ณ€์ˆ˜ NAVER_API_KEYS์—์„œ JSON ๋ฌธ์ž์—ด์„ ๊ฐ€์ ธ์˜ด
31
- naver_api_keys_json = os.getenv("NAVER_API_KEYS", "")
32
-
33
- debug_log(f"NAVER_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์›๋ณธ: {naver_api_keys_json[:100] if naver_api_keys_json else '๋นˆ ๋ฌธ์ž์—ด'}...")
34
-
35
- if not naver_api_keys_json or naver_api_keys_json.strip() == "":
36
- debug_log("NAVER_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค.")
37
- return []
38
-
39
- # JSON ๋ฌธ์ž์—ด์„ ํŒŒ์‹ฑ
40
- api_keys_data = json.loads(naver_api_keys_json)
41
- debug_log(f"JSON ํŒŒ์‹ฑ ์„ฑ๊ณต: {len(api_keys_data)}๊ฐœ ํ•ญ๋ชฉ")
42
-
43
- # ๋นˆ ํ‚ค๋‚˜ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ œ๊ฑฐ
44
- api_keys = []
45
- for i, config in enumerate(api_keys_data):
46
- debug_log(f"API ํ‚ค ์„ธํŠธ {i+1} ๊ฒ€์ฆ ์ค‘...")
47
- debug_log(f" API_KEY ์กด์žฌ: {bool(config.get('API_KEY'))}")
48
- debug_log(f" SECRET_KEY ์กด์žฌ: {bool(config.get('SECRET_KEY'))}")
49
- debug_log(f" CUSTOMER_ID ์กด์žฌ: {bool(config.get('CUSTOMER_ID'))}")
50
-
51
- if (config.get("API_KEY") and config.get("SECRET_KEY") and config.get("CUSTOMER_ID") and
52
- not config["API_KEY"].startswith("YOUR_") and
53
- not config["SECRET_KEY"].startswith("YOUR_") and
54
- not config["CUSTOMER_ID"].startswith("YOUR_") and
55
- config["API_KEY"].strip() != "" and
56
- config["SECRET_KEY"].strip() != "" and
57
- config["CUSTOMER_ID"].strip() != ""):
58
- api_keys.append(config)
59
- debug_log(f" API ํ‚ค ์„ธํŠธ {i+1} ์œ ํšจํ•จ")
60
- else:
61
- debug_log(f" API ํ‚ค ์„ธํŠธ {i+1} ๋ฌดํšจํ•จ")
62
-
63
- if not api_keys:
64
- debug_log("์œ ํšจํ•œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
65
- debug_log("ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค Settings > Variables and secrets > Secrets์—์„œ")
66
- debug_log('NAVER_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ๋‹ค์Œ ํ˜•ํƒœ๋กœ ์„ค์ •ํ•˜์„ธ์š”:')
67
- debug_log('[{"API_KEY":"your_api_key1","SECRET_KEY":"your_secret_key1","CUSTOMER_ID":"your_customer_id1"}]')
68
- return []
69
-
70
- debug_log(f"์ด {len(api_keys)}๊ฐœ์˜ ์œ ํšจํ•œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๊ฐ€ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
71
- return api_keys
72
-
73
- except json.JSONDecodeError as e:
74
- debug_log(f"NAVER_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜ JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {str(e)}")
75
- debug_log(f"ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋‚ด์šฉ: {naver_api_keys_json}")
76
- debug_log("ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ˜•์‹์„ ํ™•์ธํ•˜์„ธ์š”. ์˜ฌ๋ฐ”๋ฅธ JSON ํ˜•ํƒœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
77
- return []
78
- except Exception as e:
79
- debug_log(f"๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
80
- debug_log(f"ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋‚ด์šฉ: {naver_api_keys_json if 'naver_api_keys_json' in locals() else '์•Œ ์ˆ˜ ์—†์Œ'}")
81
- return []
82
-
83
- def load_naver_search_api_keys():
84
- """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค."""
85
- try:
86
- # ํ™˜๊ฒฝ๋ณ€์ˆ˜ NAVER_SEARCH_API_KEYS์—์„œ JSON ๋ฌธ์ž์—ด์„ ๊ฐ€์ ธ์˜ด
87
- naver_search_api_keys_json = os.getenv("NAVER_SEARCH_API_KEYS", "")
88
-
89
- debug_log(f"NAVER_SEARCH_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์›๋ณธ: {naver_search_api_keys_json[:100] if naver_search_api_keys_json else '๋นˆ ๋ฌธ์ž์—ด'}...")
90
-
91
- if not naver_search_api_keys_json or naver_search_api_keys_json.strip() == "":
92
- debug_log("NAVER_SEARCH_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค.")
93
- return []
94
-
95
- # JSON ๋ฌธ์ž์—ด์„ ํŒŒ์‹ฑ
96
- api_keys_data = json.loads(naver_search_api_keys_json)
97
- debug_log(f"JSON ํŒŒ์‹ฑ ์„ฑ๊ณต: {len(api_keys_data)}๊ฐœ ํ•ญ๋ชฉ")
98
-
99
- # ๋นˆ ํ‚ค๋‚˜ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ œ๊ฑฐ
100
- api_keys = []
101
- for i, config in enumerate(api_keys_data):
102
- debug_log(f"๊ฒ€์ƒ‰ API ํ‚ค ์„ธํŠธ {i+1} ๊ฒ€์ฆ ์ค‘...")
103
- debug_log(f" CLIENT_ID ์กด์žฌ: {bool(config.get('CLIENT_ID'))}")
104
- debug_log(f" CLIENT_SECRET ์กด์žฌ: {bool(config.get('CLIENT_SECRET'))}")
105
-
106
- if (config.get("CLIENT_ID") and config.get("CLIENT_SECRET") and
107
- not config["CLIENT_ID"].startswith("YOUR_") and
108
- not config["CLIENT_SECRET"].startswith("YOUR_") and
109
- config["CLIENT_ID"].strip() != "" and
110
- config["CLIENT_SECRET"].strip() != ""):
111
- api_keys.append(config)
112
- debug_log(f" ๊ฒ€์ƒ‰ API ํ‚ค ์„ธํŠธ {i+1} ์œ ํšจํ•จ")
113
- else:
114
- debug_log(f" ๊ฒ€์ƒ‰ API ํ‚ค ์„ธํŠธ {i+1} ๋ฌดํšจํ•จ")
115
-
116
- if not api_keys:
117
- debug_log("์œ ํšจํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
118
- debug_log("ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค Settings > Variables and secrets > Secrets์—์„œ")
119
- debug_log('NAVER_SEARCH_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ๋‹ค์Œ ํ˜•ํƒœ๋กœ ์„ค์ •ํ•˜์„ธ์š”:')
120
- debug_log('[{"CLIENT_ID":"your_client_id1","CLIENT_SECRET":"your_client_secret1"}]')
121
- return []
122
-
123
- debug_log(f"์ด {len(api_keys)}๊ฐœ์˜ ์œ ํšจํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๊ฐ€ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
124
- return api_keys
125
-
126
- except json.JSONDecodeError as e:
127
- debug_log(f"NAVER_SEARCH_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜ JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {str(e)}")
128
- debug_log(f"ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋‚ด์šฉ: {naver_search_api_keys_json}")
129
- debug_log("ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ˜•์‹์„ ํ™•์ธํ•˜์„ธ์š”. ์˜ฌ๋ฐ”๋ฅธ JSON ํ˜•ํƒœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
130
- return []
131
- except Exception as e:
132
- debug_log(f"๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
133
- debug_log(f"ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋‚ด์šฉ: {naver_search_api_keys_json if 'naver_search_api_keys_json' in locals() else '์•Œ ์ˆ˜ ์—†์Œ'}")
134
- return []
135
-
136
- # API ํ‚ค ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์ „์—ญ ๋ณ€์ˆ˜
137
- naver_api_manager = {
138
- 'keys': [],
139
- 'current_index': -1,
140
- 'failed_keys': set(), # ์‹คํŒจํ•œ ํ‚ค๋“ค์„ ์ถ”์ 
141
- 'is_initialized': False
142
- }
143
-
144
- naver_search_api_manager = {
145
- 'keys': [],
146
- 'current_index': -1,
147
- 'failed_keys': set(), # ์‹คํŒจํ•œ ํ‚ค๋“ค์„ ์ถ”์ 
148
- 'is_initialized': False
149
- }
150
-
151
- # ๋””๋ฒ„๊น…(๋กœ๊ทธ)์šฉ ํ•จ์ˆ˜
152
  def debug_log(message: str):
 
153
  print(f"[DEBUG] {message}")
154
 
155
- def initialize_naver_api_keys():
156
- """๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค ๊ด€๋ฆฌ์ž๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค."""
157
- global naver_api_manager
158
-
159
- if naver_api_manager['is_initialized']:
160
- return
161
-
162
- try:
163
- naver_api_manager['keys'] = load_naver_api_keys()
164
- naver_api_manager['is_initialized'] = True
165
- debug_log("๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค ๊ด€๋ฆฌ์ž ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
166
- except Exception as e:
167
- debug_log(f"๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
168
-
169
- def initialize_naver_search_api_keys():
170
- """๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค ๊ด€๋ฆฌ์ž๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค."""
171
- global naver_search_api_manager
172
-
173
- if naver_search_api_manager['is_initialized']:
174
- return
175
-
176
- try:
177
- naver_search_api_manager['keys'] = load_naver_search_api_keys()
178
- naver_search_api_manager['is_initialized'] = True
179
- debug_log("๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค ๊ด€๋ฆฌ์ž ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
180
- except Exception as e:
181
- debug_log(f"๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
182
-
183
- def get_next_naver_api_key():
184
- """๋‹ค์Œ ์‚ฌ์šฉํ•  ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์‹คํŒจํ•œ ํ‚ค๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค."""
185
- global naver_api_manager
186
-
187
- # ์ดˆ๊ธฐํ™” ํ™•์ธ
188
- if not naver_api_manager['is_initialized']:
189
- initialize_naver_api_keys()
190
-
191
- if not naver_api_manager['keys']:
192
- debug_log("์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
193
- return None
194
-
195
- available_keys = [
196
- key for i, key in enumerate(naver_api_manager['keys'])
197
- if i not in naver_api_manager['failed_keys']
198
- ]
199
-
200
- if not available_keys:
201
- # ๋ชจ๋“  ํ‚ค๊ฐ€ ์‹คํŒจํ–ˆ์œผ๋ฉด ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„
202
- debug_log("๋ชจ๋“  ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๊ฐ€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.")
203
- naver_api_manager['failed_keys'].clear()
204
- available_keys = naver_api_manager['keys']
205
-
206
- # ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ์€ ๋žœ๋ค์œผ๋กœ ์„ ํƒ
207
- if naver_api_manager['current_index'] == -1:
208
- available_indices = [
209
- i for i, key in enumerate(naver_api_manager['keys'])
210
- if i not in naver_api_manager['failed_keys']
211
- ]
212
- naver_api_manager['current_index'] = random.choice(available_indices)
213
- debug_log(f"์ฒซ ๋ฒˆ์งธ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค ์„ ํƒ: ๋žœ๋ค ์ธ๋ฑ์Šค {naver_api_manager['current_index'] + 1}")
214
- else:
215
- # ์ดํ›„ ์‚ฌ์šฉ์€ ์ˆœ์ฐจ์ ๏ฟฝ๏ฟฝ๋กœ ๋‹ค์Œ ํ‚ค ์„ ํƒ (์‹คํŒจํ•œ ํ‚ค๋Š” ๊ฑด๋„ˆ๋œ€)
216
- for _ in range(len(naver_api_manager['keys'])):
217
- naver_api_manager['current_index'] = (naver_api_manager['current_index'] + 1) % len(naver_api_manager['keys'])
218
- if naver_api_manager['current_index'] not in naver_api_manager['failed_keys']:
219
- break
220
-
221
- if naver_api_manager['current_index'] in naver_api_manager['failed_keys']:
222
- # ๋ชจ๋“  ํ‚ค๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ‚ค๊ฐ€ ์—†์Œ
223
- debug_log("์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.")
224
- naver_api_manager['failed_keys'].clear()
225
- naver_api_manager['current_index'] = 0
226
-
227
- debug_log(f"๋‹ค์Œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค ์„ ํƒ: ์ธ๋ฑ์Šค {naver_api_manager['current_index'] + 1}")
228
-
229
- return naver_api_manager['keys'][naver_api_manager['current_index']]
230
-
231
- def get_next_naver_search_api_key():
232
- """๋‹ค์Œ ์‚ฌ์šฉํ•  ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์‹คํŒจํ•œ ํ‚ค๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค."""
233
- global naver_search_api_manager
234
-
235
- # ์ดˆ๊ธฐํ™” ํ™•์ธ
236
- if not naver_search_api_manager['is_initialized']:
237
- initialize_naver_search_api_keys()
238
-
239
- if not naver_search_api_manager['keys']:
240
- debug_log("์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
241
- return None
242
-
243
- available_keys = [
244
- key for i, key in enumerate(naver_search_api_manager['keys'])
245
- if i not in naver_search_api_manager['failed_keys']
246
- ]
247
-
248
- if not available_keys:
249
- # ๋ชจ๋“  ํ‚ค๊ฐ€ ์‹คํŒจํ–ˆ์œผ๋ฉด ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„
250
- debug_log("๋ชจ๋“  ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๊ฐ€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.")
251
- naver_search_api_manager['failed_keys'].clear()
252
- available_keys = naver_search_api_manager['keys']
253
-
254
- # ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ์€ ๋žœ๋ค์œผ๋กœ ์„ ํƒ
255
- if naver_search_api_manager['current_index'] == -1:
256
- available_indices = [
257
- i for i, key in enumerate(naver_search_api_manager['keys'])
258
- if i not in naver_search_api_manager['failed_keys']
259
- ]
260
- naver_search_api_manager['current_index'] = random.choice(available_indices)
261
- debug_log(f"์ฒซ ๋ฒˆ์งธ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค ์„ ํƒ: ๋žœ๋ค ์ธ๋ฑ์Šค {naver_search_api_manager['current_index'] + 1}")
262
- else:
263
- # ์ดํ›„ ์‚ฌ์šฉ์€ ์ˆœ์ฐจ์ ์œผ๋กœ ๋‹ค์Œ ํ‚ค ์„ ํƒ (์‹คํŒจํ•œ ํ‚ค๋Š” ๊ฑด๋„ˆ๋œ€)
264
- for _ in range(len(naver_search_api_manager['keys'])):
265
- naver_search_api_manager['current_index'] = (naver_search_api_manager['current_index'] + 1) % len(naver_search_api_manager['keys'])
266
- if naver_search_api_manager['current_index'] not in naver_search_api_manager['failed_keys']:
267
- break
268
-
269
- if naver_search_api_manager['current_index'] in naver_search_api_manager['failed_keys']:
270
- # ๋ชจ๋“  ํ‚ค๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ‚ค๊ฐ€ ์—†์Œ
271
- debug_log("์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.")
272
- naver_search_api_manager['failed_keys'].clear()
273
- naver_search_api_manager['current_index'] = 0
274
-
275
- debug_log(f"๋‹ค์Œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค ์„ ํƒ: ์ธ๋ฑ์Šค {naver_search_api_manager['current_index'] + 1}")
276
-
277
- return naver_search_api_manager['keys'][naver_search_api_manager['current_index']]
278
-
279
- def mark_naver_api_key_failed(api_config):
280
- """๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค."""
281
- global naver_api_manager
282
-
283
- try:
284
- key_index = naver_api_manager['keys'].index(api_config)
285
- naver_api_manager['failed_keys'].add(key_index)
286
- debug_log(f"๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค ์ธ๋ฑ์Šค {key_index + 1}๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.")
287
- except ValueError:
288
- debug_log("์‹คํŒจํ•œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๋ฅผ ๋ชฉ๋ก์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
289
-
290
- def mark_naver_search_api_key_failed(api_config):
291
- """๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค."""
292
- global naver_search_api_manager
293
-
294
- try:
295
- key_index = naver_search_api_manager['keys'].index(api_config)
296
- naver_search_api_manager['failed_keys'].add(key_index)
297
- debug_log(f"๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค ์ธ๋ฑ์Šค {key_index + 1}๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.")
298
- except ValueError:
299
- debug_log("์‹คํŒจํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๋ฅผ ๋ชฉ๋ก์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
300
-
301
- # --- ์ œ๋ชฉ ์ œ๊ฑฐ ํ•จ์ˆ˜ ---
302
- def remove_title_from_content(content: str) -> str:
303
- """๋ธ”๋กœ๊ทธ ๋‚ด์šฉ์—์„œ ์ œ๋ชฉ ๋ถ€๋ถ„์„ ์ œ๊ฑฐํ•˜๋Š” ํ•จ์ˆ˜"""
304
- debug_log("์ œ๋ชฉ ์ œ๊ฑฐ ํ•จ์ˆ˜ ์‹œ์ž‘")
305
-
306
- # "[์ œ๋ชฉ]\n์ œ๋ชฉ๋‚ด์šฉ\n\n[๋ณธ๋ฌธ]\n๋ณธ๋ฌธ๋‚ด์šฉ" ํ˜•ํƒœ์˜ ํ…์ŠคํŠธ์—์„œ ์ œ๋ชฉ ๋ถ€๋ถ„๋งŒ ์ œ๊ฑฐ
307
- if content.startswith("[์ œ๋ชฉ]"):
308
- try:
309
- # "[๋ณธ๋ฌธ]" ๋˜๋Š” ์ฒซ ๋ฒˆ์งธ double newline์˜ ์œ„์น˜ ์ฐพ๊ธฐ
310
- parts = content.split("\n\n", 1)
311
- if len(parts) > 1:
312
- # ๋ณธ๋ฌธ ๋ถ€๋ถ„๋งŒ ๋ฐ˜ํ™˜
313
- body_parts = parts[1].split("\n", 1)
314
- if len(body_parts) > 1 and body_parts[0] == "[๋ณธ๋ฌธ]":
315
- result = body_parts[1]
316
- else:
317
- result = parts[1]
318
- debug_log("์ œ๋ชฉ ์ œ๊ฑฐ ์™„๋ฃŒ")
319
- return result
320
- except Exception as e:
321
- debug_log(f"์ œ๋ชฉ ์ œ๊ฑฐ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
322
-
323
- # ์ œ๋ชฉ ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜
324
- return content
325
-
326
- # --- ๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ ์Šคํฌ๋ž˜ํ•‘ (API ๋ฐฉ์‹) ---
327
  def scrape_naver_blog(url: str) -> str:
328
- debug_log("scrape_naver_blog ํ•จ์ˆ˜ ์‹œ์ž‘ (API ๋ฐฉ์‹)")
329
- debug_log(f"์š”์ฒญ๋ฐ›์€ URL: {url}")
 
 
 
330
 
331
  try:
332
- if not url:
333
- return "URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
334
-
335
- # URL ์ธ์ฝ”๋”ฉ
336
- encoded_url = quote_plus(url.strip(), encoding='utf-8')
337
- api_url = f"{API_BASE_URL}/blog?url={encoded_url}"
338
-
339
- debug_log(f"API URL: {api_url}")
340
-
341
- # API ์š”์ฒญ
342
- response = requests.get(api_url, headers=HEADERS)
343
- debug_log(f"API ์š”์ฒญ ์™„๋ฃŒ, ์ƒํƒœ์ฝ”๋“œ: {response.status_code}")
344
-
345
- if response.ok:
346
- data = response.json()
347
- if 'content' in data:
348
- content = data['content']
349
- debug_log("์ฝ˜ํ…์ธ  ์ถ”์ถœ ์™„๋ฃŒ")
350
- # ์ฝ˜ํ…์ธ ๊ฐ€ ์ด๋ฏธ ์ ์ ˆํ•œ ํ˜•ํƒœ๋กœ ํฌ๋งท๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •
351
- return content
352
- else:
353
- debug_log("์ฝ˜ํ…์ธ ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ")
354
- return "์ฝ˜ํ…์ธ ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
355
- else:
356
- debug_log(f"API ์˜ค๋ฅ˜ ์‘๋‹ต: {response.text}")
357
- return f"API ์˜ค๋ฅ˜: {response.text}"
358
-
359
  except Exception as e:
360
- debug_log(f"์—๋Ÿฌ ๋ฐœ์ƒ: {str(e)}")
361
  return f"์Šคํฌ๋ž˜ํ•‘ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
362
 
363
- # --- ํ˜•ํƒœ์†Œ ๋ถ„์„ (์ฐธ์กฐ์ฝ”๋“œ-1) ---
364
- def analyze_text(text: str):
365
- logging.basicConfig(level=logging.DEBUG)
366
- logger = logging.getLogger(__name__)
367
- logger.debug("์›๋ณธ ํ…์ŠคํŠธ: %s", text)
368
- filtered_text = re.sub(r'[^๊ฐ€-ํžฃ]', '', text)
369
- logger.debug("ํ•„ํ„ฐ๋ง๋œ ํ…์ŠคํŠธ: %s", filtered_text)
370
- if not filtered_text:
371
- logger.debug("์œ ํšจํ•œ ํ•œ๊ตญ์–ด ํ…์ŠคํŠธ๊ฐ€ ์—†์Œ.")
372
- return pd.DataFrame(columns=["๋‹จ์–ด", "๋นˆ๋„์ˆ˜"]), ""
373
- mecab_instance = mecab.MeCab()
374
- tokens = mecab_instance.pos(filtered_text)
375
- logger.debug("ํ˜•ํƒœ์†Œ ๋ถ„์„ ๊ฒฐ๊ณผ: %s", tokens)
376
- freq = {}
377
- for word, pos in tokens:
378
- if word and word.strip() and pos.startswith("NN"):
379
- freq[word] = freq.get(word, 0) + 1
380
- logger.debug("๋‹จ์–ด: %s, ํ’ˆ์‚ฌ: %s, ๋นˆ๋„: %d", word, pos, freq[word])
381
- sorted_freq = sorted(freq.items(), key=lambda x: x[1], reverse=True)
382
- logger.debug("์ •๋ ฌ๋œ ๋‹จ์–ด ๋นˆ๋„: %s", sorted_freq)
383
- df = pd.DataFrame(sorted_freq, columns=["๋‹จ์–ด", "๋นˆ๋„์ˆ˜"])
384
- logger.debug("ํ˜•ํƒœ์†Œ ๋ถ„์„ DataFrame ์ƒ์„ฑ๋จ, shape: %s", df.shape)
385
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
386
- df.to_excel(temp_file.name, index=False, engine='openpyxl')
387
- temp_file.close()
388
- logger.debug("Excel ํŒŒ์ผ ์ƒ์„ฑ๋จ: %s", temp_file.name)
389
- return df, temp_file.name
390
-
391
- # --- ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ ๋ฐ ๊ด‘๊ณ  API ๊ด€๋ จ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์ ์šฉ) ---
392
- def generate_signature(timestamp, method, uri, secret_key):
393
- message = f"{timestamp}.{method}.{uri}"
394
- digest = hmac.new(secret_key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest()
395
- return base64.b64encode(digest).decode()
396
-
397
- def get_header(method, uri, api_key, secret_key, customer_id):
398
- timestamp = str(round(time.time() * 1000))
399
- signature = generate_signature(timestamp, method, uri, secret_key)
400
- return {
401
- "Content-Type": "application/json; charset=UTF-8",
402
- "X-Timestamp": timestamp,
403
- "X-API-KEY": api_key,
404
- "X-Customer": str(customer_id),
405
- "X-Signature": signature
406
- }
407
-
408
- def fetch_search_volume_batch(keywords_batch):
409
- """ํ‚ค์›Œ๋“œ ๋ฐฐ์น˜์— ๋Œ€ํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰๋Ÿ‰ ์กฐํšŒ"""
410
- result = {}
411
-
412
- # ์ˆœ์ฐจ์ ์œผ๋กœ API ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ (๋ฐฐ์น˜๋งˆ๋‹ค ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ)
413
- api_config = get_next_naver_api_key()
414
-
415
- if not api_config:
416
- debug_log("โŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
417
- return result
418
-
419
- API_KEY = api_config["API_KEY"]
420
- SECRET_KEY = api_config["SECRET_KEY"]
421
- CUSTOMER_ID_STR = api_config["CUSTOMER_ID"]
422
 
423
- debug_log(f"=== ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ฒดํฌ (API ๊ณ„์ • {naver_api_manager['current_index'] + 1}) ===")
424
- debug_log(f"API_KEY: {'์žˆ์Œ' if API_KEY else '์—†์Œ'}")
425
- debug_log(f"SECRET_KEY: {'์žˆ์Œ' if SECRET_KEY else '์—†์Œ'}")
426
- debug_log(f"CUSTOMER_ID: {'์žˆ์Œ' if CUSTOMER_ID_STR else '์—†์Œ'}")
427
- debug_log(f"๋ฐฐ์น˜ ํฌ๊ธฐ: {len(keywords_batch)}๊ฐœ ํ‚ค์›Œ๋“œ")
428
 
429
- # CUSTOMER_ID๋ฅผ ์ •์ˆ˜๋กœ ๋ณ€ํ™˜
430
  try:
431
- CUSTOMER_ID = int(CUSTOMER_ID_STR)
432
- except ValueError:
433
- debug_log(f"โŒ CUSTOMER_ID ๋ณ€ํ™˜ ์˜ค๋ฅ˜: '{CUSTOMER_ID_STR}'๋Š” ์œ ํšจํ•œ ์ˆซ์ž๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.")
434
- mark_naver_api_key_failed(api_config)
 
 
 
 
 
435
  return result
436
-
437
- try:
438
- BASE_URL = "https://api.naver.com"
439
- uri = "/keywordstool"
440
- method = "GET"
441
- headers = get_header(method, uri, API_KEY, SECRET_KEY, CUSTOMER_ID)
442
-
443
- # ํ‚ค์›Œ๋“œ ๋ฐฐ์น˜๋ฅผ ํ•œ ๋ฒˆ์— API๋กœ ์ „์†ก
444
- params = {
445
- "hintKeywords": keywords_batch,
446
- "showDetail": "1"
447
- }
448
-
449
- debug_log(f"์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ: {len(keywords_batch)}๊ฐœ ํ‚ค์›Œ๋“œ")
450
-
451
- # API ํ˜ธ์ถœ
452
- response = requests.get(BASE_URL + uri, params=params, headers=headers)
453
-
454
- debug_log(f"์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
455
-
456
- if response.status_code != 200:
457
- debug_log(f"โŒ API ์˜ค๋ฅ˜ ์‘๋‹ต:")
458
- debug_log(f" ๋ณธ๋ฌธ: {response.text}")
459
- # API ํ‚ค ๋ฌธ์ œ์ธ ๊ฒฝ์šฐ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€
460
- if response.status_code in [401, 403]:
461
- mark_naver_api_key_failed(api_config)
462
- return result
463
-
464
- # ์‘๋‹ต ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ
465
- result_data = response.json()
466
-
467
- if isinstance(result_data, dict) and "keywordList" in result_data:
468
- debug_log(f" keywordList ๊ธธ์ด: {len(result_data['keywordList'])}")
469
-
470
- # ๋ฐฐ์น˜ ๋‚ด ๊ฐ ํ‚ค์›Œ๋“œ์™€ ๋งค์นญ
471
- for keyword in keywords_batch:
472
- found = False
473
- for item in result_data["keywordList"]:
474
- rel_keyword = item.get("relKeyword", "")
475
- if rel_keyword == keyword:
476
- pc_count = item.get("monthlyPcQcCnt", 0)
477
- mobile_count = item.get("monthlyMobileQcCnt", 0)
478
-
479
- # ์ˆซ์ž ๋ณ€ํ™˜
480
- try:
481
- if isinstance(pc_count, str):
482
- pc_count_converted = int(pc_count.replace(",", ""))
483
- else:
484
- pc_count_converted = int(pc_count)
485
- except:
486
- pc_count_converted = 0
487
-
488
- try:
489
- if isinstance(mobile_count, str):
490
- mobile_count_converted = int(mobile_count.replace(",", ""))
491
- else:
492
- mobile_count_converted = int(mobile_count)
493
- except:
494
- mobile_count_converted = 0
495
-
496
- total_count = pc_count_converted + mobile_count_converted
497
-
498
- result[keyword] = {
499
- "PC์›”๊ฒ€์ƒ‰๋Ÿ‰": pc_count_converted,
500
- "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰": mobile_count_converted,
501
- "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰": total_count
502
- }
503
- debug_log(f"โœ… '{keyword}': PC={pc_count_converted}, Mobile={mobile_count_converted}, Total={total_count}")
504
- found = True
505
- break
506
-
507
- if not found:
508
- debug_log(f"โŒ '{keyword}': ๋งค์นญ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ")
509
- # ๊ฒ€์ƒ‰๋Ÿ‰์ด ์—†๋Š” ๊ฒฝ์šฐ 0์œผ๋กœ ์„ค์ •
510
- result[keyword] = {
511
- "PC์›”๊ฒ€์ƒ‰๋Ÿ‰": 0,
512
- "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰": 0,
513
- "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰": 0
514
- }
515
- else:
516
- debug_log(f"โŒ keywordList๊ฐ€ ์—†์Œ")
517
-
518
  except Exception as e:
519
- debug_log(f"โŒ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
520
- # API ํ‚ค ๋ฌธ์ œ๏ฟฝ๏ฟฝ ๊ฒฝ์šฐ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€
521
- if "api" in str(e).lower() or "key" in str(e).lower() or "auth" in str(e).lower():
522
- mark_naver_api_key_failed(api_config)
523
- import traceback
524
- traceback.print_exc()
525
-
526
- debug_log(f"\n=== ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์™„๋ฃŒ ===")
527
- debug_log(f"์„ฑ๊ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ ํ‚ค์›Œ๋“œ ์ˆ˜: {len(result)}")
528
-
529
- return result
530
 
531
- def fetch_all_search_volumes(keywords, batch_size=5):
532
- """ํ‚ค์›Œ๋“œ ๋ฆฌ์ŠคํŠธ์— ๋Œ€ํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰๋Ÿ‰ ๋ณ‘๋ ฌ ์กฐํšŒ"""
533
- results = {}
534
- batches = []
535
-
536
- # ํ‚ค์›Œ๋“œ๋ฅผ 5๊ฐœ์”ฉ ๋ฌถ์–ด์„œ ๋ฐฐ์น˜ ์ƒ์„ฑ
537
- for i in range(0, len(keywords), batch_size):
538
- batch = keywords[i:i + batch_size]
539
- batches.append(batch)
540
-
541
- debug_log(f"์ด {len(batches)}๊ฐœ ๋ฐฐ์น˜๋กœ {len(keywords)}๊ฐœ ํ‚ค์›Œ๋“œ ์ฒ˜๋ฆฌ ์ค‘โ€ฆ")
542
- debug_log(f"๋ฐฐ์น˜ ํฌ๊ธฐ: {batch_size}, ๋ณ‘๋ ฌ ์›Œ์ปค: 10๊ฐœ, API ๊ณ„์ •: {len(naver_api_manager['keys'])}๊ฐœ ์ˆœ์ฐจ ์‚ฌ์šฉ")
543
-
544
- with ThreadPoolExecutor(max_workers=10) as executor:
545
- futures = {executor.submit(fetch_search_volume_batch, batch): batch for batch in batches}
546
- for future in as_completed(futures):
547
- batch = futures[future]
548
- try:
549
- batch_results = future.result()
550
- results.update(batch_results)
551
- debug_log(f"๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: {len(batch)}๊ฐœ ํ‚ค์›Œ๋“œ (์„ฑ๊ณต: {len(batch_results)}๊ฐœ)")
552
- except Exception as e:
553
- debug_log(f"๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}")
554
- time.sleep(0.5) # API ๋ ˆ์ดํŠธ ๋ฆฌ๋ฐ‹ ๋ฐฉ์ง€
555
-
556
- debug_log(f"๊ฒ€์ƒ‰๋Ÿ‰ ์กฐํšŒ ์™„๋ฃŒ: {len(results)}๊ฐœ ํ‚ค์›Œ๋“œ")
557
- return results
558
-
559
- def fetch_blog_count_batch(keywords_batch):
560
- """ํ‚ค์›Œ๋“œ ๋ฐฐ์น˜์— ๋Œ€ํ•œ ๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ ๋ฐœ์ƒ์ˆ˜ ์กฐํšŒ"""
561
- result = {}
562
-
563
- # ์ˆœ์ฐจ์ ์œผ๋กœ ๊ฒ€์ƒ‰ API ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ
564
- api_config = get_next_naver_search_api_key()
565
-
566
- if not api_config:
567
- debug_log("โŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
568
- return result
569
-
570
- client_id = api_config["CLIENT_ID"]
571
- client_secret = api_config["CLIENT_SECRET"]
572
-
573
- debug_log(f"=== ๋ธ”๋กœ๊ทธ ๊ฒ€์ƒ‰ API ์ฒดํฌ (๊ณ„์ • {naver_search_api_manager['current_index'] + 1}) ===")
574
- debug_log(f"CLIENT_ID: {'์žˆ์Œ' if client_id else '์—†์Œ'}")
575
- debug_log(f"CLIENT_SECRET: {'์žˆ์Œ' if client_secret else '์—†์Œ'}")
576
- debug_log(f"๋ฐฐ์น˜ ํฌ๊ธฐ: {len(keywords_batch)}๊ฐœ ํ‚ค์›Œ๋“œ")
577
-
578
- url = "https://openapi.naver.com/v1/search/blog.json"
579
- headers = {
580
- "X-Naver-Client-Id": client_id,
581
- "X-Naver-Client-Secret": client_secret
582
- }
583
-
584
- for keyword in keywords_batch:
585
- try:
586
- params = {"query": keyword, "display": 1}
587
- response = requests.get(url, headers=headers, params=params)
588
-
589
- if response.status_code == 200:
590
- data = response.json()
591
- blog_count = data.get("total", 0)
592
- result[keyword] = blog_count
593
- debug_log(f"โœ… '{keyword}': ๋ธ”๋กœ๊ทธ ๋ฌธ์„œ์ˆ˜ = {blog_count}")
594
- else:
595
- debug_log(f"โŒ '{keyword}': API ์˜ค๋ฅ˜ (์ƒํƒœ์ฝ”๋“œ: {response.status_code})")
596
- # API ํ‚ค ๋ฌธ์ œ์ธ ๊ฒฝ์šฐ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€
597
- if response.status_code in [401, 403]:
598
- mark_naver_search_api_key_failed(api_config)
599
- result[keyword] = 0
600
-
601
- except Exception as e:
602
- debug_log(f"โŒ '{keyword}': ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ = {str(e)}")
603
- # API ํ‚ค ๋ฌธ์ œ์ธ ๊ฒฝ์šฐ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€
604
- if "api" in str(e).lower() or "key" in str(e).lower() or "auth" in str(e).lower():
605
- mark_naver_search_api_key_failed(api_config)
606
- result[keyword] = 0
607
-
608
- return result
609
-
610
- def fetch_all_blog_counts(keywords, batch_size=10):
611
- """ํ‚ค์›Œ๋“œ ๋ฆฌ์ŠคํŠธ์— ๋Œ€ํ•œ ๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ ๋ฐœ์ƒ์ˆ˜ ๋ณ‘๋ ฌ ์กฐํšŒ"""
612
- results = {}
613
- batches = []
614
-
615
- # ํ‚ค์›Œ๋“œ๋ฅผ 10๊ฐœ์”ฉ ๋ฌถ์–ด์„œ ๋ฐฐ์น˜ ์ƒ์„ฑ
616
- for i in range(0, len(keywords), batch_size):
617
- batch = keywords[i:i + batch_size]
618
- batches.append(batch)
619
-
620
- debug_log(f"์ด {len(batches)}๊ฐœ ๋ฐฐ์น˜๋กœ {len(keywords)}๊ฐœ ํ‚ค์›Œ๋“œ์˜ ๋ธ”๋กœ๊ทธ ๋ฐœ์ƒ์ˆ˜ ์ฒ˜๋ฆฌ ์ค‘โ€ฆ")
621
- debug_log(f"๋ฐฐ์น˜ ํฌ๊ธฐ: {batch_size}, ๋ณ‘๋ ฌ ์›Œ์ปค: 5๊ฐœ, API ๊ณ„์ •: {len(naver_search_api_manager['keys'])}๊ฐœ ์ˆœ์ฐจ ์‚ฌ์šฉ")
622
-
623
- with ThreadPoolExecutor(max_workers=5) as executor:
624
- futures = {executor.submit(fetch_blog_count_batch, batch): batch for batch in batches}
625
- for future in as_completed(futures):
626
- batch = futures[future]
627
- try:
628
- batch_results = future.result()
629
- results.update(batch_results)
630
- debug_log(f"๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: {len(batch)}๊ฐœ ํ‚ค์›Œ๋“œ (์„ฑ๊ณต: {len(batch_results)}๊ฐœ)")
631
- except Exception as e:
632
- debug_log(f"๋ฐฐ์น˜ ์ฒ˜๏ฟฝ๏ฟฝ ์˜ค๋ฅ˜: {e}")
633
- time.sleep(0.3) # API ๋ ˆ์ดํŠธ ๋ฆฌ๋ฐ‹ ๋ฐฉ์ง€
634
-
635
- debug_log(f"๋ธ”๋กœ๊ทธ ๋ฐœ์ƒ์ˆ˜ ์กฐํšŒ ์™„๋ฃŒ: {len(results)}๊ฐœ ํ‚ค์›Œ๋“œ")
636
- return results
637
-
638
- def fetch_related_keywords(keyword):
639
- """์—ฐ๊ด€ ํ‚ค์›Œ๋“œ ์กฐํšŒ (๊ธฐ์กด ๋กœ์ง์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€)"""
640
- debug_log(f"fetch_related_keywords ํ˜ธ์ถœ, ํ‚ค์›Œ๋“œ: {keyword}")
641
-
642
- # ๋‹จ์ผ ํ‚ค์›Œ๋“œ๋งŒ ์žˆ์œผ๋ฉด ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
643
- keywords_list = [keyword]
644
- search_volumes = fetch_all_search_volumes(keywords_list)
645
-
646
- # ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„์œผ๋กœ ๋ณ€ํ™˜
647
- if search_volumes:
648
- data = []
649
- for kw, vol in search_volumes.items():
650
- data.append({
651
- "์ •๋ณดํ‚ค์›Œ๋“œ": kw,
652
- "PC์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["PC์›”๊ฒ€์ƒ‰๋Ÿ‰"],
653
- "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰"],
654
- "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰"]
655
- })
656
- df = pd.DataFrame(data)
657
- debug_log("fetch_related_keywords ์™„๋ฃŒ")
658
- return df
659
- else:
660
- return pd.DataFrame()
661
-
662
- def fetch_blog_count(keyword):
663
- """๋‹จ์ผ ํ‚ค์›Œ๋“œ์˜ ๋ธ”๋กœ๊ทธ ๋ฐœ์ƒ์ˆ˜ ์กฐํšŒ"""
664
- debug_log(f"fetch_blog_count ํ˜ธ์ถœ, ํ‚ค์›Œ๋“œ: {keyword}")
665
-
666
- # ๋‹จ์ผ ํ‚ค์›Œ๋“œ๋งŒ ์žˆ์œผ๋ฏ€๋กœ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
667
- keywords_list = [keyword]
668
- blog_counts = fetch_all_blog_counts(keywords_list)
669
-
670
- # ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
671
- if keyword in blog_counts:
672
- count = blog_counts[keyword]
673
- debug_log(f"fetch_blog_count ๊ฒฐ๊ณผ: {count}")
674
- return count
675
- else:
676
- debug_log(f"fetch_blog_count ์˜ค๋ฅ˜")
677
- return 0
678
-
679
- def create_excel_file(df):
680
- with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
681
- excel_path = tmp.name
682
- df.to_excel(excel_path, index=False)
683
- debug_log(f"Excel ํŒŒ์ผ ์ƒ์„ฑ๋จ: {excel_path}")
684
- return excel_path
685
-
686
- def process_keyword(keywords: str, include_related: bool):
687
- debug_log(f"process_keyword ํ˜ธ์ถœ, ํ‚ค์›Œ๋“œ๋“ค: {keywords}, ์—ฐ๊ด€๊ฒ€์ƒ‰์–ด ํฌํ•จ: {include_related}")
688
- input_keywords = [k.strip() for k in keywords.splitlines() if k.strip()]
689
-
690
- # ๋ชจ๋“  ํ‚ค์›Œ๋“œ๋ฅผ ํ•œ ๋ฒˆ์— ๊ฒ€์ƒ‰๋Ÿ‰ ์กฐํšŒ
691
- search_volumes = fetch_all_search_volumes(input_keywords)
692
-
693
- # ๊ฒฐ๊ณผ๋ฅผ DataFrame์œผ๋กœ ๊ตฌ์„ฑ
694
- result_data = []
695
- for kw in input_keywords:
696
- if kw in search_volumes:
697
- vol = search_volumes[kw]
698
- result_data.append({
699
- "์ •๋ณดํ‚ค์›Œ๋“œ": kw,
700
- "PC์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["PC์›”๊ฒ€์ƒ‰๋Ÿ‰"],
701
- "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰"],
702
- "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰"]
703
- })
704
-
705
- if result_data:
706
- result_df = pd.DataFrame(result_data)
707
- else:
708
- result_df = pd.DataFrame(columns=["์ •๋ณดํ‚ค์›Œ๋“œ", "PC์›”๊ฒ€์ƒ‰๋Ÿ‰", "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰", "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰"])
709
-
710
- # ๋ธ”๋กœ๊ทธ ๋ฌธ์„œ์ˆ˜ ์กฐํšŒ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ)
711
- blog_counts = fetch_all_blog_counts(input_keywords)
712
- result_df["๋ธ”๋กœ๊ทธ๋ฌธ์„œ์ˆ˜"] = result_df["์ •๋ณดํ‚ค์›Œ๋“œ"].map(blog_counts).fillna(0).astype(int)
713
-
714
- result_df.sort_values(by="ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰", ascending=False, inplace=True)
715
- debug_log("process_keyword ์™„๋ฃŒ")
716
- return result_df, create_excel_file(result_df)
717
-
718
- # --- ํ˜•ํƒœ์†Œ ๋ถ„์„๊ณผ ๊ฒ€์ƒ‰๋Ÿ‰/๋ธ”๋กœ๊ทธ๋ฌธ์„œ์ˆ˜ ๋ณ‘ํ•ฉ ---
719
- def morphological_analysis_and_enrich(text: str, remove_freq1: bool):
720
- debug_log("morphological_analysis_and_enrich ํ•จ์ˆ˜ ์‹œ์ž‘")
721
- df_freq, _ = analyze_text(text)
722
- if df_freq.empty:
723
- debug_log("ํ˜•ํƒœ์†Œ ๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ๋นˆ ๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„์ž…๋‹ˆ๋‹ค.")
724
- return df_freq, ""
725
- if remove_freq1:
726
- before_shape = df_freq.shape
727
- df_freq = df_freq[df_freq["๋นˆ๋„์ˆ˜"] != 1]
728
- debug_log(f"๋นˆ๋„์ˆ˜ 1 ์ œ๊ฑฐ ์ ์šฉ๋จ. {before_shape} -> {df_freq.shape}")
729
-
730
- # ๋ชจ๋“  ํ‚ค์›Œ๋“œ๋ฅผ ํ•œ ๋ฒˆ์— ๊ฒ€์ƒ‰๋Ÿ‰ ์กฐํšŒ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ)
731
- keywords_list = df_freq["๋‹จ์–ด"].tolist()
732
- search_volumes = fetch_all_search_volumes(keywords_list)
733
- blog_counts = fetch_all_blog_counts(keywords_list)
734
-
735
- # ๊ฒ€์ƒ‰๋Ÿ‰ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„์— ๋งคํ•‘
736
- df_keyword_info = []
737
- for kw in keywords_list:
738
- if kw in search_volumes:
739
- vol = search_volumes[kw]
740
- df_keyword_info.append({
741
- "์ •๋ณดํ‚ค์›Œ๋“œ": kw,
742
- "PC์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["PC์›”๊ฒ€์ƒ‰๋Ÿ‰"],
743
- "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰"],
744
- "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰": vol["ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰"],
745
- "๋ธ”๋กœ๊ทธ๋ฌธ์„œ์ˆ˜": blog_counts.get(kw, 0)
746
- })
747
-
748
- if df_keyword_info:
749
- df_keyword_info = pd.DataFrame(df_keyword_info)
750
- else:
751
- df_keyword_info = pd.DataFrame(columns=["์ •๋ณดํ‚ค์›Œ๋“œ", "PC์›”๊ฒ€์ƒ‰๋Ÿ‰", "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰", "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰", "๋ธ”๋กœ๊ทธ๋ฌธ์„œ์ˆ˜"])
752
-
753
- debug_log("๊ฒ€์ƒ‰๋Ÿ‰ ๋ฐ ๋ธ”๋กœ๊ทธ๋ฌธ์„œ์ˆ˜ ์กฐํšŒ ์™„๋ฃŒ")
754
- merged_df = pd.merge(df_freq, df_keyword_info, left_on="๋‹จ์–ด", right_on="๏ฟฝ๏ฟฝ๏ฟฝ๋ณดํ‚ค์›Œ๋“œ", how="left")
755
- merged_df.drop(columns=["์ •๋ณดํ‚ค์›Œ๋“œ"], inplace=True)
756
- merged_excel_path = create_excel_file(merged_df)
757
- debug_log("morphological_analysis_and_enrich ํ•จ์ˆ˜ ์™„๋ฃŒ")
758
- return merged_df, merged_excel_path
759
-
760
- # --- ์ง์ ‘ ํ‚ค์›Œ๋“œ ๋ถ„์„ (๋‹จ๋… ๋ถ„์„) ---
761
- def direct_keyword_analysis(text: str, keyword_input: str):
762
- debug_log("direct_keyword_analysis ํ•จ์ˆ˜ ์‹œ์ž‘")
763
- keywords = re.split(r'[\n,]+', keyword_input)
764
- keywords = [kw.strip() for kw in keywords if kw.strip()]
765
- debug_log(f"์ž…๋ ฅ๋œ ํ‚ค์›Œ๋“œ ๋ชฉ๋ก: {keywords}")
766
- results = []
767
- for kw in keywords:
768
- count = text.count(kw)
769
- results.append((kw, count))
770
- debug_log(f"ํ‚ค์›Œ๋“œ '{kw}'์˜ ๋นˆ๋„์ˆ˜: {count}")
771
- df = pd.DataFrame(results, columns=["ํ‚ค์›Œ๋“œ", "๋นˆ๋„์ˆ˜"])
772
- excel_path = create_excel_file(df)
773
- debug_log("direct_keyword_analysis ํ•จ์ˆ˜ ์™„๋ฃŒ")
774
- return df, excel_path
775
-
776
- # --- ํ†ตํ•ฉ ๋ถ„์„ (ํ˜•ํƒœ์†Œ ๋ถ„์„ + ์ง์ ‘ ํ‚ค์›Œ๋“œ ๋ถ„์„) ---
777
- def combined_analysis(blog_text: str, remove_freq1: bool, direct_keyword_input: str):
778
- debug_log("combined_analysis ํ•จ์ˆ˜ ์‹œ์ž‘")
779
- merged_df, _ = morphological_analysis_and_enrich(blog_text, remove_freq1)
780
- if "์ง์ ‘์ž…๋ ฅ" not in merged_df.columns:
781
- merged_df["์ง์ ‘์ž…๋ ฅ"] = ""
782
- direct_keywords = re.split(r'[\n,]+', direct_keyword_input)
783
- direct_keywords = [kw.strip() for kw in direct_keywords if kw.strip()]
784
- debug_log(f"์ž…๋ ฅ๋œ ์ง์ ‘ ํ‚ค์›Œ๋“œ: {direct_keywords}")
785
-
786
- # ์ง์ ‘ ์ž…๋ ฅ๋œ ํ‚ค์›Œ๋“œ๋“ค์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌ
787
- for dk in direct_keywords:
788
- if dk in merged_df["๋‹จ์–ด"].values:
789
- merged_df.loc[merged_df["๋‹จ์–ด"] == dk, "์ง์ ‘์ž…๋ ฅ"] = "์ง์ ‘์ž…๋ ฅ"
790
- else:
791
- freq = blog_text.count(dk)
792
- # ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ๊ฒ€์ƒ‰๋Ÿ‰ ๋ฐ ๋ธ”๋กœ๊ทธ ๋ฌธ์„œ์ˆ˜ ์กฐํšŒ
793
- search_volumes = fetch_all_search_volumes([dk])
794
- blog_counts = fetch_all_blog_counts([dk])
795
-
796
- if dk in search_volumes:
797
- vol = search_volumes[dk]
798
- pc = vol["PC์›”๊ฒ€์ƒ‰๋Ÿ‰"]
799
- mobile = vol["๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰"]
800
- total = vol["ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰"]
801
- else:
802
- pc = mobile = total = 0
803
-
804
- blog_count = blog_counts.get(dk, 0)
805
-
806
- new_row = {
807
- "๋‹จ์–ด": dk,
808
- "๋นˆ๋„์ˆ˜": freq,
809
- "PC์›”๊ฒ€์ƒ‰๋Ÿ‰": pc,
810
- "๋ชจ๋ฐ”์ผ์›”๊ฒ€์ƒ‰๋Ÿ‰": mobile,
811
- "ํ† ํƒˆ์›”๊ฒ€์ƒ‰๋Ÿ‰": total,
812
- "๋ธ”๋กœ๊ทธ๋ฌธ์„œ์ˆ˜": blog_count,
813
- "์ง์ ‘์ž…๋ ฅ": "์ง์ ‘์ž…๋ ฅ"
814
- }
815
- merged_df = pd.concat([merged_df, pd.DataFrame([new_row])], ignore_index=True)
816
-
817
- merged_df = merged_df.sort_values(by="๋นˆ๋„์ˆ˜", ascending=False).reset_index(drop=True)
818
- combined_excel = create_excel_file(merged_df)
819
- debug_log("combined_analysis ํ•จ์ˆ˜ ์™„๋ฃŒ")
820
- return merged_df, combined_excel
821
-
822
- # --- ๋ถ„์„ ํ•ธ๋“ค๋Ÿฌ ---
823
- def analysis_handler(blog_text: str, remove_freq1: bool, include_title: bool, direct_keyword_input: str, direct_keyword_only: bool):
824
- debug_log("analysis_handler ํ•จ์ˆ˜ ์‹œ์ž‘")
825
-
826
- # ์ œ๋ชฉ ํฌํ•จ/์ œ์™ธ ์ฒ˜๋ฆฌ
827
- if not include_title:
828
- debug_log("์ œ๋ชฉ ์ œ๊ฑฐ ๋กœ์ง ์ ์šฉ")
829
- analysis_text = remove_title_from_content(blog_text)
830
- else:
831
- debug_log("์ œ๋ชฉ ํฌํ•จํ•˜์—ฌ ๋ถ„์„")
832
- analysis_text = blog_text
833
-
834
- if direct_keyword_only:
835
- # "์ง์ ‘ ํ‚ค์›Œ๋“œ ์ž…๋ ฅ๋งŒ ๋ถ„์„" ์„ ํƒ ์‹œ ๋‹จ๋… ๋ถ„์„ ์ˆ˜ํ–‰
836
- return direct_keyword_analysis(analysis_text, direct_keyword_input)
837
- else:
838
- # ๊ธฐ๋ณธ ํ†ตํ•ฉ ๋ถ„์„ ์ˆ˜ํ–‰
839
- return combined_analysis(analysis_text, remove_freq1, direct_keyword_input)
840
-
841
- # --- ์Šคํฌ๋ž˜ํ•‘ ์‹คํ–‰ ---
842
  def fetch_blog_content(url: str):
843
- debug_log("fetch_blog_content ํ•จ์ˆ˜ ์‹œ์ž‘")
 
844
  content = scrape_naver_blog(url)
845
- debug_log("fetch_blog_content ํ•จ์ˆ˜ ์™„๋ฃŒ")
846
  return content
847
 
848
  # --- Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ ---
@@ -852,6 +75,7 @@ def create_interface():
852
  ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋ณ€๊ฒฝ ํ…œํ”Œ๋ฆฟ CSS
853
  ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์— ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
854
  ============================================ */
 
855
  /* 1. CSS ๋ณ€์ˆ˜ ์ •์˜ (๋ผ์ดํŠธ๋ชจ๋“œ - ๊ธฐ๋ณธ๊ฐ’) */
856
  :root {
857
  /* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */
@@ -883,6 +107,7 @@ def create_interface():
883
  /* ๊ธฐํƒ€ */
884
  --border-radius: 18px;
885
  }
 
886
  /* 2. ๋‹คํฌ๋ชจ๋“œ ์ƒ‰์ƒ ๋ณ€์ˆ˜ (์ž๋™ ๊ฐ์ง€) */
887
  @media (prefers-color-scheme: dark) {
888
  :root {
@@ -908,6 +133,7 @@ def create_interface():
908
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
909
  }
910
  }
 
911
  /* 3. ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค (Gradio ํ† ๊ธ€์šฉ) */
912
  [data-theme="dark"],
913
  .dark,
@@ -933,6 +159,7 @@ def create_interface():
933
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
934
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
935
  }
 
936
  /* 4. ๊ธฐ๋ณธ ์š”์†Œ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉ */
937
  body {
938
  font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
@@ -941,9 +168,11 @@ def create_interface():
941
  line-height: 1.6;
942
  transition: background-color 0.3s ease, color 0.3s ease;
943
  }
 
944
  footer {
945
  visibility: hidden;
946
  }
 
947
  /* 5. Gradio ์ปจํ…Œ์ด๋„ˆ ๊ฐ•์ œ ์ ์šฉ */
948
  .gradio-container,
949
  .gradio-container *,
@@ -953,6 +182,7 @@ def create_interface():
953
  background-color: var(--background-color) !important;
954
  color: var(--text-color) !important;
955
  }
 
956
  /* 6. ์นด๋“œ ๋ฐ ํŒจ๋„ ์Šคํƒ€์ผ */
957
  .gr-form,
958
  .gr-box,
@@ -966,6 +196,7 @@ def create_interface():
966
  color: var(--text-color) !important;
967
  box-shadow: var(--shadow) !important;
968
  }
 
969
  /* 7. ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
970
  input[type="text"],
971
  input[type="number"],
@@ -981,6 +212,7 @@ def create_interface():
981
  color: var(--text-color) !important;
982
  border-color: var(--border-color) !important;
983
  }
 
984
  input[type="text"]:focus,
985
  input[type="number"]:focus,
986
  input[type="email"]:focus,
@@ -994,6 +226,7 @@ def create_interface():
994
  border-color: var(--primary-color) !important;
995
  box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
996
  }
 
997
  /* 8. ๋ผ๋ฒจ ๋ฐ ํ…์ŠคํŠธ ์š”์†Œ */
998
  label,
999
  .gr-label,
@@ -1002,6 +235,7 @@ def create_interface():
1002
  p, span, div {
1003
  color: var(--text-color) !important;
1004
  }
 
1005
  /* ํผ ๋ผ๋ฒจ ํ…์ŠคํŠธ ํฌ๊ธฐ ๋Œ€ํญ ์ฆ๊ฐ€ */
1006
  .gr-form label,
1007
  .gr-textbox label,
@@ -1011,6 +245,7 @@ def create_interface():
1011
  font-weight: 600 !important;
1012
  color: var(--text-color) !important;
1013
  }
 
1014
  /* ์„ค๋ช… ํ…์ŠคํŠธ ํฌ๊ธฐ ๋Œ€ํญ ์ฆ๊ฐ€ */
1015
  .gr-form .gr-form-label,
1016
  .gr-textbox .gr-form-label,
@@ -1021,28 +256,34 @@ def create_interface():
1021
  color: var(--text-secondary) !important;
1022
  line-height: 1.4 !important;
1023
  }
 
1024
  /* 9. ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ */
1025
  table {
1026
  background-color: var(--card-bg) !important;
1027
  color: var(--text-color) !important;
1028
  border-color: var(--border-color) !important;
1029
  }
 
1030
  table th {
1031
  background-color: var(--primary-color) !important;
1032
  color: white !important;
1033
  border-color: var(--border-color) !important;
1034
  }
 
1035
  table td {
1036
  background-color: var(--card-bg) !important;
1037
  color: var(--text-color) !important;
1038
  border-color: var(--border-color) !important;
1039
  }
 
1040
  table tbody tr:nth-child(even) {
1041
  background-color: var(--table-even-bg) !important;
1042
  }
 
1043
  table tbody tr:hover {
1044
  background-color: var(--table-hover-bg) !important;
1045
  }
 
1046
  /* 10. ์ฒดํฌ๋ฐ•์Šค ๋ฐ ๋ผ๋””์˜ค ๋ฒ„ํŠผ ์Šคํƒ€์ผ ๊ฐœ์„  */
1047
  input[type="checkbox"],
1048
  input[type="radio"] {
@@ -1051,6 +292,7 @@ def create_interface():
1051
  height: 24px !important;
1052
  cursor: pointer !important;
1053
  }
 
1054
  /* ์ฒดํฌ๋ฐ•์Šค ์ปค์Šคํ…€ ์Šคํƒ€์ผ - ๋” ๊ฐ•๋ ฅํ•œ ์„ ํƒ์ž ์‚ฌ์šฉ */
1055
  .gradio-container input[type="checkbox"],
1056
  .gr-checkbox input[type="checkbox"],
@@ -1069,12 +311,14 @@ def create_interface():
1069
  margin-right: 12px !important;
1070
  flex-shrink: 0 !important;
1071
  }
 
1072
  .gradio-container input[type="checkbox"]:checked,
1073
  .gr-checkbox input[type="checkbox"]:checked,
1074
  input[type="checkbox"]:checked {
1075
  background-color: var(--primary-color) !important;
1076
  border-color: var(--primary-color) !important;
1077
  }
 
1078
  .gradio-container input[type="checkbox"]:checked::before,
1079
  .gr-checkbox input[type="checkbox"]:checked::before,
1080
  input[type="checkbox"]:checked::before {
@@ -1088,12 +332,14 @@ def create_interface():
1088
  font-weight: bold !important;
1089
  line-height: 1 !important;
1090
  }
 
1091
  .gradio-container input[type="checkbox"]:hover,
1092
  .gr-checkbox input[type="checkbox"]:hover,
1093
  input[type="checkbox"]:hover {
1094
  border-color: var(--primary-color) !important;
1095
  box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
1096
  }
 
1097
  /* ์ฒดํฌ๋ฐ•์Šค ๋ผ๋ฒจ ํ…์ŠคํŠธ ํฌ๊ธฐ ๋Œ€ํญ ์ฆ๊ฐ€ */
1098
  .gradio-container .gr-checkbox label,
1099
  .gr-checkbox label,
@@ -1106,38 +352,46 @@ def create_interface():
1106
  display: flex !important;
1107
  align-items: center !important;
1108
  }
 
1109
  /* ์ฒดํฌ๋ฐ•์Šค ์ปจํ…Œ์ด๋„ˆ ์ •๋ ฌ */
1110
  .gr-checkbox {
1111
  display: flex !important;
1112
  align-items: center !important;
1113
  gap: 8px !important;
1114
  }
 
1115
  /* 11. ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ */
1116
  ::-webkit-scrollbar {
1117
  width: 8px;
1118
  height: 8px;
1119
  }
 
1120
  ::-webkit-scrollbar-track {
1121
  background: var(--card-bg);
1122
  border-radius: 10px;
1123
  }
 
1124
  ::-webkit-scrollbar-thumb {
1125
  background: var(--primary-color);
1126
  border-radius: 10px;
1127
  }
 
1128
  ::-webkit-scrollbar-thumb:hover {
1129
  background: var(--secondary-color);
1130
  }
 
1131
  /* 12. ์•„์ฝ”๋””์–ธ ๋ฐ ๋“œ๋กญ๋‹ค์šด */
1132
  details {
1133
  background-color: var(--card-bg) !important;
1134
  border-color: var(--border-color) !important;
1135
  color: var(--text-color) !important;
1136
  }
 
1137
  details summary {
1138
  background-color: var(--card-bg) !important;
1139
  color: var(--text-color) !important;
1140
  }
 
1141
  /* 13. ํˆดํŒ ๋ฐ ํŒ์—… */
1142
  [data-tooltip]:hover::after,
1143
  .tooltip,
@@ -1147,6 +401,7 @@ def create_interface():
1147
  border-color: var(--border-color) !important;
1148
  box-shadow: var(--shadow-light) !important;
1149
  }
 
1150
  /* 14. ๋ชจ๋‹ฌ ๋ฐ ์˜ค๋ฒ„๋ ˆ์ด */
1151
  .modal,
1152
  .overlay,
@@ -1156,6 +411,7 @@ def create_interface():
1156
  color: var(--text-color) !important;
1157
  border-color: var(--border-color) !important;
1158
  }
 
1159
  /* 15. ์ถ”๊ฐ€ Gradio ์ปดํฌ๋„ŒํŠธ๋“ค */
1160
  .gr-block,
1161
  .gr-group,
@@ -1164,12 +420,14 @@ def create_interface():
1164
  background-color: var(--background-color) !important;
1165
  color: var(--text-color) !important;
1166
  }
 
1167
  /* 16. ๋ฒ„ํŠผ์€ ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ (primary-color ์‚ฌ์šฉ) */
1168
  button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) {
1169
  background-color: var(--card-bg) !important;
1170
  color: var(--text-color) !important;
1171
  border-color: var(--border-color) !important;
1172
  }
 
1173
  /* 17. ์ฝ”๋“œ ๋ธ”๋ก ๋ฐ pre ํƒœ๊ทธ */
1174
  code,
1175
  pre,
@@ -1178,6 +436,7 @@ def create_interface():
1178
  color: var(--text-color) !important;
1179
  border-color: var(--border-color) !important;
1180
  }
 
1181
  /* 18. ์•Œ๋ฆผ ๋ฐ ๋ฉ”์‹œ์ง€ */
1182
  .alert,
1183
  .message,
@@ -1189,12 +448,14 @@ def create_interface():
1189
  color: var(--text-color) !important;
1190
  border-color: var(--border-color) !important;
1191
  }
 
1192
  /* 19. ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
1193
  * {
1194
  transition: background-color 0.3s ease,
1195
  color 0.3s ease,
1196
  border-color 0.3s ease !important;
1197
  }
 
1198
  /* 20. ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ */
1199
  .container {
1200
  max-width: 1200px;
@@ -1430,48 +691,11 @@ def create_interface():
1430
  if __name__ == "__main__":
1431
  debug_log("Gradio ์•ฑ ์‹คํ–‰ ์‹œ์ž‘")
1432
 
1433
- # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ƒํƒœ ์ฒดํฌ ๋ฐ ์ƒ์„ธ ๋กœ๊ทธ
1434
- debug_log("=== ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฒดํฌ ===")
1435
- debug_log(f"API_BASE_URL: {'์„ค์ •๋จ (' + API_BASE_URL + ')' if API_BASE_URL else 'โŒ ์„ค์ •๋˜์ง€ ์•Š์Œ'}")
1436
- debug_log(f"API_KEY: {'์„ค์ •๋จ' if API_KEY else 'โŒ ์„ค์ •๋˜์ง€ ์•Š์Œ'}")
1437
-
1438
- # API_CONFIGS ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฒดํฌ
1439
- api_configs_env = os.getenv("API_CONFIGS", "")
1440
- debug_log(f"API_CONFIGS: {'์„ค์ •๋จ (๊ธธ์ด: ' + str(len(api_configs_env)) + ')' if api_configs_env else 'โŒ ์„ค์ •๋˜์ง€ ์•Š์Œ'}")
1441
-
1442
- if not API_BASE_URL or not API_KEY:
1443
- print("โŒ ์ค‘์š”: API_BASE_URL ๋˜๋Š” API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
1444
- print("ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค Settings > Variables and secrets > Secrets์—์„œ ์„ค์ •ํ•˜์„ธ์š”.")
1445
-
1446
- if not api_configs_env:
1447
- print("โŒ ์ค‘์š”: API_CONFIGS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
1448
- print('๋‹ค์Œ ํ˜•ํƒœ๋กœ ์„ค์ •ํ•˜์„ธ์š”:')
1449
- print('NAVER_API_KEYS_LIST = [{"API_KEY":"...","SECRET_KEY":"...","CUSTOMER_ID":"..."}]')
1450
- print('NAVER_SEARCH_API_KEYS_LIST = [{"CLIENT_ID":"...","CLIENT_SECRET":"..."}]')
1451
-
1452
- # API ํ‚ค ๊ด€๋ฆฌ์ž ์ดˆ๊ธฐํ™”
1453
- initialize_naver_api_keys()
1454
- initialize_naver_search_api_keys()
1455
-
1456
- debug_log("=== API ํ‚ค ๋กœ๋“œ ๊ฒฐ๊ณผ ===")
1457
- debug_log(f"๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค: {len(naver_api_manager['keys'])}๊ฐœ ๋กœ๋“œ๋จ")
1458
- debug_log(f"๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค: {len(naver_search_api_manager['keys'])}๊ฐœ ๋กœ๋“œ๋จ")
1459
-
1460
- demo = create_interface()
1461
- demo.queue()
1462
- demo.launch()...","SECRET_KEY":"...","CUSTOMER_ID":"..."}]')
1463
-
1464
- if not naver_search_api_keys_env:
1465
- print("โŒ ์ค‘์š”: NAVER_SEARCH_API_KEYS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
1466
- print('JSON ํ˜•ํƒœ๋กœ ์„ค์ •ํ•˜์„ธ์š”: [{"CLIENT_ID":"...","CLIENT_SECRET":"..."}]')
1467
-
1468
- # API ํ‚ค ๊ด€๋ฆฌ์ž ์ดˆ๊ธฐํ™”
1469
- initialize_naver_api_keys()
1470
- initialize_naver_search_api_keys()
1471
-
1472
- debug_log("=== API ํ‚ค ๋กœ๋“œ ๊ฒฐ๊ณผ ===")
1473
- debug_log(f"๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API ํ‚ค: {len(naver_api_manager['keys'])}๊ฐœ ๋กœ๋“œ๋จ")
1474
- debug_log(f"๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰ API ํ‚ค: {len(naver_search_api_manager['keys'])}๊ฐœ ๋กœ๋“œ๋จ")
1475
 
1476
  demo = create_interface()
1477
  demo.queue()
 
1
  import gradio as gr
 
 
 
 
 
 
 
2
  import os
3
+ from gradio_client import Client
4
+
5
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ์—”๋“œํฌ์ธํŠธ ๋กœ๋“œ (๋กœ๊ทธ์— ์ถœ๋ ฅํ•˜์ง€ ์•Š์Œ)
6
+ def get_api_endpoint():
7
+ """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค."""
8
+ endpoint = os.getenv("API_ENDPOINT")
9
+ if not endpoint:
10
+ raise ValueError("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
11
+ return endpoint
12
+
13
+ # ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” (๋กœ๊ทธ์— ์ •๋ณด ๋…ธ์ถœํ•˜์ง€ ์•Š์Œ)
14
+ try:
15
+ client = Client(get_api_endpoint())
16
+ except Exception as e:
17
+ print("ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์‹คํŒจ. ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")
18
+ client = None
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  def debug_log(message: str):
21
+ """๋””๋ฒ„๊น… ๋กœ๊ทธ (์—”๋“œํฌ์ธํŠธ ์ •๋ณด๋Š” ์ œ์™ธ)"""
22
  print(f"[DEBUG] {message}")
23
 
24
+ # --- ๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ ์Šคํฌ๋ž˜ํ•‘ ํ•จ์ˆ˜ ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  def scrape_naver_blog(url: str) -> str:
26
+ """๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ ์Šคํฌ๋ž˜ํ•‘ - ํด๋ผ์ด์–ธํŠธ API ํ˜ธ์ถœ"""
27
+ debug_log("๋ธ”๋กœ๊ทธ ์Šคํฌ๋ž˜ํ•‘ ํ•จ์ˆ˜ ์‹œ์ž‘")
28
+
29
+ if not client:
30
+ return "ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
31
 
32
  try:
33
+ result = client.predict(url, api_name="/fetch_blog_content")
34
+ debug_log("๋ธ”๋กœ๊ทธ ์Šคํฌ๋ž˜ํ•‘ ์™„๋ฃŒ")
35
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  except Exception as e:
37
+ debug_log(f"๋ธ”๋กœ๊ทธ ์Šคํฌ๋ž˜ํ•‘ ์˜ค๋ฅ˜: {str(e)}")
38
  return f"์Šคํฌ๋ž˜ํ•‘ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
39
 
40
+ # --- ๋ถ„์„ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜ ---
41
+ def analysis_handler(blog_text: str, remove_freq1: bool, include_title: bool, direct_keyword_input: str, direct_keyword_only: bool):
42
+ """๋ถ„์„ ํ•ธ๋“ค๋Ÿฌ - ํด๋ผ์ด์–ธํŠธ API ํ˜ธ์ถœ"""
43
+ debug_log("๋ถ„์„ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜ ์‹œ์ž‘")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ if not client:
46
+ return None, None
 
 
 
47
 
 
48
  try:
49
+ result = client.predict(
50
+ blog_text,
51
+ remove_freq1,
52
+ include_title,
53
+ direct_keyword_input,
54
+ direct_keyword_only,
55
+ api_name="/analysis_handler"
56
+ )
57
+ debug_log("๋ถ„์„ ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
58
  return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  except Exception as e:
60
+ debug_log(f"๋ถ„์„ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}")
61
+ return None, None
 
 
 
 
 
 
 
 
 
62
 
63
+ # --- ์Šคํฌ๋ž˜ํ•‘ ์‹คํ–‰ ํ•จ์ˆ˜ ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  def fetch_blog_content(url: str):
65
+ """๋ธ”๋กœ๊ทธ ์ฝ˜ํ…์ธ  ๊ฐ€์ ธ์˜ค๊ธฐ"""
66
+ debug_log("๋ธ”๋กœ๊ทธ ์ฝ˜ํ…์ธ  ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ์ž‘")
67
  content = scrape_naver_blog(url)
68
+ debug_log("๋ธ”๋กœ๊ทธ ์ฝ˜ํ…์ธ  ๊ฐ€์ ธ์˜ค๊ธฐ ์™„๋ฃŒ")
69
  return content
70
 
71
  # --- Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ ---
 
75
  ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋ณ€๊ฒฝ ํ…œํ”Œ๋ฆฟ CSS
76
  ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์— ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
77
  ============================================ */
78
+
79
  /* 1. CSS ๋ณ€์ˆ˜ ์ •์˜ (๋ผ์ดํŠธ๋ชจ๋“œ - ๊ธฐ๋ณธ๊ฐ’) */
80
  :root {
81
  /* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */
 
107
  /* ๊ธฐํƒ€ */
108
  --border-radius: 18px;
109
  }
110
+
111
  /* 2. ๋‹คํฌ๋ชจ๋“œ ์ƒ‰์ƒ ๋ณ€์ˆ˜ (์ž๋™ ๊ฐ์ง€) */
112
  @media (prefers-color-scheme: dark) {
113
  :root {
 
133
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
134
  }
135
  }
136
+
137
  /* 3. ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค (Gradio ํ† ๊ธ€์šฉ) */
138
  [data-theme="dark"],
139
  .dark,
 
159
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
160
  --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
161
  }
162
+
163
  /* 4. ๊ธฐ๋ณธ ์š”์†Œ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉ */
164
  body {
165
  font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
 
168
  line-height: 1.6;
169
  transition: background-color 0.3s ease, color 0.3s ease;
170
  }
171
+
172
  footer {
173
  visibility: hidden;
174
  }
175
+
176
  /* 5. Gradio ์ปจํ…Œ์ด๋„ˆ ๊ฐ•์ œ ์ ์šฉ */
177
  .gradio-container,
178
  .gradio-container *,
 
182
  background-color: var(--background-color) !important;
183
  color: var(--text-color) !important;
184
  }
185
+
186
  /* 6. ์นด๋“œ ๋ฐ ํŒจ๋„ ์Šคํƒ€์ผ */
187
  .gr-form,
188
  .gr-box,
 
196
  color: var(--text-color) !important;
197
  box-shadow: var(--shadow) !important;
198
  }
199
+
200
  /* 7. ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
201
  input[type="text"],
202
  input[type="number"],
 
212
  color: var(--text-color) !important;
213
  border-color: var(--border-color) !important;
214
  }
215
+
216
  input[type="text"]:focus,
217
  input[type="number"]:focus,
218
  input[type="email"]:focus,
 
226
  border-color: var(--primary-color) !important;
227
  box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
228
  }
229
+
230
  /* 8. ๋ผ๋ฒจ ๋ฐ ํ…์ŠคํŠธ ์š”์†Œ */
231
  label,
232
  .gr-label,
 
235
  p, span, div {
236
  color: var(--text-color) !important;
237
  }
238
+
239
  /* ํผ ๋ผ๋ฒจ ํ…์ŠคํŠธ ํฌ๊ธฐ ๋Œ€ํญ ์ฆ๊ฐ€ */
240
  .gr-form label,
241
  .gr-textbox label,
 
245
  font-weight: 600 !important;
246
  color: var(--text-color) !important;
247
  }
248
+
249
  /* ์„ค๋ช… ํ…์ŠคํŠธ ํฌ๊ธฐ ๋Œ€ํญ ์ฆ๊ฐ€ */
250
  .gr-form .gr-form-label,
251
  .gr-textbox .gr-form-label,
 
256
  color: var(--text-secondary) !important;
257
  line-height: 1.4 !important;
258
  }
259
+
260
  /* 9. ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ */
261
  table {
262
  background-color: var(--card-bg) !important;
263
  color: var(--text-color) !important;
264
  border-color: var(--border-color) !important;
265
  }
266
+
267
  table th {
268
  background-color: var(--primary-color) !important;
269
  color: white !important;
270
  border-color: var(--border-color) !important;
271
  }
272
+
273
  table td {
274
  background-color: var(--card-bg) !important;
275
  color: var(--text-color) !important;
276
  border-color: var(--border-color) !important;
277
  }
278
+
279
  table tbody tr:nth-child(even) {
280
  background-color: var(--table-even-bg) !important;
281
  }
282
+
283
  table tbody tr:hover {
284
  background-color: var(--table-hover-bg) !important;
285
  }
286
+
287
  /* 10. ์ฒดํฌ๋ฐ•์Šค ๋ฐ ๋ผ๋””์˜ค ๋ฒ„ํŠผ ์Šคํƒ€์ผ ๊ฐœ์„  */
288
  input[type="checkbox"],
289
  input[type="radio"] {
 
292
  height: 24px !important;
293
  cursor: pointer !important;
294
  }
295
+
296
  /* ์ฒดํฌ๋ฐ•์Šค ์ปค์Šคํ…€ ์Šคํƒ€์ผ - ๋” ๊ฐ•๋ ฅํ•œ ์„ ํƒ์ž ์‚ฌ์šฉ */
297
  .gradio-container input[type="checkbox"],
298
  .gr-checkbox input[type="checkbox"],
 
311
  margin-right: 12px !important;
312
  flex-shrink: 0 !important;
313
  }
314
+
315
  .gradio-container input[type="checkbox"]:checked,
316
  .gr-checkbox input[type="checkbox"]:checked,
317
  input[type="checkbox"]:checked {
318
  background-color: var(--primary-color) !important;
319
  border-color: var(--primary-color) !important;
320
  }
321
+
322
  .gradio-container input[type="checkbox"]:checked::before,
323
  .gr-checkbox input[type="checkbox"]:checked::before,
324
  input[type="checkbox"]:checked::before {
 
332
  font-weight: bold !important;
333
  line-height: 1 !important;
334
  }
335
+
336
  .gradio-container input[type="checkbox"]:hover,
337
  .gr-checkbox input[type="checkbox"]:hover,
338
  input[type="checkbox"]:hover {
339
  border-color: var(--primary-color) !important;
340
  box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
341
  }
342
+
343
  /* ์ฒดํฌ๋ฐ•์Šค ๋ผ๋ฒจ ํ…์ŠคํŠธ ํฌ๊ธฐ ๋Œ€ํญ ์ฆ๊ฐ€ */
344
  .gradio-container .gr-checkbox label,
345
  .gr-checkbox label,
 
352
  display: flex !important;
353
  align-items: center !important;
354
  }
355
+
356
  /* ์ฒดํฌ๋ฐ•์Šค ์ปจํ…Œ์ด๋„ˆ ์ •๋ ฌ */
357
  .gr-checkbox {
358
  display: flex !important;
359
  align-items: center !important;
360
  gap: 8px !important;
361
  }
362
+
363
  /* 11. ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ */
364
  ::-webkit-scrollbar {
365
  width: 8px;
366
  height: 8px;
367
  }
368
+
369
  ::-webkit-scrollbar-track {
370
  background: var(--card-bg);
371
  border-radius: 10px;
372
  }
373
+
374
  ::-webkit-scrollbar-thumb {
375
  background: var(--primary-color);
376
  border-radius: 10px;
377
  }
378
+
379
  ::-webkit-scrollbar-thumb:hover {
380
  background: var(--secondary-color);
381
  }
382
+
383
  /* 12. ์•„์ฝ”๋””์–ธ ๋ฐ ๋“œ๋กญ๋‹ค์šด */
384
  details {
385
  background-color: var(--card-bg) !important;
386
  border-color: var(--border-color) !important;
387
  color: var(--text-color) !important;
388
  }
389
+
390
  details summary {
391
  background-color: var(--card-bg) !important;
392
  color: var(--text-color) !important;
393
  }
394
+
395
  /* 13. ํˆดํŒ ๋ฐ ํŒ์—… */
396
  [data-tooltip]:hover::after,
397
  .tooltip,
 
401
  border-color: var(--border-color) !important;
402
  box-shadow: var(--shadow-light) !important;
403
  }
404
+
405
  /* 14. ๋ชจ๋‹ฌ ๋ฐ ์˜ค๋ฒ„๋ ˆ์ด */
406
  .modal,
407
  .overlay,
 
411
  color: var(--text-color) !important;
412
  border-color: var(--border-color) !important;
413
  }
414
+
415
  /* 15. ์ถ”๊ฐ€ Gradio ์ปดํฌ๋„ŒํŠธ๋“ค */
416
  .gr-block,
417
  .gr-group,
 
420
  background-color: var(--background-color) !important;
421
  color: var(--text-color) !important;
422
  }
423
+
424
  /* 16. ๋ฒ„ํŠผ์€ ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ (primary-color ์‚ฌ์šฉ) */
425
  button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) {
426
  background-color: var(--card-bg) !important;
427
  color: var(--text-color) !important;
428
  border-color: var(--border-color) !important;
429
  }
430
+
431
  /* 17. ์ฝ”๋“œ ๋ธ”๋ก ๋ฐ pre ํƒœ๊ทธ */
432
  code,
433
  pre,
 
436
  color: var(--text-color) !important;
437
  border-color: var(--border-color) !important;
438
  }
439
+
440
  /* 18. ์•Œ๋ฆผ ๋ฐ ๋ฉ”์‹œ์ง€ */
441
  .alert,
442
  .message,
 
448
  color: var(--text-color) !important;
449
  border-color: var(--border-color) !important;
450
  }
451
+
452
  /* 19. ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
453
  * {
454
  transition: background-color 0.3s ease,
455
  color 0.3s ease,
456
  border-color 0.3s ease !important;
457
  }
458
+
459
  /* 20. ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ */
460
  .container {
461
  max-width: 1200px;
 
691
  if __name__ == "__main__":
692
  debug_log("Gradio ์•ฑ ์‹คํ–‰ ์‹œ์ž‘")
693
 
694
+ # ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ (์—”๋“œํฌ์ธํŠธ ์ •๋ณด๋Š” ๋กœ๊ทธ์— ์ถœ๋ ฅํ•˜์ง€ ์•Š์Œ)
695
+ if not client:
696
+ print("๊ฒฝ๊ณ : ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")
697
+ else:
698
+ debug_log("ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ์„ฑ๊ณต")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
 
700
  demo = create_interface()
701
  demo.queue()