先说结论:这项目是真的能做,而且不用读内存、不用碰游戏文件,纯靠pyautogui这类模拟点击库加上OpenCV的模板匹配,就能在EVE里写出一套还过得去的自动挖矿脚本。星际题材的游戏不少,但EVE这个游戏的挖矿流程有种独特的“生产线感”:选定小行星带、发射采掘器、等货柜满了再跳回空间站卸货,循环往复。而模拟点击技术正好卡在这个生产链条上,把人从重复性劳动里解放出来,还能保证操作节奏的一致性。这篇文章我会把整套思路、关键实现步骤和我实际踩过的坑一次说清楚,适合已经会用Python基础语法、想尝试外部工具控制桌面程序游戏的读者,也包括纯粹对Windows桌面自动化的玩法感兴趣的人。
1. 项目框架与自动化思路解析
1.1 为什么选“模拟点击”而不是其他方案
很多人一听到游戏自动化,第一反应是找读写内存的接口,或者用图像识别加模拟输入之外更“黑科技”的方式。EVE这个游戏的服务器端对位置和动作的判定非常严格,直接去读写本地内存,不仅容易被服务端校验检测,还会破坏游戏本身“人是操作者”的平衡逻辑。模拟点击的原理就不一样了:它本质上只是在操作系统层面生成鼠标事件和键盘事件,看起来和真人手动操作没有区别。简单说,你不是在修改游戏,你是在帮自己按鼠标。
模拟点击的取舍也在于此:技术上轻量,不用买授权、不用过反作弊接口,Python的生态里pyautogui、pydirectinput、keyboard这些库把底层的Windows消息模拟都封装好了,几十行代码就能控制全屏窗口。代价是精度和对环境变化的适应力比读内存要差,画面换一张显卡、窗口移动一个像素,坐标就变了。合理预期应该是:它不是外挂级别的“秒挖”,而是代替你坐在屏幕前稳稳地跑矿的助手。
1.2 EVE挖矿流程自动化需要拆解成几个状态
写自动化脚本最容易犯的错是一上来就写“点击这里点击那里”,结果画面稍微一变化就崩了。正确姿势是先把人工操作抽象成状态机。EVE的挖矿循环,我实际拆解下来大致是这样:
- 状态一:从空间站出发,找一条安全的航线,跳转到指定小行星带。
- 状态二:到达矿带后,用“环境”面板锁定目标小行星,开启采掘器。
- 状态三:等待货柜舱满或者小行星被挖空,期间需要应对可能的锁定丢失。
- 状态四:货柜满了之后,收起采掘器,跳回空间站,停靠并卸货。
- 状态五:刷新货柜舱容量,重新出站,进入下一个循环。
用Python代码表达,就是写一个while True的主循环,配合state变量在五个状态里切换。每个状态的进入条件和退出条件,尽量用画面上的特征去判断:货柜容量能不能从截图里OCR读出来,采掘器是否关闭看图标颜色的变化。这些判断层会和操作层分离,操作层只负责“做动作”,判断层只负责“看结果”。
1.3 这套方案适用的游戏和场景边界
模拟点击自动化不是EVE专属的玩法,只要目标场景满足三个条件就都能套用:第一,操作流程是重复且有规律的主循环;第二,界面元素的位置相对固定,或者可以通过图像识别动态找到;第三,游戏对鼠标键盘的输入响应延迟不高,不需要格斗游戏那种帧级精度。EVE的挖矿、各类MMORPG的采集、网页游戏的挂机玩法,都在这个范围内。
边界也很清楚:如果界面是3D自由视角,视角一变坐标就满天飞,那纯坐标式的模拟点击就会失效,就得动用“特征点+相对位置”的做法。EVE挖矿场景好在它的小行星带视角可以固定,采掘器状态栏始终在屏幕左下角,这就是脚本稳定的根基。这篇文章里你的角色跟着的是这套逻辑,而不是背死坐标。
2. 核心依赖与环境准备:让Python“看见”屏幕
2.1 环境搭建与核心库选择
在动笔写代码前,先把三件事做好:Python版本、屏幕环境、依赖安装。我用的是Python 3.10,理论上3.8+都没什么差别,但要注意Windows上安装OpenCV的坑——直接pip install opencv-python会连带把numpy也装好,别重复安装造成版本冲突。需要准备的核心依赖就四个:
pyautogui:负责鼠标移动、点击、键盘输入、屏幕截图。opencv-python:图像模板匹配,用来从截图中找到目标图标和按钮。numpy:图像数据的基本容器,OpenCV处理上来就是numpy数组。keyboard:监听全局键盘事件,用来做紧急停止热键(比如按F8立刻暂停)。
装完之后建议先用官方文档里的screenshot和moveTo功能做一个回环测试:程序截取当前屏幕,在画面上标记鼠标当前位置,并移动鼠标到某个坐标。这一步能排除很多环境层的错误,比如权限、多显示器坐标偏差、后台运行时不响应等问题。
2.2 屏幕坐标系与窗口锁定方式
模拟点击最核心的一个概念就是坐标系。Windows屏幕左上角是(0, 0),向右是X正方向,向下是Y正方向,单位是像素。pyautogui默认用这套全局坐标。如果你在游戏里开的是窗口化模式,那要把游戏窗口客户区左上角的坐标换算成全局坐标,窗口越大越要注意是否有标题栏偏移。
这里推荐一个稳妥的获取方式:在项目里挂一个game_window_locate()函数,用pyautogui.locateOnScreen('assets/window_title.png', confidence=0.8)去识别窗口标题栏,或者直接用pygetwindow库先拿到窗口对象,再用window.left和window.top算出基坐标。我个人喜欢第二种,因为EVE窗口切换后标题变化不明显,识别文字图标容易失手。
鼠标缩防:Windows显示缩放如果设置成125%或者150%,pyautogui拿到的坐标和实际截图尺寸会出现比例偏差。这个问题藏得很深,很多人脚本跑歪了就是在这吃亏。最简单的规避方式:写脚本阶段把缩放临时调到100%,或者用ctypes.windll.shcore.SetProcessDpiAwareness(1)让进程自己感知真实DPI。这块不放代码后面跑起来会莫名其妙点偏,属于必经的铺垫。
2.3 游戏内显示设置与图像识别素材准备
EVE的字体和UI大小可以自己调。我实测下来,UI缩放比例保持游戏默认是最好的,因为模板匹配依赖稳定的渲染。你手动调过的窗口尺寸、字体缩放都会影响匹配分数,所以脚本上线之后游戏内的窗口尺寸尽量不要再去拖拽。
图像识别的“模板”是一张或多张只包含目标特征的小图。比如“锁定目标”按钮、采掘器开启图标、货柜舱容量条右上角的数字区域。准备模板的方法是:先截一张全屏图,拿画图软件把小区域裁剪出来,保存成PNG放到assets目录。模板尺寸不宜太大,目标越小匹配越精准,但太小又容易在重复纹理区域产生误匹配,一般推荐控制在30×30到120×120像素之间。
注意:EVE的UI自带动态高亮和透明度,直接把“高亮态”和“正常态”都切成模板,写识别逻辑时两个都匹配一遍,哪个命中走哪个。这是避免状态切换期脚本“睁眼瞎”的关键。
3. 挖矿自动化主循环的完整实现
3.1 主框架:状态机与心跳机制
核心代码长这样,这段是整个自动化的“骨架”:
import time import pyautogui import keyboard import cv2 import numpy as np pyautogui.FAILSAFE = True pyautogui.PAUSE = 0.3 STATE_IDLE = 0 STATE_MINING = 1 STATE_FULL = 2 STATE_DOCK = 3 class MiningBot: def __init__(self): self.state = STATE_IDLE self.running = True self.anchor = None # 窗口左上角坐标 def locate(self, template_path, threshold=0.8): """在屏幕上寻找模板,返回中心坐标或None""" screen = pyautogui.screenshot() screen = cv2.cvtColor(np.array(screen), cv2.COLOR_RGB2BGR) 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: h, w = template.shape[:2] cx = max_loc[0] + w // 2 cy = max_loc[1] + h // 2 return cx, cy return None def run(self): while self.running: if keyboard.is_pressed('f8'): self.running = False break self.tick() def tick(self): if self.state == STATE_IDLE: self.do_idle() elif self.state == STATE_MINING: self.do_mining() elif self.state == STATE_FULL: self.do_free() elif self.state == STATE_DOCK: self.do_dock()FAILSAFE = True是pyautogui自带的安全机制,只要鼠标被手动甩到屏幕左上角就会强制抛异常退出。tick()每一轮都会做三件事:检查急停键、刷新状态对应的动作、重置状态机。心跳机制的意义在于每一步都是可中断的,脚本不会卡死在某个循环里,这也方便你自己调试。
3.2 找矿与锁定:图像模板匹配的关键调用
自动挖矿的第一步是到了矿带之后锁定小行星。这一动作的实现依赖“先找小行星图标,再点它”的逻辑。EVE小行星在“环境”列表里会显示为可交互的条目,双击列表就能锁定。
def do_idle(self): # 找到环境列表中第一个小行星 target = self.locate('assets/asteroid_item.png', threshold=0.75) if target is None: print('没有找到小行星,等待刷新') time.sleep(2) return # 双击锁定 pyautogui.moveTo(target[0], target[1], duration=0.2) pyautogui.doubleClick() time.sleep(1) # 点击开启采掘器按钮 miner = self.locate('assets/miner_on_btn.png', threshold=0.8) if miner: pyautogui.click(miner[0], miner[1]) self.state = STATE_MINING print('进入采掘状态')这里有个细节:pyautogui.moveTo(..., duration=0.2)里的duration参数不是花架子,直接瞬移鼠标在游戏里往往会被判定为异常操作,加一个小延时更接近人手轨迹。双击锁定的逻辑如果游戏配置里是单击,也可以在config.py里做成参数,改起来方便。
3.3 货柜检测:用面积和颜色判断是否满载
采掘过程中不能无脑等到天荒地老。EVE货柜舱容量在左上角有一个清晰的数字显示,但OCR识别数字的依赖库太大,实际项目里我用的是“颜色分割+面积统计”的思路:截取容量条区域,如果容量条颜色从铁灰色变成亮黄色,就认为满了,准备返航。
def is_cargo_full(self): screen = pyautogui.screenshot(region=(30, 50, 260, 20)) # 货柜条大致位置 frame = cv2.cvtColor(np.array(screen), cv2.COLOR_RGB2BGR) hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) yellow_mask = cv2.inRange(hsv, (20, 100, 100), (35, 255, 255)) ratio = cv2.countNonZero(yellow_mask) / (frame.shape[0] * frame.shape[1]) return ratio > 0.45为什么用颜色而不是OCR数字?因为容量条的变化是实时的,颜色分割计算成本比OCR低一个数量级,而且对“满没满”这种二分类问题,稳定性足够。真实场景里,如果货柜舱的容量数字在跳动,OCR读出来反而是乱码,颜色区域的比例更可靠。每个角色货柜容量不一样,但容量条填满时的视觉特征是一致的,这一招在换船换角色之后也不用改代码。
3.4 完整主循环:出站、挖矿、回站、再出站
把上面几个功能黏在一起,加上一个简单的状态流转,就形成完整的闭环:
def do_mining(self): # 循环等待,直到货柜满 start = time.time() while time.time() - start < 600: if self.is_cargo_full(): print('货柜已满,准备回站') self.state = STATE_FULL return if not self.is_mining_active(): # 采掘器意外关闭,重新开启 miner = self.locate('assets/miner_off_btn.png') if miner: pyautogui.click(miner[0], miner[1]) time.sleep(3) def do_full(self): # 关闭采掘器,跳回空间站 pyautogui.press('space') # 停止船速 time.sleep(0.5) # 点击总览中的空间站 station = self.locate('assets/station_item.png') if station: pyautogui.click(station[0], station[1]) pyautogui.click(station[0], station[1]) # 右键菜单选择停靠 time.sleep(2) pyautogui.moveTo(station[0], station[1]) pyautogui.rightClick() self.state = STATE_DOCK def do_dock(self): # 等待停靠成功后卸货 unload = self.locate('assets/unload_btn.png', threshold=0.8) if unload: pyautogui.click(unload[0], unload[1]) time.sleep(1) pyautogui.click(unload[0], unload[1]) # 打勾关闭 self.state = STATE_IDLEdo_mining里的is_mining_active()照样是个截图颜色判断,避免采掘器在矿挖空后被系统自动关闭,脚本还傻等。I/O操作之间加sleep不是为了装模作样,是为了给游戏客户端和服务端留出处理延迟的时间,EVE的UI不是本地即时渲染,很多操作有300到800毫秒的反馈延迟。
4. 稳定性强化的隐蔽项:防卡死、重试与日志
4.1 每个“点击”都要有回读验证
模拟点击最容易翻车的场景:脚本以为点了,但游戏没响应。界面上弹出了一个对话框,或者是上一轮的网络延迟异常导致菜单没弹出来。这时候如果你继续走流程,大概率会越走越偏。解决办法是给每个关键步骤加上回读验证——点完“锁定”按钮后,隔一秒再截一次图,检测目标是否已经出现在锁定列表里,不在就重试。
def click_until_visible(self, btn_template, expect_template, retries=5): for i in range(retries): btn = self.locate(btn_template) if btn: pyautogui.click(btn[0], btn[1]) time.sleep(1.2) if self.locate(expect_template): return True return False这种模式看着朴素,但实战价值很高。网页和游戏UI的点击响应不确定性都很强,网络抖动一次是几百毫秒,消息队列一积压就可能两秒没反应。不回读,脚本就成了闭眼开车。
4.2 应急急停与断线重连
EVE服务器的稳定程度不用多说,谁玩谁知道。游戏一断线,脚本可能就一直卡在“跳转中”状态里。所以主循环里需要做两件事:第一,周期性地检查游戏客户端的连接状态图标,EVE断线时左上角会出现“与服务器断开”字样,用模板匹配把这个状态检测出来,然后走重连流程;第二,给脚本加一个“全局急停”热键,我用的是F8。
def emergency_stop(self): keyboard.add_hotkey('f8', self._on_stop) def _on_stop(self): self.running = False pyautogui.keyUp('space') print('紧急停止,脚本已暂停')别小看这个急停。很多模拟点击脚本跑着跑着就飘到奇怪的地方点击,没有急停你只能任务管理器强杀游戏。F8一按让脚本立刻退出,人再接管操作,能救你不少回。调试期间也建议开着这个热键,频繁修改坐标时快速中断比每次等超时舒服得多。
4.3 所有关键操作写日志和时间戳
日志是整个自动化项目里“看不见的刚需”。跑了一个小时之后发现货柜没满就回了站,你要是没日志,根本不知道是第几分钟哪一步出了问题。我的做法是统一用logging模块写文件,同时打印到控制台:
import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[ logging.FileHandler('miner.log', encoding='utf-8'), logging.StreamHandler() ] )每条关键动作都打一条,比如“03:15:22 已锁定小行星”“03:15:26 采掘器已开启”“03:21:47 检测到货柜已满”。出问题时翻日志,能看到完整的决策链路,比对着屏幕复盘高效太多了。这个习惯不只适用于写脚本,干任何自动化项目都好使。
5. 实测调参与常见问题排查
5.1 常见问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 点击永远偏左或偏上 | Windows显示缩放不是100% | 关闭缩放或用DPI感知代码 |
| 模板匹配经常找不到 | 游戏内UI缩放变化或模板太大 | 从高分辨率截图重新裁剪小模板 |
| 挖矿中看画面一切正常但货柜识别为满 | 容量条颜色区域误判 | 调整HSV色相范围或在识别区域加白名单定界 |
| 偶尔双击没有锁定小行星 | 网络延迟导致列表没刷新 | 用click_until_visible增加重试 |
| 脚本运行几分钟后鼠标乱飘 | 被游戏反作弊或系统弹窗打断 | 加FAILSAFE并检查是否有人工输入覆盖 |
| 窗口被最小化后脚本失效 | pyautogui无法操作最小化窗口 | 保持游戏窗口前置,或脚本自行恢复窗口 |
这张表基本覆盖了我实际运行90%以上的问题,前四条最常遇到,后两条属于运行环境层面的偶发,但出现一次就够头疼。
5.2 模板匹配阈值怎么调才合适
阈值threshold是模板匹配最容易让人疑惑的参数。调太低,随便一个相似的图标都会误触发;调太高,真实目标稍微光影变化就找不到。我的建议是:初始设0.8,然后在实际游戏画面里用cv2.matchTemplate根据最高匹配值打印出来,看清楚目标区域真实分数是多少。如果锁定按钮不变的时候稳定在0.85以上,那阈值定0.8就合理,留了余量又不会乱匹配。
另外值得一提:TM_CCOEFF_NORMED是归一化相关系数,它对光照变化相对不敏感,但也容易被大块纯色背景干扰。所以模板里如果目标本身边缘模糊,宁可把背景多裁一点,也不要裁得太紧只留图标核心。模板包含的上下文信息越丰富,定位反而越稳。
5.3 一次完整的实测记录与耗时分析
我自己的测试环境是1920×1080窗口化EVE,角色用的一艘T1挖矿护卫舰,货柜容量大概在1200立方米左右。实测数据如下:
- 出站到小行星带:约25秒
- 识别并锁定目标小行星:3秒
- 采掘器满载:单轮约4分30秒(矿带矿石类型影响明显)
- 回站停靠加卸货:约40秒
- 完整一轮(出站到再出站):5分40秒左右
这个节奏下,脚本连续跑了3小时,自动完成了31轮操作。中间因为一次服务器网络飘红,导致“卸货确认弹窗”没弹出来,日志里能看到连续三次重试后才成功,但没卡死。这个结果已经接近人工操作的九成效率,人工挖矿最大的问题不是点不过来处理不过来,而是注意力会涣散,脚本不会。
5.4 与手动操作相比的真实效率差异
在意效率的老玩家会问:自动化到底比手动快多少?我的实际感受是,在小行星带场景里两者速度差距不大,自动化并不会有额外增益,因为EVE的挖矿节奏本身受货柜容量和采掘时间限制,不是手速限制。它带来的真正价值有两个,第一是持续在线,人可以去吃饭睡觉,脚本依然保持每分钟都在挖;第二是非常稳定的操作节奏,不会因为人走神了几分钟导致采掘器开着人却开去别的星系。
但这个项目长期跑也要评估资源损耗。船体虽然T1便宜,也有被玩家威胁的风险,脚本不会主动避险,所以最好还是选安全星域的低安小行星带挂着,别跑到低安去当活靶子。安全永远是自动化方案里最需要自己负责的部分。
6. 经验沉淀与后续扩展建议
6.1 从挖矿脚本到通用桌面自动化的框架迁移
很多人以为写完这个脚本就只能挖矿,其实把state、locate、click_until_visible这套设计抽离出来,它就是一套轻量的桌面自动化框架。替换一下模板图片,完全可以去处理其他游戏的采集、合成、领取奖励等重复性流程。比如网页游戏里的“一键签到”、模拟经营游戏里的“批量收菜”,都用同一套逻辑。区别只在于资源文件不同,代码结构可以完全复用。
我把这个项目的公共部分单独抽成了botaas模块(一个人的小库),里面封装了截图、找图、点击回读、日志四件套,新项目继承这个基类再写tick()就行。自动化脚本最容易腐化的地方是图片素材路径和窗口信息写死在业务代码里,抽出来之后,换项目只需要改config.yaml。
6.2 进阶方向:视角感知与安全停止策略
如果想让挖矿脚本更聪明一点,可以往两个方向做进阶。第一个是增加视角感知:检测本船周围是否有其他玩家飞船的图标出现在屏幕上,一旦检测到,自动收起采掘器并跳回空间站。这个要写好“其他玩家”和“自己的船”的区分,EVE总览列表可以过滤,但脚本端需要在识别逻辑里加一道白名单。第二个是网络延迟感知:EVE的性能监视器里有ping数值显示,如果你能把那一小片区域的内容截下来,配合OCR或颜色识别判断当前服务器延迟,超过阈值就主动暂停,避免断线时做出无效操作。这两个功能我都没做完,只搭了雏形,留给后面有同样需求的朋友继续折腾。
6.3 安全与合规提醒
最后必须说一句实在话:EVE官方对自动化操作的态度是严格禁止的。你如果在游戏里大规模跑这种脚本,一旦被反作弊系统识别,账号很可能会被处理。这篇文章的目的不是教你去对抗游戏规则,而是通过“模拟点击”这个常见技术,理解桌面自动化的实现方式。如果你想实际使用,强烈建议只在小号、可控范围内做实验,自己承担风险,并且不要在任何公开渠道把它包装成一种“挂机特性”去传播。技术本身是中性的,用在哪里、用多少,决定权一直在你手上。
我在反复调整这套脚本的过程中最大的体会是:模拟点击项目的成败,往往不取决于代码写得多么花哨,而取决于你对目标应用的界面细节观察得多仔细。每次截图、每次调阈值、每次回读验证,其实都是你和一台真实的图形界面在对话。真正理解了这一点,任何桌面自动化项目对你来说都只是换汤不换药的工程问题。最后再分享一个小技巧:跑脚本之前把所有不需要的通知都关掉,包括系统右下角的弹窗,一次微信消息提醒弹出来,就够让整个状态机迷路半天。