Python抢购脚本实战:OCR倒计时识别与GPU加速点击触发

简介:这份资源是面向《三角洲行动》玩家的限时物品抢购自动化学习项目,聚焦“曼德尔砖皮”这一限时上架皮肤的抢购场景,适合具备一定Python基础、希望了解OCR识别与GPU加速图像处理实战的开发者参考。程序以Python 3.9+开发,底层依赖PyTorch与CUDA环境,将图像预处理、模板匹配与倒计时数字识别放在GPU上完成,并配合高精度计时与点击序列控制,构成一套完整的自动化执行流程。资源包共19个文件,以py脚本、xml配置、png截图素材和md说明文档为主,另有zbak备份与iml工程文件,压缩包约83KB,体量轻便,便于快速浏览整体结构。目前已有401人学习。通过阅读源码与说明,读者可以了解OCR倒计时识别、GPU推理队列、多分辨率坐标适配以及配置文件组织等实现思路,适合作为图像识别与自动化控制方向的学习案例。

1. 从一次抢购翻车说起:这套 Python 脚本到底在解决什么

抢购类脚本我拆过不少,但大多数都停留在「定时点击」的层面,真正难的是在正确的时间窗口内完成一次有效的交互。某次帮朋友看一个限量道具的抢购流程,手动操作连续三天全部落空,复盘时发现问题不在手速,而在于倒计时最后 3 秒的界面状态变化根本没被正确识别——人眼看到的「可点击」和程序判断的「可点击」差了将近 400 毫秒。这套基于 Python 的抢购脚本,核心思路就是用 OCR 实时读取屏幕上的倒计时数字,配合 GPU 加速的图像预处理,把「看到数字 → 判断时机 → 触发操作」这条链路压缩到尽可能短。它适合有一定 Python 基础、想理解自动化抢购底层逻辑的开发者,不是那种双击即用的成品工具,需要自己调参数、适配分辨率、处理各种边界情况。

2. OCR 倒计时识别的技术选型与预处理链路

2.1 为什么不用模板匹配而选 OCR

倒计时数字的字体、大小、颜色在不同设备上差异很大,模板匹配需要为每个分辨率单独截取模板,维护成本高。OCR 方案的优势在于泛化能力——只要数字区域定位准确,识别引擎能适应一定程度的字体变化。常见做法是用 PaddleOCR 或 EasyOCR 作为识别后端,前者对中文场景优化更好,后者安装更简单。我一般会先用 PaddleOCR,因为它的检测模型对屏幕截图的文字区域定位更稳,尤其是数字带描边或半透明背景的情况。

选型时要注意:OCR 模型本身有推理耗时,如果每帧都跑完整检测+识别流程,单次耗时可能超过 200ms,这在抢购场景里是不可接受的。所以实际方案是先做区域裁剪,再送识别,把检测范围缩小到倒计时数字所在的固定区域,识别耗时能压到 30ms 以内。

2.2 图像预处理:灰度化、二值化与 ROI 裁剪

预处理的目标是让 OCR 引擎「看得更清楚」,同时减少计算量。典型流程是:截取全屏 → 裁剪 ROI(感兴趣区域)→ 灰度化 → 自适应二值化 → 送 OCR。下面是一段可复现的预处理代码:

import cv2 import numpy as np def preprocess_roi(frame, roi): """ frame: 全屏截图 (BGR) roi: (x, y, w, h) 倒计时数字所在区域 返回: 二值化后的 ROI 图像 """ x, y, w, h = roi # 裁剪 ROI,减少后续计算量 crop = frame[y:y+h, x:x+w] # 转灰度,OCR 对灰度图足够 gray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY) # 自适应二值化,应对不同背景亮度 binary = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, blockSize=11, # 邻域大小,奇数,根据数字笔画粗细调 C=2 # 阈值偏移,值越大越容易把背景判为黑 ) # 轻微膨胀,让数字笔画更连贯,减少 OCR 断字 kernel = np.ones((2, 2), np.uint8) binary = cv2.dilate(binary, kernel, iterations=1) return binary

