Selenium driver常用方法全解:浏览器控制、元素定位与等待机制
1. 先把 driver 这个对象看透不然方法背了也白背刚接触 selenium 的同学十个里有八个是这么入门的打开教程抄一段webdriver.Chrome()然后开始往下面堆find_element能跑起来就完事。等哪天脚本在一个弹窗页面卡死或者报出一句unable to obtain driver for chrome就彻底懵了。我自己也是从这个阶段过来的所以这篇东西不打算按 API 文档的顺序念一遍而是按这个对象到底替我们干了什么活来讲。selenium 里的 driver本质上是浏览器的一个远程遥控器。你的 Python 代码不会直接操作 DOM它做的事情是把点击某个按钮这样的意图编码成一份指令交给 driver 对象driver 再通过一套标准协议把指令转发给浏览器内核浏览器执行完把结果原路返回。理解这条链路非常重要因为它解释了后面所有诡异现象为什么有的方法会阻塞、为什么元素明明在页面上却点不到、为什么同一个方法在 Chrome 上是等待、在某个无头环境里却直接抛异常。这篇文章适合三类人看刚学 selenium 自动化测试、想把常用方法系统过一遍的新手写了一段时间脚本、但方法用得比较野、遇到超时和定位失败只会加sleep的老手以及需要给团队做一套稳定封装、想把 driver 能力边界摸清楚的人。下面我会把 driver 身上的方法拆成浏览器控制、元素查找、元素交互、等待机制、JS 与上下文切换几个大块每一块都配上能直接抄的代码也会把踩过的坑一起写进去。1.1 先分清 Driver 实例、WebElement 和浏览器进程很多人调方法时出错根源在于没分清手上这个变量是什么类型。webdriver.Chrome()返回的是WebDriver 实例它代表整个浏览器会话能干的活是导航、切换窗口、管理 Cookie、截图、执行 JS。而driver.find_element(...)返回的是WebElement 实例它只代表页面上的某一个节点能干的事情就窄得多点击、输入、读文本、读属性。这两类对象的方法不能互相串用。我见过有人在 WebElement 上调用get()也见过有人在 driver 上直接调send_keys()报错信息还都挺隐晦的。判断标准很简单凡是跟整个浏览器相关的动作归 driver凡是跟页面上某个具体元素相关的动作归 element。截图这个事有点特殊driver 可以截整个视口element 也可以截自己那一块这是 selenium 后期版本才补上的能力。还有一个容易被忽略的点driver 实例背后对应着一个真实的浏览器进程和一个驱动进程Chrome 对应 chromedriverFirefox 对应 geckodriver。这两条命是绑在一起的你的脚本异常退出时如果不显式关闭这两个进程很可能变成孤儿进程挂在系统里。跑批量用例的机器上一天下来能攒出几十个僵尸浏览器内存直接被吃光然后你就开始怀疑是不是电脑该换了。1.2 实例化方式的演进以及那个恼人的驱动报错早些年写 selenium最烦的一步是手动下载 chromedriver还得保证版本和本机 Chrome 严格对应差一个小版本都可能起不来。现在 Selenium 4 引入了Selenium Manager默认情况下会自动探测本机浏览器版本并拉取匹配的驱动webdriver.Chrome()这一行就能直接跑通对新手友好了非常多。但热词里那个unable to obtain driver for chrome依然高频出现我自己总结下来无非几种情况一是公司内网没有外网出口Manager 拉不到驱动文件二是本机装的是某个定制版浏览器版本号读出来对不上三是环境变量里残留了旧版驱动的路径Manager 找到了但启动失败。这时候最稳的做法是回到手动指定驱动的老路from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) options.add_argument(--disable-gpu) service ChromeService(executable_path/opt/drivers/chromedriver) driver webdriver.Chrome(serviceservice, optionsoptions)注意Selenium 4 里executable_path已经从webdriver.Chrome()的参数里挪到了 Service 对象上。网上大量老教程还在用旧写法抄下来会直接报参数不合法这是新手最常撞的墙之一。2. 浏览器控制类方法driver 最本职的那部分活浏览器控制类方法是 driver 对象最名正言顺的职责也是整个脚本的骨架。页面怎么打开、窗口多大、登录态怎么保住、出错了怎么留证据全在这一层。这部分方法用得不讲究后面写再多定位逻辑都是沙上建塔。2.1 导航四件套get、back、forward、refreshdriver.get(url)是最常用的方法它会跳转到指定地址并且默认会等页面基本加载完成才返回严格说是等到 document.readyState 达到 complete 或超时。这一点跟很多人想象的不一样——它确实带一点等待语义所以偶尔会出现没写等待也能跑通的假象。但这个等待非常粗它只管主文档不管 AJAX 请求也不管动态渲染的组件所以后面该用显式等待的地方一个都不能少。driver.back()和driver.forward()对应浏览器的后退和前进driver.refresh()是刷新。这三个方法看着简单实际有两个坑一是它们的历史栈行为跟真实浏览器一致如果你在中间做了 JS 跳转栈的形态可能和你预期不同二是刷新会丢掉页面上所有未提交的表单数据和 JS 运行时状态脚本里无脑 refresh 结果把已填好的表单清空这种事我干过不止一次。还有一个细节值得记住get()传的地址不带协议时某些驱动会当成相对路径处理然后抛异常。养成习惯URL 一律写全https://。2.2 窗口尺寸、位置与全屏控制窗口相关的方法有maximize_window()、minimize_window()、fullscreen_window()、set_window_size(w, h)、get_window_size()、set_window_position(x, y)、get_window_position()。看起来是很边缘的一类方法实际在自动化里权重很高。原因是响应式布局。同一个页面在 1366 宽度和 1920 宽度下DOM 结构和元素可见性可能完全不同尤其是那些移动端优先的站点窄屏下会把某些按钮折叠进汉堡菜单里。你用默认窗口跑得好好的脚本换台机器显示器不一样定位就集体失效了。所以我的习惯是固定窗口尺寸driver.set_window_size(1920, 1080) # 或者 driver.maximize_window()这里有个实操经验要分享maximize_window()在无头模式下不一定有效某些驱动实现里窗口本身就是虚拟的最大化不改变视口尺寸。无头环境里更可靠的做法是启动参数指定--window-size1920,1080而不是启动后再调方法。还有一个多窗口场景的常用组合original driver.current_window_handle # 当前窗口句柄 handles driver.window_handles # 所有窗口句柄列表 driver.switch_to.window(handles[-1]) # 切到最新打开的标签页 driver.close() # 关掉当前标签页 driver.switch_to.window(original) # 切回去注意close()和quit()完全不是一回事。close()只关当前标签页quit()关掉整个浏览器会话并回收驱动进程。脚本结束时务必用quit()用close()在单标签页场景下看似也行但驱动进程会残留。2.3 Cookie 管理与登录态复用add_cookie()、get_cookies()、get_cookie(name)、delete_cookie(name)、delete_all_cookies() 这一组方法是绕过重复登录的关键。自动化测试里最浪费时间的环节就是在每个用例开头登录一次如果站点用的是普通 Cookie 会话你完全可以登录一次、把 Cookie 存下来后续直接注入。思路是这样的先跑一次登录流程把driver.get_cookies()的结果序列化成 JSON 存到本地之后的脚本启动后先get()到目标域名再逐个add_cookie()最后 refresh 一下让 Cookie 生效。import json # 第一次保存登录态 cookies driver.get_cookies() with open(cookies.json, w, encodingutf-8) as f: json.dump(cookies, f, ensure_asciiFalse) # 后续恢复登录态 driver.get(https://example.com) with open(cookies.json, encodingutf-8) as f: for ck in json.load(f): # 有些字段不能直接回写先清理 ck.pop(sameSite, None) ck.pop(expiry, None) if isinstance(ck.get(expiry), float) else None driver.add_cookie(ck) driver.refresh()必须在目标域名下加 Cookie这是硬性约束。如果你先add_cookie再get驱动会报当前域名不匹配。另外sameSite、expiry这类字段在不同驱动版本下兼容性不一致回写前做一次字段清理能显著降低失败率这是我踩了几次坑之后固定下来的做法。2.4 截图、页面源码与当前地址save_screenshot(path)、get_screenshot_as_file(path)、get_screenshot_as_png()属于同一族。区别在于get_screenshot_as_png()返回的是字节流适合直接喂给图像库做处理或者上传到报告系统save_screenshot()直接落盘最省事。用例失败时自动截图是标配动作建议放在测试框架的 teardown 里用时间戳和用例名拼文件名别问我为什么强调这个问就是曾经覆盖过一整轮失败截图。page_source是个属性不是方法返回的是当前页面渲染后的 HTML 字符串。它的价值在于当定位失败时把 page_source 落盘你就能离线分析当时页面到底长什么样——很多时候问题根本不在你的选择器而在页面还没渲染完或者被弹窗遮住了。driver.current_url和driver.title也是属性。断言时我更喜欢用current_url做跳转校验因为title经常被前端改得面目全非而 URL 的路径部分相对稳定。3. 元素查找方法定位是一切的入口写完浏览器控制就到了 selenium 的核心——把页面元素找出来。这部分方法不多但组合方式和坑的数量是全书之最。3.1 By 的八种定位方式和它们的取舍Selenium 4 里推荐用By类配合find_element(By.XXX, value)的写法。八种方式分别是 ID、NAME、CLASS_NAME、TAG_NAME、LINK_TEXT、PARTIAL_LINK_TEXT、CSS_SELECTOR、XPATH。网上很多对比文章会告诉你 ID 最快、XPath 最慢这话在十年前成立现在各家浏览器对选择器的优化都做得不错性能差异在绝大多数场景下已经不是选择依据了真正该考虑的是稳定性。我自己的排序习惯是这样的定位方式稳定性适用场景主要风险ID高有唯一 id 的输入框、按钮前端框架自动生成随机 idCSS_SELECTOR高大多数常规场景层次深时表达式长XPATH中高需要按文本或父级回溯定位绝对路径极易失效NAME中表单元素页面上可能重复CLASS_NAME中低快速试探样式类名频繁改版LINK_TEXT中纯文字链接文字多语言变化TAG_NAME低批量取同类元素几乎不唯一PARTIAL_LINK_TEXT低模糊匹配链接容易误命中提示绝对不要用浏览器右键复制出来的绝对路径 XPath那种/html/body/div[3]/div[1]/ul/li[2]/a的写法前端随便加一层 div 就全废。要用也是用相对路径配合属性或文本比如//button[contains(text(), 提交)]。CSS 和 XPath 之间怎么选我的经验是能用 CSS 就用 CSS表达式短、可读性好需要按文本内容定位、需要找父节点、需要按索引取第 N 个兄弟节点时XPath 更方便因为 CSS 本身不支持这些。3.2 find_element 和 find_elements 的差异坑都在这里这两个方法名字只差一个 s行为差异却很大而且异常类型都不一样find_element找不到时抛NoSuchElementExceptionfind_elements找不到时返回空列表不抛异常后者这个设计经常被人当成救命稻草写出类似先 find_elements 看长度是不是大于 0再决定点不点的代码。这种写法本身没错但它掩盖了一个问题如果页面是异步渲染的你查的那一瞬间元素还没出来返回空列表脚本就走了元素不存在的分支静默地跳过了一个本该执行的操作。这种 bug 最难查因为它不报错。我的建议是判断存在性用 find_elements但一定要配合显式等待不要用查一次看长度的方式。后面第 5 节会详细讲。另外还有两个容易忽略的方法族find_element在 WebElement 上也能调用作用是在子树范围内查找。这在处理列表项时非常有用比如先找到所有卡片再在每张卡片内部找标题避免全局匹配串到别的卡片上。cards driver.find_elements(By.CSS_SELECTOR, .card) for card in cards: title card.find_element(By.CSS_SELECTOR, .title).text price card.find_element(By.CSS_SELECTOR, .price).text print(title, price)这个写法的好处是作用域收敛。如果你写成全局driver.find_elements(By.CSS_SELECTOR, .title)遇到卡片数量不固定或者存在隐藏卡片时取出的顺序和数量很可能对不上。3.3 实战枚举一组非原生下拉框的元素热词里提到selenium 页面元素枚举不是原生下拉框是 div ul li 组合这是个非常典型的场景值得单独讲一段。现代前端组件库不管是哪家渲染出来的下拉框绝大多数都不是select而是一堆 div、ul、li 拼出来的。这意味着Select类完全用不上你只能当成普通元素处理。流程是这样的点击触发区让下拉展开等待选项列表出现把所有选项枚举出来再根据文本或属性匹配到目标项并点击。from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10, poll_frequency0.3) # 1. 展开下拉 wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, .select-trigger))).click() # 2. 等选项容器出现 options wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, .select-dropdown li)) ) # 3. 枚举并打印方便确认渲染结果 for i, opt in enumerate(options): print(i, repr(opt.text), opt.get_attribute(data-value)) # 4. 按文本选中 target 上海 for opt in options: if opt.text.strip() target: opt.click() break else: raise AssertionError(f下拉框中未找到选项: {target})注意这类组件经常在点击后把选项列表重新渲染一遍导致你之前拿到的 WebElement 引用失效报StaleElementReferenceException。规避办法是每次用完重新查找不要跨操作复用元素引用。还有一个细节presence_of_all_elements_located只保证元素出现在 DOM 中不保证可见。如果组件用了懒渲染可能出现元素在但不可见的情况这时候应该换成visibility_of_all_elements_located或者干脆用element_to_be_clickable对单个元素做判断。4. 元素交互方法从点击到拖拽找到元素只是第一步真正让脚本产生业务价值的是交互。这一层方法的数量不多但每个都有自己的一套脾气。4.1 click、send_keys、clear 的细节与陷阱click()是最常用的方法它执行的是原生点击事件会滚动到元素位置、模拟鼠标移动和按下抬起。但在真实项目里click()报元素不可交互ElementNotInteractableException的概率相当高常见原因有这么几种元素被其他浮层遮住了、元素尺寸为零、元素在可视区外但容器用了overflow: hidden导致滚动无效、页面还在动画过程中。我的处理顺序是这样的先判断是不是浮层遮挡把遮挡物关掉是最干净的解法如果确实是渲染动画导致的用显式等待等它稳定实在不行再用 JS 点击兜底element driver.find_element(By.CSS_SELECTOR, #submit) try: element.click() except Exception: driver.execute_script(arguments[0].click();, element)但要注意JS 点击是绕过浏览器事件模型的它不会触发某些由真实鼠标事件驱动的逻辑比如某些框架的焦点管理、hover 态切换。所以它只能当兜底方案不能当默认方案。我见过团队成员把所有 click 都换成 JS 点击结果表单提交后的校验逻辑全不触发排查了一下午。send_keys()用来输入文本它也是模拟真实键盘输入逐个字符敲进去。这个特性在大多数场景下是优点因为会触发前端绑定的 input 事件但在长文本或需要粘贴大段内容的场景下很慢可以用execute_script直接设值再手动派发事件来加速。清空输入框不要用send_keys(Keys.CONTROL, a)再删直接用clear()更稳因为组合键在某些驱动和系统下会失灵。4.2 Select 类只吃原生 select这点必须记牢Select类提供select_by_index、select_by_value、select_by_visible_text、deselect_*以及options、all_selected_options、first_selected_option这些属性。功能很全但它的前提是元素必须是真正的select标签。怎么确认在浏览器开发者工具里选中那个下拉框如果标签名是 select里面是 option那就能用如果是 div 套 ul 套 liSelect初始化的时候就会直接抛UnexpectedTagNameException。这个异常信息比较直白看到它基本就能确定是在非原生下拉框上误用了。from selenium.webdriver.support.ui import Select sel Select(driver.find_element(By.ID, city)) sel.select_by_visible_text(杭州) print([o.text for o in sel.options]) # 枚举全部选项 print(sel.first_selected_option.text) # 读当前选中项sel.options这个属性正好对应页面元素枚举的需求比手写 CSS 选择器再筛选更省事前提依然是原生 select。4.3 ActionChains悬停、拖拽、组合键ActionChains处理的是那些单次点击搞不定的操作鼠标悬停展开菜单、拖拽排序、按住 Ctrl 多选、右键菜单。它的使用方式和普通方法差别很大链式调用最后必须perform()才会真正执行这一点新人极容易漏。from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys actions ActionChains(driver) actions.move_to_element(menu).pause(0.5).click(sub_item).perform() # 按住 Ctrl 多选 ActionChains(driver).key_down(Keys.CONTROL).click(item1).click(item2).key_up(Keys.CONTROL).perform() # 拖拽 ActionChains(driver).drag_and_drop(source, target).perform()注意pause()在链式操作里比time.sleep()好用得多。它在动作序列内部插入了等待不会阻塞整个 Python 线程去做无关的等待而且能让动画有时间反应。悬停菜单类场景我基本都会在move_to_element之后加 300 到 500 毫秒的 pause。5. 等待机制把 driver 方法串起来的胶水如果只能给 selenium 新手留一条建议我会说先学会等待再学定位。绝大多数元素找不到点不到的问题本质都不是方法用错了而是时机没对上。5.1 隐式等待和显式等待的本质区别driver.implicitly_wait(10)设置的是全局的隐式等待。它的行为是每次调用查找元素的方法时如果没立刻找到就在这个时间窗口内轮询重试超时后抛异常。它只对查找元素生效对点击、对元素可见性、对文本变化一律无效。WebDriverWait是显式等待它把等待条件显式表达出来等到某个条件为真或者超时抛异常。条件由expected_conditions提供常见的有presence_of_element_located、visibility_of_element_located、element_to_be_clickable、text_to_be_present_in_element、invisibility_of_element_located、staleness_of等等。两者的关键差异我整理成了一张表维度隐式等待显式等待作用范围全局所有元素查找单次调用精确控制生效范围仅查找元素查找、可见性、可点击、文本、消失等超时行为抛 NoSuchElement抛 TimeoutException与另一个混用会叠加导致总等待时间不可控建议只留一种混用是重灾区。如果你设了隐式等待 10 秒同时显式等待也设 10 秒某些情况下单次查找的实际耗时可能达到 20 秒因为隐式等待会在显式等待的每一轮轮询里再各自消耗一遍。我的建议是新项目一律不用隐式等待全部用显式等待代码啰嗦一点但行为完全可控。5.2 自己写等待条件处理那些标准条件覆盖不了的场景expected_conditions覆盖了八成场景剩下两成得自己写。写法很简单一个接收 driver 的可调用对象返回真值表示条件满足返回假值就继续等。def element_count_more_than(locator, n): def _predicate(driver): els driver.find_elements(*locator) return els if len(els) n else False return _predicate wait.until(element_count_more_than((By.CSS_SELECTOR, .row), 5))这个数量大于 N的条件在列表加载场景里特别好用比等某一个具体元素要稳因为列表页经常是分批渲染的。另一个常用的是等属性变化def attribute_equal(locator, attr, value): def _predicate(driver): el driver.find_element(*locator) return el.get_attribute(attr) value return _predicate wait.until(attribute_equal((By.ID, status), data-state, done))5.3 封装一层让脚本不再满屏都是 wait显式等待的代码写起来比较长如果每个操作都写一遍脚本可读性会崩掉。我一般会封装一层薄薄的工具方法把查找 等待打包class Ui: def __init__(self, driver, timeout10): self.driver driver self.wait WebDriverWait(driver, timeout, poll_frequency0.3) def find(self, by, value): return self.wait.until(EC.presence_of_element_located((by, value))) def click(self, by, value): self.wait.until(EC.element_to_be_clickable((by, value))).click() def type(self, by, value, text): el self.wait.until(EC.visibility_of_element_located((by, value))) el.clear() el.send_keys(text) return el def texts(self, by, value): els self.wait.until(EC.presence_of_all_elements_located((by, value))) return [e.text for e in els]这层封装带来的最大变化是业务脚本里几乎看不到等待代码了同时每个操作都天然带着合理的等待语义。注意click用的是element_to_be_clickable而不是presence这是刻意的点击这个动作对元素的要求比单纯存在高用对条件能省掉大量零散的异常处理。6. JS 执行、上下文切换与其他进阶方法到这一层方法的使用频率开始下降但关键时刻能救命尤其是页面结构特殊或者需要绕过常规交互的时候。6.1 execute_script几乎是万能补丁execute_script(script, *args)在浏览器里执行 JS返回值会回传给 Python。它在几类场景下特别有用元素在视口外常规点击滚动不到arguments[0].scrollIntoView({block: center})需要直接读取或改写元素属性、样式需要一次性拿到大量数据避免反复跨进程通信# 滚动到元素 driver.execute_script(arguments[0].scrollIntoView({block:center});, el) # 取页面滚动高度 h driver.execute_script(return document.body.scrollHeight) # 一次性把列表文本取回来比逐个 element.text 快得多 texts driver.execute_script( return Array.from(document.querySelectorAll(.item .title)) .map(e e.innerText.trim()); )最后这个批量取值的技巧值得单独说一句每次element.text都是一次跨进程调用一百个元素就是一百次往返慢得明显。用execute_script一次取回速度差别在数据量大的时候非常直观。execute_async_script是异步版本接收一个回调参数适合等一个前端异步操作完成但实际项目里用得很少显式等待基本能替代。6.2 switch_to 三兄弟window、frame、alertdriver.switch_to下面挂了三个常用的切换对象。switch_to.window(handle)切标签页前面已经讲过注意切完之后所有元素查找都是在新的上下文里进行。switch_to.frame(...)进入 iframe。iframe 是定位失败的头号嫌疑犯因为 iframe 内部的 DOM 对外层来说是完全不可见的。判断方法很简单看看你定位的那个元素是不是在一个iframe里面。进入方式支持索引、name、WebElement 三种元素方式最稳iframe driver.find_element(By.CSS_SELECTOR, iframe.pay-frame) driver.switch_to.frame(iframe) # ... 在 iframe 内操作 driver.switch_to.default_content() # 回到最外层 # driver.switch_to.parent_frame() # 回到上一层注意default_content()和parent_frame()的区别是前者直接回到最顶层后者只退一层。iframe 嵌套三层以上的页面退的时候一定要数清楚退了几层否则元素查找全在一个错误的上下文里报错信息又看不出问题所在。switch_to.alert用来处理浏览器原生弹窗accept()确认、dismiss()取消、send_keys()输入、text读文本。原生 alert 会阻塞整个页面的 JS 执行所以如果脚本卡住不动先想想是不是有弹窗没处理。6.3 超时设置、日志和其他实用的边角能力driver.set_page_load_timeout(n)控制页面加载的最长等待某些加载特别慢的页面各种第三方统计脚本一直挂在那儿会把 get() 卡到超时设一个合理的值然后在 TimeoutException 里继续走是很实用的止损策略。driver.set_script_timeout(n)对应 execute_async_script 的超时。driver.implicitly_wait(n)前面说过了。日志和性能数据属于可选能力需要在启动参数里开启options.set_capability(goog:loggingPrefs, {browser: ALL, performance: ALL}) ... for entry in driver.get_log(browser): print(entry[level], entry[message])浏览器控制台的报错信息对排查前端问题非常有用比如某个接口 404 导致列表加载不出来页面上看不出任何异常控制台里一清二楚。我习惯在用例失败时把日志和 page_source 一起存下来事后分析省掉大量复现时间。7. 报错排查速查与实操心得方法讲完了最后这块是我觉得比 API 本身更值钱的部分。下面这张表是我这些年攒下来的高频报错对照基本覆盖了日常八成的问题。7.1 高频异常速查表异常 / 现象真正的原因处理思路NoSuchElementException选择器错、iframe 内、未渲染完先看 page_source再查 iframe最后补显式等待ElementNotInteractableException被遮挡、尺寸为零、动画未结束关浮层、等可见可点击、JS 点击兜底StaleElementReferenceException页面局部刷新导致元素引用失效用完即弃不要缓存 WebElementElementClickInterceptedException有其他元素挡在点击位置用 element_to_be_clickable或滚动后重试TimeoutException条件始终不满足检查条件是否写错必要时用自定义条件UnexpectedTagNameException对非原生下拉框用了 Select改成通用的点击 枚举方案unable to obtain driver for chrome驱动不可得或版本不匹配手动指定 chromedriver 路径脚本跑完进程残留没调 quit()teardown 里统一 quit异常也要保证执行关于最后一条补充一个写法上的经验把quit()放进finally块或者测试框架的 teardown比在脚本末尾直接写一行要可靠得多因为中间任何一步抛异常都会跳过末尾那一行。7.2 踩坑之后总结出来的几条硬经验第一条别用 sleep 当等待。time.sleep(3)看着简单但它是无条件等待页面 0.5 秒就加载完了你也要白白等 3 秒脚本一长总时间直接翻倍而遇到慢一点的页面 3 秒又不够你还是得改成 5 秒最后到处都是魔法数字。显式等待是条件驱动的快的时候立刻返回慢的时候顶到上限这才是正解。第二条元素不要缓存。我早期写脚本喜欢把常用的元素存成变量复用结果在 SPA 项目里被StaleElementReferenceException教育了好几次。凡是页面会局部刷新的项目一律现查现用。如果确实需要跨多次操作使用缓存定位器By value而不是缓存元素本身。第三条定位器要当成代码资产来维护。建议统一放在一个模块里集中管理比如LOCATORS {login_btn: (By.ID, login)}业务脚本只引用键名。这样页面改版时只需要改一个文件而不是全局搜索替换。这个习惯我坚持了几年维护成本至少降了一半。第四条调试的时候把 headless 关掉。无头模式跑得快但出问题时你什么都看不见。定位失败时切回有头模式肉眼看一下元素在不在、有没有被遮挡比盯着报错信息猜要高效得多。现在 Selenium 4 支持--headlessnew新版本的有头无头渲染路径基本统一但调试阶段我还是建议开着界面。第五条注意页面上看起来一样的多个元素。同一个按钮可能在页面顶部导航、底部导航、弹窗里各出现一次CSS 选择器如果只写了类名find_element拿到的很可能是第一个也就是你不想点的那个。所以选择器尽量带上父级限定或者业务属性别图省事。最后分享一个我自己常用的小工具方法用来在定位失败时快速看清现场def dump_context(driver, tagdebug): import time ts time.strftime(%Y%m%d_%H%M%S) driver.save_screenshot(f{tag}_{ts}.png) with open(f{tag}_{ts}.html, w, encodingutf-8) as f: f.write(driver.page_source) print(当前地址:, driver.current_url) print(当前标题:, driver.title)出问题的时候把这段挂上去截图、源码、URL、标题一次性拿到大多数定位问题看一眼 HTML 就能确定原因是 iframe、是动态 id、还是压根还没渲染出来。这个习惯帮我省下的时间比学任何一个新方法都多。

