Selenium自动化测试与爬虫实战:从环境搭建到高级技巧

📅 2026/7/4 18:38:04
Selenium自动化测试与爬虫实战:从环境搭建到高级技巧
1. 项目概述为什么我们需要Selenium如果你是一名测试工程师、爬虫开发者或者经常需要和网页打交道的程序员那你大概率听说过Selenium。简单来说Selenium就是一个能让你用代码控制浏览器的工具。想象一下你写一段脚本它就能像真人一样自动打开浏览器、点击按钮、输入文字、滚动页面甚至截图和下载文件。这个能力直接把我们从一个手动、重复的“体力劳动者”变成了一个能指挥浏览器大军自动作战的“指挥官”。我最初接触Selenium是因为厌倦了每天重复的Web应用回归测试。几十个测试用例每个都要手动点一遍不仅耗时还容易因为疲劳而出错。后来我又用它来抓取一些动态加载数据的网站比如电商平台的价格信息、社交媒体上的公开数据这些用传统的静态爬虫库如requests很难搞定因为数据是随着用户滚动或点击才加载出来的。Selenium完美地解决了这个问题因为它驱动的是一个“真实”的浏览器能执行JavaScript渲染出完整的页面。所以这篇教程的目标很明确手把手带你从零开始完成Selenium的安装、配置并写出第一个能真正跑起来的自动化脚本。无论你是想入门自动化测试还是想构建一个强大的网络爬虫这里的内容都是你坚实的第一步。我会把我在实际项目中踩过的坑、总结的技巧毫无保留地分享出来让你少走弯路。2. 环境准备与核心组件选型在真正动手写代码之前把环境搭建好是成功的一半。Selenium的安装不仅仅是pip install selenium那么简单它涉及到几个核心组件的协同工作。理解它们之间的关系能让你在后续遇到问题时快速定位根源。2.1 理解Selenium的“三驾马车”很多人以为Selenium就是一个Python库其实它是一个庞大的项目生态主要由三个核心部分组成Selenium WebDriver这是我们的核心武器。它提供了一套编程接口API允许我们用Python、Java、C#等语言发送指令如“找到登录按钮”、“输入用户名”。WebDriver本身并不包含浏览器它只是一个“遥控器”。浏览器驱动这是连接“遥控器”WebDriver和“电视机”浏览器的“数据线”。每个浏览器Chrome、Firefox、Edge等都需要一个对应的驱动文件如chromedriver、geckodriver。WebDriver的指令通过这条“数据线”传递给真实的浏览器去执行。浏览器本体最终执行操作的主体如Google Chrome、Mozilla Firefox。非常重要的一点是浏览器驱动的版本必须与你的浏览器版本兼容否则指令无法正确传达。我们通常所说的“安装Selenium”主要是指安装WebDriver的语言绑定库和对应的浏览器驱动。2.2 Python环境与Selenium库安装对于Python用户这是最简单的一步。强烈建议使用虚拟环境来管理项目依赖避免不同项目间的包版本冲突。# 1. 创建并激活虚拟环境以venv为例 python -m venv selenium_env # Windows selenium_env\Scripts\activate # macOS/Linux source selenium_env/bin/activate # 2. 安装selenium库 pip install selenium安装完成后你可以通过pip show selenium查看版本。目前主流版本是Selenium 4.x它相比3.x在性能和API上有不少改进本教程也基于4.x版本。2.3 浏览器驱动的下载与配置这是新手最容易出错的地方。我们以最常用的Chrome浏览器为例。第一步查看你的Chrome浏览器版本。打开Chrome点击右上角三个点 - 帮助 - 关于Google Chrome。记下版本号例如128.0.6613.138。第二步下载对应版本的ChromeDriver。前往ChromeDriver的官方下载站。你需要下载与你的Chrome浏览器主版本号一致的驱动。例如Chrome版本是128.x.x.x就下载主版本号为128的ChromeDriver。注意如果官网没有完全匹配的128.x.x.x的小版本通常下载主版本号128相同的最高版本驱动即可大部分情况下是兼容的。如果遇到问题可以尝试下载主版本号一致的稍旧版本。第三步配置驱动路径。下载的是一个可执行文件Windows是chromedriver.exemacOS/Linux是chromedriver。有三种常用配置方法放入系统PATH路径将chromedriver文件放在系统环境变量PATH包含的目录下比如/usr/local/bin(macOS/Linux)或C:\Windows(Windows)。这样Selenium就能自动找到它。指定绝对路径在代码中初始化WebDriver时直接指定驱动文件的完整路径。这是我最推荐新手使用的方法清晰且不易出错。from selenium import webdriver driver webdriver.Chrome(executable_path/你的路径/chromedriver) # Selenium 4.10之前 # Selenium 4.10之后推荐使用Service对象 from selenium.webdriver.chrome.service import Service service Service(executable_path/你的路径/chromedriver) driver webdriver.Chrome(serviceservice)使用第三方管理工具如webdriver-manager库它可以自动下载和管理匹配的浏览器驱动非常方便。pip install webdriver-managerfrom selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)对于Firefoxgeckodriver或Edgemsedgedriver流程完全类似去各自的官网或镜像站下载对应驱动即可。3. 第一个Selenium脚本从打开浏览器到元素操作环境配好了让我们立刻开始写第一个脚本感受一下自动化控制的魔力。我们会从一个最简单的“打开网页”开始逐步增加复杂度。3.1 初始化驱动与打开网页创建一个新的Python文件比如first_script.py。from selenium import webdriver from selenium.webdriver.chrome.service import Service import time # 1. 指定ChromeDriver路径请替换为你的实际路径 chrome_driver_path ./chromedriver # 示例路径驱动文件放在同级目录 # 2. 创建Service对象并初始化WebDriver service Service(executable_pathchrome_driver_path) driver webdriver.Chrome(serviceservice) # 3. 控制浏览器窗口可选但有用的操作 driver.maximize_window() # 最大化窗口确保元素可见 # driver.set_window_size(1200, 800) # 或者设置特定大小 # 4. 打开目标网址 driver.get(https://www.baidu.com) # 5. 等待几秒让我们能看到浏览器操作 time.sleep(3) # 6. 在控制台打印当前页面标题验证成功打开 print(f页面标题是{driver.title}) # 7. 关闭浏览器 driver.quit()运行这个脚本你应该会看到一个新的Chrome窗口自动打开访问百度首页停留3秒后关闭并在控制台打印出“页面标题是百度一下你就知道”。关键点解析webdriver.Chrome()这个调用会启动一个全新的、干净的Chrome浏览器实例用户数据目录是临时的。它和你手动打开的Chrome是隔离的。driver.get(url)这是导航到某个网址的核心方法。driver.quit()非常重要它会关闭浏览器并终止WebDriver进程释放系统资源。务必在脚本最后调用。使用driver.close()只会关闭当前标签页如果只有一个标签页则关闭浏览器但WebDriver进程可能还在。3.2 定位页面元素八种“武器”自动化操作的核心是找到页面上的元素按钮、输入框、链接等。Selenium提供了8种主要的定位策略我把它们比喻成不同的“武器”各有适用场景。定位器示例 (By.XXX)描述适用场景IDBy.ID通过元素的id属性定位。首选。ID通常唯一定位最快、最准。NameBy.NAME通过元素的name属性定位。常用于表单元素如输入框。Class NameBy.CLASS_NAME通过元素的class属性定位。当元素有唯一或特征明显的类名时。Tag NameBy.TAG_NAME通过HTML标签名定位如input。定位特定类型的元素集合如获取所有链接。Link TextBy.LINK_TEXT通过超链接的完整可见文本定位。精准定位带有特定文字的链接。Partial Link TextBy.PARTIAL_LINK_TEXT通过超链接的部分可见文本定位。文本较长或动态时使用不如完整文本精确。CSS SelectorBy.CSS_SELECTOR使用CSS选择器语法定位。功能强大且灵活可组合多种条件性能好。XPathBy.XPATH使用XML路径语言定位。功能最强大可遍历DOM树能处理几乎所有情况但可能稍慢。让我们以百度首页为例实践几种定位方式。目标是实现“在搜索框输入关键词并点击搜索”。首先你需要用浏览器的开发者工具F12查看搜索框和按钮的HTML结构。在百度首页你会发现搜索框的id是kw搜索按钮的id是su。from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By # 导入By类 import time service Service(executable_path./chromedriver) driver webdriver.Chrome(serviceservice) driver.get(https://www.baidu.com) # 方法1使用ID定位最推荐 search_box driver.find_element(By.ID, kw) search_button driver.find_element(By.ID, su) # 方法2使用CSS Selector定位同样高效 # search_box driver.find_element(By.CSS_SELECTOR, #kw) # search_button driver.find_element(By.CSS_SELECTOR, #su) # 方法3使用XPath定位非常灵活 # search_box driver.find_element(By.XPATH, //input[idkw]) # search_button driver.find_element(By.XPATH, //input[idsu]) # 在搜索框中输入文本 search_box.send_keys(Selenium 自动化测试) time.sleep(1) # 稍作等待模拟真人输入 # 点击搜索按钮 search_button.click() # 等待搜索结果加载 time.sleep(3) print(f搜索后页面标题是{driver.title}) driver.quit()实操心得定位优先级IDCSS SelectorXPath 其他。ID是首选因为它是唯一的。如果ID不可用或动态变化CSS Selector通常是性能和可读性的最佳平衡。XPath虽然强大但表达式可能复杂且性能略差在复杂的单页应用SPA中可能不稳定。find_elementvsfind_elementsfind_element返回找到的第一个匹配元素如果没找到会抛出NoSuchElementException。find_elements返回一个列表包含所有匹配元素如果没找到则返回空列表。根据你的需求选择。3.3 模拟用户交互点击、输入与更多定位到元素后我们就可以与之交互了。Selenium提供了丰富的交互方法。1. 点击操作除了标准的click()还有一些特殊点击from selenium.webdriver.common.action_chains import ActionChains element driver.find_element(By.ID, someButton) # 普通点击 element.click() # 双击 action_chains ActionChains(driver) action_chains.double_click(element).perform() # 右键点击上下文菜单 action_chains.context_click(element).perform()2. 输入文本send_keys()是最常用的方法。input_box driver.find_element(By.NAME, username) # 输入文本 input_box.send_keys(my_username) # 组合键操作如全选(CtrlA)后删除 input_box.send_keys(Keys.CONTROL, a) # 全选 input_box.send_keys(Keys.BACKSPACE) # 删除 # 清空输入框推荐 input_box.clear() input_box.send_keys(new_text)3. 处理下拉选择框对于select标签Selenium提供了专门的Select类。from selenium.webdriver.support.ui import Select select_element driver.find_element(By.ID, country) select Select(select_element) # 通过可见文本选择 select.select_by_visible_text(中国) # 通过value属性选择 select.select_by_value(cn) # 通过索引选择从0开始 select.select_by_index(1) # 获取所有选项 all_options select.options for option in all_options: print(option.text)4. 文件上传对于input typefile元素直接使用send_keys()传入文件本地绝对路径即可。file_input driver.find_element(By.CSS_SELECTOR, input[typefile]) file_input.send_keys(/Users/yourname/Desktop/test_image.png)注意不要尝试用click()去点文件上传按钮然后模拟系统对话框这是行不通的。必须直接定位到input[typefile]元素并发送文件路径。4. 高级技巧等待、窗口与滚动当页面元素加载慢或者操作涉及新窗口、复杂交互时我们需要更高级的策略来保证脚本的稳定性和健壮性。4.1 三种等待策略告别time.sleep新手最爱用time.sleep(秒数)来等待但这是一种糟糕的实践。它固定等待指定时间无论页面是否已加载完成既低效又不稳定。Selenium提供了两种智能等待方式。1. 隐式等待在WebDriver对象的整个生命周期内设置一个全局的等待时间。当查找元素时如果元素没有立即出现WebDriver会轮询DOM默认每0.5秒直到找到该元素或超时。driver.implicitly_wait(10) # 单位秒 # 此后所有的find_element操作都会最多等待10秒 element driver.find_element(By.ID, “dynamicElement”)优点设置一次全局生效代码简洁。缺点不够灵活无法等待特定条件如元素可点击、元素包含特定文本。对于某些异步加载的元素仅仅“存在”于DOM中并不意味着它“可交互”。2. 显式等待这是生产环境推荐的最佳实践。它允许你为某个特定的操作设置等待条件在指定时间内轮询直到条件满足。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 创建一个等待对象最多等待10秒轮询间隔0.5秒 wait WebDriverWait(driver, timeout10, poll_frequency0.5) # 等待元素出现在DOM中并可见 element wait.until(EC.visibility_of_element_located((By.ID, “dynamicElement”))) element.click() # 等待元素可被点击 button wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, “.submit-btn”))) button.click() # 等待新窗口出现并切换过去 wait.until(EC.number_of_windows_to_be(2)) # 等待窗口数变为2 new_window driver.window_handles[1] driver.switch_to.window(new_window)expected_conditions模块提供了大量预定义条件如title_is、presence_of_element_located、text_to_be_present_in_element等。优点精准、高效、灵活。只在需要的地方等待且条件明确。建议在项目中混合使用。设置一个较短的全局隐式等待如5秒作为兜底然后在关键交互点如点击按钮后等待新页面、等待Ajax加载使用显式等待。4.2 处理多窗口与iframe多窗口/标签页点击一个链接有时会打开新窗口或标签页。你需要管理这些窗口句柄。# 获取当前所有窗口的句柄一个列表 main_window driver.current_window_handle # 当前窗口句柄 all_handles driver.window_handles # 所有窗口句柄列表 # 点击一个会打开新窗口的链接 driver.find_element(By.LINK_TEXT, “在新窗口打开”).click() # 等待新窗口出现显式等待最佳 wait.until(EC.number_of_windows_to_be(2)) # 切换到新窗口 for handle in driver.window_handles: if handle ! main_window: driver.switch_to.window(handle) break # 在新窗口操作... print(f”新窗口标题{driver.title}”) # 关闭新窗口切换回主窗口 driver.close() driver.switch_to.window(main_window)iframe处理iframe是页面中的嵌套框架你需要先“切入”才能操作其中的元素。# 通过ID、Name或索引切入iframe driver.switch_to.frame(“iframe_id”) # 通过ID driver.switch_to.frame(driver.find_element(By.TAG_NAME, “iframe”)) # 通过元素对象 driver.switch_to.frame(0) # 通过索引第一个iframe # 在iframe内操作元素 driver.find_element(By.ID, “inner_button”).click() # 操作完成后切回主文档 driver.switch_to.default_content() # 或者切回上一级父级iframe driver.switch_to.parent_frame()4.3 执行JavaScript与页面滚动有些操作通过WebDriver API难以直接实现或者需要更底层的控制这时可以借助JavaScript。# 执行简单的JS比如修改元素样式 driver.execute_script(“document.body.style.backgroundColor ‘lightyellow’;”) # 执行带返回值的JS title driver.execute_script(“return document.title;”) print(title) # 最常用的场景滚动页面 # 滚动到页面底部 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) time.sleep(2) # 滚动到页面顶部 driver.execute_script(“window.scrollTo(0, 0);”) # 滚动到特定元素位置 element driver.find_element(By.ID, “footer”) driver.execute_script(“arguments[0].scrollIntoView(true);”, element) # true表示元素与窗口顶部对齐对于需要“滚动加载”内容的页面如很多社交媒体的信息流滚动是获取数据的关键。# 模拟滚动加载多次 last_height driver.execute_script(“return document.body.scrollHeight”) while True: # 滚动到底部 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) # 等待新内容加载 time.sleep(2) # 这里可以用显式等待替代等待某个加载中图标消失 new_height driver.execute_script(“return document.body.scrollHeight”) if new_height last_height: print(“已滚动到底部没有新内容了。”) break last_height new_height5. 实战演练构建一个简易爬虫与常见问题排查掌握了基本操作和高级技巧后让我们通过一个实战项目来串联所有知识。同时我也会分享一些我踩过的“坑”和排查问题的经验。5.1 实战爬取一个动态商品列表假设我们要从一个模拟的电商页面这里以一个有动态加载的商品列表页为例爬取商品名称和价格。页面在滚动时会不断加载新商品。from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import json service Service(executable_path“./chromedriver”) # 添加浏览器选项有时能提升稳定性或避免被检测 options webdriver.ChromeOptions() options.add_argument(‘--disable-blink-featuresAutomationControlled’) # 禁用自动化控制特征 options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) options.add_experimental_option(‘useAutomationExtension’, False) driver webdriver.Chrome(serviceservice, optionsoptions) driver.execute_cdp_cmd(‘Page.addScriptToEvaluateOnNewDocument’, { ‘source’: ‘’ Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); ‘’ }) # 更彻底地隐藏WebDriver特征 wait WebDriverWait(driver, 10) driver.get(“https://example.com/dynamic-product-list”) # 替换为实际目标URL product_list [] seen_products set() # 用于去重 last_product_count 0 try: # 假设商品项有共同的CSS类名 ‘product-item’ for i in range(5): # 最多滚动加载5次 # 等待商品元素出现 product_elements wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, “.product-item”)) ) current_count len(product_elements) if current_count last_product_count: print(f”第{i1}次滚动后商品数量未增加可能已加载完毕。”) # 可以增加一个滚动判断如果滚动后高度不变则跳出 break # 解析当前批次可见的商品 for index in range(last_product_count, current_count): elem product_elements[index] try: # 假设名称在 .name 类里价格在 .price 类里 name_elem elem.find_element(By.CSS_SELECTOR, “.name”) price_elem elem.find_element(By.CSS_SELECTOR, “.price”) name name_elem.text.strip() price price_elem.text.strip() product_id f”{name}_{price}” if product_id not in seen_products: seen_products.add(product_id) product_list.append({“name”: name, “price”: price}) print(f”已获取: {name} - {price}”) except Exception as e: print(f”解析第{index}个商品时出错: {e}”) continue last_product_count current_count # 滚动到底部触发加载更多 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) print(f”第{i1}次滚动完成等待新内容加载...”) time.sleep(2) # 等待网络请求和渲染可根据实际情况调整或替换为显式等待 finally: # 保存数据到JSON文件 with open(‘products.json’, ‘w’, encoding‘utf-8’) as f: json.dump(product_list, f, ensure_asciiFalse, indent2) print(f”共爬取 {len(product_list)} 个商品数据已保存到 products.json”) driver.quit()这个脚本涵盖了等待、滚动、元素查找、数据解析、去重和保存的完整流程是一个实用的爬虫骨架。5.2 常见问题与排查技巧实录即使按照教程操作你也可能会遇到各种问题。下面是我总结的一些高频问题及解决方法。问题1NoSuchElementException(找不到元素)这是最常见的问题。可能原因及排查等待时间不足页面或元素尚未加载完成。解决方案增加隐式/显式等待时间特别是使用EC.visibility_of_element_located或EC.presence_of_element_located。元素在iframe或shadow DOM内你没有切入正确的上下文。解决方案检查页面结构使用driver.switch_to.frame()或Selenium 4提供的shadow DOM相关方法。定位器写错了ID/Class是动态生成的或者你写的CSS/XPath表达式不对。解决方案用浏览器开发者工具F12的“检查”功能仔细核对元素的属性。对于动态ID尝试使用更稳定的属性如>options.add_argument(‘--headless’) # 无头模式 options.add_argument(‘--disable-gpu’) # 禁用GPU在某些系统上需要禁用图片加载如果不需要渲染图片可以提升加载速度。prefs {“profile.managed_default_content_settings.images”: 2} options.add_experimental_option(“prefs”, prefs)复用浏览器会话对于需要登录的复杂测试可以考虑手动登录后保存cookies在脚本中加载cookies复用会话避免每次重新登录。问题5SessionNotCreatedException或UnknownError: cannot connect to chrome可能原因浏览器版本与驱动版本不匹配或者有多个Chrome实例冲突。解决方案再次确认并下载匹配的ChromeDriver。确保没有手动打开的Chrome浏览器占用相同端口。在代码中确保每次driver.quit()都被执行清理旧进程。尝试使用webdriver-manager自动管理驱动版本。调试时善用driver.save_screenshot(‘error.png’)在出错时截图以及print(driver.page_source)或print(driver.current_url)来查看当前页面状态能极大提升排查效率。