用青龙面板监控股票的涨跌,并推送消息到手机

前言

这几天懂王那老头子不老实乱说话,造成全球股市涨跌幅度特别大;MJJ们上班时不能看股票,最多只能看一眼手机消息。今天闲着没事用gpt写了一个小脚本来监控推送涨跌通知,现分享出来。


青龙部署教程

  • 1、进青龙面板-脚本管理里面,新建脚本或者上传脚本。命名:stock_monitor.py,如图示:

  • 2、添加环境变量 STOCK_CODES ,PUSHDEER_PUSHKEY,THRESHOLD_PERCENT和PUSHDEER_PUSHKEY。说明:
    • STOCK_CODES=”sh601006,sz000001,sz300750″ # 监控多个股票用逗号隔开
    • PUSHDEER_PUSHKEY=”your_pushkey” #你的pushdeer秘钥
    • THRESHOLD_PERCENT=”2.0″ # 设置涨跌阈值
  • 添加好后如图:

  • 3、添加定时任务,自定义名称,脚本task stock_monitor.py,定时规则:
    • 25-59/5 9 1-5
    • /5 10-11 * 1-5
    • /5 13-14 * 1-5

添加好后如下图:

  • 最后手动运行一次,推送效果如下:


说明:防止手机信息骚扰。到达设置的涨跌幅时才推送消息,没有时则不发送通知,并且首次推送通知后,再次推送间隔须1小时。运行时间为开市时才运行。


最后附上脚本代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import time
import os
import json
from typing import Dict, Optional
# 环境变量配置
STOCK_CODES = os.environ.get("STOCK_CODES", "sh000422").split(",")     # 股票代码列表(逗号分隔)
PUSHDEER_PUSHKEY = os.environ.get("PUSHDEER_PUSHKEY")                 # PushDeer密钥
THRESHOLD_PERCENT = float(os.environ.get("THRESHOLD_PERCENT", "1.5")) # 涨跌阈值
INTERVAL_SECONDS = int(os.environ.get("INTERVAL_SECONDS", "3600"))    # 推送间隔
# 固定配置
TENCENT_API_URL = "http://qt.gtimg.cn/q={codes}"
TIME_FILE = "stock_push_times.json"  # 改为JSON格式存储时间戳
def validate_config():
    """验证必要配置"""
    if not PUSHDEER_PUSHKEY:
        raise ValueError("PUSHDEER_PUSHKEY 环境变量未设置")
    if not STOCK_CODES:
        raise ValueError("STOCK_CODES 不能为空")
    if THRESHOLD_PERCENT <= 0:
        raise ValueError("THRESHOLD_PERCENT 必须大于0")
def load_push_times() -> Dict[str, float]:
    """加载推送时间记录"""
    try:
        with open(TIME_FILE, 'r') as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {}
def save_push_time(stock_code: str, push_time: float):
    """保存单个股票的推送时间"""
    push_times = load_push_times()
    push_times[stock_code] = push_time
    try:
        with open(TIME_FILE, 'w') as f:
            json.dump(push_times, f)
    except Exception as e:
        print(f"保存时间记录失败: {e}")
def get_stocks_data() -> Dict[str, dict]:
    """批量获取股票数据"""
    headers = {
        "Referer": "http://qt.gtimg.cn/",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    try:
        resp = requests.get(TENCENT_API_URL.format(codes=",".join(STOCK_CODES)), 
                          headers=headers, timeout=15)
        resp.encoding = 'gbk'
        return parse_stock_data(resp.text)
    except Exception as e:
        print(f"获取股票数据失败: {e}")
        return {}
def parse_stock_data(raw_data: str) -> Dict[str, dict]:
    """解析腾讯财经返回数据"""
    stocks = {}
    for line in raw_data.splitlines():
        if not line:
            continue
        try:
            # 示例数据行:v_sh601006="1~大秦铁路~600006~6.74~6.72~6.73...
            code_part, data_part = line.split("=")
            stock_code = code_part.split("_")[1].lower()  # 统一转为小写
            data_str = data_part.strip('";')
            data = data_str.split("~")

            if len(data) < 5:
                continue

            stocks[stock_code] = {
                "name": data[1],
                "price": float(data[3]),
                "prev_close": float(data[4])
            }
        except Exception as e:
            print(f"解析数据异常: {e}")
    return stocks
def calculate_change(current: float, previous: float) -> float:
    """计算涨跌幅百分比"""
    if previous == 0:
        return 0.0
    return round((current - previous) / previous * 100, 2)
def check_cooldown(stock_code: str) -> bool:
    """检查单个股票的冷却时间"""
    push_times = load_push_times()
    last_push = push_times.get(stock_code, 0)
    return (time.time() - last_push) >= INTERVAL_SECONDS
def push_notification(text: str, desp: str) -> bool:
    """通过PushDeer发送通知"""
    push_url = "https://api2.pushdeer.com/message/push"
    params = {
        "pushkey": PUSHDEER_PUSHKEY,
        "text": text,
        "desp": desp
    }
    try:
        resp = requests.get(push_url, params=params, timeout=10)
        return resp.json().get("code") == 0
    except Exception as e:
        print(f"推送失败: {e}")
        return False
def process_stock(stock_code: str, data: dict):
    """处理单个股票的逻辑"""
    current = data["price"]
    prev_close = data["prev_close"]
    stock_name = data["name"]

    change = calculate_change(current, prev_close)
    if abs(change) < THRESHOLD_PERCENT:
        print(f"[{stock_code}] 涨跌幅未达阈值: {change}%")
        return

    if not check_cooldown(stock_code):
        print(f"[{stock_code}] 冷却时间内不推送")
        return

    # 准备推送内容
    direction = "涨" if change > 0 else "跌"
    text = f"{stock_name} 价格波动预警"
    desp = f"""股票代码: {stock_code}
当前价格: {current}元
昨日收盘: {prev_close}元
涨跌幅: {change}% ({direction}幅超过{THRESHOLD_PERCENT}%)"""

    if push_notification(text, desp):
        save_push_time(stock_code, time.time())
        print(f"[{stock_code}] 推送成功")
    else:
        print(f"[{stock_code}] 推送失败")
def main():
    try:
        validate_config()
        stocks_data = get_stocks_data()

        if not stocks_data:
            print("未获取到有效股票数据")
            return

        for code in STOCK_CODES:
            # 统一转为小写匹配
            code_lower = code.strip().lower()
            if stock_data := stocks_data.get(code_lower):
                process_stock(code_lower, stock_data)
            else:
                print(f"[{code}] 未找到相关数据")

    except Exception as e:
        print(f"程序异常: {e}")
if __name__ == "__main__":
    main()