相关新闻

Selenium面试底层原理:从WebDriver驱动到元素定位与等待策略

Selenium面试底层原理:从WebDriver驱动到元素定位与等待策略

1. 面试官真正想考你的,不是背答案,而是底层原理我只说一个事实:Selenium相关面试题在网上随便一搜就有上百条,但大多数候选人挂在第一轮的原因,根本不是答案背得不够熟,而是被追问几句“为什么”就露馅了。…

2026/10/1 16:24:41 阅读更多 →
基于Spark的商品销售数据分析与线性回归预测系统详解

基于Spark的商品销售数据分析与线性回归预测系统详解

毕业设计的尽头,大概率是"大数据"三个字。每年这个时候找我聊毕设选题的学弟学妹,十个里有六七个开口就是"想做个大数据项目",但真要问他打算处理多少数据、解决什么问题,又答不上来。不是大家不想做好&#…

2026/10/1 16:24:41 阅读更多 →
Tekla OpenAPI Reference解析:程序集引用与二次开发避坑指南

Tekla OpenAPI Reference解析:程序集引用与二次开发避坑指南

简介:Tekla OpenAPI 参考文档网页版,面向使用 C#、VB.NET 等 .NET 语言从事 Tekla Structures 二次开发的工程师、BIM 技术负责人及结构设计自动化人员。该接口允许开发者通过编程方式深度访问结构模型,实现构件自动创建、属性批量修改、数据…

