免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Python+OpenCV+PyAutoGUI实现游戏交易行自动抢单脚本实战

Python+OpenCV+PyAutoGUI实现游戏交易行自动抢单脚本实战 我平时玩三角洲行动最烦的一件事就是蹲交易行。想低价囤点素材要么没事就切过去刷新要么对着价格排序一页一页翻手都点麻了。后来我干脆花了一个周末写了一套交易行自动化脚本挂在后台帮我盯盘、比价、抢单。今天把整套思路和源码级拆解分享出来从技术选型到落地实操再到我踩过的坑一次性讲清楚。这个项目说白了就是用 Python 模拟人工操作自动完成“打开交易行—搜索道具—读取价格—判断是否值得买—点击购买”这一整条链路。它适合三种人一是想低价扫货的玩家二是想研究 Windows 桌面自动化的朋友三是想了解 OpenCV 图像识别和 PyAutoGUI 键鼠模拟怎么配合落地的新手。1. 整体设计思路为什么选图像识别键鼠模拟而不是走内存或封包先回答一个很多人会问的问题都做自动化了为什么不直接读内存、嗅探封包或者直接调游戏接口那样既快又准确还能绕过界面逻辑。我一开始确实犹豫过。读完内存能找到物品价格在内存中的地址截包能拿到服务器下发的价格数据看起来都是“更正规”的路子。但真往下做问题就来了游戏的内存数据有 CRC 校验改动会被立刻检测而纯读取虽然风险小但每次游戏更新都会换偏移量维护成本高到离谱封包被加密后你还要先逆向加密算法这已经超出“交易行脚本”的范畴属于外挂开发的领域了。最核心的一点是这两种方案都已经跨过了脚本的边界在合规性上站不住脚。所以我把方案定为“OpenCV 图像识别 PyAutoGUI 键鼠模拟”。这个组合的本质是把屏幕当作输入源把鼠标键盘当作输出手段模拟一个真人坐在电脑前的所有操作。它不读内存、不碰封包、不注入进程风险等级低很多而且游戏怎么更新都不影响因为识别的是画面不是数据。整个系统的运作链路是这样设计的截屏获取当前屏幕画面OpenCV 在图中定位交易行的搜索框、价格列表、购买按钮等关键区域OCR 或模板匹配把图像中的价格数字、物品名转换成文本Python 判断当前价格是否低于我的心理价位低于就移动鼠标点击购买。这套流程的工程设计重点在于每一步都要稳不能快快就容易出错出错就等于白干。2. 核心工具链与界面识别原理解析2.1 工具选型Python 生态三件套我用的是 Python 3.10配合三个核心库PyAutoGUI 负责鼠标键盘控制OpenCV 负责图像处理PIL 负责快速截屏。辅助用 numpy 做图像数组运算用 pyperclip 处理剪贴板内容。PyAutoGUI 是这套方案里最容易上手也最容易出问题的库。它的鼠标移动、点击、键盘输入全部是模拟系统级事件不需要管理员权限但正因为太底层它对屏幕分辨率极其敏感。我前期测试时在 2560×1440 的屏幕上定位好坐标一换到 1920×1080 就全部偏移后来统一封装了一个坐标换算函数所有点位都按当前分辨率动态计算才算彻底解决。OpenCV 负责的核心任务是模板匹配和边缘识别。模板匹配是这么工作的我预先截取一张“搜索框”的小图作为模板然后在整个屏幕截图中滑动匹配找到相似度最高的位置返回坐标。这个原理跟拼图找人很像模板越小、特征越明显匹配越准、越快。2.2 图像识别模板匹配和 OCR 的取舍交易行界面里有静态的部分也有动态的部分。静态部分比如搜索框、标签页、按钮图标用模板匹配就够了。动态部分比如价格数字、物品名称必须用 OCR。我对比过 Tesseract 和百度 OCR 接口最终选了 Tesseract 5.0 的英文数字模型配合 psml 模式识别价格数字非常稳中文道具名准确率就差一些所以我做了一层映射处理。道具名的识别我建议别直接上通用 OCR。交易行的物品名背景色统一、字体统一完全可以走模板匹配。先按稀有度颜色过滤出候选区域再用每个物品的名称小图去做匹配这样准确率能达到 99% 以上速度也快。我的代码里维护了一个“目标物品图片库”每个物品存一张裁好的名称栏截图脚本上线后先加载进内存匹配时逐个对比。价格数字的识别用 OCR 就够了因为数字只有 10 个字符识别率本身不低。需要注意的是游戏里价格会用逗号分隔比如 1,234,000OCR 容易把逗号识别成小数点或直接漏掉我在后处理里做了强制规则数字之间不允许出现小数点只允许出现逗号逗号后必须跟三位数。2.3 为什么不做深度学习目标检测有人问我现在 YOLO 这么成熟为什么不训练一个模型直接检测界面元素精度更高、泛化更好这个问题的答案很现实投入产出比。做一个 YOLO 模型先要标注几千张交易行界面的截图然后训练、验证、调参一套流程下来至少两周。而界面元素就固定那么几个搜索框、按钮、价格列表模板匹配已经能 99% 搞定。唯一会变的场景是分辨率不同但这个我用动态缩放解决了。杀鸡不用牛刀做自动化脚本的第一原则是用最简单可靠的技术解决 80% 的问题剩下 20% 用规则补齐。3. 实操过程从截屏到点击购买的完整实现3.1 项目目录与基础配置先看我的项目结构trade_bot/ ├── main.py # 主逻辑入口 ├── config.py # 所有配置参数 ├── modules/ │ ├── screen.py # 截屏与图像预处理 │ ├── recognize.py # 模板匹配与OCR │ ├── decision.py # 价格判断逻辑 │ ├── action.py # 键鼠模拟操作 │ └── logger.py # 日志记录 ├── templates/ # 界面元素模板图 ├── items/ # 目标物品名称模板图 └── logs/ # 运行日志config.py 里放的是全局参数最重要的几个是目标物品清单每个物品的心理价位和最大购买数量扫描间隔时间默认 8 秒一轮安全延时范围鼠标移动和点击之间的随机等待区间。# config.py 核心配置 ITEMS { 4级甲修复工具: {max_price: 45000, max_buy: 5}, 5级弹药箱: {max_price: 120000, max_buy: 2}, } SCAN_INTERVAL 8 # 每轮扫描间隔秒 MIN_MOVE_DELAY 0.2 # 鼠标移动后最小等待 MAX_MOVE_DELAY 0.5 # 鼠标移动后最大等待 CONFIDENCE_THRESHOLD 0.85 # 模板匹配置信度阈值3.2 截屏与图像预处理截屏我用的是 PIL 的 ImageGrab它比 PyAutoGUI 自带的截图更快返回的是 PIL Image 对象转成 numpy 数组后直接交给 OpenCV 处理。# modules/screen.py import numpy as np from PIL import ImageGrab import cv2 def grab_screen(regionNone): 截取屏幕指定区域region 为 (x1, y1, x2, y2) img ImageGrab.grab(bboxregion) frame cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) return frame def preprocess(gray_img): 灰度图二值化增强文字区域对比度 _, binary cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) return binary预处理这一步特别关键。游戏界面本身有背景纹理直接拿彩色图去匹配干扰因素太多。我先把图片转灰度再用 Otsu 自适应阈值做二值化把前景文字和背景彻底分开。这样模板匹配的置信度能从 0.6 直接拉升到 0.9 以上。烫知识ImageGrab 在 Windows 上走的是 GDI 接口部分游戏全屏模式会黑屏解决方法是把游戏窗口改成无边框窗口化或者用 DXGI 截图方案。我实测无边框窗口化最省事对性能影响可以忽略。3.3 搜索框定位与物品搜索整体流程的第一步是找到搜索框并输入物品名。我的实现是先点击游戏内置的交易行标签唤起搜索界面然后截一张图用模板匹配定位搜索框的坐标。# main.py 中搜索物品的片段 import pyautogui import cv2 def find_template(screen, template_path, threshold0.85): template cv2.imread(template_path) result cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(result) if max_val threshold: return max_loc return None def search_item(item_name): screen grab_screen() pos find_template(screen, templates/search_box.png) if pos is None: log(搜索框未找到重试) return False x, y pos pyautogui.click(x 20, y 10) pyautogui.hotkey(ctrl, a) pyautogui.write(item_name, interval0.02) pyautogui.press(enter) time.sleep(1) return True这段代码有一个细节click 之后不是马上输入字符而是先 ctrlA 全选再写入新的物品名。这样做是为了防止上一次搜索残留的字符没有被清空。很多新手脚本在这块会翻车清空输入框用 ctrlA 比反复按退格键可靠一百倍速度也更快。write 函数里的 interval0.02 是每个字符间隔 20 毫秒这个参数被很多人忽略但它的作用是真实模拟人的打字速度。如果 interval 设成 0系统会瞬间发送一串字符游戏端的输入框处理逻辑可能来不及响应轻则丢字符重则被当作异常操作。3.4 价格读取与比价决策搜索结果出来后交易行会展示一排物品列表每个条目包含名称、价格、数量等信息。我需要读取第一个可购买物品的价格也就是列表第一行通常是价格最低的排列方式。# modules/recognize.py import pytesseract def read_price_from_region(screen, region): 从指定区域读取价格数字 x1, y1, x2, y2 region roi screen[y1:y2, x1:x2] gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) scaled cv2.resize(gray, None, fx3, fy3, interpolationcv2.INTER_CUBIC) _, binary cv2.threshold(scaled, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) text pytesseract.image_to_string(binary, config--psm 7 -c tessedit_char_whitelist0123456789,) price parse_price_text(text) return price这里我做了两件事来提高 OCR 准确性放大三倍过滤白名单字符。交易行的价格字体本来就是比较小的像素字体直接识别很容易把 8 认成 3把 0 认成 8。放大三倍后笔画特征更明显白名单限制后只会输出数字和逗号把干扰项全部干掉了。parse_price_text 是处理 OCR 原始结果的。OCR 输出的文本经常有换行、空格、识别错误我需要提取第一个有效的数字串import re def parse_price_text(raw_text): 从 OCR 原始文本中提取价格 cleaned re.sub(r[^\d,], , raw_text) parts cleaned.split(,) if len(parts) 2 and len(parts[1]) 3: digits parts[0] parts[1] return int(digits) if cleaned: return int(cleaned) return None拿到价格之后决策逻辑就简单了# modules/decision.py def should_buy(item_name, current_price): if current_price is None: return False config ITEMS.get(item_name) if not config: return False if current_price config[max_price]: return True return False比价的逻辑不是简单的当前价低于心理价就买我建议加一个上下浮动缓冲区。比如心理价是 45000那么 44999 买45001 就不买这个 1 块钱的差距其实毫无意义但脚本会频繁触发。我会做一个小优化当前价 心理价 * 1.02 的时候记录日志但不购买等到下一轮再看。这样能过滤掉大量价格在临界点波动的噪音。3.5 模拟点击购买与异常处理价格确认满足条件后就是定位购买按钮并点击。购买按钮是固定位置的我用绝对坐标偏移来定位因为它是和搜索结果列表联动的一般在价格下方的固定偏移位置。def click_buy(price_pos): x, y price_pos buy_x x 180 buy_y y 60 pyautogui.moveTo(buy_x, buy_y, durationrandom.uniform(0.2, 0.5)) time.sleep(random.uniform(0.1, 0.3)) pyautogui.click() time.sleep(0.8) confirm_screen grab_screen() confirm_pos find_template(confirm_screen, templates/confirm_btn.png) if confirm_pos: pyautogui.click(confirm_pos[0] 30, confirm_pos[1] 15)这里有个非常大的坑交易行购买时弹出的确认框不是每次都一样的。有时候是“确认购买”有时候是“价格已变动是否继续”有时候是排队人数过多后的“重新尝试”。我最初只做了“确认购买”一个模板结果遇到价格变动确认框时脚本直接卡住直到超时。解决方式是做一个状态机每个弹窗模板对应一个点击策略弹窗类型模板特征处理动作确认购买确认按钮为大面积绿色直接点击确认价格变动提示文字“价格已更新”点击取消重新搜索排队限制提示“该物品热度高”等待 3 秒后重试库存不足按钮灰置不可点结束该物品流程状态机的实现思路是每轮操作后等待 0.8 秒截屏依次匹配四个弹窗模板看命中哪个就走哪个分支。都不命中就认为操作成功进入下一轮。4. 防错机制、日志系统与踩坑记录4.1 运行监控与异常自恢复脚本跑长时间之后意外情况一定会出现。最典型的一种是网络延迟导致界面停留在一个中间状态搜索框输入了文字但结果还没加载这时候去读价格区域会读到空白脚本继续往下走就会点错位置。我加入了心跳监控机制。每一轮开始前先检测交易行界面的标志性元素比如顶部“交易行”三个字的位置检测不到就直接判定界面异常执行一次系统重置按下 ESC 键两次等待 2 秒重新打开交易行。这个自恢复逻辑虽然粗暴但实测可以把脚本的无人值守时间从 2 小时延长到 12 小时以上。日志系统也值得说一下。我用的 Python 标准库 logging同时输出到文件和控制台每条日志包含时间戳、模块名、事件类型和关键参数。日志格式长这样[2025-01-12 14:23:31] [action] 搜索物品: 5级弹药箱, 搜索框位置: (840, 412) [2025-01-12 14:23:32] [ocr] 读取到价格: 115000, 原始文本: 115,000 [2025-01-12 14:23:32] [decision] 5级弹药箱 当前价 115000 心理价 120000, 不购买不要小看日志。第一次跑连续报错的时候没有日志你根本不知道脚本在哪一步卡住了。我后来专门在每次 OCR 后记录原始文本就是因为识别出来的数字偶尔会多一位或者少一位看原文才能判断是 OCR 的问题还是显示的问题。4.2 边界情况与异常处理速查表实操中我整理了这些坑按出现频率排序问题现象解决方法OCR 价格多出空格换行数字对不上正则过滤所有非数字逗号字符搜索框点击偏移点击后焦点不在输入框用模板匹配命中区域中心偏右偏移搜索结果为空物品名打错了检查输入法状态改用英文/数字时禁用中文输入鼠标移出游戏窗口点击无效开启 PyAutoGUI failSafe并加屏幕边缘检测游戏窗口被遮挡截图截到别的程序运行期间禁止窗口切换脚本检测前台窗口标题网络延迟波动界面加载慢关键步骤后统一增加 1 秒等待不做硬编码 sleep第二条和第四条我重点补充一下。搜索框模板匹配返回的是图片左上角的坐标但实际可点击区域是搜索框内部偏右的位置。如果直接点击左上角可能点到搜索框边框光标虽然看起来在框里但没有触发输入焦点。我的处理是点击坐标加上一个固定偏移搜索框宽的 20%搜索框高的 50%。PyAutoGUI 有一个 failSafe 功能把鼠标移到屏幕左上角会触发 FailSafeException 强制停止。这个功能我一直开着脚本跑飞的时候可以一把甩鼠标到左上角紧急刹车。但 failSafe 只防失控防不了游戏窗口被遮挡。我加了一个前置检测每次操作前用 win32gui 读取当前前台窗口句柄和游戏窗口句柄不一致就暂停脚本直到用户切回游戏。4.3 为什么延时策略比连点更可靠看过一些脚本作者喜欢把操作间隔压到极限点击之间只隔 0.05 秒觉得越快效率越高。我实测后彻底放弃了这种思路。交易行系统的购买成功判定核心在于服务器是否在截止时间前收到请求而客户端显示的价格只是本地渲染结果。如果我在 0.05 秒内连续点击购买服务器端可能已经把当前价格更新为更高的值但我还在按旧价格提交结果就是频繁触发“价格变动”失败提示白忙一场。我最终把每轮操作总时长控制在 8 到 12 秒之间搜索 1 秒读价 1 秒判断 0.5 秒点击 2 秒确认等待 2 秒额外随机缓冲 1 到 3 秒。这样虽然慢但每一单的成功率高整体收益反而比疯狂连点强得多。而且从风控角度过于规律的高速操作是最容易被识别的随机延时能把操作特征打散。4.4 关于账号收益与风险说点清醒的做交易行自动化最核心的收益来源是价差。游戏里道具价格不是实时动态波动的很多时候一个热门物品在白天和晚上的价格差能到 10% 到 15%如果你常驻扫货能以心理价买到再在高峰期卖出利润空间是存在的。但这完全取决于市场行情和你的选品能力脚本本身只是帮你把“蹲点”这件事自动化了它不创造价值只节省时间。关于风险我必须说明白任何第三方自动化脚本在游戏里都有被检测和封禁的可能性。三角洲行动官方对于脚本的态度是零容忍的虽然图像识别加键鼠模拟属于最外围的方案检测难度高但不代表绝对安全。我个人的使用原则是小金额、低频率、不跨天挂机单次运行不超过 4 小时并且绝不使用脚本进行囤货后高价倒卖这种明显异常的操作。技术归技术边界感还是要有。5. 后续优化方向与个人心得做完这套脚本之后我最大的收获不是“挂在后台蹲到了多少便宜材料”而是对整个 Windows 桌面自动化技术栈有了完整的理解。图像识别、坐标换算、状态机、异常恢复这些技术在任何一个桌面端 RPA 项目里都能复用。后续值得优化的方向有三个。第一个是买入后自动上架卖出形成完整的低买高卖闭环但这一步涉及价格预测和市场分析复杂度和风险都上了一个台阶。第二个是把价格数据落库每天跑完存一份 CSV积累几周后做价格趋势分析找到道具价格的最低点时间段在那个时间段集中运行效率会更高。第三个是用多开方案同时监视多个账号的浏览记录但多开本身就是高风险行为我不建议也不支持。最后分享一个我在反复调参中发现的细节脚本的扫描间隔不该是一个固定值而应该配合物品的成交热度动态调整。热门物品比如弹药箱、高级修复工具价格波动快间隔可以短到 5 秒冷门物品半小时都未必有人上新间隔拉到 30 秒也不影响。你把所有物品统一用 8 秒间隔去扫CPU 占用高不说还容易触发游戏端的异常流量检测。我最终的方案是给每个物品单独配置 scan_weight整体扫描顺序按权重循环热门多扫、冷门少扫。这个思路套用到任何自动化采集项目里都成立。
返回列表