逻辑说明:roi参数需要根据实际屏幕分辨率手动标定,通常倒计时数字在屏幕中央偏上或偏下位置。blockSize和C是两个关键参数——blockSize太小会导致数字内部出现空洞,太大则会把背景噪声也二值化出来;C越大,二值化后黑色区域越多,适合浅色背景上的深色数字。膨胀操作是为了应对数字笔画断裂,但迭代次数不宜超过 2,否则相邻数字会粘连。

2.3 GPU 加速:把预处理从 CPU 挪到 CUDA

如果只是单次识别,CPU 预处理完全够用。但抢购场景需要高频轮询,比如每 50ms 截一次屏、跑一次识别,CPU 很快就会被占满。这时候可以用 OpenCV 的 CUDA 模块把灰度化、二值化、膨胀这些操作搬到 GPU 上:

import cv2 # 检查 CUDA 是否可用 if cv2.cuda.getCudaEnabledDeviceCount() > 0: # 上传图像到 GPU gpu_frame = cv2.cuda_GpuMat() gpu_frame.upload(crop) # GPU 灰度化 gpu_gray = cv2.cuda.cvtColor(gpu_frame, cv2.COLOR_BGR2GRAY) # GPU 二值化(OpenCV CUDA 版阈值函数) gpu_binary = cv2.cuda.threshold( gpu_gray, 127, 255, cv2.THRESH_BINARY_INV )[1] # 下载回 CPU 送 OCR result = gpu_binary.download() else: # 回退到 CPU 路径 result = preprocess_roi(frame, roi)

参数说明:cv2.cuda.getCudaEnabledDeviceCount()返回可用 GPU 数量,为 0 时说明当前 OpenCV 没编译 CUDA 支持,需要换用预编译的 CUDA 版 OpenCV 或自己编译。GPU 路径的收益在批量处理时更明显——单张 200x100 的 ROI 图,CPU 预处理约 2ms,GPU 约 0.5ms,差距不大;但如果同时处理多个区域或做多帧缓冲,GPU 能把 CPU 占用率从 60% 降到 15% 以下。

注意:OpenCV CUDA 模块对显卡驱动和 CUDA 版本有要求,常见做法是先用nvidia-smi确认驱动版本,再选对应的 OpenCV 预编译包。如果环境搞不定,别硬上 GPU,CPU 路径调好参数一样能跑。

3. 抢购触发逻辑:从识别结果到点击动作的完整链路

3.1 倒计时状态机设计

OCR 识别出来的是一串文本,比如「00:03」「3」「即将开始」,需要把它映射成程序能理解的状态。我一般会设计一个简单的状态机:

识别文本模式状态动作
匹配\d+:\d+或纯数字COUNTING更新剩余时间,继续轮询
包含「开始」「抢购」「立即」READY触发点击
包含「结束」「已抢完」ENDED停止脚本
无法识别UNKNOWN记录日志,继续轮询

状态机的关键是去抖动——OCR 偶尔会把「3」识别成「8」,或者把「00:01」识别成「00:0l」。常见做法是连续 2~3 帧识别结果一致才认为状态有效,避免单帧误判导致提前点击或漏点。

3.2 点击时机的判断:提前量怎么设

这是整个脚本最玄学的地方。理论上应该在倒计时归零的瞬间点击,但实际上从「识别到归零」到「点击事件生效」还有一段延迟,包括 OCR 推理时间、网络请求时间、系统事件队列处理时间。我一般会设一个提前量,比如识别到剩余 0.3 秒时就触发点击,让点击动作和倒计时归零对齐。

提前量需要实测:写一个记录脚本,在倒计时归零时手动点击,记录从「屏幕显示归零」到「点击生效」的时间差,重复 20 次取平均。这个值因设备而异,通常在 100~500ms 之间。设得太早会点空,设得太晚就抢不到。

