基于OCR的屏幕价格识别与自动点击脚本实战
做这个项目之前我其实已经被“人工盯价格”这件事折磨很久了。手动刷新页面、反复盯数字、等到符合心理价位再赶紧点购买这一套流程不仅枯燥而且特别容易手慢。后来我想既然价格是屏幕上的文字自动点击又不过是在固定位置模拟鼠标那为什么不用 OCR 把价格读出来再用脚本去点于是就有了这个代号“三角洲”的自动购买工具屏幕截图、OCR 文字识别、价格解析、自动点击四个环节串成一条流水线。项目本身是一个桌面自动化练习但思路完全可以迁移到自动签到、数据录入、UI 自动化测试等场景。如果你正想学 OCR 落地或者需要写一个能“看见文字再操作”的脚本这篇分享应该能帮你省不少弯路。1. 项目整体设计为什么是“OCR 自动点击”而不是别的方案动手之前最难的不是写代码而是想清楚技术路线。市面上做自动点击的思路不少按键精灵能录制鼠标轨迹Windows 自动化可以用 UIA 抓控件甚至某些付费工具自带“找图”“找色”功能。可一旦目标界面里的文字是动态变化的比如实时刷新的价格数字前面这些方案都会乏力。“找固定图片”匹配不了变化的字符“录制固定轨迹”只要界面偏移一次就全废。所以核心思路锁定为不关心按钮长什么样不关心价格出现在第几行只关心“屏幕上某个区域里有没有出现满足条件的价格文本”。这套逻辑拆成四个模块截屏模块定时抓取目标区域的屏幕图像预处理模块裁切、放大、灰度化让 OCR 更容易识别OCR 识别模块把图像中的文字转换成字符串决策与点击模块解析价格对比阈值条件决定是否模拟点击这个方案的优点是抗界面变化能力强价格字号变了、颜色变了、背景图换了只要人眼能看清OCR 大多也能识别缺点是整体链路比单纯模拟点击长每一步都有丢精度的可能。用术语说这是一个“感知-决策-执行”的闭环感知层靠 OCR决策层靠代码逻辑执行层靠自动点击。任何一个环节出错购买动作都可能误触发或者漏触发。1.1 需求拆解价格识别真正要解决的三件事表面上看价格识别就是“把图片里的数字变成字符串”。但实际做下来难点有三个比想象中麻烦得多。第一价格文本的上下文干扰。屏幕上往往不止一个数字有库存量、商品编号、倒计时还可能有一堆“第几件”“打几折”之类的小字。如果不先定位到价格区域直接把整屏扔给 OCR识别结果会乱成一锅粥。所以要先裁切把范围限定在“价格大概率出现”的那块矩形里。第二价格的格式问题。有的界面显示“¥ 1,299.00”有的显示“1299”有的用全角符号还有的带千分位逗号、货币缩写。OCR 识别出来的字符串往往是不干净的比如“¥1,299.00”可能被识别成“¥1,299.00”也可能漏掉点号变成“1299.00”更糟糕的是逗号和句号会被误判。解析时必须写正则、做清洗统一转成浮点数。第三识别置信度问题。任何 OCR 引擎都不可能 100% 准确尤其当背景复杂、字体过小、光线不均的时候。如果直接把置信度低的识别结果拿来做购买判断一旦把“1299”看成“1999”判断逻辑就完全错了。所以判断逻辑里必须加入容错和二次确认机制不能识别一次就点。这三件事决定了整个项目的代码结构不能只写一个 OCR 调用就完事必须把“区域定位”“文本清洗”“置信度校验”都做成独立函数。1.2 技术选型PaddleOCR、Tesseract、iTextSharp 与按键精灵怎么挑我一开始列了四个候选方案PaddleOCR、Tesseract、C# 环境的 iTextSharp、按键精灵本地 OCR。为什么要比较这些因为网上教程太多每个都说自己快实际一跑就翻车。PaddleOCR 是目前中文场景下最省心的开源方案。它对中文、数字、特殊符号的支持都比较好模型体积可选部署也不算复杂CPU 环境也能跑。我最后选它作为主力引擎。Tesseract 是传统老牌 OCR优点是轻量、跨平台但中文识别率确实一般而且对简单数字场景有时反而会因为“太聪明”而认错。iTextSharp 实际上是一个 PDF 操作库OCR 只是它的边缘功能适合从 PDF 里抽取文字不适合实时截屏识别如果你用它来抓屏幕价格等于拿错工具干活。按键精灵的本地 OCR 思路其实和我这个项目类似也是“识别文字再点击”但它的脚本语言偏封闭图像处理和模型升级都受限适合快速做小脚本不适合做复杂逻辑。选型的时候我给自己定了几条标准能离线跑、中文数字识别准、社区资料多、可以自定义模型。按这个标准PaddleOCR 几乎没有对手。如果你预算很敏感也可以先用 Tesseract 做原型但要做好心理准备识别率的调优成本会高出不少。1.3 工作流程总览一个完整闭环如何串起来整个工具的运行逻辑并不复杂我用一个无限循环来描述程序启动后先进入初始化阶段加载 OCR 模型、读取配置文件然后进入主循环第一步截屏第二步预处理第三步 OCR 识别第四步解析价格第五步判断是否满足购买条件满足则执行点击不满足则等待几秒再进入下一轮。用伪代码写就是while True: screenshot capture_screen(region) processed preprocess(screenshot) text ocr_recognize(processed) price parse_price(text) if price is not None and price target_price: click_purchase_button() time.sleep(cooldown) time.sleep(interval)循环本身不复杂真正的工程难点在于每一步的稳定性。截屏可能截到遮挡预处理可能把数字搞糊OCR 可能认错解析可能碰到异常字符点击时可能鼠标被占用。所以代码里每步都要有异常处理把状态日志打出来。调试的时候我最常做的事就是盯着日志看它到底卡在哪一步。2. 核心细节解析与实操要点方案定下来之后就是一步步抠细节。这个项目里没有哪一步是可以“随便写写就行的”尤其 OCR 识别之前的图像处理几乎决定了整个项目的成功率。很多新手一上来就调模型参数其实问题往往出在图像质量上。2.1 OCR 引擎部署PaddleOCR 的安装与环境坑PaddleOCR 的安装门槛不算高但坑确实多。官方推荐用 Python 3.7 到 3.10虚拟环境建议单独建一个不要直接装到系统 Python 里。安装分两步先装 PaddlePaddle 框架再装 PaddleOCR 包。CPU 环境安装比较简单python -m pip install paddlepaddle2.5.2 -i https://mirror.baidu.com/pypi/simple pip install paddleocr2.7.0如果机器有 NVIDIA 显卡想用 GPU 加速可以去 PaddlePaddle 官网按 CUDA 版本选对应的安装命令。不过我这个项目里 OCR 识别区域很小CPU 推理一张截图也就几百毫秒所以 GPU 不是必须的。有个特别常见的坑是“OCR could not create a primitive ...”。这个报错在部分旧版本 PaddleOCR 或者依赖冲突时会出现通常和 GPU 显存不足、Xbyak 库加载失败有关。CPU 环境遇到这个问题多半是模型推理库初始化失败最简单的解决办法是把 paddlepaddle-gpu 卸掉改装纯 CPU 版。如果是显存不足可以调低 batch_size 和 det_limit_side_len或者干脆把识别区域裁得小一点。另一个常见问题是 VS2017 环境下编译报错。PaddlePaddle 官方发布的是预编译包正常情况下不需要编译源码但如果你的 Python 版本太新找不到对应的 wheel就会尝试从源码编译这时候会要求完整的 VC 工具链。建议直接新建 Python 3.9 虚拟环境别和高版本 Python 死磕。2.2 图像预处理放大、灰度、二值化的经验参数OCR 对图像质量极其敏感。同一个价格数字原图直接识别可能报“no text detected”放大两倍再灰度化之后就能轻松识别。我在项目里总结了一套比较稳定的预处理流程按顺序做先把截图裁切到价格区域这个区域在配置里写死坐标。然后对区域做缩放目标是把价格数字的高度放大到 40 像素左右。比如原图价格字高只有 16 像素那就把图像放大 2.5 倍。接着转灰度因为颜色信息对 OCR 没有帮助还会引入干扰。再之后做二值化把背景变白、文字变黑减少阴影和噪点的影响。最后做一次轻微的中值滤波去处椒盐噪点。处理时这套代码可以作为参考import cv2 def preprocess_image(crop_img, scale2.5): img cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) img cv2.resize(img, None, fxscale, fyscale, interpolationcv2.INTER_CUBIC) _, img cv2.threshold(img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) img cv2.medianBlur(img, 3) return img有一点要提醒二值化不是越极端越好。如果背景是浅色、字体是深色用 Otsu 自适应阈值通常没问题如果界面有渐变背景或者半透明遮罩强制二值化会把文字一并涂掉。遇到这种情况可以先不二值化只做灰度和放大效果反而更好。所以预处理千万不能做成“一根筋”最好预留一个参数开关遇到识别率低时切换策略。2.3 价格文本解析对付千分位、逗号和 OCR 错的实战写法OCR 识别出来的文本不可能是干净的“1299”或“199.9”。常态是“¥ 1,299.00”被识别成“¥1,299.00”或者“1,299.00”有时候逗号会变成句号有时候小数点直接消失最头疼的是“0”和“O”、“1”和“l”混淆。所以解析函数必须有很强的清洗能力。我的解析思路分四步先移除货币符号和空格比如“¥”、“$”、“CNY”再用正则把千分位逗号去掉但要注意别把小数点前的千分位和小数点本身搞混接着统一小数点和全角数字把中文全角数字转成半角最后用 float 转换并捕获异常。代码写出来大致是这样import re def parse_price(text): if not text: return None # 去掉货币符号和相关字符 text re.sub(r[¥$€,\s], , text) # 全角数字转半角 text text.replace(, :).replace(, .) text text.replace(, 0).replace(, 1).replace(, 2) text text.replace(, 3).replace(, 4).replace(, 5) text text.replace(, 6).replace(, 7).replace(, 8) text text.replace(, 9) # 提取浮点数 match re.search(r\d\.?\d{0,2}, text) if match: try: return float(match.group()) except ValueError: return None return None这个解析逻辑看着简单但实际调参时我发现一个细节OCR 偶尔会把“1299”识别成“1299.0”或者“1299.OO”用\d\.?\d{0,2}这个正则能匹配“1299”也能匹配“1299.0”但匹配不了“1.299”这种点号加数字的形式。所以如果目标界面的价格喜欢用“1.299”而非“1299”解析函数需要额外处理检测到点号后去掉它再当成整数处理。这就是那种“真正跑起来才发现的坑”文档里永远教不到。2.4 自动点击坐标怎么定位才能不点歪价格识别出来之后接下来就是点击购买按钮。这里最忌讳的是直接把按钮坐标写死因为窗口在屏幕上移动、分辨率变化、DPI 缩放都会导致坐标失效。我把坐标分为两种固定偏移坐标和动态识别坐标。固定偏移坐标适用于按钮位置相对价格区域恒定的情况比如价格在中间、按钮在价格下方 80 像素处。这种方案最简单只要在配置文件里写“按钮相对价格中心的偏移量”就行。动态识别坐标则是用模板匹配或者二次 OCR 找到按钮文字然后点击对应位置这种方法更稳但识别成本更高。在实际项目中我采取了折中方案先定位价格区域中心点然后按配置里的偏移量计算按钮位置。代码用 pyautogui 实现import pyautogui def click_purchase(center_x, center_y, offset_x, offset_y): # 从价格中心偏移到购买按钮 btn_x center_x offset_x btn_y center_y offset_y pyautogui.moveTo(btn_x, btn_y, duration0.2) pyautogui.click()点击前我还加了一道保险在按钮位置附近小范围移动鼠标避免快速连点造成系统识别为异常操作。点击之后立刻进入冷却期防止程序因为界面跳转而重复点击。这里的间隔不能太短我一般设置 3 秒到 5 秒给页面刷新留出时间。3. 实操过程从搭建环境到跑通自动购买很多教程喜欢把环境准备一笔带过但这些往往是新手最容易卡住的地方。我把自己完整跑通的过程记录下来你照着做大概率能一次成功。3.1 环境准备与依赖安装我先创建一个干净的虚拟环境推荐用 Python 3.9因为 PaddlePaddle 对它的支持最稳。命令行执行python -m venv delta_env delta_env\Scripts\activate # Windows 系统 # Linux/Mac 使用: source delta_env/bin/activate激活环境后先升级 pip 再装依赖python -m pip install --upgrade pip pip install paddlepaddle2.5.2 pip install paddleocr2.7.0 pip install opencv-python pillow numpy pyautogui安装过程如果遇到网络慢可以临时把 pip 源切换到国内镜像。装完之后先做一个最简验证确认 PaddleOCR 能正常加载from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) print(OCR engine loaded)如果这一步没报错说明环境基本没问题。如果报“no module named paddle”说明 PaddlePaddle 没装成功得重新检查 Python 版本和 pip 命令。3.2 截图与价格区域定位截屏我用的是 pyautogui 自带接口它能截取整个屏幕也能只截取指定区域。首先要确定价格区域坐标我建议先打开目标界面用截图工具查看价格文字左上角和右下角的像素坐标然后填进配置文件。举个例子如果价格显示在屏幕坐标 (800, 450) 到 (1000, 500) 之间配置文件里就写{ price_region: [800, 450, 200, 50], target_price: 1500.0, purchase_offset: [0, 80], interval_seconds: 2, cooldown_seconds: 5 }这里的price_region四个数字是 x、y、width、height。截屏代码很简单import pyautogui def capture_screen(region): x, y, w, h region img pyautogui.screenshot(region(x, y, w, h)) return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)有一点必须注意如果你的 Windows 开了缩放比如显示缩放 125%pyautogui 截图的坐标和实际逻辑坐标会不一致。最简单的解决办法是让程序运行时把缩放设为 100%或者在代码里乘以缩放系数。3.3 识别、解析与判断购买条件的完整代码我把核心流程封装成一个类方便调试和扩展。下面这段代码是核心逻辑的简化版但可以跑通整个闭环import time import cv2 import numpy as np import pyautogui from paddleocr import PaddleOCR class DeltaAutoBuyer: def __init__(self, config): self.region config[price_region] self.target_price config[target_price] self.offset config[purchase_offset] self.interval config[interval_seconds] self.cooldown config[cooldown_seconds] self.ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) def preprocess(self, img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, None, fx2.5, fy2.5, interpolationcv2.INTER_CUBIC) _, thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) return cv2.medianBlur(thresh, 3) def recognize_price(self, img): processed self.preprocess(img) result self.ocr.ocr(processed, clsTrue) if not result or not result[0]: return None text .join([line[1][0] for line in result[0]]) return self.parse_price(text) def parse_price(self, text): # 前面写的解析函数略 pass def run(self): print(开始监控价格...) while True: try: img self.capture_screen(self.region) price self.recognize_price(img) print(f识别到的价格: {price}) if price is not None and price self.target_price: print(价格满足条件准备点击...) center_x self.region[0] self.region[2] // 2 center_y self.region[1] self.region[3] // 2 btn_x center_x self.offset[0] btn_y center_y self.offset[1] pyautogui.click(btn_x, btn_y) time.sleep(self.cooldown) except Exception as e: print(f运行异常: {e}) time.sleep(self.interval)实际使用中识别和点击之间我还加了一个“事后确认”流程点击后立刻再截一次图重新识别价格如果发现价格已经不是目标区间说明点击成功如果价格没变可能是点偏了需要把鼠标移动到按钮中心重试。这一步对防止误点很有帮助。3.4 运行效果与调试记录我拿一个模拟界面做了多次测试设置目标价格为 1500价格区域固定在屏幕某块区域每次刷新后随机生成 800 到 2000 之间的价格。跑下来整体识别成功率能到 95% 左右大多数失败集中在背景特别复杂、价格数字太小的场景。有一次遇到特别奇葩的情况界面在价格旁边有个旋转的“加载”图标图标扫过的瞬间把价格数字遮住了一半OCR 直接把“1499”识别成了“199”。输出日志里价格跳变很大我当时没注意差点触发一次错误购买。后来我在预处理里加了多帧判断连续识别三次如果价格一致再执行点击或者直接等加载图标消失再截屏。这种“动态场景下的脏数据”问题靠纯图像预处理解决不了必须结合时序逻辑来过滤。调试的时候还有个小技巧程序运行过程中把每次截图、处理后的图片、识别文本都保存到本地做成一个日志目录。出问题的时候回看这些中间产物比盯着一堆打印日志直观太多。我用的是logs/{timestamp}.png的格式方便按时间排序。4. 常见问题与排查技巧实录做这个项目的过程中我踩了不少坑也总结了一堆容易被忽略的细节。放在最后希望能帮你节省大量排查时间。4.1 OCR 识别率低、报 “no text detected” 怎么办这是我在开头提到的最常见的错误。如果 OCR 能成功加载模型但始终识别不出文字或者经常报 no text detected大概率不是模型问题而是图像输入质量太低。我从这三个方向入手排查首先检查截屏区域。截到空白区域、纯色背景或者窗口外区域OCR 当然什么都识别不出来。其次检查预处理参数。如果截图的字很小放大倍数不够中文和数字特征不明显识别率会直线下降。最后检查二值化阈值。Otsu 自适应阈值适合大多数场景但遇到浅色数字、阴影背景时会直接把文字抹掉。我的经验是先把原图保存在本地用 OpenCV 的 imshow 看一下能不能“人眼识别”。如果人眼都看不清OCR 也不可能看清。如果人眼能看清但 OCR 不行那就调整预处理参数尤其是放大倍数和对比度。4.2 点击偏移、误点、点不中的排查思路点击不准是所有自动点击类工具的通病。我遇到过三种情况鼠标点到了按钮旁边、点到了别的元素、完全没反应。排查思路是先确认坐标计算方式再看 DPI 缩放。DPI 缩放是最隐蔽的坑。当系统设置里“更改文本、应用等项目的大小”不是 100% 时pyautogui 的点击坐标和截屏坐标很可能不在同一个坐标系里。解决方案有两种把程序运行时的缩放调成 100%或者使用pyautogui的size()和position()反复校验坐标。另一个高频问题点击之后页面有动画过渡比如渐入渐出、弹窗、滚动按钮位置在动画结束后发生偏移。解决方式是点击前先用短等待等动画结束再点或者把“点击”改成“先移动、再停顿、再按下”的三段式动作。4.3 程序稳定性频繁崩溃、内存占用过高跑一两天不崩是自动化工具体验的基本要求。PaddleOCR 模型加载后占用内存不算小如果每轮循环都重新加载模型内存会爆掉。正确做法是只在程序启动时加载一次模型循环里复用实例。另外每次截图生成的是 PIL 或 numpy 对象用完之后要及时释放引用必要时手动 gc.collect()。还有一个容易忽视的问题循环之间没有抖动随机延迟。连续快速识别、点击不仅容易被系统检测为异常行为还可能导致界面响应不过来。我建议把固定间隔改成随机间隔比如 2 秒上下浮动 30%让运行节奏更像人工操作。4.4 常见问题速查表现象可能原因解决建议OCR 报 “no text detected”截图区域无文字、字太小扩大区域、提高放大倍数、检查截图坐标OCR 报 “could not create a primitive...”模型初始化失败、显存或依赖问题换 CPU 版 PaddlePaddle、调低 batch、重建环境识别价格偶尔跳变背景干扰、文字遮挡多帧确认、等动画消失再截屏点击无反应DPI 缩放、坐标偏移调 100% 缩放打印鼠标坐标校验程序越跑越卡内存泄漏、每轮加载模型模型只初始化一次及时清理不再使用的对象安装 PaddleOCR 卡住网络源太慢使用国内镜像源或离线安装 wheel经常有人问我为什么不用按键精灵直接做。按键精灵确实能快出一版但它的 OCR 能力普遍较弱而且脚本可读性差想加“连续三次识别再点击”这种复杂逻辑时很别扭。Python 方案的好处是拆开每个模块都能单独调试识别不准就换模型点击不准就校准坐标扩展性完全不一样。这也是我宁可多写几百行代码也不用现成录制工具的原因。最后再分享一个我后来才加的细节程序在每次正确购买后会自动把点击前后的截图和识别文本打包存成一个“购买成功案例”每周复盘一次。这些历史数据能帮我发现很多偶发性问题比如哪个时间段背景图会变化、哪个字体在某种背景下识别率特别低。在自动点击这类项目里真正决定工具好坏的不是“能不能跑通”而是“长时间不跑偏、不误判”。希望这套完整的思路能帮你从零搭出自己的价格识别自动点击工具。