四、爬虫自动化——Selenium
1、Selenium基础知识与元素定位
1.1 Selenium介绍
在之前的文章中我们能够深刻学习到使用 request 库进行网页爬取。requests 只能获取服务器返回的 HTML 源码,但是现在网页越来越复杂,很多东西都是通过 JavaScript 进行动态加载。
- Ajax异步加载:微博、知乎的内容都是滚动自动加载更多
- SPA单页应用:整个应用在一个页面,切换页面不刷新浏览器
- 动态渲染:搜索结果、评论列表等需要JS执行后才能够显示
这时候,requests就无法获取到这些动态加载的内容了。因此需要一个能够执行JS的工具库——Selenium。
1.2 Selenium工作原理
Selenium整体架构图示:
工作流程分解:
- Python脚本通过WebDriver协议与浏览器驱动通信
- 浏览器驱动解析命令并且控制浏览器
- 浏览器执行实际操作
- 结果通过驱动返回给Python脚本
1.3 安装与配置Selenium
安装Python库:
💡pip install selenium
下载浏览器驱动:
Selenium版本使用的是W3C WebDriver协议,每个浏览器都需要对应的驱动:
| 浏览器 | 驱动下载 |
| Chrome | https://chromedriver.chromium.org/downloads |
| Firefox | https://github.com/mozilla/geckodriver/releases |
| Edge | https://msedgedriver.azureedge.net/ |
放置驱动:
- 将驱动放到系统PATH环境变量所在目录
- 在代码中指定驱动路径
1.4 8中元素点位方式
from selenium import webdriver from selenium.webdriver.common.by import By # 定位方式在这个类里 # 方式1:通过ID定位(最推荐,有唯一性) driver.find_element(By.ID, 'kw') # 查找 id="kw" 的元素 # 方式2:通过NAME属性定位 driver.find_element(By.NAME, 'wd') # 查找 name="wd" 的元素 # 方式3:通过XPATH定位(最强大,可写任意条件) driver.find_element(By.XPATH, '//input[@id="kw"]') # 相对路径 driver.find_element(By.XPATH, '//div[@class="wrapper"]//input') # 支持层级 # 方式4:通过CSS选择器定位(也很强大) driver.find_element(By.CSS_SELECTOR, '#kw') # ID选择器 driver.find_element(By.CSS_SELECTOR, '.s_ipt') # 类选择器 driver.find_element(By.CSS_SELECTOR, 'input[name="wd"]') # 属性选择器 # 方式5:通过CLASS_NAME定位(注意:class有多个时只能选一个) driver.find_element(By.CLASS_NAME, 's_ipt') # 查找 class="s_ipt" 的元素 # 方式6:通过标签名定位 driver.find_element(By.TAG_NAME, 'input') # 查找第一个 <input> 标签 # 方式7:通过完整链接文字定位 driver.find_element(By.LINK_TEXT, '新闻') # 查找链接文字完全匹配"新闻"的<a> # 方式8:通过部分链接文字定位 driver.find_element(By.PARTIAL_LINK_TEXT, '新') # 查找链接文字包含"新"的<a>定位方式选择建议:
- 有ID使用ID
- 复杂条件使用XPATH或CSS
- 链接文字使用LINK_TEXT或PARTIALL_LINK_TEXT
- 多个同类元素,使用find_element(加s)获取列表
1.5 实战示例
""" Selenium基本使用流程: 1. 导入库 2. 配置选项(可选) 3. 创建driver对象 4. 打开网页 5. 定位元素并操作 6. 获取结果 7. 关闭浏览器 """ from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys # 键盘按键 from selenium.webdriver.chrome.options import Options # Chrome配置 from selenium.webdriver.chrome.service import Service # ==================== 配置无头模式 ==================== # 无头模式:不显示浏览器窗口,后台运行 options = Options() #options.add_argument('--headless') # 开启无头模式 options.add_argument('--disable-gpu') # 禁用GPU硬件加速 options.add_argument('--window-size=1920,1080') # 设置窗口大小 # ==================== 创建driver并操作 ==================== # 创建Chrome浏览器实例 # 指定 chromedriver 路径 service = Service('chromedriver.exe') driver = webdriver.Chrome(service=service, options=options) # 打开百度首页 driver.get('https://www.baidu.com') # 定位搜索框(通过ID) search_box = driver.find_element(By.ID, 'kw') # 在搜索框输入文字 search_box.send_keys('Python') # 按下回车键(Keys.RETURN = Enter键) search_box.send_keys(Keys.RETURN) # 等待页面加载后,获取标题 print(f'页面标题:{driver.title}') # 截图保存 driver.save_screenshot('result.png') # ==================== 关闭浏览器 ==================== driver.quit() # 正常关闭 # driver.close() # 只关闭当前窗口,不释放资源💡使用异常处理机制:
1.NoSuchElementException:找不到元素,可能是还没加载完成,需要加等待 2.ElementNotInteractableException:元素不可交互,可能在iframe里或被遮挡 3.StaleElementReferenceException:元素过期,页面已刷新需要重新定位 4.WebDriverException:驱动问题,检查驱动版本是否匹配
2、Selenium等待与交互
2.1 等待用途
在使用Selenium时,常见的问题是“元素找不到”,主要是因为:
- 网络延迟导致页面加载慢
- 网页使用Ajax异步加载,数据不是一次性返回
- JavaScript执行需要时间
例如当加载页面时:
- 收到HTML文档(requests获取)
- 解析HTML,发现需要加载JS/CSS/图片
- 执行JavaScript,需要请求更多的数据
- DOM更新,页面内容完成最终渲染
如果在第一步完成后就立刻查找元素,那些通过JS动态生成的内容肯定找不到,因此需要等待。
2.2 等待方式
| 等待方式 | 语法 | 优点 | 缺点 | 场景 |
| 强制等待 | time.sleep() | 简单 | 浪费时间 | 调试时临时调用 |
| 隐式等待 | driver.implicitly_wait() | 设置一次全局生效 | 只对于查找元素生效 | 配合显示等待使用 |
| 显示等待 | WebDriverWait() | 条件满足继续 | 代码复杂 | 推荐使用 |
import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC service = Service('chromedriver.exe') driver = webdriver.Chrome(service=service) # ==================== 方式1:强制等待 ==================== # 不管元素是否加载完成,都等够指定时间 time.sleep(3) # 必须等3秒,即使1秒就加载完了 # ==================== 方式2:隐式等待 ==================== # 设置全局等待时间,在查找元素时会轮询直到找到或超时 driver.implicitly_wait(10) # 10秒内一直尝试找元素 driver.get('https://example.com') element = driver.find_element(By.ID, 'content') # 会等待最多10秒 # ==================== 方式3:显式等待(推荐) ==================== # 更智能,只等到条件满足为止 wait = WebDriverWait(driver, timeout=10) # 最多等10秒 # 等待元素出现(存在DOM中) element = wait.until( EC.presence_of_element_located((By.ID, 'content')) ) # 等待元素可点击 button = wait.until( EC.element_to_be_clickable((By.ID, 'submit')) ) # 等待元素可见 visible_element = wait.until( EC.visibility_of_element_located((By.CLASS_NAME, 'result')) )2.3 常用等待条件
expected_conditions 模块提供了预支等待条件:
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 判断元素存在(DOM中存在即可,不一定可见) EC.presence_of_element_located((By.ID, 'element_id')) # 判断元素可见(肉眼可见) EC.visibility_of_element_located((By.ID, 'element_id')) # 判断元素可点击(可见且启用) EC.element_to_be_clickable((By.ID, 'element_id')) # 判断元素被选中(checkbox/radio) EC.element_to_be_selected((By.ID, 'element_id')) # 判断文本出现在元素中 EC.text_to_be_present_in_element((By.ID, 'element_id'), '期望的文本') # 判断页面标题包含某文字 EC.title_contains('期望的标题') # 判断元素可交互(可见且启用) EC.element_to_be_clickable((By.XPATH, '//button[@type="submit"]')) # 等待多个元素中的任意一个出现 EC.presence_of_all_elements_located((By.TAG_NAME, 'a'))示例:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.chrome.service import Service service = Service('chromedriver.exe') driver = webdriver.Chrome(service=service) driver.get('https://www.baidu.com') # 创建等待对象,设置超时10秒,轮询间隔0.5秒 wait = WebDriverWait(driver, 10, poll_frequency=0.5) # 等待搜索框出现并可交互 search_box = wait.until( EC.element_to_be_clickable((By.ID, 'kw')) ) search_box.send_keys('Selenium') # 等待搜索按钮出现并可点击 search_button = wait.until( EC.element_to_be_clickable((By.ID, 'su')) ) search_button.click() # 等待搜索结果标题包含关键词 wait.until(EC.title_contains('Selenium')) print(f'搜索完成,标题:{driver.title}')2.4 执行JavaScript
# ==================== 滚动页面 ==================== # 滚动到页面底部 driver.execute_script('window.scrollTo(0, document.body.scrollHeight)') # 滚动到页面顶部 driver.execute_script('window.scrollTo(0, 0)') # 滚动到指定元素位置 element = driver.find_element(By.ID, 'footer') driver.execute_script('arguments[0].scrollIntoView();', element) # ==================== 获取页面信息 ==================== # 获取页面标题 title = driver.execute_script('return document.title') # 获取页面URL url = driver.execute_script('return window.location.href') # 获取页面高度 height = driver.execute_script('return document.body.scrollHeight') # ==================== 操作隐藏元素 ==================== # 对于隐藏的input,可以直接用JS赋值 driver.execute_script("arguments[0].value='test';", hidden_input) # ==================== 处理alert弹窗 ==================== # 获取alert文本 alert_text = driver.execute_script("return document.querySelector('.modal').textContent") # 关闭alert driver.execute_script("window.confirm = function(){return true;}")2.5 交互操作
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains # ==================== 输入框操作 ==================== element.send_keys('文本') # 输入文本 element.send_keys(Keys.RETURN) # 按回车 element.send_keys(Keys.ENTER) # 按回车(与RETURN等价) element.send_keys(Keys.CONTROL, 'a') # 全选 element.send_keys(Keys.BACKSPACE) # 删除 element.clear() # 清空输入框 # ==================== 鼠标操作 ==================== # 单击 element.click() # 右键点击(上下文菜单) ActionChains(driver).context_click(element).perform() # 双击 ActionChains(driver).double_click(element).perform() # 悬停(鼠标移动到元素上) ActionChains(driver).move_to_element(element).perform() # 拖拽 source = driver.find_element(By.ID, 'drag') target = driver.find_element(By.ID, 'drop') ActionChains(driver).drag_and_drop(source, target).perform() # ==================== 键盘操作 ==================== # 组合键 ActionChains(driver).key_down(Keys.CONTROL).send_keys('a').key_up(Keys.CONTROL).perform()