2026/10/1 16:24:41 阅读更多 →

最新新闻

K9s v0.1.3 版本解析:热键体系重构、多集群配置迁移与 ReplicationController 支持

K9s v0.1.3 版本解析:热键体系重构、多集群配置迁移与 ReplicationController 支持

云原生容器编排CLI运维 【免费下载链接】k9s 🐶 Kubernetes CLI To Manage Your Clusters In Style! 项目地址: https://gitcode.com/GitHub_Trending/k9s/k9s 点击查看 免费下载 导读 K9s v0.1.3 是该项目早期发展中一次承上启下的关键发布&#xff1…

2026/10/1 18:39:46 阅读更多 →
计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度建模与实现

计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度建模与实现

做虚拟电厂优化调度这几年,我几乎每个月都会碰到同行在群里问同一个问题:垃圾焚烧、碳捕集、电转气这些单元单独看都很“绿”,但把它们放进同一个虚拟电厂里到底该怎么配合?不少人的模型里各算各的,碳捕集出来的二氧化…

2026/10/1 18:39:46 阅读更多 →
嵌入式学习第十七天:用Linux个人博客项目打通服务部署全流程

嵌入式学习第十七天:用Linux个人博客项目打通服务部署全流程

嵌入式学习找工作第十七天--第一个项目(Linux个人博客) 走到第十七天这个节点,算是一个值得停下来认真复盘的位置。前面两周多的时间里,大概率已经啃完了 Linux 基础命令、vim 的基本操作、环境变量的概念,甚至可能被…

