Kims12 commited on
Commit
c9c3967
Β·
verified Β·
1 Parent(s): 9b804be

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -58
app.py CHANGED
@@ -8,12 +8,13 @@ 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
  def get_header(method, uri, api_key, secret_key, customer_id):
18
  timestamp = str(round(time.time() * 1000))
19
  signature = generate_signature(timestamp, method, uri, secret_key)
@@ -25,8 +26,13 @@ def get_header(method, uri, api_key, secret_key, customer_id):
25
  "X-Signature": signature
26
  }
27
 
28
- # --- 넀이버 κ΄‘κ³  API: 연관검색어 및 κ²€μƒ‰λŸ‰ 쑰회 ---
29
  def fetch_related_keywords(keyword):
 
 
 
 
 
30
  API_KEY = os.environ["NAVER_API_KEY"]
31
  SECRET_KEY = os.environ["NAVER_SECRET_KEY"]
32
  CUSTOMER_ID = os.environ["NAVER_CUSTOMER_ID"]
@@ -35,6 +41,7 @@ def fetch_related_keywords(keyword):
35
  uri = "/keywordstool"
36
  method = "GET"
37
  headers = get_header(method, uri, API_KEY, SECRET_KEY, CUSTOMER_ID)
 
38
  params = {
39
  "hintKeywords": [keyword],
40
  "showDetail": "1"
@@ -44,68 +51,60 @@ def fetch_related_keywords(keyword):
44
  if "keywordList" not in data:
45
  return pd.DataFrame()
46
  df = pd.DataFrame(data["keywordList"])
 
47
  if len(df) > 100:
48
  df = df.head(100)
49
 
 
50
  def parse_count(x):
51
  try:
52
- return int(str(x).replace(",", ""))
 
53
  except:
54
  return 0
55
 
 
56
  df["PCμ›”κ²€μƒ‰λŸ‰"] = df["monthlyPcQcCnt"].apply(parse_count)
57
  df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"] = df["monthlyMobileQcCnt"].apply(parse_count)
58
  df["ν† νƒˆμ›”κ²€μƒ‰λŸ‰"] = df["PCμ›”κ²€μƒ‰λŸ‰"] + df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"]
 
59
  df.rename(columns={"relKeyword": "μ •λ³΄ν‚€μ›Œλ“œ"}, inplace=True)
 
60
  result_df = df[["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"]]
61
  return result_df
62
 
63
- # --- 넀이버 검색 API: λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회 ---
64
- def fetch_blog_count(keyword):
65
- client_id = os.environ["NAVER_SEARCH_CLIENT_ID"]
66
- client_secret = os.environ["NAVER_SEARCH_CLIENT_SECRET"]
67
- url = "https://openapi.naver.com/v1/search/blog.json"
68
- headers = {
69
- "X-Naver-Client-Id": client_id,
70
- "X-Naver-Client-Secret": client_secret
71
- }
72
- params = {"query": keyword, "display": 1}
73
- response = requests.get(url, headers=headers, params=params)
74
- if response.status_code == 200:
75
- data = response.json()
76
- return data.get("total", 0)
77
- else:
78
- return 0
79
-
80
- # --- μž„μ‹œ μ—‘μ…€ 파일 생성 ---
81
  def create_excel_file(df):
82
  with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
83
  excel_path = tmp.name
84
  df.to_excel(excel_path, index=False)
85
  return excel_path
86
 
87
- # --- μž…λ ₯ ν‚€μ›Œλ“œ 처리 ν•¨μˆ˜ ---
88
  def process_keyword(keywords: str, include_related: bool):
89
  """
90
- 1. μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό μ—”ν„°λ‘œ κ΅¬λΆ„ν•˜μ—¬ 리슀트둜 λ§Œλ“­λ‹ˆλ‹€.
91
- 2. 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ 넀이버 κ΄‘κ³  API둜 κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•˜κ³ ,
92
- 첫 번째 ν‚€μ›Œλ“œμ— λŒ€ν•΄ μ˜΅μ…˜(연관검색어 포함)이 True인 경우 연관검색어도 μΆ”κ°€ν•©λ‹ˆλ‹€.
93
- 3. μ΅œμ’… κ²°κ³Ό DataFrame에 각 "μ •λ³΄ν‚€μ›Œλ“œ"λ§ˆλ‹€ 넀이버 검색 API둜 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ₯Ό μ‘°νšŒν•˜μ—¬ "λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜" μ»¬λŸΌμ„ μΆ”κ°€ν•©λ‹ˆλ‹€.
94
  """
95
- input_keywords = [k.strip() for k in keywords.splitlines() if k.strip()]
 
96
  result_dfs = []
97
 
98
  for idx, kw in enumerate(input_keywords):
99
  df_kw = fetch_related_keywords(kw)
100
  if df_kw.empty:
101
  continue
102
- # μž…λ ₯ ν‚€μ›Œλ“œμ— ν•΄λ‹Ήν•˜λŠ” κ²°κ³Ό 포함
103
  row_kw = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] == kw]
