Kims12 commited on
Commit
08b8a4f
Β·
verified Β·
1 Parent(s): 68610a8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -94
app.py CHANGED
@@ -1,20 +1,21 @@
 
 
 
 
 
1
  import os
2
  import time
3
  import hmac
4
  import hashlib
5
  import base64
6
  import requests
7
- import pandas as pd
8
- import tempfile
9
- import gradio as gr
10
 
11
- # 넀이버 κ΄‘κ³  API 호좜 μ‹œ μ‚¬μš©ν•  μ„œλͺ… 생성 ν•¨μˆ˜
12
  def generate_signature(timestamp, method, uri, secret_key):
13
  message = f"{timestamp}.{method}.{uri}"
14
  digest = hmac.new(secret_key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest()
15
  return base64.b64encode(digest).decode()
16
 
17
- # 넀이버 κ΄‘κ³  API 호좜 헀더 생성 ν•¨μˆ˜
18
  def get_header(method, uri, api_key, secret_key, customer_id):
19
  timestamp = str(round(time.time() * 1000))
20
  signature = generate_signature(timestamp, method, uri, secret_key)
@@ -26,13 +27,14 @@ def get_header(method, uri, api_key, secret_key, customer_id):
26
  "X-Signature": signature
27
  }
28
 
29
- # 넀이버 κ΄‘κ³  APIλ₯Ό 톡해 단일 ν‚€μ›Œλ“œμ˜ 연관검색어 및 κ²€μƒ‰λŸ‰ 정보λ₯Ό κ°€μ Έμ˜€λŠ” ν•¨μˆ˜
30
  def fetch_related_keywords(keyword):
31
- # ν™˜κ²½λ³€μˆ˜μ—μ„œ κ΄‘κ³  API 킀값듀을 λΆˆλŸ¬μ˜΅λ‹ˆλ‹€.
32
- API_KEY = os.environ["NAVER_API_KEY"]
33
- SECRET_KEY = os.environ["NAVER_SECRET_KEY"]
34
- CUSTOMER_ID = os.environ["NAVER_CUSTOMER_ID"]
35
-
 
36
  BASE_URL = "https://api.naver.com"
37
  uri = "/keywordstool"
38
  method = "GET"
@@ -41,18 +43,20 @@ def fetch_related_keywords(keyword):
41
  "hintKeywords": [keyword],
42
  "showDetail": "1"
43
  }
44
- response = requests.get(BASE_URL + uri, params=params, headers=headers)
45
- data = response.json()
 
 
 
46
  if "keywordList" not in data:
47
  return pd.DataFrame()
48
  df = pd.DataFrame(data["keywordList"])
49
  if len(df) > 100:
50
  df = df.head(100)
51
-
52
  def parse_count(x):
53
  try:
54
- x_str = str(x).replace(",", "")
55
- return int(x_str)
56
  except:
57
  return 0
58
 
@@ -63,93 +67,152 @@ def fetch_related_keywords(keyword):
63
  result_df = df[["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"]]
64
  return result_df
65
 
66
- # 넀이버 검색 개발 APIλ₯Ό ν™œμš©ν•˜μ—¬ λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ₯Ό μ‘°νšŒν•˜λŠ” ν•¨μˆ˜
67
  def fetch_blog_count(keyword):
68
- # ν™˜κ²½λ³€μˆ˜μ—μ„œ 넀이버 검색 API 자격증λͺ…을 λΆˆλŸ¬μ˜΅λ‹ˆλ‹€.
69
- client_id = os.environ["NAVER_SEARCH_CLIENT_ID"]
70
- client_secret = os.environ["NAVER_SEARCH_CLIENT_SECRET"]
 
71
  url = "https://openapi.naver.com/v1/search/blog.json"
72
  headers = {
73
  "X-Naver-Client-Id": client_id,
74
  "X-Naver-Client-Secret": client_secret
75
  }
76
  params = {"query": keyword, "display": 1}
