吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 335|回复: 3
上一主题 下一主题
收起左侧

[Python 原创] 基于pywebview实现某tdx的一键选股助手

[复制链接]
跳转到指定楼层
楼主
tedelon 发表于 2026-6-11 09:14 回帖奖励
pywebview 是一个轻量级的 BSD 许可证下的跨平台webview 组件。它允许在自身原生 GUI 窗口中显示 HTML 内容。它让您可以在桌面应用程序中使用WEB技术,同时隐藏 GUI 依赖浏览器的事实。 pywebview 集成了内置 HTTP 服务器、Python 中的 DOM 支持以及窗口管理功能。
[Python] 纯文本查看 复制代码
# -*- coding: utf-8 -*-
"""
通达信一键选股工具 - 极简版
功能:过滤 tdxw.exe 进程,选择主窗口,一键选股,查看日志
"""

import threading
import time
import psutil
from collections import deque
import win32gui
import win32api
import win32con
import win32process
import webview

# ---------- 窗口操作辅助函数 ----------
def get_tdx_windows():
    """获取所有通达信进程窗口"""
    tdx_windows = []
    def enum_callback(hwnd, windows):
        if win32gui.IsWindowVisible(hwnd):
            try:
                _, pid = win32process.GetWindowThreadProcessId(hwnd)
                process = psutil.Process(pid)
                if 'tdxw' in process.name().lower():
                    title = win32gui.GetWindowText(hwnd)
                    if title:
                        windows.append((hwnd, title, win32gui.GetClassName(hwnd)))
            except:
                pass
        return True
    win32gui.EnumWindows(enum_callback, tdx_windows)
    return tdx_windows

def find_window_by_title(title, max_wait=5):
    """等待指定标题窗口出现"""
    start = time.time()
    while time.time() - start < max_wait:
        def enum_callback(hwnd, found):
            if win32gui.IsWindowVisible(hwnd) and title in win32gui.GetWindowText(hwnd):
                found[0] = hwnd
                return False
            return True
        found = [0]
        try:
            win32gui.EnumWindows(enum_callback, found)
            if found[0]:
                return found[0]
        except:
            pass
        time.sleep(0.3)
    return None

def activate_window(hwnd):
    try:
        if win32gui.IsIconic(hwnd):
            win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
        win32gui.SetForegroundWindow(hwnd)
        time.sleep(0.2)
        return True
    except:
        return False

def send_command(hwnd, cmd_id):
    if not hwnd or not win32gui.IsWindow(hwnd):
        raise Exception("窗口无效")
    win32api.PostMessage(hwnd, win32con.WM_COMMAND, cmd_id, 0)

def close_window(hwnd):
    if hwnd and win32gui.IsWindow(hwnd):
        win32api.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
        return True
    return False

# ---------- 后端 API ----------
class AutoSelectAPI:
    def __init__(self):
        self._log_queue = deque()
        self._running = False
        self._lock = threading.Lock()
        self._selected_hwnd = None

    def _add_log(self, msg):
        with self._lock:
            self._log_queue.append(msg)

    def get_new_logs(self):
        with self._lock:
            logs = list(self._log_queue)
            self._log_queue.clear()
        return logs

    def get_windows(self):
        return [{"hwnd": h, "title": t, "class": c, "display": f"{t} (句柄:{h})"} 
                for h, t, c in get_tdx_windows()]

    def select_window(self, hwnd):
        hwnd = int(hwnd)
        if win32gui.IsWindow(hwnd):
            self._selected_hwnd = hwnd
            title = win32gui.GetWindowText(hwnd)
            self._add_log(f"&#9989; 已选择窗口:{title} (句柄 {hwnd})")
            return True
        self._add_log("&#10060; 窗口无效")
        return False

    def run_auto_select(self):
        """一键选股,固定参数"""
        with self._lock:
            if self._running:
                self._add_log("&#9888;&#65039; 已有任务执行中")
                return
            self._running = True

        def task():
            try:
                self._execute()
            except Exception as e:
                self._add_log(f"&#10060; 异常: {e}")
            finally:
                with self._lock:
                    self._running = False
                self._add_log("&#9989; 任务结束")
        threading.Thread(target=task, daemon=True).start()

    def _execute(self):
        if not self._selected_hwnd:
            self._add_log("&#10060; 请先选择窗口")
            return
        if not win32gui.IsWindow(self._selected_hwnd):
            self._add_log("&#10060; 窗口已关闭,请重新选择")
            self._selected_hwnd = None
            return

        title = win32gui.GetWindowText(self._selected_hwnd)
        self._add_log(f"&#128640; 目标: {title}")
        activate_window(self._selected_hwnd)
        time.sleep(0.3)
        self._add_log("&#128225; 发送一键选股命令 (9005)")
        send_command(self._selected_hwnd, 9005)
        self._add_log("&#9203; 等待「自动选股」窗口...")
        target = find_window_by_title("自动选股", max_wait=8)
        if not target:
            self._add_log("&#9888;&#65039; 未找到「自动选股」窗口,请检查通达信版本")
            return
        self._add_log("&#128194; 窗口已打开,停留8秒执行选股")
        for i in range(8, 0, -1):
            self._add_log(f"&#9203; 剩余 {i} 秒")
            time.sleep(1)
        close_window(target)
        self._add_log("&#128274; 窗口已关闭")

