def _tick(self, force_now=False):
def worker():
prices = fetch_prices_all()
text = " ".join(prices)
self.after(0, lambda: self._update_text_and_resize(text))
if force_now:
worker()
else:
threading.Thread(target=worker, daemon=True).start()
ms = max(1, int(self.interval.get())) * 1000
self._job = self.after(ms, self._tick)
import tkinter as tk
import tkinter.font as tkfont
import threading
import requests
CODES = ("sh603392", "sz300133")
HEADERS = {"User-Agent": "Mozilla/5.0", "Referer": "https://finance.sina.com.cn"}
def fetch_prices_all():
if not CODES:
return []
api = "https://hq.sinajs.cn/list=" + ",".join(CODES)
try:
r = requests.get(api, headers=HEADERS, timeout=5)
txt = r.content.decode("gbk", errors="ignore")
lines = [line for line in txt.splitlines() if line.strip()]
prices = []
n = len(CODES)
for line in lines[:n]:
try:
data = line.split("=", 1)[1].strip().strip('";')
parts = data.split(",")
prev_close = float(parts[2]) if parts[2] else 0.0
price = float(parts[3]) if parts[3] else 0.0
if prev_close > 0:
pct = (price - prev_close) / prev_close * 100
else:
pct = 0.0
sign = "+" if pct >= 0 else ""
prices.append(f"{price:.2f}({sign}{pct:.2f}%)")
except Exception:
prices.append("--(--%)")
while len(prices) < n:
prices.append("--(--%)")
return prices[:n]
except Exception:
return ["--(--%)" for _ in CODES]
class MiniTicker(tk.Tk):
def __init__(self):
super().__init__()
self.configure(bg="#1e1e1e")
self.resizable(False, False)
self.geometry("120x30+100+100")
self.attributes("-topmost", True)
self.overrideredirect(True)
self.interval = tk.IntVar(value=10)
self._job = None
self._font = tkfont.Font(family="Segoe UI", size=10, weight="bold")
self.label = tk.Label(
self, text="--(--%)", fg="#d4d4d4", bg="#1e1e1e",
font=self._font, justify="center",
)
self.label.pack(expand=True, fill="both")
self.label.bind("<Button-1>", self._start_move)
self.label.bind("<B1-Motion>", self._do_move)
self.menu = tk.Menu(self, tearoff=0)
self.menu.add_command(label="刷新一次", command=lambda: self._tick(force_now=True))
self.menu.add_command(label="置顶 开/关", command=self._toggle_topmost)
self.menu.add_separator()
interval_menu = tk.Menu(self.menu, tearoff=0)
for sec in (5, 10, 30):
interval_menu.add_radiobutton(
label=f"{sec} 秒", value=sec, variable=self.interval,
command=lambda s=sec: self._change_interval(s)
)
self.menu.add_cascade(label="刷新间隔", menu=interval_menu)
self.menu.add_separator()
self.menu.add_command(label="退出", command=self.destroy)
self.label.bind("<Button-3>", self._show_menu)
self.after(200, self._tick)
def _start_move(self, e):
self._ox, self._oy = e.x, e.y
def _do_move(self, e):
x = self.winfo_x() + (e.x - self._ox)
y = self.winfo_y() + (e.y - self._oy)
self.geometry(f"+{x}+{y}")
def _show_menu(self, e):
self.menu.tk_popup(e.x_root, e.y_root)
def _toggle_topmost(self):
self.attributes("-topmost", not self.attributes("-topmost"))
def _change_interval(self, sec: int):
if self._job is not None:
try:
self.after_cancel(self._job)
except Exception:
pass
self._job = None
self.interval.set(sec)
self._tick(force_now=True)
def _update_text_and_resize(self, text: str):
self.label.config(text=text)
w_text = self._font.measure(text)
pad_lr = 16
target_w = max(110, w_text + pad_lr)
line_h = self._font.metrics("linespace")
pad_tb = 10
target_h = max(26, line_h + pad_tb)
x, y = self.winfo_x(), self.winfo_y()
self.geometry(f"{target_w}x{target_h}+{x}+{y}")
def _tick(self, force_now=False):
def worker():
prices = fetch_prices_all()
text = " ".join(prices)
self.after(0, lambda: self._update_text_and_resize(text))
if force_now:
worker()
else:
threading.Thread(target=worker, daemon=True).start()
ms = max(1, int(self.interval.get())) * 1000
self._job = self.after(ms, self._tick)
if __name__ == "__main__":
MiniTicker().mainloop()