import time import re class CountdownState: def __init__(self, advance_ms=300): self.advance_ms = advance_ms # 提前量,单位毫秒 self.last_text = "" self.stable_count = 0 def update(self, ocr_text): """输入 OCR 文本,返回是否应该触发点击""" # 去抖动:连续两帧相同才认为有效 if ocr_text == self.last_text: self.stable_count += 1 else: self.stable_count = 0 self.last_text = ocr_text if self.stable_count < 1: return False # 匹配倒计时数字 match = re.search(r'(\d+):(\d+)', ocr_text) if match: minutes = int(match.group(1)) seconds = int(match.group(2)) total_ms = (minutes * 60 + seconds) * 1000 if total_ms <= self.advance_ms: return True # 进入提前量窗口,触发点击 return False

参数说明:advance_ms就是提前量,默认 300ms 是保守值。如果实测发现总是点早,就调小;如果总是点晚,就调大。stable_count控制去抖动强度,设为 1 表示连续两帧一致即可,设为 2 则需要三帧一致,更稳但响应更慢。

3.3 点击动作的实现与防检测

点击可以用pyautogui或pydirectinput,后者对游戏窗口的兼容性更好。但要注意,很多平台会对「非人类点击模式」做检测,比如固定间隔的点击、坐标完全一致的点击。常见做法是加入随机偏移和随机延迟:

import pyautogui import random import time def human_like_click(x, y): """带随机偏移和延迟的点击""" # 坐标随机偏移 ±3 像素 offset_x = random.randint(-3, 3) offset_y = random.randint(-3, 3) # 点击前随机延迟 10~50ms time.sleep(random.uniform(0.01, 0.05)) pyautogui.click(x + offset_x, y + offset_y) # 点击后短暂停顿,模拟人类反应 time.sleep(random.uniform(0.05, 0.15))

逻辑说明:随机偏移是为了避免每次点击坐标完全相同,随机延迟是为了打破固定节奏。这些手段不能保证完全不被检测,但能降低被风控的概率。如果平台有更复杂的检测机制,比如鼠标移动轨迹分析,那就需要更精细的模拟,但那是另一个话题了。

4. 避坑与排查:那些让我熬夜的翻车现场

4.1 OCR 识别率突然暴跌

现象:脚本跑了一周都正常,某天开始倒计时数字频繁识别错误,把「1」认成「7」,「0」认成「O」。

原因:平台更新了 UI,倒计时数字的字体或颜色变了,原来的二值化参数不再适用。或者系统更新了显示缩放比例,ROI 区域偏移了。

解决:先截图保存当前倒计时区域的原始图像,用cv2.imwrite存下来,然后手动跑预处理看二值化效果。如果 ROI 偏移,重新标定坐标;如果字体变化,调整blockSize和C参数,或者换用更强的 OCR 模型。

4.2 GPU 加速反而更慢

现象:开了 CUDA 之后,单次识别耗时从 35ms 涨到了 80ms。

原因:ROI 区域太小(比如只有 100x50),GPU 上传/下载数据的开销超过了计算节省的时间。GPU 加速适合大图或批量处理,小图反而吃亏。

解决:如果 ROI 小于 200x200,直接用 CPU 路径。或者把多帧图像攒成一批再送 GPU,但抢购场景对延迟敏感,批量处理不现实。我的经验是:ROI 宽度小于 300px 就别折腾 GPU 了。

4.3 点击触发但游戏没反应

现象:日志显示已经触发点击,但游戏内没有任何反应。

原因:游戏窗口没有获得焦点,或者点击坐标被其他窗口遮挡。也可能是游戏用了 DirectInput,pyautogui的点击事件不被识别。

解决:点击前先激活游戏窗口,可以用pygetwindow找到窗口并activate()。如果还是不行,换pydirectinput,它直接调用 Windows API 发送输入事件,对 DirectInput 游戏兼容性更好。

4.4 脚本跑一段时间后卡死

现象:脚本运行 10~20 分钟后无响应,CPU 占用率飙升。

原因:截图函数没有释放资源,或者 OCR 模型在循环中反复加载导致内存泄漏。也可能是pyautogui的截图在部分系统上会累积 GDI 对象。

解决:用mss替代pyautogui做截图,mss基于更底层的 API,资源释放更干净。OCR 模型在初始化时加载一次,循环中复用。另外加一个定时重启机制,每 30 分钟重启一次脚本进程,简单粗暴但有效。