77
- response = requests.get(url, headers=headers, params=params)
78
- if response.status_code == 200:
79
- data = response.json()
80
- return data.get("total", 0)
81
- else:
 
 
 
82
  return 0
83
 
84
- # μž„μ‹œ μ—‘μ…€ 파일 생성 ν•¨μˆ˜
85
- def create_excel_file(df):
86
- with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
87
- excel_path = tmp.name
88
- df.to_excel(excel_path, index=False)
89
- return excel_path
90
-
91
- # μž…λ ₯된 μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό μ²˜λ¦¬ν•˜λŠ” ν•¨μˆ˜
92
- def process_keyword(keywords: str, include_related: bool):
93
  """
94
- 1. ν…μŠ€νŠΈλ°•μŠ€μ— μ—”ν„°λ‘œ κ΅¬λΆ„λœ μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό λ°›μ•„ 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ 넀이버 κ΄‘κ³  APIλ₯Ό 톡해 κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.
95
- 2. 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ μž…λ ₯ν•œ ν‚€μ›Œλ“œ 자체의 κ²°κ³Όλ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.
96
- 3. μ²΄ν¬λ°•μŠ€(True)인 경우, 첫 번째 ν‚€μ›Œλ“œμ— λŒ€ν•΄μ„œλ§Œ 연관검색어(μž…λ ₯ ν‚€μ›Œλ“œλ₯Ό μ œμ™Έν•œ κ²°κ³Ό)λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
97
- 4. λ§ˆμ§€λ§‰μœΌλ‘œ, 각 "μ •λ³΄ν‚€μ›Œλ“œ"에 λŒ€ν•΄ 넀이버 검색 APIλ₯Ό ν˜ΈμΆœν•˜μ—¬ λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ₯Ό μ‘°νšŒν•˜κ³  "λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜" μ»¬λŸΌμ— μΆ”κ°€ν•©λ‹ˆλ‹€.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  """
99
- # μ€„λ°”κΏˆμœΌλ‘œ λΆ„λ¦¬ν•˜μ—¬ μž…λ ₯ ν‚€μ›Œλ“œ 리슀트 생성 (빈 쀄 μ œμ™Έ)
100
- input_keywords = [k.strip() for k in keywords.splitlines() if k.strip() != ""]
101
- result_dfs = []
102
-
103
- for idx, kw in enumerate(input_keywords):
104
- df_kw = fetch_related_keywords(kw)
105
- if df_kw.empty:
106
- continue
107
- # μž…λ ₯ ν‚€μ›Œλ“œ 자체의 κ²°κ³Όλ₯Ό μš°μ„  포함
108
- row_kw = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] == kw]
109
- if not row_kw.empty:
110
- result_dfs.append(row_kw)
111
- else:
112
- # μž…λ ₯ ν‚€μ›Œλ“œμ— ν•΄λ‹Ήν•˜λŠ” 행이 μ—†μœΌλ©΄ 첫 번째 행을 λŒ€μ²΄λ‘œ μΆ”κ°€
113
- result_dfs.append(df_kw.head(1))
114
-
115
- # μ²΄ν¬λ°•μŠ€κ°€ True이고, 첫 번째 ν‚€μ›Œλ“œμ— λŒ€ν•΄μ„œλ§Œ 연관검색어 μΆ”κ°€ (μž…λ ₯ ν‚€μ›Œλ“œ μ œμ™Έ)
116
- if include_related and idx == 0:
117
- df_related = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] != kw]
118
- if not df_related.empty:
119
- result_dfs.append(df_related)
120
-
121
- if result_dfs:
122
- result_df = pd.concat(result_dfs, ignore_index=True)
123
- result_df.drop_duplicates(subset=["μ •λ³΄ν‚€μ›Œλ“œ"], inplace=True)
124
- else:
125
- result_df = pd.DataFrame(columns=["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"])
126
-
127
- # λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 컬럼 μΆ”κ°€: 각 μ •λ³΄ν‚€μ›Œλ“œλ§ˆλ‹€ 넀이버 λΈ”λ‘œκ·Έ 검색 API둜 총 λ¬Έμ„œμˆ˜λ₯Ό 쑰회
128
- result_df["λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜"] = result_df["μ •λ³΄ν‚€μ›Œλ“œ"].apply(fetch_blog_count)
129
-
130
- result_df.sort_values(by="ν† νƒˆμ›”κ²€μƒ‰λŸ‰", ascending=False, inplace=True)
131
- return result_df, create_excel_file(result_df)
132
-
133
- # Gradio UI ꡬ성
134
- with gr.Blocks() as demo:
135
- gr.Markdown("### 넀이버 연관검색어 및 κ²€μƒ‰λŸ‰, λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회 μ•±")
136
- gr.Markdown(
137
- "μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό μ—”ν„°λ‘œ κ΅¬λΆ„ν•˜μ—¬ μž…λ ₯ν•˜λ©΄ 각 ν‚€μ›Œλ“œμ˜ κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•˜κ³ , "
138
- "첫 번째 ν‚€μ›Œλ“œμ˜ 경우 '연관검색어 포함' 체크 μ‹œ 연관검색어도 ν•¨κ»˜ μ‘°νšŒν•©λ‹ˆλ‹€. "
139
- "λ˜ν•œ, 각 μ •λ³΄ν‚€μ›Œλ“œμ— λŒ€ν•œ 넀이버 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ„ ν•¨κ»˜ 좜λ ₯λ©λ‹ˆλ‹€."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  )
141
-
142
- with gr.Row():
143
- keyword_input = gr.Textbox(label="ν‚€μ›Œλ“œ μž…λ ₯ (μ—¬λŸ¬ 개일 경우 μ—”ν„°λ‘œ ꡬ뢄)", lines=5, placeholder="예:\nκ°•μ›λ„ν’€λΉŒλΌ\nμžλ°”μŠ€ν¬λ¦½νŠΈ")
144
- include_checkbox = gr.Checkbox(label="연관검색어 포함 (첫번째 ν‚€μ›Œλ“œμ— ν•œν•¨)", value=False)
145
- search_button = gr.Button("검색")
146
-
147
- with gr.Row():
148
- df_output = gr.Dataframe(label="검색 κ²°κ³Ό")
149
- excel_output = gr.File(label="μ—‘μ…€ λ‹€μš΄λ‘œλ“œ")
150
-
151
- # λ²„νŠΌ 클릭 μ‹œ process_keyword ν•¨μˆ˜ μ‹€ν–‰
152
- search_button.click(fn=process_keyword, inputs=[keyword_input, include_checkbox], outputs=[df_output, excel_output])
153
-
154
- # μ•± μ‹€ν–‰ (Hugging Face Spaces 배포 κ°€λŠ₯)
155
- demo.launch()
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import re
4
+ from io import BytesIO
5
+ import tempfile
6
  import os