2026/10/1 18:39:46 阅读更多 →
AI论文能洗白吗?实测Paperxie降AIGC率真相

AI论文能洗白吗?实测Paperxie降AIGC率真相

这段时间后台总有读者追着我问同一件事:用 AI 写完论文以后,拿去跑 Paperxie 这类降重工具,出来的 AIGC 率到底能不能压到个位数?尤其是知网、维普据说要在 2026 年执行更严的新规,很多人怕现在辛辛苦苦改完&#xff0…

2026/10/1 18:39:46 阅读更多 →
基于Unet的医学影像分割实战:从PyTorch模型训练到Dice评估与避坑指南

基于Unet的医学影像分割实战:从PyTorch模型训练到Dice评估与避坑指南

简介:一套基于U-Net的医学影像分割系统完整项目源码,适合计算机、人工智能、自动化等专业学生完成毕设、课程设计或医学影像分割入门实践,也便于小白进阶学习。项目实现了从数据标注到模型部署的全流程,包含标注格式转换、训练与预…

2026/10/1 18:39:46 阅读更多 →
AI求职Demo失效真相:从玩具到产品的能力表达重构

AI求职Demo失效真相:从玩具到产品的能力表达重构

1. 这不是技术问题,是求职信号系统失灵了 “做了3个AI Demo,为什么还是拿不到面试?”——这句话我去年在技术社区刷到不下二十次,每次看到都下意识点开,不是因为好奇,而是太熟悉了。熟悉到能一眼看出提问者…

2026/10/1 18:38:45 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →