
1. 项目概述从“面试官等级”到Selenium爬虫的实战跨越看到这个标题你可能会有点懵——“字节跳动面试官是什么等级”和“Selenium爬虫详解”有什么关系这恰恰是我想和你聊的起点。作为一个在数据抓取和自动化领域摸爬滚打了十来年的老手我见过太多求职者简历上写着“精通Python爬虫”但被问到Selenium的核心原理、如何应对动态渲染、乃至如何设计一个稳定可维护的爬虫架构时却支支吾吾。字节跳动这类大厂的面试官他们评判的“等级”往往不是你会不会写几行driver.find_element的代码而是你能否理解工具背后的逻辑能否用工程化的思维解决实际、复杂的问题。所以这篇“万字详解”的目的不是给你一堆可以复制粘贴的代码片段而是带你穿透Selenium的表面直抵其作为浏览器自动化利器的内核并建立起符合大厂要求的爬虫思维。无论你是想系统学习Selenium还是为即将到来的技术面试做准备这篇文章都将从原理到实战从基础操作到避坑经验为你提供一份详尽的路线图。Selenium的核心价值在于它能模拟真实用户操作浏览器这对于解决当今互联网上越来越多的JavaScript动态渲染页面至关重要。传统的requestsBeautifulSoup组合在面对由React、Vue等框架构建的单页面应用SPA时往往束手无策因为关键数据是在浏览器端执行JS后才生成的。而Selenium通过驱动真实的浏览器如Chrome、Firefox能够完整地执行页面上的所有JavaScript拿到最终渲染后的DOM树从而实现对动态内容的抓取。接下来我们就从环境搭建这个看似简单、实则暗藏玄关的第一步开始。2. 环境搭建与核心组件深度解析2.1 浏览器驱动不只是下载一个exe文件很多人觉得安装Selenium就是pip install selenium然后下载一个浏览器驱动这没错但仅仅做到这一步你只完成了10%。浏览器驱动如ChromeDriver、geckodriver是Selenium与浏览器通信的桥梁它遵循W3C的WebDriver协议。这里面的第一个坑就是版本匹配。浏览器在频繁更新驱动也必须对应更新。一个常见的错误是使用旧版驱动打开新版浏览器导致报错“This version of ChromeDriver only supports Chrome version XX”。解决这个问题最佳实践不是手动去猜版本而是使用webdriver-manager这个第三方库。它可以自动检测你系统已安装的浏览器版本并下载匹配的驱动。pip install webdriver-manager在代码中你可以这样使用它来避免手动管理驱动的烦恼from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # 自动下载并匹配当前Chrome版本的驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)注意在公司内网或网络受限环境下webdriver-manager可能无法从云端下载驱动。此时你需要手动下载驱动并通过Service(executable_path‘你的驱动路径’)来指定。务必从浏览器厂商的官方渠道如ChromeDriver官网下载第三方来源可能有安全风险。除了版本驱动的放置位置也有讲究。通常有三种方式1. 放在项目目录下2. 放在Python的Scripts目录下3. 将其所在目录添加到系统的PATH环境变量中。我推荐第一种因为这样便于项目迁移和版本管理尤其是在团队协作时能确保所有成员使用同一版本的驱动。2.2 Selenium库本身理解其多层架构运行pip install selenium安装的其实是Selenium的客户端库Client Library。它提供了一套友好的Python API比如find_element,click供你调用。当你执行这些命令时客户端库会将其转换为符合WebDriver协议的HTTP请求通常是JSON格式发送给浏览器驱动。驱动接收到命令后再通过浏览器提供的调试接口如Chrome DevTools Protocol来控制浏览器执行相应操作。这个过程是跨语言的这也是为什么Java、C#、JavaScript等语言都有Selenium绑定。理解这个“客户端-驱动-浏览器”的三层架构非常重要。当你的脚本卡住或无响应时你需要能判断问题是出在你的代码逻辑客户端、驱动进程崩溃还是浏览器本身卡死了。例如一个元素找不到可能是你的定位策略问题客户端也可能是页面还没加载完浏览器还可能是驱动与浏览器的通信中断了。2.3 浏览器选择与启动参数为爬虫量身定制默认情况下webdriver.Chrome()会启动一个带有完整用户界面UI的浏览器窗口。这对于调试非常方便但在生产环境进行爬取时这会造成巨大的资源浪费内存、CPU并且容易受到操作系统图形界面的干扰。因此我们通常需要以“无头模式”运行。from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_argument(--headless) # 启用无头模式 chrome_options.add_argument(--disable-gpu) # 在Windows系统上无头模式建议禁用GPU chrome_options.add_argument(--no-sandbox) # 在Linux系统下有时需要此参数来绕过沙盒限制 chrome_options.add_argument(--disable-dev-shm-usage) # 解决Docker等容器内共享内存空间不足的问题 driver webdriver.Chrome(optionschrome_options)启动参数是优化爬虫性能和稳定性的关键。除了无头模式还有一些常用参数--window-size1920,1080即使是无头模式也设置一个窗口大小因为有些网站的响应式布局会依赖于此。--blink-settingsimagesEnabledfalse禁止加载图片可以显著加快页面加载速度。--disable-javascript极端情况下如果不需要JS执行可以禁用但这会使其失去处理动态页面的核心优势需谨慎。user-agent修改User-Agent是基础的反反爬策略之一。浏览器的选择上Chrome/Chromium系是主流因为其生态和DevTools协议最完善。Firefoxgeckodriver也是一个可靠的选择。至于Edge由于它现在也基于Chromium所以可以使用ChromeDriver只需在Options中指定二进制文件路径即可。3. 元素定位八种策略的精准运用与避坑指南定位页面元素是Selenium所有操作的基础。Selenium提供了八种主要的定位策略每种都有其适用场景和陷阱。3.1 八大定位器详解与选择策略IDfind_element(By.ID, “id_value”)。最理想、最高效的定位方式因为ID在HTML中应该是唯一的。但现实是很多大型网站由多个团队开发ID可能不唯一或动态生成。Namefind_element(By.NAME, “name_value”)。常用于表单元素但同样可能不唯一。Class Namefind_element(By.CLASS_NAME, “class_value”)。Class常用于样式一个元素可能有多个class一个class也可能用于多个元素。定位时需注意如果class名包含空格表示多个class此时不能直接用By.CLASS_NAME定位整个字符串而应选择其中一个唯一class或用CSS Selector。Tag Namefind_element(By.TAG_NAME, “div”)。最不精确通常用于查找特定类型的元素集合如获取页面所有链接find_elements(By.TAG_NAME, ‘a’)。Link Textfind_element(By.LINK_TEXT, “完整的链接文本”)。精准定位超链接。Partial Link Textfind_element(By.PARTIAL_LINK_TEXT, “部分链接文本”)。链接文本较长时的模糊匹配。XPathfind_element(By.XPATH, “//tag[attribute‘value’]”)。功能最强大的定位器可以遍历XML/HTML文档的任何节点。但也是性能相对较差、编写最复杂、最容易因页面结构微小变动而失效的定位方式。CSS Selectorfind_element(By.CSS_SELECTOR, “tag#id.class[attributevalue]”)。通常比XPath性能更好语法更简洁是现代Web开发的标准也是我个人的首选推荐。如何选择一个简单的优先级建议ID CSS Selector XPath 其他。尽量避免使用绝对路径的XPath如/html/body/div[3]/div[2]/span只要页面结构稍有调整比如中间多了一个div定位就会失败。应尽量使用相对路径和属性结合的方式。3.2 等待机制解决元素未加载问题的核心钥匙这是Selenium爬虫中最容易出错、也最能体现工程师经验的地方。你写的定位代码语法完全正确但一运行就报NoSuchElementException十有八九是等待问题。1. 强制等待time.sleeptime.sleep(10)。这是最糟糕的方式它固定死等待时间无论页面是否提前加载完成。这会造成时间浪费或者时间到了页面还没加载完导致失败。除非在极少数调试场景否则不要在产品代码中使用。2. 隐式等待Implicit Waitdriver.implicitly_wait(10)。设置一个全局的等待时间在查找任何一个元素时如果元素没有立即找到WebDriver会轮询DOM默认每0.5秒直到找到它或超时。它的问题是作用域是全局的且只对find_element这类查找操作有效对元素的状态如可点击、可见无效。混合使用显式等待时也可能导致意想不到的超时延长。3. 显式等待Explicit Wait这是工业级爬虫的黄金标准。它允许你为某个特定的条件设置等待条件满足则立即继续否则在超时后抛出异常。它提供了极大的灵活性。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待最多10秒直到ID为‘myElement’的元素出现在DOM中并且可见 wait WebDriverWait(driver, 10) element wait.until(EC.visibility_of_element_located((By.ID, “myElement”))) element.click() # 等待元素可被点击 button wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, “.submit-btn”))) button.click() # 等待页面标题包含特定文字 wait.until(EC.title_contains(“订单提交成功”))expected_conditions模块提供了大量预定义条件如元素是否存在、是否可见、是否可点击、弹窗是否出现等。你应该根据你的具体操作来选择合适的条件。例如在点击一个按钮前用element_to_be_clickable等待在获取一个文本前用visibility_of_element_located等待。最佳实践在我的项目中我通常会禁用隐式等待driver.implicitly_wait(0)然后在整个脚本中统一使用显式等待。这提供了最精确的控制避免了隐式和显式等待时间叠加导致的超时混乱。3.3 定位实战与高级技巧有时元素藏在iframe里或者shadow DOM中直接定位会失败。对于iframe你必须先切换到对应的iframe上下文# 通过ID、Name或索引切换 driver.switch_to.frame(“iframe_id”) # 操作iframe内的元素... # 操作完成后切回主文档 driver.switch_to.default_content()对于现代Web组件产生的shadow DOM需要使用JavaScript执行器来穿透# 假设有一个自定义组件其shadow root下有一个按钮 shadow_host driver.find_element(By.CSS_SELECTOR, “my-custom-element”) shadow_root driver.execute_script(‘return arguments[0].shadowRoot’, shadow_host) inner_button shadow_root.find_element(By.CSS_SELECTOR, “button”) inner_button.click()另一个常见问题是动态ID或Class。例如一个元素的ID是“button-12345”其中12345每次刷新页面都会变。这时就不能用完整的ID定位而需要使用XPath或CSS Selector的模糊匹配功能# XPath 使用 contains 或 starts-with element driver.find_element(By.XPATH, “//button[contains(id, ‘button-’)]”) # CSS Selector 使用 ^ (以...开头), $ (以...结尾), * (包含) element driver.find_element(By.CSS_SELECTOR, “button[id^‘button-’]”)4. 核心操作与浏览器控制模拟真实用户行为定位到元素后我们就可以与之交互了。这些操作看似简单但细节决定成败。4.1 基础交互操作点击element.click()。但前面提到最好在点击前用EC.element_to_be_clickable等待。对于某些用div或span模拟的按钮可能需要对父元素点击。有时还需要用ActionChains进行更复杂的点击。输入文本element.send_keys(“your text”)。在输入前一个好的习惯是先element.clear()一下清空可能存在的默认文本或前一次输入的内容。对于富文本编辑器如CKEditor、TinyMCE可能需要直接执行JavaScript来设置内容。获取文本和属性element.text获取元素可见文本。element.get_attribute(“href”)获取元素属性如链接的href图片的src。注意text属性获取的是渲染后的可见文本而get_attribute(“innerHTML”)或get_attribute(“outerHTML”)获取的是HTML源码。下拉选择对于select标签Selenium提供了专门的Select类。from selenium.webdriver.support.ui import Select select_element Select(driver.find_element(By.ID, “country”)) select_element.select_by_visible_text(“中国”) # 通过可见文本选择 select_element.select_by_value(“CN”) # 通过value属性选择 select_element.select_by_index(1) # 通过索引选择从0开始4.2 浏览器导航与窗口管理打开网页driver.get(“https://www.example.com”)。get方法会等待页面完全加载即document.readyState为complete后才返回但这不意味着所有异步JS都执行完了所以通常还需要配合显式等待。前进后退driver.forward(),driver.back()。刷新driver.refresh()。窗口和标签页获取当前窗口句柄current_window driver.current_window_handle。获取所有窗口句柄all_windows driver.window_handles。切换到新窗口driver.switch_to.window(new_window_handle)。关闭当前窗口driver.close()然后记得把句柄切回原来的窗口。4.3 执行JavaScript突破Selenium API的限制execute_script是Selenium的一把瑞士军刀可以完成许多WebDriver API无法直接做到的事情。# 滚动到页面底部 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) # 滚动到指定元素附近 element driver.find_element(By.ID, “target”) driver.execute_script(“arguments[0].scrollIntoView(true);”, element) # 修改元素属性可用于隐藏干扰元素 driver.execute_script(“document.querySelector(‘.ad-banner’).style.display ‘none’;”) # 获取通过JS计算后的样式 bg_color driver.execute_script(“return window.getComputedStyle(arguments[0]).backgroundColor;”, element) # 处理简单的Canvas滑块验证仅限原理实际更复杂 # 可以获取滑块和背景图的像素数据通过JS计算缺口位置这通常需要复杂的图像识别JS并非最佳选择此处仅为示例思路注意虽然execute_script很强大但过度依赖它会让你的脚本变得脆弱因为JS执行依赖于具体的页面实现。优先使用WebDriver原生API当原生API无法实现时再考虑JS。4.4 处理弹窗、Cookie和存储弹窗分为alert、confirm、prompt。使用driver.switch_to.alert来获取弹窗对象然后accept()、dismiss()或send_keys()。alert driver.switch_to.alert print(alert.text) # 获取弹窗文本 alert.accept() # 点击确定 # alert.dismiss() # 点击取消Cookie爬虫中常用于维持登录状态。# 获取所有cookie cookies driver.get_cookies() # 添加cookie (注意添加cookie必须在当前域的页面下进行) driver.add_cookie({‘name’: ‘sessionid’, ‘value’: ‘abc123’}) # 删除所有cookie driver.delete_all_cookies()一个常见技巧是先用浏览器手动登录一次通过开发者工具Application - Storage - Cookies复制关键的登录状态Cookie如sessionid,token然后在Selenium脚本启动浏览器后先访问目标域名下的任意页面如首页再通过add_cookie添加这些Cookie刷新页面后通常就处于登录状态了避免了自动化登录的复杂流程尤其是带验证码的登录。本地存储driver.execute_script(“return localStorage.getItem(‘key’);”)和driver.execute_script(“localStorage.setItem(‘key’, ‘value’);”)。对sessionStorage同理。5. 爬虫实战工程化思维与反反爬策略掌握了基本操作我们将其组合起来构建一个健壮的爬虫。这里以一个需要登录、有分页、数据为动态加载的电商网站商品列表为例。5.1 项目结构与配置管理不要把所有代码都写在一个.py文件里。一个基本的工程化结构如下my_scraper/ ├── config.py # 配置文件存放URL、账号密码、等待时间等常量 ├── logger.py # 日志配置 ├── browser.py # 浏览器驱动初始化、关闭等封装 ├── pages/ # 页面对象模型Page Object Model, POM │ ├── login_page.py │ ├── home_page.py │ └── product_list_page.py ├── utils/ # 工具函数如数据清洗、请求重试 │ └── helpers.py ├── main.py # 主程序入口 └── requirements.txt # 项目依赖在config.py中使用配置文件或环境变量来管理敏感信息不要将账号密码硬编码在代码中。5.2 页面对象模型POM设计模式POM是Selenium自动化测试中的最佳实践同样适用于复杂爬虫。其核心思想是将每个页面封装成一个类页面的元素定位和操作作为这个类的方法。这样当页面UI发生变化时你只需要修改对应的页面类而不需要到处修改定位符。# pages/login_page.py from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class LoginPage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) property def username_input(self): return self.wait.until(EC.presence_of_element_located((By.ID, “username”))) property def password_input(self): return self.driver.find_element(By.ID, “password”) property def submit_button(self): return self.driver.find_element(By.CSS_SELECTOR, “button[type‘submit’]”) def login(self, username, password): self.username_input.send_keys(username) self.password_input.send_keys(password) self.submit_button.click() # 可以返回下一个页面的对象例如首页 from .home_page import HomePage return HomePage(self.driver)在主程序中使用起来非常清晰from browser import get_driver from pages.login_page import LoginPage driver get_driver() login_page LoginPage(driver) home_page login_page.login(“your_username”, “your_password”) # 继续使用 home_page 进行操作...5.3 数据抓取与解析流程以抓取商品列表为例假设列表是异步加载的我们需要滚动页面触发加载。# pages/product_list_page.py import time from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class ProductListPage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 15) # 列表加载可能较慢延长等待 def scroll_to_load_all(self, max_scrolls20): 滚动页面直到没有新商品加载或达到最大滚动次数 last_height self.driver.execute_script(“return document.body.scrollHeight”) scroll_attempts 0 while scroll_attempts max_scrolls: # 滚动到页面底部 self.driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) time.sleep(2) # 等待新内容加载这里可以用更智能的等待比如等待某个加载动画消失 new_height self.driver.execute_script(“return document.body.scrollHeight”) if new_height last_height: # 高度未变可能已加载完毕或触发了“没有更多”的提示 # 可以检查是否存在“没有更多”的元素 break last_height new_height scroll_attempts 1 def get_all_products(self): 获取当前页面所有商品元素的信息 product_elements self.wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, “.product-item”)) ) products_data [] for elem in product_elements: try: # 使用相对定位在元素内部查找子元素避免全局定位冲突 name elem.find_element(By.CSS_SELECTOR, “.product-name”).text # 价格可能被分割线等元素干扰获取整个文本再清洗 price_text elem.find_element(By.CSS_SELECTOR, “.product-price”).text # 简单的数据清洗 price float(price_text.replace(‘¥’, ‘’).replace(‘,’, ‘’).strip()) # 获取属性 link elem.find_element(By.CSS_SELECTOR, “a”).get_attribute(“href”) products_data.append({ “name”: name, “price”: price, “link”: link }) except Exception as e: # 记录日志而不是让单个商品失败导致整个任务终止 print(f”解析商品元素时出错: {e}”) continue return products_data5.4 反反爬策略与道德考量使用Selenium本身就会暴露一些自动化特征如window.navigator.webdriver属性为true。一些高级反爬系统能检测到。我们需要进行一些伪装和优化修改WebDriver属性这是最基本的一步。chrome_options.add_argument(‘--disable-blink-featuresAutomationControlled’) chrome_options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) chrome_options.add_experimental_option(‘useAutomationExtension’, False) # 通过CDPChrome DevTools Protocol执行脚本覆盖navigator.webdriver driver.execute_cdp_cmd(‘Page.addScriptToEvaluateOnNewDocument’, { ‘source’: ‘ Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); ‘ })使用代理IP防止IP被封锁。可以通过chrome_options添加代理。chrome_options.add_argument(‘--proxy-serverhttp://your-proxy-ip:port’)对于需要认证的代理更复杂一些可能需要使用插件或selenium-wire这样的库。控制访问频率在操作之间随机加入等待时间模拟人类行为。不要用固定的sleep用random.uniform(1, 3)这样的随机延迟。模拟人类行为随机滚动页面、随机移动鼠标轨迹使用ActionChains、在输入时加入随机延迟。处理验证码这是最棘手的部分。对于简单的图形验证码可以截图后使用OCR库如pytesseract但识别率堪忧或第三方打码平台。对于复杂的行为验证码如滑块、点选通常需要专门的研究或绕过方案这超出了基础Selenium的范围。遵守robots.txt在爬取前检查目标网站的robots.txt文件通常在网站根目录如https://example.com/robots.txt尊重Disallow规则。这是法律和道德的要求。设置合理的User-Agent轮换使用常见的浏览器User-Agent字符串。6. 性能优化、调试与常见问题排查6.1 性能优化技巧禁用不必要的功能如前所述无头模式、禁用图片、禁用CSS如果需要、禁用WebGL等。使用page_source谨慎driver.page_source获取的是整个DOM的字符串操作较慢。如果只需要部分数据尽量使用元素定位后获取局部HTML或属性。避免频繁的find_element如果需要对同一组元素进行多次操作先find_elements获取列表存起来而不是每次操作都重新查找。并行化对于大规模抓取可以考虑使用多线程或多进程每个进程/线程管理一个独立的WebDriver实例。但要注意资源消耗每个浏览器实例占用内存很大和目标网站的承受能力。更常见的做法是用Selenium处理登录和获取动态数据然后用轻量级的requests库去并发抓取后续的静态页面。6.2 调试技巧非无头模式运行在开发阶段一定要使用有界面的浏览器直观地看到每一步操作。截图当脚本出错时自动截图保存现场。try: some_operation() except Exception as e: driver.save_screenshot(‘error_screenshot.png’) raise e保存页面源码出错时保存page_source到HTML文件方便离线分析。with open(‘debug_page.html’, ‘w’, encoding‘utf-8’) as f: f.write(driver.page_source)使用driver.get_log(‘browser’)可以获取浏览器控制台的错误和日志信息对于调试JavaScript错误非常有用。手动暂停在代码中插入input(“按回车继续...”)方便你手动检查页面状态。6.3 常见问题与解决方案实录问题1ElementNotInteractableException或ElementClickInterceptedException原因元素存在但不可交互。可能被其他元素遮挡、元素不可见CSSdisplay: none或visibility: hidden、或者元素尚未处于可交互状态如仍在动画中。解决使用EC.element_to_be_clickable等待。尝试用ActionChains移动鼠标到元素上再点击。尝试用JavaScript直接点击driver.execute_script(“arguments[0].click();”, element)。检查是否有弹窗、悬浮广告遮挡。问题2StaleElementReferenceException原因你之前找到的元素引用“过期”了。通常发生在你定位了一个元素并存储到变量中然后页面发生了刷新、导航或重大DOM更新如Ajax重新加载了部分内容之前那个元素的引用就失效了。解决不要长时间持有元素引用。最好是每次需要操作时重新定位元素。如果逻辑上必须持有那么在操作前用try...except包裹捕获此异常后重新定位。问题3脚本运行慢CPU/内存占用高原因浏览器实例本身资源消耗大等待策略不佳页面资源加载过多。解决确保使用无头模式。优化启动参数禁用不必要的功能。使用精准的显式等待代替固定sleep和过长的隐式等待。任务完成后及时调用driver.quit()不是close()彻底关闭浏览器和驱动进程释放资源。问题4如何抓取需要鼠标悬停hover才显示的内容解决使用ActionChains。from selenium.webdriver.common.action_chains import ActionChains menu driver.find_element(By.ID, “dropdown-menu”) ActionChains(driver).move_to_element(menu).perform() # 等待悬停后出现的子菜单 sub_menu wait.until(EC.visibility_of_element_located((By.LINK_TEXT, “子项”))) sub_menu.click()问题5文件上传怎么处理解决对于input type“file”元素直接使用send_keys传入文件的绝对路径即可。upload_input driver.find_element(By.CSS_SELECTOR, “input[type‘file’]”) upload_input.send_keys(“/Users/yourname/Desktop/test.jpg”)注意不能与ActionChains一起使用直接对元素操作。从环境搭建的细节到元素定位的哲学再到工程化的爬虫架构和反反爬的攻防Selenium远不止是一个“自动化测试工具”。把它用好在数据抓取领域要求你既懂前端技术HTML/CSS/JS又懂后端逻辑网络、会话、并发还要有软件工程的思维设计模式、可维护性。这大概就是为什么大厂面试官会如此关注你对Selenium的理解深度——它像一个缩影考察的是你解决复杂、动态问题的综合能力。我个人的体会是初期多踩坑、多调试把每一个异常都搞清楚背后的原因积累自己的“避坑指南”中期开始思考架构如何让代码更健壮、更易扩展后期则要关注效率和伦理做一个负责任的数据获取者。最后分享一个小技巧在编写复杂的等待条件时可以自定义expected_condition这能让你的代码更清晰比如等待某个元素内部的文本变成特定值这在实际项目中非常有用。