7
  import time
8
  import hmac
9
  import hashlib
10
  import base64
11
  import requests
 
 
 
12
 
13
+ # --- 넀이버 κ΄‘κ³  API: μ„œλͺ… 생성 및 헀더 ꡬ성 ---
14
  def generate_signature(timestamp, method, uri, secret_key):
15
  message = f"{timestamp}.{method}.{uri}"
16
  digest = hmac.new(secret_key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest()
17
  return base64.b64encode(digest).decode()
18
 
 
19
  def get_header(method, uri, api_key, secret_key, customer_id):
20
  timestamp = str(round(time.time() * 1000))
21
  signature = generate_signature(timestamp, method, uri, secret_key)
 
27
  "X-Signature": signature
28
  }
29
 
30
+ # --- 넀이버 κ΄‘κ³  API: κ²€μƒ‰λŸ‰ 쑰회 (연관검색어 μ œμ™Έ) ---
31
  def fetch_related_keywords(keyword):
32
+ API_KEY = os.environ.get("NAVER_API_KEY")
33
+ SECRET_KEY = os.environ.get("NAVER_SECRET_KEY")
34
+ CUSTOMER_ID = os.environ.get("NAVER_CUSTOMER_ID")
35
+
36
+ if not API_KEY or not SECRET_KEY or not CUSTOMER_ID:
37
+ return pd.DataFrame()
38
  BASE_URL = "https://api.naver.com"
