File size: 6,184 Bytes
25c28fc
 
 
 
 
 
 
 
 
 
 
087e783
25c28fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c9375d
25c28fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
087e783
25c28fc
 
 
 
 
 
 
 
 
 
087e783
25c28fc
 
 
 
 
 
 
087e783
25c28fc
 
 
a577b50
 
 
 
 
 
 
 
 
 
 
25c28fc
 
 
480e488
25c28fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
158
159
160
161
import gradio as gr
import pandas as pd
from pytrends.request import TrendReq
import plotly.express as px
import plotly.graph_objects as go

DEVELOPER_NAME = "黃千宥、陳奕瑄、汪于捷、李哲弘、洪寓澤"

# 初始化 pytrends
# hl='zh-TW' -> 繁體中文, tz=480 -> 台灣時區 (GMT+8)
pytrends = TrendReq(hl='zh-TW', tz=480)
PLOTLY_TEMPLATE = "plotly_dark"
def analyze_google_trends(keywords_str: str, timeframe: str):
    """
    根據輸入的關鍵字和時間範圍,從 Google Trends 獲取並分析資料。

    Args:
        keywords_str: 以逗號分隔的關鍵字字串。
        timeframe: Gradio 選項對應的時間範圍字串。

    Returns:
        一個包含兩個 Plotly 圖表的元組 (時間趨勢圖, 區域熱度圖)。
    """
    if not keywords_str:
        gr.Warning("請至少輸入一個關鍵字!")
        # 回傳空的圖表
        return go.Figure(), go.Figure()

    # 解析關鍵字
    kw_list = [kw.strip() for kw in keywords_str.split(',')]
    if len(kw_list) > 5:
        gr.Warning("為了圖表清晰,最多支援比較 5 個關鍵字。")
        kw_list = kw_list[:5]

    # 對應 Gradio 選項到 pytrends 的時間格式
    timeframe_map = {
        "過去 7 天": 'now 7-d',
        "過去一個月": 'today 1-m',
        "過去三個月": 'today 3-m',
        "過去一年": 'today 12-m',
    }
    selected_timeframe = timeframe_map.get(timeframe, 'now 7-d')

    try:
        # 1. 獲取時間序列資料
        pytrends.build_payload(kw_list, cat=0, timeframe=selected_timeframe, geo='', gprop='')
        interest_over_time_df = pytrends.interest_over_time()

        if interest_over_time_df.empty:
            gr.Warning(f"找不到關於 '{keywords_str}' 的時間趨勢資料。")
            time_fig = go.Figure()
        else:
            interest_over_time_df = interest_over_time_df.drop(columns=['isPartial'], errors='ignore')
            time_fig = plot_interest_over_time(interest_over_time_df, f"'{', '.join(kw_list)}' 在 {timeframe} 的搜尋熱度趨勢")

        # 2. 獲取區域熱度資料
        # 注意:區域熱度分析不支援多個關鍵字同時比較,因此我們只分析第一個關鍵字
        first_keyword = kw_list[0]
        pytrends.build_payload([first_keyword], cat=0, timeframe=selected_timeframe, geo='', gprop='')
        interest_by_region_df = pytrends.interest_by_region(resolution='COUNTRY', inc_low_vol=True, inc_geo_code=False)
        
        if interest_by_region_df.empty:
            gr.Warning(f"找不到關於 '{first_keyword}' 的區域熱度資料。")
            region_fig = go.Figure()
        else:
            # 只取前 20 名
            interest_by_region_df = interest_by_region_df.sort_values(by=first_keyword, ascending=False).head(20)
            region_fig = plot_interest_by_region(interest_by_region_df, f"'{first_keyword}' 在全球的區域熱度 Top 20")

        return time_fig, region_fig

    except Exception as e:
        gr.Error(f"查詢時發生錯誤: {e}")
        return go.Figure(), go.Figure()

def plot_interest_over_time(df: pd.DataFrame, title: str):
    """使用 Plotly 繪製時間趨勢圖。"""
    fig = px.line(df, x=df.index, y=df.columns, title=title, labels={'value': '相對熱度', 'date': '日期', 'variable': '關鍵字'})
    fig.update_layout(
        template=PLOTLY_TEMPLATE,
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0.2)',
        legend_title_text=''
    )
    return fig

def plot_interest_by_region(df: pd.DataFrame, title: str):
    """使用 Plotly 繪製區域熱度長條圖。"""
    fig = px.bar(df, x=df.index, y=df.columns[0], title=title, labels={'y': '相對熱度', 'index': '國家/地區'})
    fig.update_layout(
        template=PLOTLY_TEMPLATE,
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0.2)'
    )
    fig.update_xaxes(categoryorder='total descending')
    return fig

with gr.Blocks(
    theme=gr.themes.Soft(
        primary_hue="blue", 
        secondary_hue="cyan", 
        font=["Arial", "sans-serif"]
    ), 
    js="""
function refresh() {
    const url = new URL(window.location);

    if (url.searchParams.get('__theme') !== 'dark') {
        url.searchParams.set('__theme', 'dark');
        window.location.href = url.href;
    }
}
"""
) as app:
    gr.Markdown(f"""
    <div style='text-align: center; padding: 20px; color: white;'>
        <h1 style='font-size: 3em; color: #2563eb;'>📊 關鍵字趨勢分析儀表板</h1>
        <p style='font-size: 1.2em; color: #A9A9A9;'>輸入關鍵字,洞察全球搜尋趨勢與市場脈動</p>
        <p style='font-size: 0.9em; color: #888;'>Designed by: {DEVELOPER_NAME}</p>
    </div>
    """)

    with gr.Group():
        with gr.Row():
            keywords_input = gr.Textbox(
                label="🔍 輸入關鍵字",
                placeholder="例如:Bitcoin, Ethereum, Dogecoin (以逗號分隔)",
                scale=3
            )
            timeframe_input = gr.Radio(
                ["過去 7 天", "過去一個月", "過去三個月", "過去一年"],
                label="🗓️ 選擇時間範圍",
                value="過去 7 天",
                scale=2
            )
        analyze_button = gr.Button("🚀 開始分析", variant="primary")

    with gr.Tabs():
        with gr.TabItem("📈 時間趨勢比較"):
            time_series_plot = gr.Plot()
        with gr.TabItem("🌍 全球區域熱度"):
            region_plot = gr.Plot()
            gr.Markdown("<p style='text-align: center; color: #888;'>註:區域熱度分析僅針對您輸入的第一個關鍵字。</p>")


    analyze_button.click(
        fn=analyze_google_trends,
        inputs=[keywords_input, timeframe_input],
        outputs=[time_series_plot, region_plot]
    )

    gr.Examples(
        examples=[
            ["Bitcoin, Ethereum", "過去三個月"],
            ["穩定幣, Coinbase", "過去一年"],
            ["NVIDIA, AMD, TSMC", "過去一個月"],
        ],
        inputs=[keywords_input, timeframe_input]
    )

app.launch(share=True, debug=False, show_error=True, show_api=False)