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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +40 -26
app.py CHANGED
@@ -8,13 +8,13 @@ import pandas as pd
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,13 +26,9 @@ def get_header(method, uri, api_key, secret_key, customer_id):
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,7 +37,6 @@ def fetch_related_keywords(keyword):
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,11 +46,9 @@ def fetch_related_keywords(keyword):
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(",", "")
@@ -63,16 +56,31 @@ def fetch_related_keywords(keyword):
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:
@@ -83,10 +91,10 @@ def create_excel_file(df):
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() != ""]
@@ -96,15 +104,15 @@ def process_keyword(keywords: str, include_related: bool):
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,18 +120,24 @@ def process_keyword(keywords: str, include_related: bool):
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μžλ°”μŠ€ν¬λ¦½νŠΈ")
@@ -134,7 +148,7 @@ with gr.Blocks() as demo:
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 배포 κ°€λŠ₯)
 
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
  "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"]
 
37
  uri = "/keywordstool"
38
  method = "GET"
39
  headers = get_header(method, uri, API_KEY, SECRET_KEY, CUSTOMER_ID)
 
40
  params = {
41
  "hintKeywords": [keyword],
42
  "showDetail": "1"
 
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(",", "")
 
56
  except:
57
  return 0
58
 
 
59
  df["PCμ›”κ²€μƒ‰λŸ‰"] = df["monthlyPcQcCnt"].apply(parse_count)
60
  df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"] = df["monthlyMobileQcCnt"].apply(parse_count)
61
  df["ν† νƒˆμ›”κ²€μƒ‰λŸ‰"] = df["PCμ›”κ²€μƒ‰λŸ‰"] + df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"]
 
62
  df.rename(columns={"relKeyword": "μ •λ³΄ν‚€μ›Œλ“œ"}, inplace=True)
 
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:
 
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() != ""]
 
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:
 
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μžλ°”μŠ€ν¬λ¦½νŠΈ")
 
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 배포 κ°€λŠ₯)