39
  uri = "/keywordstool"
40
  method = "GET"
 
43
  "hintKeywords": [keyword],
44
  "showDetail": "1"
45
  }
46
+ try:
47
+ response = requests.get(BASE_URL + uri, params=params, headers=headers)
48
+ data = response.json()
49
+ except Exception as e:
50
+ return pd.DataFrame()
51
  if "keywordList" not in data:
52
  return pd.DataFrame()
53
  df = pd.DataFrame(data["keywordList"])
54
  if len(df) > 100:
55
  df = df.head(100)
56
+
57
  def parse_count(x):
58
  try:
59
+ return int(str(x).replace(",", ""))
 
60
  except:
61
  return 0
62
 
 
67
  result_df = df[["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"]]
68
  return result_df
69
 
70
+ # --- 넀이버 검색 API: λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회 ---
71
  def fetch_blog_count(keyword):
72
+ client_id = os.environ.get("NAVER_SEARCH_CLIENT_ID")
73
+ client_secret = os.environ.get("NAVER_SEARCH_CLIENT_SECRET")
74
+ if not client_id or not client_secret:
75
+ return 0
76
  url = "https://openapi.naver.com/v1/search/blog.json"
77
  headers = {
78
  "X-Naver-Client-Id": client_id,
79
  "X-Naver-Client-Secret": client_secret
80
  }
81
  params = {"query": keyword, "display": 1}
82
+ try:
83
+ response = requests.get(url, headers=headers, params=params)
84
+ if response.status_code == 200:
85
+ data = response.json()
86
+ return data.get("total", 0)
87
+ else:
88
+ return 0
89
+ except:
90
  return 0
91
 
92
+ def process_excel(file_bytes):
 
 
 
 
 
 
 
 
93
  """
94
+ μ—…λ‘œλ“œλœ μ—‘μ…€ νŒŒμΌμ—μ„œ D4μ…€λΆ€ν„° Dμ—΄μ˜ μƒν’ˆλͺ…을 μΆ”μΆœν•˜μ—¬,
95
+ 각 μ…€μ—μ„œ 특수문자λ₯Ό μ œκ±°ν•œ ν›„ 곡백 κΈ°μ€€μœΌλ‘œ ν‚€μ›Œλ“œλ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€.
96
+ ν•œ μ…€ λ‚΄μ—μ„œ μ€‘λ³΅λœ ν‚€μ›Œλ“œλŠ” ν•œ 번만 μΉ΄μš΄νŠΈν•˜κ³ , 전체 셀에 λŒ€ν•΄
97
+ ν‚€μ›Œλ“œμ˜ λΉˆλ„λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€.
98
+
99
+ 이후, 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ 넀이버 APIλ₯Ό ν™œμš©ν•˜μ—¬
100
+ - PCμ›”κ²€μƒ‰λŸ‰, λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰, ν† νƒˆμ›”κ²€μƒ‰λŸ‰ 및
101
+ - 넀이버 검색 APIλ₯Ό ν†΅ν•œ λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ₯Ό μ‘°νšŒν•˜μ—¬
102
+ κ²°κ³Ό μ—‘μ…€ 파일과 λ°μ΄ν„°ν”„λ ˆμž„μœΌλ‘œ 좜λ ₯ν•©λ‹ˆλ‹€.
103
+
104
+ μ΅œμ’… μ—‘μ…€ 파일의 μ—΄ ꡬ성은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
105
+ Aμ—΄ : ν‚€μ›Œλ“œ
106
+ Bμ—΄ : λΉˆλ„μˆ˜
107
+ Cμ—΄ : PCμ›”κ²€μƒ‰λŸ‰
108
+ Dμ—΄ : λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰
109
+ Eμ—΄ : ν† νƒˆμ›”κ²€μƒ‰λŸ‰
110
+ Fμ—΄ : λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜
111
+
112
+ μ—λŸ¬ λ°œμƒ μ‹œ, μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό ν…μŠ€νŠΈ 파일과 λ°μ΄ν„°ν”„λ ˆμž„ ν˜•νƒœλ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€.
113
  """