104
  if not row_kw.empty:
105
  result_dfs.append(row_kw)
106
  else:
 
107
  result_dfs.append(df_kw.head(1))
108
- # 첫 번째 ν‚€μ›Œλ“œμ˜ 연관검색어 μΆ”κ°€ (μž…λ ₯ ν‚€μ›Œλ“œ μ œμ™Έ)
 
109
  if include_related and idx == 0:
110
  df_related = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] != kw]
111
  if not df_related.empty:
@@ -113,44 +112,30 @@ def process_keyword(keywords: str, include_related: bool):
113
 
114
  if result_dfs:
115
  result_df = pd.concat(result_dfs, ignore_index=True)
 
116
  result_df.drop_duplicates(subset=["μ •λ³΄ν‚€μ›Œλ“œ"], inplace=True)
117
  else:
118
  result_df = pd.DataFrame(columns=["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"])
119
-
120
- # 각 μ •λ³΄ν‚€μ›Œλ“œμ— λŒ€ν•΄ λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회
121
- result_df["λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜"] = result_df["μ •λ³΄ν‚€μ›Œλ“œ"].apply(fetch_blog_count)
122
  result_df.sort_values(by="ν† νƒˆμ›”κ²€μƒ‰λŸ‰", ascending=False, inplace=True)
123
-
124
  return result_df, create_excel_file(result_df)
125
 
126
- # --- Gradio UI ꡬ성 ---
127
- with gr.Blocks(css=".gradio-container { max-width: 960px; margin: auto; }") as demo:
128
- gr.Markdown("# 넀이버 연관검색어, κ²€μƒ‰λŸ‰ 및 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회")
129
- gr.Markdown(
130
- "μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό **μ—”ν„°**둜 κ΅¬λΆ„ν•˜μ—¬ μž…λ ₯ν•˜μ„Έμš”. 각 ν‚€μ›Œλ“œμ— λŒ€ν•œ κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•˜λ©°, "
131
- "첫 번째 ν‚€μ›Œλ“œμ— λŒ€ν•΄ '연관검색어 포함' μ˜΅μ…˜μ„ μ„ νƒν•˜λ©΄ 연관검색어 결과도 ν•¨κ»˜ μ‘°νšŒλ©λ‹ˆλ‹€. \n\n"
132
- "λ˜ν•œ, 각 μ •λ³΄ν‚€μ›Œλ“œμ— λŒ€ν•œ 넀이버 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ„ ν•¨κ»˜ 좜λ ₯λ©λ‹ˆλ‹€."
133
- )
 
134
 
135
  with gr.Row():
136
- with gr.Column(scale=1):
137
- keyword_input = gr.Textbox(
138
- label="ν‚€μ›Œλ“œ μž…λ ₯ (μ—¬λŸ¬ 개일 경우 μ—”ν„°λ‘œ ꡬ뢄)",
139
- lines=6,
140
- placeholder="예:\nκ°•μ›λ„ν’€λΉŒλΌ\nμžλ°”μŠ€ν¬λ¦½νŠΈ"
141
- )
142
- include_checkbox = gr.Checkbox(label="연관검색어 포함 (첫번째 ν‚€μ›Œλ“œμ— ν•œν•¨)", value=False)
143
- search_button = gr.Button("검색", variant="primary")
144
- with gr.Column(scale=1):
145
- gr.Markdown("### 검색 κ²°κ³Ό")
146
- df_output = gr.Dataframe(label="κ²°κ³Ό ν…Œμ΄λΈ”")
147
- excel_output = gr.File(label="μ—‘μ…€ λ‹€μš΄λ‘œλ“œ")
148
 
149
- search_button.click(
150
- fn=process_keyword,
151
- inputs=[keyword_input, include_checkbox],
152
- outputs=[df_output, excel_output]
153
- )
154
 
155
  # μ•± μ‹€ν–‰ (Hugging Face Spaces 배포 κ°€λŠ₯)
156
  demo.launch()
 
8
  import tempfile
9
  import gradio as gr
10
 
11
+ # 인증 μ„œλͺ…을 μƒμ„±ν•˜λŠ” ν•¨μˆ˜
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
  "X-Signature": signature
27
  }
28
 
29
+ # 넀이버 연관검색어 및 κ²€μƒ‰λŸ‰ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” ν•¨μˆ˜
30
  def fetch_related_keywords(keyword):
31
+ """
32
+ 단일 ν‚€μ›Œλ“œμ— λŒ€ν•΄ 넀이버 검색광고 APIλ₯Ό ν˜ΈμΆœν•˜μ—¬ 연관검색어 및 κ²€μƒ‰λŸ‰ 데이터λ₯Ό DataFrame으둜 λ°˜ν™˜ν•©λ‹ˆλ‹€.
33
+ λ°˜ν™˜ 컬럼: "μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"
34
+ """
35
+ # ν™˜κ²½λ³€μˆ˜μ—μ„œ API ν‚€ 등을 μ½μ–΄μ˜΅λ‹ˆλ‹€ (κΈ°λ³Έκ°’ 없이 μ‚¬μš©)
36
  API_KEY = os.environ["NAVER_API_KEY"]
37
  SECRET_KEY = os.environ["NAVER_SECRET_KEY"]
38
  CUSTOMER_ID = os.environ["NAVER_CUSTOMER_ID"]
 
41
  uri = "/keywordstool"
42
  method = "GET"
43
  headers = get_header(method, uri, API_KEY, SECRET_KEY, CUSTOMER_ID)
44
+ # API νŒŒλΌλ―Έν„°: hintKeywordsλŠ” 리슀트둜 전달
45
  params = {
46
  "hintKeywords": [keyword],
47
  "showDetail": "1"
 
51
  if "keywordList" not in data:
52
  return pd.DataFrame()
53
  df = pd.DataFrame(data["keywordList"])
54
+ # μ΅œλŒ€ 100κ°œκΉŒμ§€ κ²°κ³Ό μ‚¬μš©
55
  if len(df) > 100:
56
  df = df.head(100)
57
 
58
+ # λ¬Έμžμ—΄ ν˜•νƒœμ˜ κ²€μƒ‰λŸ‰μ„ μ •μˆ˜λ‘œ λ³€ν™˜ν•˜λŠ” ν•¨μˆ˜
59
  def parse_count(x):
60
  try:
61
+ x_str = str(x).replace(",", "")
62
+ return int(x_str)
63
  except:
64
  return 0
65
 
66
+ # 각 κ²€μƒ‰λŸ‰ ν•„λ“œλ₯Ό μ •μˆ˜ν˜•μœΌλ‘œ λ³€ν™˜ ν›„ ν† νƒˆ κ²€μƒ‰λŸ‰ 계산
67
  df["PCμ›”κ²€μƒ‰λŸ‰"] = df["monthlyPcQcCnt"].apply(parse_count)
68
  df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"] = df["monthlyMobileQcCnt"].apply(parse_count)
69
  df["ν† νƒˆμ›”κ²€μƒ‰λŸ‰"] = df["PCμ›”κ²€μƒ‰λŸ‰"] + df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"]
70
+ # 'relKeyword' 컬럼λͺ…을 "μ •λ³΄ν‚€μ›Œλ“œ"둜 λ³€κ²½
71
  df.rename(columns={"relKeyword": "μ •λ³΄ν‚€μ›Œλ“œ"}, inplace=True)
72
+ # ν•„μš”ν•œ 컬럼만 선택
73
  result_df = df[["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"]]
74
  return result_df
75
 
76
+ # μž„μ‹œ μ—‘μ…€ 파일 생성 ν•¨μˆ˜
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  def create_excel_file(df):
78
  with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
79
  excel_path = tmp.name
80
  df.to_excel(excel_path, index=False)
81
  return excel_path
82
 
83
+ # μž…λ ₯된 μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό μ²˜λ¦¬ν•˜λŠ” ν•¨μˆ˜
84
  def process_keyword(keywords: str, include_related: bool):
85
  """
86
+ - ν…μŠ€νŠΈλ°•μŠ€μ— μ—”ν„°λ‘œ κ΅¬λΆ„λœ μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό λ°›μ•„ 각 ν‚€μ›Œλ“œμ˜ κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.
87
+ - 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ μž…λ ₯ν•œ ν‚€μ›Œλ“œ 자체의 κ²°κ³Ό(κ²€μƒ‰λŸ‰)λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
88
+ - μ²΄ν¬λ°•μŠ€κ°€ μ„ νƒλœ 경우, 첫 번째 ν‚€μ›Œλ“œμ— λŒ€ν•΄μ„œλ§Œ 연관검색어(μž…λ ₯ ν‚€μ›Œλ“œλ₯Ό μ œμ™Έν•œ λ‚˜λ¨Έμ§€ κ²°κ³Ό)λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
89
+ - κ²°κ³Ό DataFrameκ³Ό μ—‘μ…€ 파일 경둜λ₯Ό νŠœν”Œλ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€.
90
  """
91
+ # μ€„λ°”κΏˆμœΌλ‘œ λΆ„λ¦¬ν•˜μ—¬ μž…λ ₯ ν‚€μ›Œλ“œ 리슀트 생성 (빈 쀄 μ œμ™Έ)
92
+ input_keywords = [k.strip() for k in keywords.splitlines() if k.strip() != ""]
93
  result_dfs = []
94
 
95
  for idx, kw in enumerate(input_keywords):
96
  df_kw = fetch_related_keywords(kw)
97
  if df_kw.empty:
98
  continue
99
+ # μž…λ ₯ ν‚€μ›Œλ“œμ— ν•΄λ‹Ήν•˜λŠ” 행을 μ°ΎμŠ΅λ‹ˆλ‹€.
100
  row_kw = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] == kw]