4.5 提前量设了但总是抢不到

现象:提前量从 100ms 调到 500ms 都试过了,还是抢不到。

原因:可能不是提前量的问题,而是网络延迟或服务器端处理时间。抢购请求从客户端发出到服务器确认,中间有网络往返时间,这个时间可能远大于本地点击延迟。

解决:先测网络延迟,用ping或tcping看目标服务器的响应时间。如果网络延迟超过 100ms,本地再怎么优化提前量也没用。这种情况要么换网络环境,要么接受抢不到的现实——脚本只能优化本地链路,管不了服务器端。

5. 进阶技巧:用多帧差分做倒计时归零的精确判定

OCR 识别倒计时有个天然缺陷:它只能识别到「秒」级别,比如「00:01」到「00:00」之间的切换,OCR 最快也要 30ms 才能反应过来。如果想更精确地判断归零时刻,可以用多帧差分辅助。

思路是:在倒计时最后几秒,连续截取 ROI 区域,计算相邻帧的像素差异。当数字从「1」变成「0」时,ROI 区域会发生明显变化,差分值会出现一个峰值。把这个峰值时刻和 OCR 识别结果结合,能把归零判定精度提高到 10ms 以内。

import cv2 import numpy as np class FrameDiffDetector: def __init__(self, threshold=5000): self.prev_frame = None self.threshold = threshold # 差分阈值,根据实际画面调 def detect_change(self, current_roi): """返回当前帧与上一帧的差异是否超过阈值""" gray = cv2.cvtColor(current_roi, cv2.COLOR_BGR2GRAY) if self.prev_frame is None: self.prev_frame = gray return False # 计算绝对差分 diff = cv2.absdiff(self.prev_frame, gray) # 统计差异像素数量 change_count = np.count_nonzero(diff > 30) self.prev_frame = gray return change_count > self.threshold

参数说明:threshold需要根据 ROI 大小和数字变化幅度调整。ROI 越大,阈值越高。常见做法是先跑一遍记录change_count的分布,取倒计时变化时的最小值作为阈值。diff > 30里的 30 是像素差异阈值,用来过滤压缩噪声和轻微抖动。

实际使用时,OCR 和差分检测并行跑:OCR 负责识别当前倒计时数值,差分检测负责捕捉数值变化的瞬间。当 OCR 识别到「00:01」且差分检测到变化时,说明刚刚从「00:02」跳到「00:01」,再等一个差分变化就是归零。这样比单纯等 OCR 识别到「00:00」要快得多。

还有一个细节:差分检测对画面抖动很敏感,如果游戏有动态背景或特效,ROI 区域会一直有变化。这时候需要先做背景抑制,比如只保留数字笔画区域,或者用帧间平均来平滑噪声。我一般会先用固定 ROI 跑 100 帧,统计change_count的均值和方差,把阈值设为均值加 3 倍标准差,这样能过滤掉大部分背景噪声。

从那以后我每次调抢购脚本,都会先把 OCR 和差分两条链路分开测,确认各自延迟后再合并。别指望一套参数通吃所有场景,分辨率、字体、背景、网络环境,任何一个变了都得重新标定。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

macOS 27 自带的 AI 模型,能直接使用吗?

macOS 27 自带的 AI 模型,能直接使用吗?

我最近买了一个Macmini M6芯片的&#xff0c;买来就提示升级Macos 27系统&#xff0c;我想弄清一个很实际的问题&#xff1a;Mac mini 已经运行 macOS 27&#xff0c;能不能直接调用系统里内置的 AI 模型&#xff0c;为自己开发工作和AI工具提供能力&#xff1f; 可以&#xff…

2026/10/20 11:30:28 阅读更多 →
Ubuntu22.04下源码编译安装USD-Brep

Ubuntu22.04下源码编译安装USD-Brep

Ubuntu22.04下源码编译安装USD-Brep 环境&#xff1a; Ubuntu22.04 仓库&#xff1a;https://github.com/NVIDIA-Omniverse/usd-brep 网络环境&#xff1a;需要简介&#xff1a;2026年10月08日&#xff0c;英伟达开源的一个几何内核&#xff1b;这是继OCC之后&#xff0c;第二款…

2026/10/20 11:29:27 阅读更多 →
GDB调试三绝技:变量监控、动态修改与条件断点

GDB调试三绝技:变量监控、动态修改与条件断点

目录 前言 一、git中的常见命令&#xff1a; git常见命令 gitignore介绍 二、gdb预备知识&#xff1a;编译与模式 2.1 gdb常见命令 2.2 断点之用途 2.3 程序的运行与暂停 2.4 堆栈跟踪 三、gdb中的三种调试技巧 3.1 变量监测&#xff08;watch&#xff09; 3.2 set…

2026/10/20 11:29:27 阅读更多 →

最新新闻

Markdown+YAML批量生成PPT:ppt-master自动化流水线实践

Markdown+YAML批量生成PPT:ppt-master自动化流水线实践

如果你也被周报、月报、季度复盘这些演示文稿折磨过&#xff0c;那你大概能理解我为什么写了 ppt-master 这个工具。它做的事情听起来很简单&#xff1a;给一份 Markdown 写的大纲和一份 YAML 配置&#xff0c;就能批量生成结构完整、自带图表和表格的 .pptx 文件。没有花哨的界…

2026/10/20 14:42:38 阅读更多 →
SQLite 核心原理与实战:事务、锁、索引及性能调优

SQLite 核心原理与实战:事务、锁、索引及性能调优

sqlite3 这门数据库技术&#xff0c;一直处于一个很有意思的位置&#xff1a;有人把它当玩具&#xff0c;有人把它当生产环境里的秘密武器&#xff0c;而更多人是把它当成一个“会出现在面试题里的名词”&#xff0c;背完就忘。我这些年和各类数据库打交道下来&#xff0c;最直…

2026/10/20 14:42:38 阅读更多 →
从代码到实物:用生成艺术与激光切割打造实体作品

从代码到实物:用生成艺术与激光切割打造实体作品

artcraft 是我给一套个人创作流程起的名字&#xff0c;也是这两年反复打磨的一个核心项目&#xff1a;用代码生成艺术图案&#xff0c;再通过激光切割、CNC 雕刻这些数字制造手段&#xff0c;把图案变成木片、亚克力甚至金属的实体作品。很多朋友第一次听到这个组合&#xff0c…

2026/10/20 14:42:38 阅读更多 →
PPT制作系统化实战:从内容梳理到演示呈现的完整链路

PPT制作系统化实战:从内容梳理到演示呈现的完整链路

做PPT这件事&#xff0c;说难不难&#xff0c;说简单也不简单。我在不同团队里见过太多人&#xff0c;花了一整天调字号、对齐图标&#xff0c;最后讲的时候还是被一句“这页到底想说什么”问住。问题从来不在软件操作&#xff0c;而在没人把“做一份能用的PPT”拆成一套可复用…

2026/10/20 14:42:38 阅读更多 →
AI4S实战派:从科学问题到可复现结果的全链路避坑指南

AI4S实战派:从科学问题到可复现结果的全链路避坑指南

AI4S是这两年被谈到最多的方向之一&#xff0c;但在我身边&#xff0c;真正动手做的人反而越来越少愿意公开聊细节。原因很简单&#xff1a;不好做&#xff0c;而且踩坑的过程很难写成一页漂亮的汇报。新闻里天天有模型刷新指标&#xff0c;教程里处处是“一行代码搞定”&#…

2026/10/20 14:42:38 阅读更多 →
PostgreSQL AI 生态实战:向量检索、混合检索与数据管道构建指南

PostgreSQL AI 生态实战:向量检索、混合检索与数据管道构建指南

1. 先回答一个老问题&#xff1a;AI 时代还需要关系型数据库吗这两年只要一聊到 AI 应用落地&#xff0c;我听到最多的一个论调就是“传统数据库要被向量数据库取代了”。之前做技术选型评审的时候&#xff0c;好几个组都在争论要不要把核心业务数据迁到一个专门的向量库里。我…

2026/10/20 14:41:29 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →