File size: 6,425 Bytes
b43fad9
67096dc
 
b43fad9
67096dc
 
863de1b
67096dc
b43fad9
71ba060
bfacc72
b43fad9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bfacc72
b43fad9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bfacc72
b43fad9
cb9d18f
67096dc
b43fad9
 
 
 
 
 
 
bfacc72
b43fad9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
863de1b
bfacc72
b43fad9
 
 
 
 
863de1b
bfacc72
b43fad9
71ba060
bfacc72
 
 
 
71ba060
bfacc72
b43fad9
 
 
 
 
 
bfacc72
b43fad9
 
 
71ba060
b43fad9
bfacc72
b43fad9
 
 
 
 
 
 
 
71ba060
b43fad9
71ba060
bfacc72
b43fad9
 
bfacc72
b43fad9
 
bfacc72
 
 
b43fad9
bfacc72
 
b43fad9
 
 
 
bfacc72
 
 
 
 
 
 
 
 
 
 
 
b43fad9
bfacc72
 
 
 
 
67096dc
b43fad9
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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()