101
  if not row_kw.empty:
102
  result_dfs.append(row_kw)
103
  else:
104
+ # λ§Œμ•½ μž…λ ₯ ν‚€μ›Œλ“œ 행이 μ—†λ‹€λ©΄ 첫번째 행을 μΆ”κ°€ (λŒ€μ²΄)
105
  result_dfs.append(df_kw.head(1))
106
+
107
+ # μ²΄ν¬λ°•μŠ€κ°€ True이고, 첫 번째 ν‚€μ›Œλ“œμΈ κ²½μš°μ—λ§Œ 연관검색어 μΆ”κ°€ (μž…λ ₯ ν‚€μ›Œλ“œ μ œμ™Έ)
108
  if include_related and idx == 0:
109
  df_related = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] != kw]
110
  if not df_related.empty:
 
112
 
113
  if result_dfs:
114
  result_df = pd.concat(result_dfs, ignore_index=True)
115
+ # 쀑볡 ν–‰ 제거 (λ§Œμ•½ μž…λ ₯ ν‚€μ›Œλ“œκ°€ 연관검색어 결과에 ν¬ν•¨λœ 경우)
116
  result_df.drop_duplicates(subset=["μ •λ³΄ν‚€μ›Œλ“œ"], inplace=True)
117
  else:
118
  result_df = pd.DataFrame(columns=["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"])
119
+ # ν† νƒˆμ›”κ²€μƒ‰λŸ‰ λ‚΄λ¦Όμ°¨μˆœ μ •λ ¬ (선택사항)
 
 
120
  result_df.sort_values(by="ν† νƒˆμ›”κ²€μƒ‰λŸ‰", ascending=False, inplace=True)
 
121
  return result_df, create_excel_file(result_df)
122
 
123
+ # Gradio UI ꡬ성
124
+ with gr.Blocks() as demo:
125
+ gr.Markdown("### 넀이버 연관검색어 및 κ²€μƒ‰λŸ‰ 쑰회 μ•±")
126
+ gr.Markdown("μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό μ—”ν„°λ‘œ κ΅¬λΆ„ν•˜μ—¬ μž…λ ₯ν•˜λ©΄ 각 ν‚€μ›Œλ“œμ˜ κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€. (첫 번째 ν‚€μ›Œλ“œμ˜ 경우, '연관검색어 포함' 체크 μ‹œ 연관검색어도 ν•¨κ»˜ μ‘°νšŒλ©λ‹ˆλ‹€.)")
127
+
128
+ with gr.Row():
129
+ keyword_input = gr.Textbox(label="ν‚€μ›Œλ“œ μž…λ ₯ (μ—¬λŸ¬ 개일 경우 μ—”ν„°λ‘œ ꡬ뢄)", lines=5, placeholder="예:\nκ°•μ›λ„ν’€λΉŒλΌ\nμžλ°”μŠ€ν¬λ¦½νŠΈ")
130
+ include_checkbox = gr.Checkbox(label="연관검색어 포함 (첫번째 ν‚€μ›Œλ“œμ— ν•œν•¨)", value=False)
131
+ search_button = gr.Button("검색")
132
 
133
  with gr.Row():
134
+ df_output = gr.Dataframe(label="검색 κ²°κ³Ό")
135
+ excel_output = gr.File(label="μ—‘μ…€ λ‹€μš΄λ‘œλ“œ")
 
 
 
 
 
 
 
 
 
 
136
 
137
+ # λ²„νŠΌ 클릭 μ‹œ process_keyword ν•¨μˆ˜λ₯Ό μ‹€ν–‰ (두 개의 μž…λ ₯: ν‚€μ›Œλ“œ, μ²΄ν¬λ°•μŠ€ μƒνƒœ)
138
+ search_button.click(fn=process_keyword, inputs=[keyword_input, include_checkbox], outputs=[df_output, excel_output])
 
 
 
139
 
140
  # μ•± μ‹€ν–‰ (Hugging Face Spaces 배포 κ°€λŠ₯)
141
  demo.launch()