这事儿我有发言权。Selenium 我从读研时就开始用。后来做新闻源采集、视频元数据再到现在跑跨境电商页面前前后后写过不少浏览器自动化脚本。它给我的感觉一直很矛盾入门特别快写稳特别难。打开网页输入关键词点击按钮。半小时就会了。结果呢同一段代码上午跑得好好的下午突然提示元素不存在。你加了三秒等待它下一次偏偏第四秒才出来。这能不烦先搞明白 Selenium 在干什么Selenium 是一套浏览器自动化工具。它不是在后台简单下载网页源码而是在控制一个真实浏览器。你平时能在 Chrome 里完成的动作它基本都能模拟比如打开页面、填写表单、点击按钮、切换窗口、上传文件、滚动页面和保存截图。这也是 Selenium 和普通 HTTP 请求最大的差别。如果目标页面的内容直接存在于 HTML 中或者网站提供了稳定的公开 API我一般不会先用 Selenium。启动一个浏览器要占内存页面渲染还要花时间。为了读取一段静态文字把整个 Chrome 拉起来我图啥呢Selenium 真正适合的是动态页面。比如内容需要执行 JavaScript 才出现搜索结果必须点击后加载或者任务要在登录状态下完成一整套操作。到了这种场景直接请求处理起来可能更绕浏览器自动化反而省事。第一次运行别先折腾驱动Python 项目建议放在独立虚拟环境里。安装过程很简单python-mvenv .venvWindows 激活环境.venv\Scripts\activatemacOS 或 Linux 激活环境source.venv/bin/activate接着安装 Seleniumpipinstall-Uselenium现代版本通常可以通过 Selenium Manager 处理常见浏览器驱动。以前那种先查 Chrome 版本再找对应 ChromeDriver下载完还要配置路径的流程现在大多数情况下不用手动做了。先跑一段最小代码fromseleniumimportwebdriver driverwebdriver.Chrome()try:driver.get(https://example.com)print(driver.title)finally:driver.quit()浏览器能打开终端能输出网页标题基础环境就没问题。这里的finally别省。脚本中途报错时它仍然会关闭浏览器。否则调试十几次后台留下一排 Chrome 进程内存一路往上走。那场面我见过。离谱。调试阶段我一般保留浏览器界面。至少脚本停住时我能直接看到是按钮没出现还是弹窗把页面挡住了。等流程跑稳以后再切到无头模式fromseleniumimportwebdriver optionswebdriver.ChromeOptions()options.add_argument(--headlessnew)options.add_argument(--window-size1440,1000)driverwebdriver.Chrome(optionsoptions)无头模式也要设置窗口尺寸。很多网站用了响应式布局窗口太窄时桌面导航会折叠成手机菜单。页面布局一变原来的按钮可能直接消失。代码本身没改定位却失效了这就很尴尬。元素定位别追求长追求稳浏览器打开以后Selenium 得先找到页面上的元素才能点击或者输入。常用定位方式没几种fromselenium.webdriver.common.byimportBy driver.find_element(By.ID,username)driver.find_element(By.NAME,keyword)driver.find_element(By.CSS_SELECTOR,button.submit)driver.find_element(By.XPATH,//button[contains(text(), 查询)])我自己最常用的是id和 CSS Selector。如果前端提供了data-testid之类的测试属性会更省心search_inputdriver.find_element(By.CSS_SELECTOR,input[data-testidsearch-input])search_input.send_keys(Selenium)XPath 当然可以用。需要按文字、相邻关系或者复杂层级找元素时它很方便。问题是很多人会直接从浏览器开发者工具里复制完整 XPath/html/body/div[2]/div/div[3]/form/input看上去非常精确对吧前端只要多套一层div整条路径就废了。我做跨境页面时尤其不爱依赖显示文字。同一个按钮在英文环境叫Search切到其他语言以后文案就变了。脚本如果按按钮文字定位地区一换就集体罢工。关键还是找稳定的业务属性而不是赌页面永远不改。为什么加了 sleep 还是会报错9 月 17 日早上 9 点 12 分我刚把工位上的黑咖啡打开组里同事发来一段代码driver.get(url)time.sleep(5)buttondriver.find_element(By.ID,submit)button.click()他说已经等了五秒为什么偶尔还是找不到按钮答案很简单。因为时间过去五秒不等于按钮已经可以点击。页面一秒加载完脚本白等四秒。网络慢一点按钮第六秒才出现五秒结束后照样报错。把等待时间改成十秒只是让脚本在大多数时候多等五秒并没有解决判断条件的问题。更合适的做法是显式等待fromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasEC waitWebDriverWait(driver,10)buttonwait.until(EC.element_to_be_clickable((By.ID,submit)))button.click()这段代码等的不是固定时长而是“按钮已经可以点击”。按钮两秒后准备好脚本两秒后继续按钮八秒后准备好脚本就等八秒。如果只是要求元素进入页面结构可以使用presence_of_element_located。如果必须看得见再用visibility_of_element_located。遇到加载遮罩或者弹窗也可以等它消失wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR,.loading-mask)))time.sleep()不是完全不能出现。调试动画、观察执行过程或者控制操作间隔时偶尔用一下没问题。真正坑的是把它当成页面状态判断。页面有没有准备好应该问页面。问时钟干什么一段完整表单代码应该怎么写下面这段示例会打开测试表单填写文字和密码选择下拉项勾选复选框提交以后再验证结果fromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECfromselenium.webdriver.support.uiimportSelect optionswebdriver.ChromeOptions()options.add_argument(--window-size1440,1000)driverwebdriver.Chrome(optionsoptions)waitWebDriverWait(driver,10)try:driver.get(https://www.selenium.dev/selenium/web/web-form.html)text_inputwait.until(EC.visibility_of_element_located((By.NAME,my-text)))text_input.send_keys(Selenium 自动化测试)driver.find_element(By.NAME,my-password).send_keys(demo-password)select_elementdriver.find_element(By.NAME,my-select)Select(select_element).select_by_visible_text(Two)checkboxdriver.find_element(By.ID,my-check-1)ifnotcheckbox.is_selected():checkbox.click()driver.find_element(By.CSS_SELECTOR,button).click()messagewait.until(EC.visibility_of_element_located((By.ID,message)))assertmessage.textReceived!driver.save_screenshot(form-result.png)finally:driver.quit()这已经是一段完整的浏览器自动化流程。浏览器启动后先进入页面等输入框可见再完成填写和提交。提交以后不盲等而是等结果消息出现。确认文案符合预期后截图无论中途是否报错最终都关闭浏览器。刚入门不用急着引入很多框架。先把这段代码看明白再换成自己的页面。学习速度反而更快。元素明明在页面上为什么找不到这种情况先别急着换选择器。如果元素位于iframe中Selenium 默认在主页面里查找当然找不到。需要先切进去wait.until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR,iframe.payment-frame)))driver.find_element(By.ID,card-number).send_keys(1234)操作完成后再回到主页面driver.switch_to.default_content()新标签页也是同一个道理。点击链接以后浏览器虽然打开了新页面Selenium 当前控制的却可能还是原窗口original_windowdriver.current_window_handle old_windowsset(driver.window_handles)driver.find_element(By.LINK_TEXT,查看详情).click()wait.until(lambdad:len(d.window_handles)len(old_windows))new_window(set(driver.window_handles)-old_windows).pop()driver.switch_to.window(new_window)print(driver.title)driver.close()driver.switch_to.window(original_window)很多所谓的“元素定位失败”根本不是定位表达式写错了而是查找上下文错了。人已经去了新页面脚本还留在旧页面这能找到才奇怪。页面更新以后别继续拿着旧元素动态页面局部刷新后原来保存的元素对象可能会失效。这时常见的报错是StaleElementReferenceException我第一次遇到时对着 CSS Selector 改了半天。后来才反应过来选择器没问题是页面已经变了我还在操作更新前的对象。例如先获取一批卡片再逐个点进去cardsdriver.find_elements(By.CSS_SELECTOR,.result-card)进入详情页再返回后列表可能已经重新渲染。原来的cards不一定还能继续用。更稳的做法是每轮重新定位cards_locator(By.CSS_SELECTOR,.result-card)countlen(wait.until(EC.presence_of_all_elements_located(cards_locator)))forindexinrange(count):cardswait.until(EC.presence_of_all_elements_located(cards_locator))cards[index].click()# 在这里处理详情页print(driver.title)driver.back()wait.until(EC.presence_of_all_elements_located(cards_locator))页面状态变了就重新找元素。别跟旧对象谈感情。项目写大以后怎么整理脚本只有几十行时把定位、操作和断言放在一起没什么问题。页面一多同一个按钮选择器可能散落在十几个文件中。前端改一次页面测试代码跟着全线报错。这时可以引入 Page Object把页面细节封装起来fromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECclassLoginPage:URLhttps://example.com/loginUSERNAME(By.ID,username)PASSWORD(By.ID,password)SUBMIT(By.CSS_SELECTOR,button[typesubmit])def__init__(self,driver):self.driverdriver self.waitWebDriverWait(driver,10)defopen(self):self.driver.get(self.URL)returnselfdeflogin(self,username,password):self.wait.until(EC.visibility_of_element_located(self.USERNAME)).send_keys(username)self.driver.find_element(*self.PASSWORD).send_keys(password)self.driver.find_element(*self.SUBMIT).click()调用时只描述业务动作pageLoginPage(driver).open()page.login(demo-user,demo-password)这样一来页面改了选择器只需要修改LoginPage。测试用例不必知道按钮用了什么 CSS也不用到处复制等待代码。不过别刚写三十行脚本就拆出十二个类。先跑通再把重复部分整理出来。为了看起来专业而搭一大堆空架子也挺折磨人的。Selenium 能用来做网页采集吗可以。特别是页面必须执行 JavaScript、经过点击或者登录后才显示内容时Selenium 很实用。问题是工具能操作浏览器不代表所有内容都可以随便获取。我们组在自动化任务上线前会先确认目标数据是否允许访问操作会不会触发订单、评论或其他写入行为是否涉及个人敏感信息以及访问频率是否合理。验证码、登录保护和平台风控是安全边界不是拿来练手的题目。如果只是正常测试自己的系统这些问题比较好控制。涉及第三方页面就要先看平台规则和授权范围。脚本跑通只是技术问题能不能跑则是另一回事。真正需要掌握的不是 APISelenium 的方法名随用随查就行。真正决定脚本稳不稳的是你能不能看懂页面状态。按钮还没出现就等它出现按钮被遮住就等遮挡层消失页面开了新窗口就切换过去页面重新渲染就重新定位元素。9 月 18 日晚上 10 点 40 分我把初稿发给我家那位看。他扫完后问“你是不是漏了一句最重要的”我问哪句。他说“成功一次不叫稳定。”行吧。后端说这话确实没毛病。一段 Selenium 脚本连续运行几十次遇到加载波动和页面更新也能正确处理才有资格交给定时任务。能打开浏览器只是开始。