Selenium - 动态网页抓取神器
# Selenium - 动态网页抓取神器
当我们面对那些通过 JavaScript 动态加载内容(例如,无限滚动、点击加载更多)的现代网站时,requests
库就显得力不从心了,因为它只能获取到初始的 HTML 源码。为了抓取“所见即所得”的最终页面内容,我们需要 Selenium
。
Selenium
是一个 Web 应用程序的自动化测试框架,但它在爬虫领域被广泛用于模拟真实用户操作浏览器。它可以驱动浏览器执行点击、滚动、输入等各种操作,并获取到经过 JavaScript 渲染后的最终网页源代码。
# 1. 环境准备:安装 Selenium 与浏览器驱动
使用 Selenium
需要两样东西:
selenium
Python 库: 通过 pip 安装。- 浏览器驱动 (WebDriver): 一个独立的可执行文件,作为 Python 脚本与浏览器之间的桥梁。
# 1.1 安装 Selenium 库
pip install selenium
# 1.2 安装浏览器驱动
你需要下载与你的浏览器版本完全对应的 WebDriver。这是一个至关重要的步骤,版本不匹配是导致 Selenium 无法启动的最常见原因。
核心原则:WebDriver 的主版本号必须与你的浏览器主版本号一致。 例如,如果你的 Chrome 版本是
125.0.6422.113
,你就必须使用125.0.6422.xx
版本的 ChromeDriver。
# 第一步:检查你的浏览器版本
- Chrome: 在地址栏输入
chrome://version
并回车。 - Edge: 在地址栏输入
edge://version
并回车。 - Firefox: 在菜单中选择 “帮助” -> “关于 Firefox”。
# 第二步:获取对应的 WebDriver
你有以下两个选择:
选项 A:升级浏览器(强烈推荐)
最简单的方法是确保你的浏览器是最新版本。通常浏览器会自动更新。手动检查更新后,你只需要下载最新的稳定版 WebDriver 即可。
选项 B:寻找匹配的历史版本(当无法升级浏览器时)
如果你的浏览器版本较旧(例如你的版本是 135.0.7049.42
),而官网只显示最新的 138.x
版本,你就需要去官方提供的历史版本库中寻找。
访问 Chrome for Testing 官方仪表板: https://googlechromelabs.github.io/chrome-for-testing/ (opens new window)
找到你的主版本号: 这个页面列出了所有可用的
ChromeDriver
版本。你需要找到与你的浏览器主版本号(比如135
)最接近的那个版本。通常,你应该寻找版本号等于或略低于你浏览器完整版本号的最新一个构建版本。下载对应的驱动: 找到版本后,复制对应平台(如
win64
)的chromedriver
的 URL 并下载。
# 第三步:放置 WebDriver 文件
下载后,解压得到 chromedriver.exe
(或 geckodriver.exe
)。你需要将这个文件放在一个系统可以找到的路径下。推荐两种方式:
放在 Python 脚本同级目录下:最简单,但每个项目都要复制一份。
放在 Python 解释器目录下:一劳永逸。将驱动文件(如
chromedriver.exe
)复制到你的 Python 安装路径的Scripts
文件夹下(例如C:\Python39\Scripts
)。Windwos: py -0p 查看Python路径
# 2. Selenium 基础操作
# 2.1 启动浏览器并访问页面
from selenium import webdriver
import time
# 1. 创建一个 WebDriver 实例,这里以 Chrome 为例
# 这会自动打开一个新的 Chrome 浏览器窗口
driver = webdriver.Chrome()
# 2. 使用 .get() 方法访问一个 URL
driver.get("https://www.baidu.com")
# 3. 打印当前页面的标题
print(f"页面标题: {driver.title}")
# 4. 打印当前页面的 URL
print(f"当前 URL: {driver.current_url}")
# 5. 暂停 3 秒,方便我们观察
time.sleep(3)
# 6. 关闭当前浏览器窗口
driver.close()
# 7. 彻底退出 WebDriver,释放资源
driver.quit()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 2.2 定位页面元素
定位元素是所有自动化操作的第一步。Selenium
提供了多种定位策略,都通过 driver.find_element()
(找一个) 和 driver.find_elements()
(找所有) 方法实现,但需要配合 By
类来指定定位方式。
首先,需要导入 By
类:
from selenium.webdriver.common.by import By
By 定位策略 | 说明 | 适用场景 |
---|---|---|
By.ID | 通过元素 id 属性 | ID 唯一时,最快最稳定 |
By.CLASS_NAME | 通过元素 class 属性 | 按类名查找 |
By.NAME | 通过元素 name 属性 | 多用于表单元素 |
By.TAG_NAME | 通过标签名 | 如 div , p , a |
By.LINK_TEXT | 通过链接的完整文本 | 精确匹配链接文本 |
By.PARTIAL_LINK_TEXT | 通过链接的部分文本 | 模糊匹配链接文本 |
By.CSS_SELECTOR | 通过 CSS 选择器 | 功能最强大、最常用 |
By.XPATH | 通过 XPath 表达式 | 功能同样强大,语法稍复杂 |
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
# 1. 通过 ID 定位搜索框
search_box = driver.find_element(By.ID, "kw")
print(f"找到搜索框: {search_box.tag_name}")
# 2. 通过 NAME 定位搜索框
search_box_by_name = driver.find_element(By.NAME, "wd")
print(f"通过 name 找到: {search_box_by_name.tag_name}")
# 3. 通过 CSS SELECTOR 定位“百度一下”按钮
# 这是最推荐的方式
submit_button = driver.find_element(By.CSS_SELECTOR, "#su")
print(f"找到提交按钮: {submit_button.get_attribute('value')}") # 获取按钮的 value 属性
# 4. 查找所有的 a 标签 (find_elements 返回一个列表)
all_links = driver.find_elements(By.TAG_NAME, "a")
print(f"页面上共有 {len(all_links)} 个链接")
time.sleep(2)
driver.quit()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 3. 与页面元素交互
找到元素后,我们就可以模拟用户的各种操作了。
.click()
: 点击元素。.send_keys(text)
: 在输入框中输入文本。.clear()
: 清空输入框的文本。.submit()
: 提交表单(不常用,通常直接点击提交按钮)。
# 模拟搜索操作
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
# 1. 定位到搜索框
search_box = driver.find_element(By.ID, "kw")
# 2. 在搜索框中输入文字
search_box.send_keys("Python Selenium")
time.sleep(2)
# 3. 定位到“百度一下”按钮并点击
submit_button = driver.find_element(By.ID, "su")
submit_button.click()
# 4. 等待页面加载,观察结果
time.sleep(5)
print(f"搜索结果页标题: {driver.title}")
driver.quit()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 4. 等待!处理动态加载的关键
现代网站大量使用 AJAX 技术,在你访问页面后,内容可能还在加载中。如果你的 find_element
代码执行得太快,元素还没出现,就会抛出 NoSuchElementException
异常。
绝对不要使用 time.sleep()
作为主要的等待方式! 它不智能,要么等待时间太长浪费时间,要么时间太短元素还没加载出来。
Selenium
提供了两种更智能的等待机制:隐式等待和显式等待。
# 4.1 隐式等待 (Implicit Wait)
隐式等待是一个全局设置。一旦设置,WebDriver
在执行任何 find_element
操作时,如果第一时间没找到元素,它会在指定的时间内(例如10秒)反复轮询查找,直到找到元素或超时为止。
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
# 设置一个全局的隐式等待,最长等待 10 秒
driver.implicitly_wait(10)
driver.get("https://www.some-slow-website.com")
# 现在,当执行 find_element 时,如果元素没立即出现,
# driver 会自动等待最多 10 秒。
# 这使得代码看起来很清爽,无需到处写 wait。
my_element = driver.find_element(By.ID, "some_dynamic_element")
2
3
4
5
6
7
8
9
10
11
12
13
14
优点:设置一次,全局生效,代码简洁。 缺点:不够灵活,只能用于等待“元素出现”,无法等待更复杂的条件(如“元素可点击”)。
# 4.2 显式等待 (Explicit Wait) - 最佳实践
显式等待是针对特定元素的、更灵活、更精确的等待方式。它让你等待某个特定条件发生后,再继续执行后续代码。这是最被推荐的等待策略。
你需要导入:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
2
WebDriverWait
结合 expected_conditions
(EC) 提供了丰富的等待条件:
EC.presence_of_element_located(locator)
: 等待元素出现在 DOM 中。EC.visibility_of_element_located(locator)
: 等待元素在页面上可见。EC.element_to_be_clickable(locator)
: 等待元素可被点击。- ... 等等
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
driver = webdriver.Chrome()
driver.get("https://www.taobao.com")
try:
# 1. 创建一个 WebDriverWait 对象
# 参数:driver, 最长等待时间 (秒)
wait = WebDriverWait(driver, 10)
# 2. 定义你要等待的条件
# 这里我们等待 id='q' 的搜索框变得“可被定位到”
# 注意:locator 是一个元组 (By.ID, 'q')
locator = (By.ID, 'q')
search_input = wait.until(EC.presence_of_element_located(locator))
# 3. 等待成功后,再执行操作
search_input.send_keys("笔记本电脑")
# 4. 等待搜索按钮“可被点击”
search_button_locator = (By.CSS_SELECTOR, ".btn-search")
search_button = wait.until(EC.element_to_be_clickable(search_button_locator))
search_button.click()
print("操作成功!")
except Exception as e:
print(f"操作失败: {e}")
finally:
time.sleep(5)
driver.quit()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
结论: 优先使用显式等待。它让你的代码更稳定、更可靠,能精确控制等待的条件和时机。
# 5. 高级操作
# 5.1 获取页面源代码和 Cookies
driver.page_source
: 获取经过浏览器渲染后的完整 HTML 源代码。这是Selenium
对比requests
的核心优势。你可以将它交给Beautiful Soup
解析。driver.get_cookies()
: 获取当前站点的所有 Cookies,返回一个字典列表。
# ... 接上一个淘宝搜索的例子
from bs4 import BeautifulSoup
# 获取渲染后的页面源代码
html = driver.page_source
# 交给 Beautiful Soup 解析
soup = BeautifulSoup(html, 'lxml')
# 现在你可以用 soup.select() 等方法为所欲为了
# 获取 cookies
cookies = driver.get_cookies()
print(f"获取到的 Cookies: {cookies}")
2
3
4
5
6
7
8
9
10
11
12
13
# 5.2 执行 JavaScript 脚本
Selenium
允许你直接在当前页面执行任意的 JavaScript 代码,这在处理某些特殊场景时非常有用,比如滚动页面。
driver.execute_script(script, *args)
# ...
# 1. 将页面滚动到底部
# 这常用于触发“无限滚动”加载更多内容
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(3)
# 2. 你还可以用 JS 来直接操作元素,例如点击
# 这有时可以绕过一些对 .click() 的监听
button_to_click = driver.find_element(By.ID, "some_button")
driver.execute_script("arguments[0].click();", button_to_click)
2
3
4
5
6
7
8
9
10
11
12
# 5.3 无头模式 (Headless Mode)
在开发和调试时,我们希望看到浏览器窗口。但在部署到服务器上时,我们不需要图形界面。Selenium
支持在“无头模式”下运行,即在后台运行浏览器,不显示窗口。
from selenium.webdriver.chrome.options import Options
# 1. 创建一个 Options 对象
chrome_options = Options()
# 2. 添加无头模式参数
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu") # 在某些系统上需要
# 3. 在创建 driver 时传入这个 options
driver = webdriver.Chrome(options=chrome_options)
# 后续操作完全一样,只是你看不到浏览器界面了
driver.get("https://www.python.org")
print(driver.title)
driver.quit()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
掌握了 Selenium
,你就拥有了应对几乎所有类型网站的终极爬虫能力。虽然它的运行速度比 requests
慢,资源消耗也更大,但在处理动态内容时,它是不可或缺的。
最佳实践: 将 requests
和 Selenium
结合使用。对于静态页面或 API 接口,使用高效的 requests
;仅在需要处理动态加载、模拟登录等复杂场景时,才动用 Selenium
。