Python Selenium 自动化实战:从脚本到exe交付
把一个Selenium脚本变成客户双击就能运行的exe,中间的坑比想象的要多。这篇文章记录了我从零搭建MSD自动化系统到交付exe的全过程,尤其是那些踩出来的坑。
项目背景
MSD采购自动化系统需要处理以下流程:弹窗附件下载(CD0625.py处理)、封门照片自动上传、入库计划解析、免堆期存储状态追踪。整个流程涉及多页面跳转、动态加载、弹窗处理——典型的"页面跳转多、元素加载慢"的复杂自动化场景。
技术选型
- Python 3.13:主语言
- Selenium 4.x:浏览器自动化框架
- ChromeDriver 149.0.7827.53:匹配Chrome v149
- PyInstaller:打包为独立exe
坑一:ChromeDriver版本对齐
这是第一个也是最容易犯的错:Chrome浏览器的版本必须与ChromeDriver版本精确匹配。
Chrome v149对应ChromeDriver 149.0.7827.53。如果版本不一致,Selenium初始化时就会报错。而且Chrome会自动更新,所以需要关闭自动更新或固定版本。
解决方案:下载对应版本的ChromeDriver,放在项目目录中,通过代码指定路径:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
service = Service('chromedriver.exe')
driver = webdriver.Chrome(service=service)
坑二:DOM StaleElementReferenceException
这是最头疼的问题。当页面发生跳转、刷新或DOM更新时,之前获取的元素引用会"过期"。你明明用xpath定位到了元素,但click()的时候却报StaleElementReferenceException。
这个错误不总出现——取决于页面的渲染速度和网络延迟。有时跑10次才出现1次,极难复现。
解决方案:
- 操作前重新获取元素:不要缓存WebElement对象,每次需要操作时重新find_element
- 加等待策略:使用WebDriverWait配合EC.element_to_be_clickable,确保元素完全就绪
- 加重试机制:捕获StaleElementReferenceException后重新定位重试
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, xpath, max_retries=3):
for i in range(max_retries):
try:
element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, xpath))
)
element.click()
return
except StaleElementReferenceException:
if i == max_retries - 1:
raise
坑三:PyInstaller打包时缺少hidden-import
脚本在本地Python环境跑得好好的,打包成exe之后就报"ModuleNotFoundError: No module named 'selenium.webdriver'"。
这是因为PyInstaller的分析引擎没有自动检测到Selenium的部分动态导入。需要手动指定hidden-import。
pyinstaller --onefile ^
--hidden-import=selenium.webdriver.common.by ^
--hidden-import=selenium.webdriver.support.ui ^
--hidden-import=selenium.webdriver.support.expected_conditions ^
--add-binary "chromedriver.exe;." ^
main.py
关键参数说明:
--onefile:打包为单个exe文件--hidden-import:手动声明PyInstaller没检测到的模块--add-binary:把chromedriver.exe也打包进去
坑四:exe运行时chromedriver路径问题
打包后的exe运行时,chromedriver.exe会被解压到临时目录,但路径变了。直接写死路径会找不到。
解决方案:使用sys._MEIPASS获取PyInstaller的临时解压目录:
import sys
import os
def get_driver_path():
if getattr(sys, 'frozen', False):
base_dir = sys._MEIPASS
else:
base_dir = os.path.dirname(__file__)
return os.path.join(base_dir, 'chromedriver.exe')
交付checklist
做完以上这些,你的脚本就可以交付了。交付前确认:
- exe在全新Windows系统上能直接运行(无需安装Python)
- Chrome版本与ChromeDriver匹配
- 所有异常都被捕获,有友好的错误提示
- 脚本执行完成后自动关闭浏览器(释放资源)
- 提供使用说明文档
总结
Selenium自动化从脚本到交付exe,核心挑战不是"写逻辑",而是处理边界情况:版本对齐、元素过期、打包配置。把这三个坑填平了,你就能交付一个稳定、用户友好的自动化工具。