114
+ # μ—‘μ…€ 파일 읽기
115
+ try:
116
+ df = pd.read_excel(BytesIO(file_bytes), header=None, engine="openpyxl")
117
+ except Exception as e:
118
+ error_message = "μ—‘μ…€ νŒŒμΌμ„ μ½λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν•˜μ˜€μŠ΅λ‹ˆλ‹€: " + str(e)
119
+ temp_error = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", mode="wb")
120
+ temp_error.write(error_message.encode("utf-8"))
121
+ temp_error.close()
122
+ error_df = pd.DataFrame({"μ—λŸ¬": [error_message]})
123
+ return temp_error.name, error_df
124
+
125
+ # μ—‘μ…€ 파일 ν˜•μ‹ 체크 (μ΅œμ†Œ 4μ—΄, μ΅œμ†Œ 4ν–‰)
126
+ if df.shape[1] < 4 or df.shape[0] < 4:
127
+ error_message = "μ—‘μ…€ 파일의 ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."
128
+ temp_error = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", mode="wb")
129
+ temp_error.write(error_message.encode("utf-8"))
130
+ temp_error.close()
131
+ error_df = pd.DataFrame({"μ—λŸ¬": [error_message]})
132
+ return temp_error.name, error_df
133
+
134
+ # Dμ—΄(4번째 μ—΄, 인덱슀 3)μ—μ„œ 4ν–‰(인덱슀 3)λΆ€ν„° 데이터λ₯Ό κ°€μ Έμ˜΄
135
+ product_names_series = df.iloc[3:, 3]
136
+ product_names_series = product_names_series.dropna()
137
+
138
+ keyword_counts = {}
139
+ for cell in product_names_series:
140
+ if not isinstance(cell, str):
141
+ cell = str(cell)
142
+ cleaned = re.sub(r'[^0-9a-zA-Zκ°€-힣\s]', '', cell)
143
+ keywords = cleaned.split()
144
+ unique_keywords = set(keywords)
145
+ for keyword in unique_keywords:
146
+ keyword_counts[keyword] = keyword_counts.get(keyword, 0) + 1
147
+
148
+ sorted_keywords = sorted(keyword_counts.items(), key=lambda x: (-x[1], x[0]))
149
+
150
+ # 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ 넀이버 APIλ₯Ό ν™œμš©ν•˜μ—¬ κ²€μƒ‰λŸ‰ 및 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회
151
+ result_data = []
152
+ for keyword, count in sorted_keywords:
153
+ pc_search = 0
154
+ mobile_search = 0
155
+ total_search = 0
156
+ df_api = fetch_related_keywords(keyword)
157
+ if not df_api.empty:
158
+ row = df_api[df_api["μ •λ³΄ν‚€μ›Œλ“œ"] == keyword]
159
+ if row.empty:
160
+ row = df_api.iloc[[0]]
161
+ pc_search = int(row["PCμ›”κ²€μƒ‰λŸ‰"].iloc[0])
162
+ mobile_search = int(row["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"].iloc[0])
163
+ total_search = int(row["ν† νƒˆμ›”κ²€μƒ‰λŸ‰"].iloc[0])
164
+ blog_count = fetch_blog_count(keyword)
165
+ result_data.append({
166
+ "ν‚€μ›Œλ“œ": keyword,
167
+ "λΉˆλ„μˆ˜": count,
168
+ "PCμ›”κ²€μƒ‰λŸ‰": pc_search,
169
+ "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰": mobile_search,
170
+ "ν† νƒˆμ›”κ²€μƒ‰λŸ‰": total_search,
171
+ "λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜": blog_count
172
+ })
173
+ result_df = pd.DataFrame(result_data)
174
+
175
+ # κ²°κ³Ό μ—‘μ…€ 파일 생성 (헀더: Aμ—΄λΆ€ν„° Fμ—΄κΉŒμ§€)
176
+ output = BytesIO()
177
+ try:
178
+ with pd.ExcelWriter(output, engine="openpyxl") as writer:
179
+ result_df.to_excel(writer, index=False, startrow=1, header=False)
180
+ worksheet = writer.sheets["Sheet1"]
181
+ worksheet.cell(row=1, column=1, value="ν‚€μ›Œλ“œ")
182
+ worksheet.cell(row=1, column=2, value="λΉˆλ„μˆ˜")
183
+ worksheet.cell(row=1, column=3, value="PCμ›”κ²€μƒ‰λŸ‰")
184
+ worksheet.cell(row=1, column=4, value="λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰")
185
+ worksheet.cell(row=1, column=5, value="ν† νƒˆμ›”κ²€μƒ‰λŸ‰")
186
+ worksheet.cell(row=1, column=6, value="λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜")
187
+ output.seek(0)
188
+ except Exception as e:
189
+ error_message = "μ—‘μ…€ νŒŒμΌμ„ μƒμ„±ν•˜λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν•˜μ˜€μŠ΅λ‹ˆλ‹€: " + str(e)
190
+ temp_error = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", mode="wb")
191
+ temp_error.write(error_message.encode("utf-8"))
192
+ temp_error.close()
193
+ error_df = pd.DataFrame({"μ—λŸ¬": [error_message]})
194
+ return temp_error.name, error_df
195
+
196
+ temp_excel = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx", mode="wb")
197
+ temp_excel.write(output.getvalue())
198
+ temp_excel.close()
199
+
200
+ return temp_excel.name, result_df
201
+
202
+ iface = gr.Interface(
203
+ fn=process_excel,
204
+ inputs=gr.File(label="μ—‘μ…€ 파일 μ—…λ‘œλ“œ", type="binary"),
205
+ outputs=[
206
+ gr.File(label="κ²°κ³Ό μ—‘μ…€ 파일"),
207
+ gr.DataFrame(label="ν‚€μ›Œλ“œ 뢄석 ν‘œ")
208
+ ],
209
+ title="μ—‘μ…€ μƒν’ˆλͺ… ν‚€μ›Œλ“œ μΆ”μΆœ 및 κ²€μƒ‰λŸ‰/λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회",
210
+ description=(
211
+ "μ—‘μ…€ 파일의 D4μ…€λΆ€ν„° D열에 μžˆλŠ” μƒν’ˆλͺ… 데이터λ₯Ό λΆ„μ„ν•˜μ—¬, "
212
+ "특수문자λ₯Ό μ œκ±°ν•œ ν›„ 곡백 κΈ°μ€€μœΌλ‘œ ν‚€μ›Œλ“œλ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€. "
213
+ "각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ 넀이버 APIλ₯Ό ν™œμš©ν•˜μ—¬ PC/λͺ¨λ°”일/ν† νƒˆ μ›” κ²€μƒ‰λŸ‰κ³Ό "
214
+ "넀이버 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ₯Ό μ‘°νšŒν•œ κ²°κ³Όλ₯Ό μ—‘μ…€ 파일과 ν‘œ(λ°μ΄ν„°ν”„λ ˆμž„)둜 좜λ ₯ν•©λ‹ˆλ‹€."
215
  )
216
+ )
217
+
218
+ iface.launch()