这是一个使用 Python 内置库 tkinter 改造的图形界面(GUI)版本。它保留了你原有脚本的所有核心功能,并将其封装在一个现代化的窗口中,支持实时日志显示和结果查看。


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
多平台热点聚合抓取引擎 - GUI版
支持:知乎热榜、微博热搜、百度热搜、B站排行榜
"""
import json
import os
import sys
import ssl
import time
import certifi
import threading
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError
# ---------------- 核心抓取逻辑 (保持原样) ----------------
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
_SAFE_SSL_CTX = ssl.create_default_context()
_SAFE_SSL_CTX.load_verify_locations(certifi.where())
def_urlopen(req, timeout=10):
return urlopen(req, timeout=timeout, context=_SAFE_SSL_CTX)
HEADERS = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
}
deffetch_zhihu(limit=20):
url = "https://api.zhihu.com/topstory/hot-lists/total?limit={}".format(limit)
try:
req = Request(url, headers=HEADERS)
with _urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode("utf-8"))
except Exception as e:
return [], f"知乎抓取失败: {str(e)}"
results = []
for item in data.get("data", []):
target = item.get("target", {})
title = target.get("title", "").strip()
ifnot title: continue
detail = item.get("detail_text", "")
heat_num = 0
if"万"in detail:
try: heat_num = float(detail.replace("万热度", "").replace("万", "").strip()) * 10000
except ValueError: pass
results.append({
"title": title, "platform": "知乎", "heat": int(heat_num),
"heat_display": detail, "url": f"https://www.zhihu.com/question/{target.get('id', '')}",
"excerpt": (target.get("excerpt", "") or"")[:100]
})
return results, "知乎抓取完成"
deffetch_weibo(limit=20):
url = "https://weibo.com/ajax/side/hotSearch"
headers = {**HEADERS, "Referer": "https://weibo.com/"}
try:
req = Request(url, headers=headers)
with _urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode("utf-8"))
except Exception as e:
return [], f"微博抓取失败: {str(e)}"
results = []
realtime = data.get("data", {}).get("realtime", [])
for item in realtime[:limit]:
note = item.get("note", "").strip()
ifnot note: continue
label = item.get("label_name", "")
if label: note = f"[{label}]{note}"
results.append({
"title": note, "platform": "微博", "heat": int(item.get("num", 0)),
"heat_display": str(item.get("num", 0)),
"url": f"https://s.weibo.com/weibo?q=%23{item.get('word', item.get('note', ''))}%23",
"excerpt": ""
})
return results, "微博抓取完成"
deffetch_baidu(limit=20):
url = "https://top.baidu.com/api/board?platform=wise&tab=realtime"
try:
req = Request(url, headers=HEADERS)
with _urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode("utf-8"))
except Exception as e:
return [], f"百度抓取失败: {str(e)}"
results = []
cards = data.get("data", {}).get("cards", [])
if cards:
content = cards[0].get("content", [])
for item in content[:limit]:
query = item.get("query", "").strip()
ifnot query: continue
results.append({
"title": query, "platform": "百度", "heat": int(item.get("hotScore", 0)),
"heat_display": item.get("desc", "")[:30],
"url": f"https://www.baidu.com/s?wd={query}",
"excerpt": item.get("desc", "")[:100]
})
return results, "百度抓取完成"
deffetch_bilibili(limit=20):
url = "https://api.bilibili.com/x/web-interface/ranking/v2?rid=0&type=all"
try:
req = Request(url, headers=HEADERS)
with _urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode("utf-8"))
except Exception as e:
return [], f"B站抓取失败: {str(e)}"
results = []
items = data.get("data", {}).get("list", [])
for item in items[:limit]:
title = item.get("title", "").strip()
ifnot title: continue
stat = item.get("stat", {})
results.append({
"title": title, "platform": "B站", "heat": int(stat.get("view", 0)),
"heat_display": f"{_format_num(stat.get('view', 0))}播放",
"url": item.get("short_link_v2", f"https://www.bilibili.com/video/{item.get('bvid', '')}"),
"excerpt": item.get("description", "")[:100] if item.get("description") else""
})
return results, "B站抓取完成"
def_format_num(n):
n = int(n)
if n >= 10000: returnf"{n/10000:.1f}万"
elif n >= 1000: returnf"{n/1000:.1f}k"
return str(n)
# ---------------- GUI 应用逻辑 ----------------
classTrendApp:
def__init__(self, root):
self.root = root
self.root.title("多平台热点聚合助手")
self.root.geometry("900x650")
# 变量
self.platforms = {
"zhihu": tk.BooleanVar(value=True),
"weibo": tk.BooleanVar(value=True),
"baidu": tk.BooleanVar(value=True),
"bilibili": tk.BooleanVar(value=True)
}
self.limit_val = tk.IntVar(value=15)
self.keyword_val = tk.StringVar()
self.setup_ui()
defsetup_ui(self):
# 1. 顶部控制栏
control_frame = ttk.LabelFrame(self.root, text="控制面板", padding=10)
control_frame.pack(fill=tk.X, padx=10, pady=5)
# 平台选择
plat_frame = ttk.Frame(control_frame)
plat_frame.pack(side=tk.LEFT, fill=tk.Y)
ttk.Label(plat_frame, text="选择平台:").pack(anchor=tk.W)
for key, var in self.platforms.items():
ttk.Checkbutton(plat_frame, text=key.capitalize(), variable=var).pack(side=tk.LEFT)
# 参数设置
param_frame = ttk.Frame(control_frame)
param_frame.pack(side=tk.LEFT, fill=tk.Y, padx=20)
ttk.Label(param_frame, text="单平台条数:").grid(row=0, column=0, sticky=tk.W)
ttk.Entry(param_frame, textvariable=self.limit_val, width=8).grid(row=0, column=1, padx=5)
ttk.Label(param_frame, text="关键词过滤:").grid(row=1, column=0, sticky=tk.W, pady=5)
ttk.Entry(param_frame, textvariable=self.keyword_val, width=20).grid(row=1, column=1, padx=5, pady=5)
# 按钮
btn_frame = ttk.Frame(control_frame)
btn_frame.pack(side=tk.RIGHT)
self.fetch_btn = ttk.Button(btn_frame, text="开始抓取", command=self.start_fetch_thread)
self.fetch_btn.pack(pady=10)
ttk.Button(btn_frame, text="清空结果", command=lambda: self.result_text.delete(1.0, tk.END)).pack()
# 2. 日志显示
log_frame = ttk.LabelFrame(self.root, text="运行日志", padding=5)
log_frame.pack(fill=tk.X, padx=10, pady=0)
self.log_text = scrolledtext.ScrolledText(log_frame, height=6, state='disabled')
self.log_text.pack(fill=tk.BOTH, expand=True)
# 3. 结果显示
res_frame = ttk.LabelFrame(self.root, text="热点结果 (按热度排序)", padding=5)
res_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.result_text = scrolledtext.ScrolledText(res_frame, font=("Microsoft YaHei", 10))
self.result_text.pack(fill=tk.BOTH, expand=True)
deflog(self, message):
self.log_text.config(state='normal')
self.log_text.insert(tk.END, f"[{time.strftime('%H:%M:%S')}] {message}\n")
self.log_text.see(tk.END)
self.log_text.config(state='disabled')
defstart_fetch_thread(self):
# 检查是否至少选择了一个平台
ifnot any(v.get() for v in self.platforms.values()):
messagebox.showwarning("提示", "请至少选择一个平台!")
return
self.fetch_btn.config(state='disabled')
self.result_text.delete(1.0, tk.END)
threading.Thread(target=self.run_fetch, daemon=True).start()
defrun_fetch(self):
all_items = []
fetch_map = {
"zhihu": fetch_zhihu,
"weibo": fetch_weibo,
"baidu": fetch_baidu,
"bilibili": fetch_bilibili
}
limit = self.limit_val.get()
keyword = self.keyword_val.get().strip()
for name, var in self.platforms.items():
ifnot var.get(): continue
self.log(f"正在抓取 {name.capitalize()}...")
try:
items, msg = fetch_map[name](limit)
self.log(msg)
if items:
# 关键词过滤
if keyword:
items = [i for i in items if keyword.lower() in i["title"].lower()]
all_items.extend(items)
except Exception as e:
self.log(f"错误: {str(e)}")
# 排序
all_items.sort(key=lambda x: x.get("heat", 0), reverse=True)
# 显示结果
self.display_results(all_items, keyword)
self.root.after(0, lambda: self.fetch_btn.config(state='normal'))
defdisplay_results(self, items, keyword):
ifnot items:
self.result_text.insert(tk.END, "未抓取到任何数据。\n")
return
header = f"=== 热点聚合报告 (关键词: {keyword if keyword else'无'}) ===\n\n"
self.result_text.insert(tk.END, header)
# 简单分组显示
current_plat = None
for item in items:
if item['platform'] != current_plat:
current_plat = item['platform']
self.result_text.insert(tk.END, f"\n🔥 【{current_plat}】\n", "platform_tag")
heat_str = f" {item['heat_display']}"if item['heat_display'] else""
line = f" • {item['title']}{heat_str}\n"
self.result_text.insert(tk.END, line)
if item.get('excerpt'):
self.result_text.insert(tk.END, f" 摘要: {item['excerpt']}\n", "excerpt_tag")
# 配置标签颜色
self.result_text.tag_config("platform_tag", foreground="#FF5722", font=("Microsoft YaHei", 11, "bold"))
self.result_text.tag_config("excerpt_tag", foreground="#666666")
if __name__ == "__main__":
root = tk.Tk()
# 简单的样式美化
try:
style = ttk.Style()
style.theme_use('clam')
except:
pass
app = TrendApp(root)
root.mainloop()
threading),点击“开始抓取”时界面不会卡死。certifi 库(你的原脚本也用到了):pip install certifi
中文全平台热搜聚合。知乎·微博·百度·B站·抖音·头条,一键获取。
# 全平台热搜
python3 scripts/fetch_trends.py
# 单平台
python3 scripts/fetch_trends.py --platform zhihu
# AI选题推荐
python3 scripts/fetch_trends.py --platform zhihu --limit 5 --recommend
# JSON格式
python3 scripts/fetch_trends.py --json
🎯 AI选题推荐
1. 李想朋友圈发飙...
📍 平台: 知乎 | 专业分析文 | 给背景、分析、结论
🎯 可写角度: 行业分析 / 竞争格局 / 创始人故事
📝 建议标题:「关于「李想朋友圈发飙」,我从3个角度拆解」
所有数据来自平台公开 API,无需登录无需 Key。
# OpenClaw 用户直接安装
clawhub install freedompixels/cn-hot-trends
# 或手动复制到 skills 目录
cp -r cn-hot-trends ~/.qclaw/skills/