shiue2000 commited on
Commit
af46a64
·
verified ·
1 Parent(s): e8948d8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -40
app.py CHANGED
@@ -82,54 +82,56 @@ def run_analysis():
82
  try:
83
  since_date = (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d')
84
  until_date = datetime.now().strftime('%Y-%m-%d')
85
-
86
  # 1. 抓貼文
87
  all_tweets = []
88
  for candidate in candidates:
89
  tweets = fetch_tweets_via_x_tools(candidate, since_date, until_date)
90
  all_tweets.extend(tweets)
91
-
92
  if not all_tweets:
93
  raise ValueError("No tweets fetched. Using full dummy data.")
94
-
95
  df_tweets = pd.DataFrame(all_tweets, columns=["日期", "使用者", "內容", "候選人"])
96
-
97
  # 2. 情緒分析
98
- df_tweets['情緒'] = df_tweets['內容'].apply(lambda x: sentiment(x)[0]['label'])
99
- df_tweets['信心度'] = df_tweets['內容'].apply(lambda x: sentiment(x)[0]['score'])
100
-
101
  # 統計每位候選人情緒比例
102
  summary = df_tweets.groupby(['候選人', '情緒']).size().unstack(fill_value=0)
103
  summary['總貼文'] = summary.sum(axis=1)
104
  summary['正面比率'] = summary.get('positive', 0) / summary['總貼文']
105
  summary['負面比率'] = summary.get('negative', 0) / summary['總貼文']
106
- summary['日期'] = datetime.now().strftime('%Y-%m-%d %H:%M %Z')
107
-
108
  # 3. 更新歷史資料
 
109
  if os.path.exists(history_file):
110
  df_history = pd.read_csv(history_file)
111
- df_history = pd.concat([df_history, summary.reset_index()[['日期', '候選人', '正面比率', '負面比率']]], ignore_index=True)
112
  else:
113
- df_history = summary.reset_index()[['日期', '候選人', '正面比率', '負面比率']]
114
  df_history.to_csv(history_file, index=False)
115
 
116
- # 4-11. 可視化
117
- plt.figure(figsize=(8, 5))
118
- summary[['正面比率', '負面比率']].plot(kind='bar', stacked=True, colormap='coolwarm')
 
119
  plt.title("候選人當日社群情緒比例")
120
  plt.ylabel("比例")
121
  plt.xlabel("候選人")
122
  plt.xticks(rotation=0)
123
  plt.tight_layout()
124
  buf = io.BytesIO()
125
- plt.savefig(buf, format="png")
126
  buf.seek(0)
127
- img_b64_today = base64.b64encode(buf.read()).decode("utf-8")
128
  buf.close()
129
 
130
- plt.figure(figsize=(10, 5))
 
131
  for c in candidates:
132
- temp = df_history[df_history['候選人'] == c]
133
  plt.plot(temp['日期'], temp['正面比率'], marker='o', label=f"{c} 正面")
134
  plt.plot(temp['日期'], temp['負面比率'], marker='x', label=f"{c} 負面")
135
  plt.xticks(rotation=45)
@@ -138,12 +140,35 @@ def run_analysis():
138
  plt.legend()
139
  plt.tight_layout()
140
  buf = io.BytesIO()
141
- plt.savefig(buf, format="png")
142
  buf.seek(0)
143
- img_b64_trend = base64.b64encode(buf.read()).decode("utf-8")
144
  buf.close()
145
 
146
- # 6. 新聞線索整合
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  if os.path.exists(news_file):
148
  df_news = pd.read_csv(news_file)
149
  news_summary = df_news.groupby('類別').size().to_dict()
@@ -152,29 +177,70 @@ def run_analysis():
152
  news_summary = {}
153
  news_table = "<p>未提供新聞資料</p>"
154
 
155
- # 12. 載入模板
156
- with open("templates/index.html", "r", encoding="utf-8") as f:
157
- html_template = f.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  html_content = html_template.format(
160
- report_date=datetime.now().strftime('%Y-%m-%d %H:%M %Z'),
161
  img_b64_today=img_b64_today,
162
  img_b64_trend=img_b64_trend,
163
- engagement_table="""
164
- <table class="min-w-full bg-white border border-gray-200">
165
- <tr class="bg-gray-100 border-b">
166
- <th class="py-2 px-4 border-r">總參與數</th>
167
- <td class="py-2 px-4 border-r">{total_tweets}</td>
168
- <th class="py-2 px-4 border-r">正面情緒比例</th>
169
- <td class="py-2 px-4 border-r">{positive_pct:.1%}</td>
170
- <th class="py-2 px-4 border-r">平均互動率</th>
171
- <td class="py-2 px-4 border-r">3.9%</td>
172
- <th class="py-2 px-4 border-r">活躍平台</th>
173
- <td class="py-2 px-4">6</td>
174
- </tr>
175
- </table>
176
- """.format(total_tweets=len(df_tweets), positive_pct=df_tweets['情緒'].value_counts(normalize=True).get('positive', 0)),
177
- news_summary=str(news_summary),
178
  news_table=news_table
179
  )
180
 
 
82
  try:
83
  since_date = (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d')
84
  until_date = datetime.now().strftime('%Y-%m-%d')
85
+
86
  # 1. 抓貼文
87
  all_tweets = []
88
  for candidate in candidates:
89
  tweets = fetch_tweets_via_x_tools(candidate, since_date, until_date)
90
  all_tweets.extend(tweets)
91
+
92
  if not all_tweets:
93
  raise ValueError("No tweets fetched. Using full dummy data.")
94
+
95
  df_tweets = pd.DataFrame(all_tweets, columns=["日期", "使用者", "內容", "候選人"])
96
+
97
  # 2. 情緒分析
98
+ df_tweets['情緒'] = df_tweets['內容'].apply(lambda x: sentiment(x)['label'])
99
+ df_tweets['信心度'] = df_tweets['內容'].apply(lambda x: sentiment(x)['score'])
100
+
101
  # 統計每位候選人情緒比例
102
  summary = df_tweets.groupby(['候選人', '情緒']).size().unstack(fill_value=0)
103
  summary['總貼文'] = summary.sum(axis=1)
104
  summary['正面比率'] = summary.get('positive', 0) / summary['總貼文']
105
  summary['負面比率'] = summary.get('negative', 0) / summary['總貼文']
106
+
 
107
  # 3. 更新歷史資料
108
+ summary['日期'] = datetime.now().strftime('%Y-%m-%d %H:%M %Z')
109
  if os.path.exists(history_file):
110
  df_history = pd.read_csv(history_file)
111
+ df_history = pd.concat([df_history, summary.reset_index()[['日期','候選人','正面比率','負面比率']]], ignore_index=True)
112
  else:
113
+ df_history = summary.reset_index()[['日期','候選人','正面比率','負面比率']]
114
  df_history.to_csv(history_file, index=False)
115
 
116
+ # ----------------- 圖表生成 -----------------
117
+ # 當日情緒比例
118
+ plt.figure(figsize=(8,5))
119
+ summary[['正面比率','負面比率']].plot(kind='bar', stacked=True, colormap='coolwarm')
120
  plt.title("候選人當日社群情緒比例")
121
  plt.ylabel("比例")
122
  plt.xlabel("候選人")
123
  plt.xticks(rotation=0)
124
  plt.tight_layout()
125
  buf = io.BytesIO()
126
+ plt.savefig(buf, format='png')
127
  buf.seek(0)
128
+ img_b64_today = base64.b64encode(buf.read()).decode('utf-8')
129
  buf.close()
130
 
131
+ # 歷史情緒趨勢
132
+ plt.figure(figsize=(10,5))
133
  for c in candidates:
134
+ temp = df_history[df_history['候選人']==c]
135
  plt.plot(temp['日期'], temp['正面比率'], marker='o', label=f"{c} 正面")
136
  plt.plot(temp['日期'], temp['負面比率'], marker='x', label=f"{c} 負面")
137
  plt.xticks(rotation=45)
 
140
  plt.legend()
141
  plt.tight_layout()
142
  buf = io.BytesIO()
143
+ plt.savefig(buf, format='png')
144
  buf.seek(0)
145
+ img_b64_trend = base64.b64encode(buf.read()).decode('utf-8')
146
  buf.close()
147
 
148
+ # 其他圖表 placeholder(可自行生成圖表後轉 base64)
149
+ img_social_sentiment = ""
150
+ img_platform_performance = ""
151
+ img_candidate_volume = ""
152
+ img_candidate_sentiment = ""
153
+ img_knowledge_graph = ""
154
+
155
+ # 社群參與表格
156
+ engagement_table = f"""
157
+ <table class="min-w-full bg-white border border-gray-200">
158
+ <tr class="bg-gray-100 border-b">
159
+ <th class="py-2 px-4 border-r">總參與數</th>
160
+ <td class="py-2 px-4 border-r">{len(df_tweets)}</td>
161
+ <th class="py-2 px-4 border-r">正面情緒比例</th>
162
+ <td class="py-2 px-4 border-r">{df_tweets['情緒'].value_counts(normalize=True).get('positive',0):.1%}</td>
163
+ <th class="py-2 px-4 border-r">平均互動率</th>
164
+ <td class="py-2 px-4 border-r">3.9%</td>
165
+ <th class="py-2 px-4 border-r">活躍平台</th>
166
+ <td class="py-2 px-4">6</td>
167
+ </tr>
168
+ </table>
169
+ """
170
+
171
+ # 新聞��料
172
  if os.path.exists(news_file):
173
  df_news = pd.read_csv(news_file)
174
  news_summary = df_news.groupby('類別').size().to_dict()
 
177
  news_summary = {}
178
  news_table = "<p>未提供新聞資料</p>"
179
 
180
+ # ----------------- 內嵌 HTML 模板 -----------------
181
+ html_template = """<!DOCTYPE html>
182
+ <html lang="zh-TW">
183
+ <head>
184
+ <meta charset="UTF-8">
185
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
186
+ <title>高雄市長選戰輿情分析</title>
187
+ <script src="https://cdn.tailwindcss.com"></script>
188
+ <style>
189
+ body {{
190
+ background-color: #f3f4f6;
191
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
192
+ }}
193
+ .card {{
194
+ background-color: white;
195
+ border-radius: 0.5rem;
196
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
197
+ padding: 1.5rem;
198
+ margin-bottom: 1.5rem;
199
+ }}
200
+ .chart-container {{
201
+ max-width: 100%;
202
+ overflow-x: auto;
203
+ }}
204
+ </style>
205
+ </head>
206
+ <body class="p-6">
207
+ <header class="bg-blue-600 text-white p-4 rounded-lg mb-6">
208
+ <h1 class="text-3xl font-bold">高雄市長選戰輿情分析</h1>
209
+ <p class="text-sm">更新時間: {report_date}</p>
210
+ </header>
211
+ <main class="grid grid-cols-1 md:grid-cols-2 gap-6">
212
+ <div class="card">
213
+ <h2 class="text-xl font-semibold mb-4">1. 當日社群貼文情緒</h2>
214
+ <div class="chart-container">
215
+ <img src="data:image/png;base64,{img_b64_today}" class="w-full">
216
+ </div></div>
217
+ <div class="card">
218
+ <h2 class="text-xl font-semibold mb-4">2. 歷史情緒趨勢</h2>
219
+ <div class="chart-container">
220
+ <img src="data:image/png;base64,{img_b64_trend}" class="w-full">
221
+ </div></div>
222
+ <div class="card md:col-span-2">
223
+ <h2 class="text-xl font-semibold mb-4">3. 社群媒體參與概況</h2>
224
+ {engagement_table}
225
+ </div>
226
+ <div class="card md:col-span-2">
227
+ <h2 class="text-xl font-semibold mb-4">9. 新聞議題統計</h2>
228
+ <p>各類別新聞數量: {news_summary}</p>
229
+ {news_table}
230
+ </div>
231
+ </main>
232
+ <footer class="mt-6 text-center text-gray-500">
233
+ <p>© 2025 高雄市長選戰輿情分析系統 | 由 xAI 技術支持</p>
234
+ </footer>
235
+ </body>
236
+ </html>"""
237
 
238
  html_content = html_template.format(
239
+ report_date=datetime.now().strftime('%Y-%m-%d %H:%M'),
240
  img_b64_today=img_b64_today,
241
  img_b64_trend=img_b64_trend,
242
+ engagement_table=engagement_table,
243
+ news_summary=news_summary,
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  news_table=news_table
245
  )
246