na_ver / app.py
Kims12's picture
Update app.py
bfacc72 verified
import os
import time
import hmac
import hashlib
import base64
import requests
import pandas as pd
import tempfile
import gradio as gr
# --- 넀이버 κ΄‘κ³  API: μ„œλͺ… 생성 및 헀더 ꡬ성 ---
def generate_signature(timestamp, method, uri, secret_key):
message = f"{timestamp}.{method}.{uri}"
digest = hmac.new(secret_key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest()
return base64.b64encode(digest).decode()
def get_header(method, uri, api_key, secret_key, customer_id):
timestamp = str(round(time.time() * 1000))
signature = generate_signature(timestamp, method, uri, secret_key)
return {
"Content-Type": "application/json; charset=UTF-8",
"X-Timestamp": timestamp,
"X-API-KEY": api_key,
"X-Customer": str(customer_id),
"X-Signature": signature
}
# --- 넀이버 κ΄‘κ³  API: 연관검색어 및 κ²€μƒ‰λŸ‰ 쑰회 ---
def fetch_related_keywords(keyword):
API_KEY = os.environ["NAVER_API_KEY"]
SECRET_KEY = os.environ["NAVER_SECRET_KEY"]
CUSTOMER_ID = os.environ["NAVER_CUSTOMER_ID"]
BASE_URL = "https://api.naver.com"
uri = "/keywordstool"
method = "GET"
headers = get_header(method, uri, API_KEY, SECRET_KEY, CUSTOMER_ID)
params = {
"hintKeywords": [keyword],
"showDetail": "1"
}
response = requests.get(BASE_URL + uri, params=params, headers=headers)
data = response.json()
if "keywordList" not in data:
return pd.DataFrame()
df = pd.DataFrame(data["keywordList"])
if len(df) > 100:
df = df.head(100)
def parse_count(x):
try:
return int(str(x).replace(",", ""))
except:
return 0
df["PCμ›”κ²€μƒ‰λŸ‰"] = df["monthlyPcQcCnt"].apply(parse_count)
df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"] = df["monthlyMobileQcCnt"].apply(parse_count)
df["ν† νƒˆμ›”κ²€μƒ‰λŸ‰"] = df["PCμ›”κ²€μƒ‰λŸ‰"] + df["λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰"]
df.rename(columns={"relKeyword": "μ •λ³΄ν‚€μ›Œλ“œ"}, inplace=True)
result_df = df[["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"]]
return result_df
# --- 넀이버 검색 API: λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회 ---
def fetch_blog_count(keyword):
client_id = os.environ["NAVER_SEARCH_CLIENT_ID"]
client_secret = os.environ["NAVER_SEARCH_CLIENT_SECRET"]
url = "https://openapi.naver.com/v1/search/blog.json"
headers = {
"X-Naver-Client-Id": client_id,
"X-Naver-Client-Secret": client_secret
}
params = {"query": keyword, "display": 1}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
data = response.json()
return data.get("total", 0)
else:
return 0
# --- μž„μ‹œ μ—‘μ…€ 파일 생성 ---
def create_excel_file(df):
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
excel_path = tmp.name
df.to_excel(excel_path, index=False)
return excel_path
# --- μž…λ ₯ ν‚€μ›Œλ“œ 처리 ν•¨μˆ˜ ---
def process_keyword(keywords: str, include_related: bool):
"""
1. μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό μ—”ν„°λ‘œ κ΅¬λΆ„ν•˜μ—¬ 리슀트둜 λ§Œλ“­λ‹ˆλ‹€.
2. 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ 넀이버 κ΄‘κ³  API둜 κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•˜κ³ ,
첫 번째 ν‚€μ›Œλ“œμ— λŒ€ν•΄ μ˜΅μ…˜(연관검색어 포함)이 True인 경우 연관검색어도 μΆ”κ°€ν•©λ‹ˆλ‹€.
3. μ΅œμ’… κ²°κ³Ό DataFrame에 각 "μ •λ³΄ν‚€μ›Œλ“œ"λ§ˆλ‹€ 넀이버 검색 API둜 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ₯Ό μ‘°νšŒν•˜μ—¬ "λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜" μ»¬λŸΌμ„ μΆ”κ°€ν•©λ‹ˆλ‹€.
"""
input_keywords = [k.strip() for k in keywords.splitlines() if k.strip()]
result_dfs = []
for idx, kw in enumerate(input_keywords):
df_kw = fetch_related_keywords(kw)
if df_kw.empty:
continue
# μž…λ ₯ ν‚€μ›Œλ“œμ— ν•΄λ‹Ήν•˜λŠ” κ²°κ³Ό 포함
row_kw = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] == kw]
if not row_kw.empty:
result_dfs.append(row_kw)
else:
result_dfs.append(df_kw.head(1))
# 첫 번째 ν‚€μ›Œλ“œμ˜ 연관검색어 μΆ”κ°€ (μž…λ ₯ ν‚€μ›Œλ“œ μ œμ™Έ)
if include_related and idx == 0:
df_related = df_kw[df_kw["μ •λ³΄ν‚€μ›Œλ“œ"] != kw]
if not df_related.empty:
result_dfs.append(df_related)
if result_dfs:
result_df = pd.concat(result_dfs, ignore_index=True)
result_df.drop_duplicates(subset=["μ •λ³΄ν‚€μ›Œλ“œ"], inplace=True)
else:
result_df = pd.DataFrame(columns=["μ •λ³΄ν‚€μ›Œλ“œ", "PCμ›”κ²€μƒ‰λŸ‰", "λͺ¨λ°”μΌμ›”κ²€μƒ‰λŸ‰", "ν† νƒˆμ›”κ²€μƒ‰λŸ‰"])
# 각 μ •λ³΄ν‚€μ›Œλ“œμ— λŒ€ν•΄ λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회
result_df["λΈ”λ‘œκ·Έλ¬Έμ„œμˆ˜"] = result_df["μ •λ³΄ν‚€μ›Œλ“œ"].apply(fetch_blog_count)
result_df.sort_values(by="ν† νƒˆμ›”κ²€μƒ‰λŸ‰", ascending=False, inplace=True)
return result_df, create_excel_file(result_df)
# --- Gradio UI ꡬ성 ---
with gr.Blocks(css=".gradio-container { max-width: 960px; margin: auto; }") as demo:
gr.Markdown("# 넀이버 연관검색어, κ²€μƒ‰λŸ‰ 및 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜ 쑰회")
gr.Markdown(
"μ—¬λŸ¬ ν‚€μ›Œλ“œλ₯Ό **μ—”ν„°**둜 κ΅¬λΆ„ν•˜μ—¬ μž…λ ₯ν•˜μ„Έμš”. 각 ν‚€μ›Œλ“œμ— λŒ€ν•œ κ²€μƒ‰λŸ‰ 정보λ₯Ό μ‘°νšŒν•˜λ©°, "
"첫 번째 ν‚€μ›Œλ“œμ— λŒ€ν•΄ '연관검색어 포함' μ˜΅μ…˜μ„ μ„ νƒν•˜λ©΄ 연관검색어 결과도 ν•¨κ»˜ μ‘°νšŒλ©λ‹ˆλ‹€. \n\n"
"λ˜ν•œ, 각 μ •λ³΄ν‚€μ›Œλ“œμ— λŒ€ν•œ 넀이버 λΈ”λ‘œκ·Έ λ¬Έμ„œμˆ˜λ„ ν•¨κ»˜ 좜λ ₯λ©λ‹ˆλ‹€."
)
with gr.Row():
with gr.Column(scale=1):
keyword_input = gr.Textbox(
label="ν‚€μ›Œλ“œ μž…λ ₯ (μ—¬λŸ¬ 개일 경우 μ—”ν„°λ‘œ ꡬ뢄)",
lines=6,
placeholder="예:\nκ°•μ›λ„ν’€λΉŒλΌ\nμžλ°”μŠ€ν¬λ¦½νŠΈ"
)
include_checkbox = gr.Checkbox(label="연관검색어 포함 (첫번째 ν‚€μ›Œλ“œμ— ν•œν•¨)", value=False)
search_button = gr.Button("검색", variant="primary")
with gr.Column(scale=1):
gr.Markdown("### 검색 κ²°κ³Ό")
df_output = gr.Dataframe(label="κ²°κ³Ό ν…Œμ΄λΈ”")
excel_output = gr.File(label="μ—‘μ…€ λ‹€μš΄λ‘œλ“œ")
search_button.click(
fn=process_keyword,
inputs=[keyword_input, include_checkbox],
outputs=[df_output, excel_output]
)
# μ•± μ‹€ν–‰ (Hugging Face Spaces 배포 κ°€λŠ₯)
demo.launch()