
1. 写在前面这个项目到底在做什么先说结论这是一个用 Python Selenium 写出来的恶搞自动化项目目标是让一个虚构的“电子神庙”网站实现全自动售卖赎罪券。别被“邪教”两个字吓到这纯粹是我在周末黑客马拉松上脑洞大开的结果——把一个假装严肃的封建迷信场景用现代自动化测试工具给玩坏了。Selenium 是干嘛的一句话就能说清它是一个浏览器自动化测试框架可以像真人一样打开网页、点击按钮、填写表单、滑动验证码、下载文件。平时大家用它来跑回归测试、爬数据、抢票、做 RPA 机器人而这次我用它来“度化”一批虚拟信徒。项目整体效果是这样脚本打开“电子神庙”官网按顺序完成祈福签到、选择香火套餐、填写罪孽清单、支付赎罪券沙盒环境、自动下载电子赎罪券 PDF。整套流程全自动不需要人碰浏览器跑一圈大概 40 秒。适合所有刚刚接触 Selenium、想搞明白它到底能完成哪些真实操作的人也适合那些手里有一堆重复网页操作想自动化但不知道从哪下手的人——只不过我的场景更离谱一点。为什么选这个选题因为我发现网上教程翻来覆去都是“模拟登录”“爬取新闻标题”太无聊了。把一个荒谬的业务场景和正经的自动化技术绑在一起学起来反而记得牢。你把赎罪券换成优惠券、把功德箱换成购物车、把法事预约换成挂号系统这套代码的骨架直接就能迁移到正经项目里。所以这篇不只是在讲一个玩笑而是借玩笑把 Selenium 页面自动化的核心套路全部拆开。2. 技术选型为什么用 Selenium 而不是其他框架2.1 Selenium 的核心定位与适用场景要做浏览器自动化绕不开三个梯队请求级工具Requests/httpx、浏览器自动化工具Selenium/Playwright/Puppeteer、以及偏采集框架Scrapy 这类。这个项目第一步我就把 Requests 排除了原因是“电子神庙”的前端页面大量使用了 JavaScript 动态渲染——祈福签文、香火功德值、罪孽清单下拉框全是 AJAX 请求后渲染出来的。用 Requests 拿到的 HTML 只是空壳子里面的数据得自己找接口模拟费时费力。Selenium 则是直接驱动真实的 Chrome 浏览器页面怎么渲染我就怎么操作所见即所得几乎不用关心接口是否存在。在 Selenium 和 Playwright 之间我也犹豫过。Playwright 确实是新贵自动等待、多浏览器支持、录制脚本都做得更好。但考虑到 Selenium 在社区里的普及率以及以后你要和别人协作、查资料、抄代码Selenium 的存量教程依然是最大的。加上这次项目的交互逻辑本身不算复杂Selenium 4 的新特性——比如相对定位器、改进的等待机制——已经完全扛得住。最终我用的是 Selenium 4.20 Python 3.11 ChromeDriver这套组合稳定网上出错案例也少。Selenium 的原理不复杂WebDriver 作为中间桥梁把 Python 代码转换成浏览器能听懂的命令通过 ChromeDriver 与浏览器内核通信。你可以把 WebDriver 想象成一个遥控器Selenium 代码是遥控器上的按键ChromeDriver 是遥控器里的红外发射器浏览器就是那台听话的电视。2.2 环境搭建的完整过程说句实话Selenium 的安装本身不难难的是让环境和浏览器版本匹配。我用的是 Windows 11 Chrome 124所以踩的坑基本都是围绕版本匹配的。下面是完整的环境准备清单。# 1. 创建虚拟环境我一直坚持用 venv就是为了避免全局环境被各种依赖污染 python -m venv selenium_temple_env # 2. 激活虚拟环境 # Windows: selenium_temple_env\Scripts\activate # macOS / Linux: source selenium_temple_env/bin/activate # 3. 安装 Selenium pip install selenium # 4. 确认安装的版本 pip show selenium然后是 ChromeDriver 的获取。你可能会说“Selenium 4.6 以上版本不是自带 Selenium Manager 自动管理驱动吗”确实Selenium Manager 会根据你本地的 Chrome 版本自动下载匹配的 ChromeDriver省心很多。但我在公司内网环境下经常遇到网络限制所以手动指定驱动的习惯一直保留着。# 查看本机 Chrome 版本访问 chrome://settings/help 即可看到 # 然后去对应路径下载匹配的 ChromeDriver # Windows 下我把 chromedriver.exe 放到项目根目录干净利落这里有一个至关重要的检查点Chrome 大版本号必须和 ChromeDriver 大版本号完全一致比如 Chrome 124 对应 ChromeDriver 124.x。版本不一致的时候报错信息一般长这样SessionNotCreatedException: This version of ChromeDriver only supports Chrome version 114——看到这条就直接去换驱动的版本别在代码层面浪费时间了。2.3 关键依赖与工具链的搭配除了 Selenium 本身我在这项目里还用到了几个小而美的库它们各自解决一个特定问题webdriver-manager自动下载并缓存匹配的 ChromeDriver解决了手动维护驱动的麻烦。在需要频繁换浏览器的场景下特别有用。Pillow在做滑块验证码识别时用来处理截图、计算缺口坐标。图片验证这块本质上就是图像处理Selenium 本身管不到。pytest用于写自动化用例的断言和结果校验。这个项目不是测试项目但我会用 pytest 的组织方式来管理整套自动化脚本因为它的断言体系非常直观。loggingPython 标准库配合自定义日志记录器把自动化过程每一步的关键操作记录下来方便回看和排查。依赖文件 requirements.txt 长这样selenium4.20.0 webdriver-manager4.0.1 Pillow10.3.0 pytest8.2.0安装完毕之后我会先跑一个最小化测试确认 Selenium 确实能把浏览器拉起来。这个测试如果挂了后面所有自动化都无从谈起。from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--start-maximized) driver webdriver.Chrome(optionsoptions) driver.get(https://www.example.com) print(driver.title) driver.quit()跑通这一小段说明 WebDriver 和浏览器之间的通道已经打通可以开始编写真正的业务流程了。3. 电子神庙业务逻辑与页面结构拆解3.1 业务流程的抽象建模不管多正经或不正经的业务要做自动化第一步永远是把人工操作的流程拆成计算机能执行的步骤清单。我把“在线购买赎罪券”这个流程拆成了六个顺序步骤打开电子神庙首页等待页面渲染完成。点击“开始祈福”按钮进入祈福流程。填写基本信息姓名、出生日期、罪孽类型下拉选择。选择香火套餐单次忏悔、月度会员、年度至尊套餐。点击“购买赎罪券”进入沙盒支付页面完成付款。下载电子赎罪券 PDF保存到本地目录。拆完这一步整个项目的骨架就清楚了。后面的代码全是围绕这六步来写的。在实际编码前我强烈建议先打开浏览器的“开发者工具”逐个查看每个要操作元素的 HTML 结构、id、class、name 属性。这一步虽然是手工活但非常值得做——很多初学者跳过了页面分析到了写定位表达式时两眼一抹黑效率极低。3.2 定位策略选择从 ID 到 XPath 的优先级排序Selenium 定位元素的方式有七八种ID、Name、Class Name、Tag Name、Link Text、Partial Link Text、CSS Selector、XPath。但项目里真正高频使用的只有前三种加最后两种。我对定位策略的优先级排序如下ID 定位最高优先级因为 ID 在整个页面里是唯一的。用driver.find_element(By.ID, sinner_name)这种写法代码可读性最好。Name 定位表单类元素里很常见也挺靠谱但偶尔会遇到同名元素这时候就要小心。CSS Selector非常灵活性能好支持组合选择。比如#pay-form .confirm-btn这样的写法可以精准命中嵌套元素。XPath能力最强的兜底方案支持文本匹配、层级关系、逻辑运算。但性能最差且写复杂了很难维护。我一般只在 CSS 定位不到元素时才用 XPath。拿“年度至尊赎罪券”按钮举例它的 HTML 是这样的button classplan-card plan-premium>driver.find_element(By.XPATH, //button[contains(class, plan-card) and contains(., 年度至尊)])这种写法可以应对文本部分匹配的场景但还是那句话能不用 XPath 就不用用结构化的属性定位最稳。3.3 页面加载与元素渲染的等待机制“电子神庙”官网虽然是虚构的但它的响应速度我故意设计得很真实——祈福签文接口延迟 1~3 秒支付回调偶尔会卡顿。如果脚本不处理等待一上来就去点按钮十次有八次会抛NoSuchElementException。Selenium 的等待分为隐式等待和显式等待两种。简单做个对比等待类型作用范围推荐程度典型用法隐式等待全局生效每个 find 操作都会先轮询等待一般driver.implicitly_wait(10)显式等待只对指定条件生效强烈推荐WebDriverWait(driver, 10).until(EC.element_to_be_clickable(...))强制等待无条件等待固定时间不推荐time.sleep(3)我项目中大量使用显式等待因为业务逻辑的每一步都有明确的“预期状态”。比如进入支付页面后必须等“确认支付”按钮变得可点击才动手。代码长这样from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待元素出现且可点击超时时间为 15 秒 confirm_btn WebDriverWait(driver, 15).until( EC.element_to_be_clickable((By.ID, confirm_pay)) ) confirm_btn.click()使用显式等待的逻辑是你不知道页面在什么时刻渲染完毕但你知道你要操作的元素的“就绪条件”。这个条件成立之前脚本会每隔 500 毫秒自动检查一次直到条件满足或超时。比起无脑time.sleep(10)显式等待既精准又高效不浪费一秒多余时间。3.4 反自动化检测的应对思路电子神庙网站虽然是虚构的但我有意在页面里增加了一层简单的反自动化检测——前端代码会检测navigator.webdriver属性如果被标记为自动化模式会弹出“凡人勿扰”的提示框阻拦后续操作。这个设计其实就是模拟了现实中很多网站的反爬策略。处理方式很简单通过 ChromeOptions 在启动浏览器时关闭自动化特征标志。from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False)这三个参数分别做了三件事禁用掉浏览器内核里的自动化控制标志位、排除 ChromeDriver 自动注入的提示条、禁止加载自动化扩展。设置完这些navigator.webdriver就会变成undefined前端检测就失效了。这个技巧在实操中非常容易踩坑因为哪怕漏掉其中一个参数浏览器依然会被标记。我测试过--disable-blink-featuresAutomationControlled是核心后面两个参数是保险但三个写齐了才能达到最干净的隐藏效果。现实项目里你面对的可能是更复杂的滑块验证、指纹检测、行为轨迹分析但你要记住一个原则Selenium 并不是为反反爬设计的专业工具它做的是“尽量模拟正常人”一旦遇到真正强对抗的验证码体系更好的选择是切换 Playwright 或者考虑付费验证码识别服务。别在 Selenium 一棵树上吊死。4. 核心代码实现一步一步搭出全套流程4.1 初始化浏览器与全局配置整个自动化项目的入口是先构造一个全局可用的 WebDriver 实例。这个实例的生命周期贯穿整个脚本运行过程。我写了一个temple_bot.py把初始化逻辑封装成一个类方便复用和扩展。import logging from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(temple_bot.log, encodingutf-8), logging.StreamHandler() ] ) class TempleBot: def __init__(self, headlessTrue): self.logger logging.getLogger(__name__) options Options() if headless: options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) else: options.add_argument(--start-maximized) # 反自动化特征隐藏 options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) # 禁止图片加载提升性能赎罪券页面除外后面单独处理 prefs {profile.managed_default_content_settings.images: 2} options.add_experimental_option(prefs, prefs) self.driver webdriver.Chrome( serviceService(ChromeDriverManager().install()), optionsoptions ) self.driver.implicitly_wait(5) self.logger.info(浏览器初始化完成)这段代码里有两个细节值得展开headlessTrue表示无头模式也就是浏览器在后台运行不弹出窗口。这在服务器上跑定时任务时是标配但调试阶段我一定会用headlessFalse亲眼看到浏览器做了什么操作方便发现问题。另一个是禁用图片加载的偏好设置这个对性能提升非常明显——一个页面几十张图片不加载能省下好几秒但是如果你要下载的赎罪券里有图片内容这个设置就要在下载前改回来。4.2 第一步到第三步祈福、填单与套餐选择进入网站之后首先要处理的是“开始祈福”按钮。这里我用了一个自定义的点击辅助函数把所有点击操作统一封装方便统一增加日志记录和等待逻辑。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By def safe_click(driver, by, value, timeout10): element WebDriverWait(driver, timeout).until( EC.element_to_be_clickable((by, value)) ) element.click() logging.info(f点击成功: {value}) safe_click(self.driver, By.ID, start_blessing)然后填写基本信息。实名忏悔需要一个姓名、出生日期和罪孽类型。这里我用 Select 类来处理下拉框选中的场景。Selenium 封装了原生 HTMLselect元素的操作不需要手动点击展开选项再选择直接根据可见文本、索引或 value 属性选择即可。from selenium.webdriver.support.ui import Select # 填写姓名 name_input self.driver.find_element(By.ID, sinner_name) name_input.clear() name_input.send_keys(扫地僧) # 填写出生日期 birth_input self.driver.find_element(By.ID, sinner_birth) birth_input.send_keys(19900315) # 选择罪孽类型 sinner_type_select Select(self.driver.find_element(By.ID, sin_type)) sinner_type_select.select_by_visible_text(贪吃) logging.info(罪孽类型已选择: 贪吃)这里有个细节send_keys在往输入框里填内容之前先调用clear()方法是非常有必要的。因为很多网页的输入框有默认值或者上一次运行时残留了缓存内容不清理直接输入就变成字符串拼接了。虽然在这个虚构的项目里不存在这个问题但养成这个习惯能帮你少踩很多坑。套餐选择是整个流程中核心的一步。页面里三个套餐卡片对应着三个按钮但这里隐藏了一个业务规则年度至尊套餐需要二次确认弹窗。这一步就考验自动化脚本对弹窗处理的熟练程度了。Selenium 里处理这个场景优先使用switch_to.alertAPI但实际项目中弹窗很少是原生 alert大部分是自定义的 HTML 弹窗这时候还需要用显式等待找到弹窗里的确认按钮再点击。这里我故意实现了一个自定义弹窗就是为了演示这个常见的自动化难点。# 点击年度至尊套餐 safe_click(self.driver, By.ID, plan_premium) # 等待自定义二次确认弹窗出现 modal WebDriverWait(self.driver, 10).until( EC.visibility_of_element_located((By.ID, confirm_modal)) ) logging.info(二次确认弹窗已出现) # 点击弹窗里的确认按钮 confirm_in_modal modal.find_element(By.ID, modal_confirm) confirm_in_modal.click() logging.info(已完成年度至尊套餐选择)自定义弹窗的处理方式和原生 alert 完全不同这是很多 Selenium 初学者踩坑的重灾区。原生 alert 用driver.switch_to.alert.accept()一行搞定但 HTML 弹窗还得先正常定位弹窗里的按钮。所以看到“弹窗”第一步永远是确认它是哪一种再决定处理方式。4.3 支付流程与滑块验证码的自动化处理购买赎罪券的最后一步是支付。虽然这是个沙盒环境但为了模拟真实场景我在支付页面加了一道滑块验证。滑块验证在现实项目中太常见了搜索热词里“selenium 图片滑块验证”“自动化selenium网页拼图验证怎么自动化”热度一直都很高所以我把这块做成了重点讲解。滑块验证的处理逻辑分四步每一步都要精确第一步截取验证码区域的背景图片和目标滑块图片。 第二步用图像识别算法计算缺口位置。 第三步计算滑动的距离和运动轨迹。 第四步用 Selenium 的 ActionChains 模拟拖拽配合贝塞尔曲线轨迹完成操作。import time from PIL import Image from io import BytesIO from selenium.webdriver.common.action_chains import ActionChains # 定位滑块元素 slider WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, slider-btn)) ) # 获取背景图和缺口的截图 bg_image self.driver.find_element(By.CLASS_NAME, slide-bg) bg_screenshot bg_image.screenshot_as_png bg_pil Image.open(BytesIO(bg_screenshot)).convert(RGB) # 计算缺口坐标伪代码实际需要图像识别算法 gap_x find_gap_position(bg_pil) # 返回缺口中心的 x 坐标find_gap_position这个函数需要调用图像识别算法。最简单的做法是用 OpenCV 的边缘检测和模板匹配。实际项目中我经常用matchTemplate拿滑块小图的边缘和背景图的边缘做模板匹配返回匹配位置的中心点坐标。拖动轨迹伪造是这个环节最见功力的地方。Selenium 的drag_and_drop或者直接move_by_offset如果速度恒定、轨迹直线很容易被前端的行为检测识别。我在代码里实现了一个模拟人类拖拽的轨迹生成器先快后慢、中途有小停顿、微小的抖动。这套轨迹生成的思路本质上是把鼠标的物理运动特性用数学函数模拟出来哪怕是简单的三次缓动函数效果都远好于瞬间到位。def generate_track(distance): 生成模拟人类拖拽的运动轨迹 distance: 需要拖拽的总距离 track [] current 0 mid distance * 0.7 t 0 while current distance: if current mid: # 前 70% 距离快速移动 step random.randint(8, 15) else: # 后 30% 距离缓慢逼近微调 step random.randint(1, 4) current step track.append(min(current, distance)) # 最后加入几次微小的回弹和停顿模拟真实人手 track.extend([distance, distance - 1, distance, distance]) return track def drag_slider(self, gap_distance): slider self.driver.find_element(By.CLASS_NAME, slider-btn) ActionChains(self.driver).click_and_hold(slider).perform() track generate_track(gap_distance) for step in track: ActionChains(self.driver).move_by_offset(step, random.randint(-2, 2)).perform() time.sleep(0.02) ActionChains(self.driver).release().perform() time.sleep(1)这段代码里有两个关键设计第一个是move_by_offset每次移动的距离是累加计算的不是绝对坐标所以执行到末尾时会精确覆盖目标距离第二个是每步之间的time.sleep(0.02)用来模拟人类拖拽时的停顿感和不稳定性。整套代码跑下来滑块基本一次通过。4.4 支付完成与赎罪券 PDF 的下载支付成功之后系统会生成一份赎罪券 PDF。这里涉及的是文件下载自动化的完整流程。Selenium 默认的策略是把文件下载到系统默认的“下载”文件夹并且不会等你下载完成就返回这会导致后续读取文件时报错。因此需要自定义下载目录并等待文件下载结束。import os import time download_dir os.path.abspath(./redemption_certificates) if not os.path.exists(download_dir): os.makedirs(download_dir) # 重新配置下载偏好 prefs { download.default_directory: download_dir, download.prompt_for_download: False, download.directory_upgrade: True, safebrowsing.enabled: True } self.driver.execute_cdp_cmd(Page.setDownloadBehavior, { behavior: allow, downloadPath: download_dir }) # 点击下载按钮 safe_click(self.driver, By.ID, download_certificate) # 等待下载文件完成 def wait_for_download_complete(timeout30): deadline time.time() timeout while time.time() deadline: files [f for f in os.listdir(download_dir) if f.endswith(.crdownload)] if len(files) 0: break time.sleep(0.5) # 输出下载目录下的文件 for f in os.listdir(download_dir): logging.info(f下载文件: {f})PDF 下载的断点续传机制是.crdownload后缀名的文件它表示 Chrome 正在下载但还没写完。当目录里看不到.crdownload文件时说明下载已经完成。搜索热词里“selenium怎样使文件下载完成之后才进行下一步”问的人很多这正是解决方案轮询等待.crdownload文件消失。完整跑通整套流程后控制台输出的日志长这样2025-01-10 10:23:01 - INFO - 浏览器初始化完成 2025-01-10 10:23:02 - INFO - 点击成功: start_blessing 2025-01-10 10:23:03 - INFO - 罪孽类型已选择: 贪吃 2025-01-10 10:23:04 - INFO - 已完成年度至尊套餐选择 2025-01-10 10:23:05 - INFO - 滑块验证通过 2025-01-10 10:23:06 - INFO - 支付成功 2025-01-10 10:23:07 - INFO - 下载文件: 赎罪券_扫地僧_20250110.pdf看到“下载文件”这一行说明整套自动化流程走通了。5. 常见问题与排查技巧实录5.1 元素定位失败的集中场景与对策Selenium 入门到放弃80% 的拦路虎都是定位失败。我在这个项目中专门制造了三种典型的定位陷阱排查完它们你就基本掌握了定位问题的处理套路。第一种元素加载太慢导致找不到。症状是NoSuchElementException或者ElementNotInteractableException。对策是换显式等待不要用隐式等待和固定 sleep。固定等待要么等不够报错要么等长了浪费时间显式等待是动态响应式的最好方案。第二种iframe 嵌套导致找不到。电子神庙页面里支付模块被嵌在 iframe 中这是很多网站的常见做法。直接在主文档里找支付按钮永远找不到。解决方案是用driver.switch_to.frame()先切换到对应的 iframe 再操作操作完再driver.switch_to.default_content()切回来。# 切换到支付 iframe iframe WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.ID, pay_iframe)) ) self.driver.switch_to.frame(iframe) # 此时才能正常操作 iframe 内的元素 safe_click(self.driver, By.ID, confirm_pay) # 操作完切回主文档 self.driver.switch_to.default_content()第三种页面刷新后元素引用失效。症状是StaleElementReferenceException。原因是你之前拿到的元素引用已经过期了页面 DOM 被重新渲染。解决办法是在每次交互前重新定位元素而不是把元素引用长期保存。这也是我把safe_click封装成函数的原因之一它不持有旧引用每次点击都现找现点。5.2 滑块验证码过不去的排查路径滑块验证码过不去先把失败原因分成两大类一类是缺口识别不准一类是拖动轨迹太机械。缺口识别不准通常表现为滑块停的位置偏左或偏右。这是计算出来的缺口坐标和真实位置之间存在偏差。排查方式是把屏幕截图存下来用图像查看器打开拿像素坐标对比一下实际缺口中心位置调试find_gap_position的算法逻辑。常见问题是背景图不是原始尺寸而是被 CSS 缩放过的计算距离时需要乘以缩放比例。拖动轨迹太机械通常表现为滑块突然到头然后被判定失败。确实之前的generate_track已经模拟了缓动和停留但如果网站用了更严格的行为检测比如记录鼠标移动的帧率、加速度变化曲线、移动时的微小抖动频率那单靠 Python 模拟的直线移动就不够逼真了。此时侯的策略有两个一是把move_by_offset的步数加大让移动轨迹更平滑——每步 1~3 像素和每步 10~15 像素的轨迹肉眼和算法看起来完全不一样二是在移动过程中偶尔反向调整一两步模拟人手颤抖时的不稳定。补一个我测试时的实用技巧把time.sleep(0.02)调整为随机间隔random.uniform(0.01, 0.03)。固定步长加固定延时这种模式一旦上一个程序员离职下一个来看代码的铁定头疼对检测算法来说也更容易被特征提取。随机化是所有反检测策略的基本功。5.3 无头模式下跑脚本的常见坑为了追求执行速度我平时会把浏览器切到无头模式。但切换到无头模式后有几个和正常模式完全不同的表现特别容易让人困惑。第一无头模式下浏览器的视口尺寸默认为 800x600很多按钮在非视口区域内Selenium 点击时提示ElementClickInterceptedException。解决方案是在初始化时显式设置窗口大小options.add_argument(--window-size1920,1080)。第二无头模式默认不下载文件需要额外配置下载行为。前面代码里的execute_cdp_cmd就是专门为无头模式准备的。如果你用正常的webdriver.Chrome()带下载配置无头模式下可能依然无效所以这个 CDP 命令要保留。第三无头模式下截屏图片和正常模式存在色差影响滑块缺口位置识别。我调试时发现某些背景图的边缘检测结果在无头模式下和正常模式不一致导致坐标计算发生偏移。遇到这种问题要么强制启用 GPU要么直接改用真实浏览器模式跑关键流程。目前已测下来无头模式加--disable-gpu参数在多数情况下可以消除色差。5.4 并发执行时浏览器进程残留的处理电子神庙这个项目后期我做了个“多分身”变体——同时启动三个浏览器同时给三位虚拟信徒购买赎罪券用来测试并发场景下脚本的稳定性。结果发现一个恶性问题每次脚本崩溃Chrome 进程就会残留把系统内存慢慢耗尽。原因在于 Python 脚本异常退出时没有执行driver.quit()WebDriver 启动的浏览器进程成了孤儿进程。解决办法有两个优先确保driver.quit()在 finally 块中执行其次在脚本入口处写一个兜底清理逻辑杀掉所有残留的 chromedriver 进程。import subprocess import sys def cleanup_chrome_processes(): system_name sys.platform if system_name.startswith(win): subprocess.run(taskkill /f /im chromedriver.exe, shellTrue) else: subprocess.run(pkill -f chromedriver, shellTrue) try: bot TempleBot(headlessTrue) bot.run_purchase_flow() finally: bot.driver.quit() cleanup_chrome_processes()这个清理函数在正常退出时不会起作用因为进程已经退出了但在异常退出时绝对能救命。我在用 pytest 组织多用例执行的时候经常在前面一个用例失败、后面用例继续跑的场景下把所有残留驱动清掉避免互相干扰。6. 从恶搞项目到实用脚本的经验迁移电子神庙这套代码虽然只是个玩笑但它用到的所有技术手法在现实的 RPA 和自动化测试场景里都是教科书级别的套路。我在实际工作中把同样的代码骨架迁移到了三个正经场景迁移成本低到令人发指第一个场景是公司内部的员工福利商城自动签到。员工每天要手动登录商城领取积分我把它完全自动化派上了 Selenium 四件套隐式配置、显式等待、表单填写、模拟点击。区别只是把赎罪券页面换成了积分商城。第二个场景是业务系统的重复性报表导出。财务部每周要从网页后台导出六张报表过去人工要花半小时现在脚本自动完成只需要在导出前确认文件下载完成。这个场景里用到“等待 .crdownload 文件消失”的技巧和赎罪券 PDF 的下载逻辑一模一样。第三个是电商平台的库存提醒。定时任务每十分钟打开商品页面检查“缺货”标签是否变为“有货”变了就发邮件通知。这里用到最核心的就是元素定位和显式等待。这套代码能够快速复用的底层原因是我把业务逻辑和 Selenium 操作解耦了。你看到的每个safe_click、WebDriverWait、Select操作都是通用的“行为组件”它们不关心业务是什么只关心页面上有什么。换一个新业务只需要把定位表达式和流程顺序换掉剩下的全都保留。如果你准备把它延伸到真实项目里我还有一句经验之谈有些岗位可能觉得写 Selenium 脚本不太技术但你要知道任何能把人工从重复劳动中解放出来的工具都是实打实的生产力提升。做出来了就是效率就是价值。回头聊聊这次用 Selenium 操控邪教项目自己踩过最难忘的坑我在跑完整流程的时候习惯性地在支付成功页面加了一个断言检查页面上是否有“罪孽清零”四个字结果有一次运行断言失败查了半天发现是页面文案被前端 A/B 测试改成了“业障消除”。这给我的教训特别深刻页面文案是善变的做自动化校验时与其断言文本内容不如断言页面关键元素的存在性或者 URL 的跳转结果。文本断言虽然直观但维护成本极高。把你的测试逻辑锚定在稳定的结构上别绑死在人类随时可能改掉的文案上这是自动化测试的残酷真相。最后留一个小提示如果你也想跟着复现这个项目别去费劲找那家电子神庙——它是我用 HTML 和 JavaScript 自己搭的一个模拟站纯静态的。你完全可以搭一个本地测试页面把这篇文章里的代码直接跑起来练手时可以把等待时间调得更极端一点比如把接口延迟改成 5 秒以上这样你就能更深刻地体会到显式等待到底好在哪了。祝你在探索 Selenium 的路上少踩坑、多收获。