# ---------- 极简 HTML 界面 ----------
HTML = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>通达信一键选股</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'Segoe UI', sans-serif;
            background: #1e2a3a;
            padding: 16px;
            color: #eef4ff;
        }
        .container {
            max-width: 550px;
            margin: 0 auto;
            background: #0f172a;
            border-radius: 16px;
            padding: 18px;
            border: 1px solid #334155;
        }
        h1 { font-size: 1.4rem; margin-bottom: 12px; }
        select, button {
            background: #0f1722;
            border: 1px solid #475569;
            border-radius: 8px;
            padding: 8px 12px;
            color: white;
            font-size: 0.9rem;
        }
        select {
            width: 100%;
            margin-bottom: 12px;
        }
        .flex-row {
            display: flex;
            gap: 10px;
            margin-bottom: 16px;
        }
        .flex-row button {
            flex: 1;
            background: #3b82f6;
            cursor: pointer;
        }
        .flex-row button:hover { background: #2563eb; }
        .primary {
            width: 100%;
            background: #e74c3c;
            padding: 10px;
            font-weight: bold;
            font-size: 1rem;
            margin-top: 8px;
        }
        .primary:hover { background: #c0392b; }
        .log-header {
            display: flex;
            justify-content: space-between;
            margin-top: 16px;
            padding: 8px 0;
            cursor: pointer;
            border-top: 1px solid #334155;
            user-select: none;
        }
        .log-header span:first-child { font-weight: bold; }
        .copy-btn {
            background: #334155;
            padding: 2px 10px;
            border-radius: 12px;
            font-size: 0.7rem;
            cursor: pointer;
        }
        .log-content {
            background: #01060e;
            border-radius: 8px;
            font-family: monospace;
            font-size: 11px;
            padding: 8px;
            max-height: 200px;
            overflow-y: auto;
            user-select: text;
        }
        .log-content div {
            border-left: 2px solid #3b82f6;
            padding-left: 6px;
            margin-bottom: 4px;
        }
        .collapsed { display: none; }
        footer {
            font-size: 0.65rem;
            text-align: center;
            margin-top: 16px;
            color: #6c86a3;
        }
    </style>
</head>
<body>
<div class="container">
    <h1>&#128200; 通达信一键选股</h1>
    <select id="winSelect" size="3">
        <option>-- 点击刷新 --</option>
    </select>
    <div class="flex-row">
        <button id="refreshBtn">&#128260; 刷新窗口</button>
        <button id="confirmBtn" style="background:#2563eb;">&#9989; 确认选择</button>
    </div>
    <button id="runBtn" class="primary">&#9654; 一键选股</button>

    <div class="log-header" id="logHeader">
        <span>&#128203; 执行日志</span>
        <div style="display:flex; gap:8px;">
            <span class="copy-btn" id="copyLogBtn">复制日志</span>
            <span id="toggleIcon">▼ 折叠</span>
        </div>
    </div>
    <div id="logBox" class="log-content">
        <div>&#10024; 就绪,请选择通达信主窗口</div>
    </div>
    <footer>自动使用命令9005 · 等待「自动选股」窗口 · 停留8秒</footer>
</div>

<script>
    let pollInterval = null;
    let selectedHwnd = null;
    let logCollapsed = false;

    // 等待 pywebview 就绪的可靠方法
    function whenReady(callback) {
        if (window.pywebview && window.pywebview.api) {
            callback();
        } else {
            const check = setInterval(() => {
                if (window.pywebview && window.pywebview.api) {
                    clearInterval(check);
                    callback();
                }
            }, 50);
        }
    }

    function addLog(msg) {
        const box = document.getElementById('logBox');
        const div = document.createElement('div');
        div.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
        box.appendChild(div);
        div.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }

    function copyLog() {
        const box = document.getElementById('logBox');
        let text = '';
        for (let child of box.children) text += child.textContent + '\\n';
        navigator.clipboard.writeText(text).then(() => addLog('&#128203; 日志已复制')).catch(() => alert('复制失败'));
    }

    async function refresh() {
        if (!window.pywebview?.api) { addLog('&#9203; API 未就绪'); return; }
        const select = document.getElementById('winSelect');
        select.innerHTML = '<option>加载中...</option>';
        try {
            const wins = await pywebview.api.get_windows();
            select.innerHTML = '';
            if (!wins.length) { select.innerHTML = '<option>未找到通达信窗口</option>'; return; }
            for (let w of wins) {
                let opt = document.createElement('option');
                opt.value = w.hwnd;
                opt.text = w.display;
                select.appendChild(opt);
            }
            addLog(`&#128203; 找到 ${wins.length} 个通达信窗口`);
        } catch(e) {
            select.innerHTML = '<option>加载失败</option>';
            addLog(`&#10060; 刷新失败: ${e.message || e}`);
        }
    }

    async function confirm() {
        if (!window.pywebview?.api) { addLog('&#9203; API 未就绪'); return; }
        const select = document.getElementById('winSelect');
        const hwnd = select.value;
        if (!hwnd || hwnd.includes('未找到')) { addLog('&#9888;&#65039; 请先刷新并选择窗口'); return; }
        try {
            const ok = await pywebview.api.select_window(parseInt(hwnd));
            if (ok) { selectedHwnd = hwnd; addLog(`&#9989; 已确认窗口 ${hwnd}`); }
            else addLog('&#10060; 选择失败');
        } catch(e) { addLog(`&#10060; 确认出错: ${e.message}`); }
    }

    async function run() {
        if (!window.pywebview?.api) { addLog('&#9203; API 未就绪'); return; }
        const btn = document.getElementById('runBtn');
        if (btn.disabled) return;
        if (!selectedHwnd) { addLog('&#9888;&#65039; 请先确认窗口'); return; }
        btn.disabled = true;
        btn.textContent = '&#9203; 执行中...';
        addLog('&#128295; 开始一键选股');
        try {
            await pywebview.api.run_auto_select();
        } catch(e) {
            addLog(`&#10060; 执行失败: ${e.message}`);
            btn.disabled = false;
            btn.textContent = '&#9654; 一键选股';
        }
    }

    async function pollLogs() {
        if (!window.pywebview?.api) return;
        try {
            const logs = await pywebview.api.get_new_logs();
            if (logs && logs.length) {
                for (let log of logs) {
                    addLog(log);
                    if (log.includes('任务结束')) {
                        const btn = document.getElementById('runBtn');
                        btn.disabled = false;
                        btn.textContent = '&#9654; 一键选股';
                    }
                }
            }
        } catch(e) { console.error(e); }
    }

    function toggleLog() {
        const box = document.getElementById('logBox');
        const icon = document.getElementById('toggleIcon');
        if (logCollapsed) {
            box.classList.remove('collapsed');
            icon.innerHTML = '▼ 折叠';
        } else {
            box.classList.add('collapsed');
            icon.innerHTML = '&#9654; 展开';
        }
        logCollapsed = !logCollapsed;
    }

    // 初始化
    whenReady(() => {
        addLog('&#10024; API 就绪');
        refresh();
        if (pollInterval) clearInterval(pollInterval);
        pollInterval = setInterval(pollLogs, 600);
    });

    document.getElementById('refreshBtn').onclick = refresh;
    document.getElementById('confirmBtn').onclick = confirm;
    document.getElementById('runBtn').onclick = run;
    document.getElementById('logHeader').onclick = (e) => { if (e.target.id !== 'copyLogBtn') toggleLog(); };
    document.getElementById('copyLogBtn').onclick = (e) => { e.stopPropagation(); copyLog(); };
</script>
</body>
</html>
"""

def main():
    api = AutoSelectAPI()
    webview.create_window(
        title="通达信一键选股",
        html=HTML,
        js_api=api,
        width=500,
        height=460,
        resizable=False
    )
    webview.start(debug=False, http_server=True)

if __name__ == "__main__":
    main()

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
jiyu0418 + 1 + 1 用心讨论,共获提升!
wangxp + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
mattstar 发表于 2026-6-11 13:39
基于pywebview实现某tdx的一键选股助手
3#
YeGlod 发表于 2026-6-11 14:33
4#
ldfansion 发表于 2026-6-11 15:16
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-11 15:30

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表