当静态请求再也拿不到数据时,是时候请出Selenium这位“浏览器代言人”了。
一、为什么你的爬虫需要Selenium?
如果你写过爬虫,一定遇到过这种情况:用requests辛辛苦苦写好的代码,运行后却发现——页面源码里根本没有你想要的数据!
这不是你的代码有问题,而是因为现代网页早已不是简单的静态HTML了。
1.1 动态网页的“障眼法”
如今的网站普遍采用动态渲染技术:数据通过JavaScript异步加载,页面内容在浏览器中才“拼装”完成。你在“检查元素”里看到的内容,在最初的HTML源码中根本不存在。
| 对比维度 | 静态网页 | 动态网页(AJAX/JS渲染) |
|---|---|---|
| 数据位置 | HTML源码中直接存在 | 通过JS异步加载后插入 |
| 请求方式 | 一次GET搞定 | 可能需多次请求API |
| 传统爬虫 | requests+BeautifulSoup直接解析 | 只能拿到空壳页面 |
| 典型代表 | 旧式博客、文档站 | 电商、社交媒体、SPA应用 |
1.2 Selenium的独特价值
Selenium是一个浏览器自动化工具,它能做的就是——像真人一样操作浏览器。这意味着:
- 自动执行JavaScript,拿到完全渲染的页面
- 模拟点击、滚动、输入等用户操作
- 处理需要交互才能加载的数据(无限滚动、点击“加载更多”)
- 应对复杂的登录、弹窗、验证码场景
形象点说:requests是派个“信使”去拿快递,快递没到他就走了;Selenium是派个“人”去等,等到快递到了再拿回来。
二、环境搭建:半小时搞定Selenium
2.1 安装Selenium库
pip install selenium
2.2 下载浏览器驱动
Selenium需要“驱动程序”来控制浏览器。以最常用的Chrome为例:
- 查看Chrome版本:打开浏览器 → 设置 → 关于Chrome
- 下载对应版本的ChromeDriver:访问ChromeDriver下载页
- 将驱动放入系统PATH,或在代码中指定路径
新手福利:使用webdriver-manager自动管理驱动,再也不用手动下载匹配了!
pip install webdriver-manager
2.3 验证环境
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
# 自动下载并配置驱动
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
# 访问网页
driver.get("https://www.baidu.com")
# 打印标题,验证成功
print(driver.title)
# 关闭浏览器
driver.quit()
如果看到浏览器自动打开并打印出“百度一下,你就知道”,恭喜你,环境配置成功!
三、核心用法:从入门到精通
3.1 元素定位的N种姿势
Selenium提供了8种定位方式,通过By模块调用:
from selenium.webdriver.common.by import By
# 最常用的几种定位方式
driver.find_element(By.ID, "login-btn") # ID定位(最快)
driver.find_element(By.NAME, "username") # name属性
driver.find_element(By.CLASS_NAME, "product-title") # 类名
driver.find_element(By.TAG_NAME, "h1") # 标签名
driver.find_element(By.CSS_SELECTOR, "#catalog li") # CSS选择器(推荐)
driver.find_element(By.XPATH, "//div[@class='price']") # XPath
# 获取多个元素
elements = driver.find_elements(By.CSS_SELECTOR, ".product-item")
实战建议:优先使用ID和CSS选择器,它们通常比XPath更稳定、更快速。
3.2 等待机制:告别“元素未找到”错误
动态网页最大的坑就是时序问题——你找元素的时候,它还没加载出来。Selenium提供了两种等待机制:
显式等待(推荐)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 等待最多10秒,直到元素可见
wait = WebDriverWait(driver, 10)
element = wait.until(
EC.visibility_of_element_located((By.ID, "dynamic-content"))
)
# 等待元素可点击
button = wait.until(
EC.element_to_be_clickable((By.CLASS_NAME, "load-more"))
)
常用的等待条件:
presence_of_element_located:元素存在于DOM中visibility_of_element_located:元素可见element_to_be_clickable:元素可点击text_to_be_present_in_element:元素中包含特定文本
隐式等待(简单但不够精准)
# 设置全局等待时间(所有find操作最多等3秒)
driver.implicitly_wait(3)
显式等待 vs 隐式等待:显式等待更精准、更高效,是处理动态内容的首选。
3.3 常用操作大全
import time
# 1. 页面导航
driver.get("https://example.com")
driver.back() # 后退
driver.forward() # 前进
driver.refresh() # 刷新
# 2. 元素交互
element = driver.find_element(By.ID, "search-input")
element.send_keys("Python爬虫") # 输入文本
element.clear() # 清空输入
driver.find_element(By.ID, "search-btn").click() # 点击
# 3. 获取信息
print(element.text) # 获取文本
print(element.get_attribute("href")) # 获取属性
print(driver.page_source) # 获取整个页面源码
print(driver.current_url) # 获取当前URL
# 4. 执行JavaScript(万能操作)
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
value = driver.execute_script("return document.title;")
# 5. 处理下拉框
from selenium.webdriver.support.ui import Select
select = Select(driver.find_element(By.ID, "city"))
select.select_by_visible_text("北京") # 根据可见文本选择
select.select_by_value("beijing") # 根据value值选择
四、实战案例一:无限滚动页面抓取
很多网站采用“无限滚动”加载方式——滚动到底部,自动加载更多内容。这种场景正是Selenium的强项。
4.1 抓取知乎“回答”列表
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
def fetch_zhihu_answers(question_url, max_scrolls=10):
"""
抓取知乎问题下的所有回答(无限滚动加载)
"""
driver = webdriver.Chrome()
driver.get(question_url)
# 等待初始回答加载
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".AnswerItem")))
answers_data = []
scroll_count = 0
while scroll_count < max_scrolls:
# 滚动到底部
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 等待新内容加载
time.sleep(2)
# 获取当前页面的所有回答
answer_items = driver.find_elements(By.CSS_SELECTOR, ".AnswerItem")
print(f"第{scroll_count+1}次滚动后,共发现{len(answer_items)}条回答")
# 提取当前页面前10条(示例)
current_answers = []
for item in answer_items[:10]:
try:
# 获取作者
author = item.find_element(By.CSS_SELECTOR, ".AuthorInfo-name").text
# 获取内容(简化版)
content = item.find_element(By.CSS_SELECTOR, ".RichText").text
# 获取点赞数
vote = item.find_element(By.CSS_SELECTOR, ".VoteButton").text
current_answers.append({
'author': author,
'content': content[:100] + "...", # 只取前100字
'votes': vote
})
except Exception as e:
print(f"解析某条回答时出错: {e}")
answers_data.extend(current_answers)
scroll_count += 1
# 检查是否已经到底(没有新加载更多)
new_height = driver.execute_script("return document.body.scrollHeight")
old_height = driver.execute_script("return document.body.scrollHeight")
if new_height == old_height and scroll_count > 1:
print("已滚动到底部")
break
driver.quit()
return answers_data
# 使用示例
# answers = fetch_zhihu_answers("https://www.zhihu.com/question/xxxxx")
# print(f"共抓取{len(answers)}条回答")
五、实战案例二:点击“加载更多”按钮
有些网站用“加载更多”按钮而不是无限滚动,Selenium同样能轻松应对。
5.1 抓取电商商品列表
def fetch_all_products(url, max_clicks=5):
"""
抓取电商网站所有商品(通过点击“加载更多”)
"""
driver = webdriver.Chrome()
driver.get(url)
wait = WebDriverWait(driver, 10)
click_count = 0
while click_count < max_clicks:
try:
# 等待“加载更多”按钮出现并可点击
more_btn = wait.until(
EC.element_to_be_clickable((By.CLASS_NAME, "see-more-button"))
)
# 点击按钮
more_btn.click()
click_count += 1
print(f"第{click_count}次点击加载更多")
# 等待新商品加载
time.sleep(3)
except Exception as e:
print("没有更多商品了")
break
# 获取所有商品
products = driver.find_elements(By.CSS_SELECTOR, ".product-tile")
print(f"共加载{len(products)}件商品")
data = []
for prod in products:
try:
name = prod.find_element(By.CSS_SELECTOR, ".product-name").text
price = prod.find_element(By.CSS_SELECTOR, ".product-price").text
link = prod.find_element(By.CSS_SELECTOR, "a").get_attribute("href")
data.append({
'name': name,
'price': price,
'link': link
})
except:
continue
driver.quit()
return data
六、高级技巧:反反爬虫策略
随着反爬技术升级,直接使用Selenium很容易被识别。网站通过检测浏览器特征来判断是否是自动化工具。
6.1 常见检测点
| 检测点 | 正常浏览器 | Selenium自动化 |
|---|---|---|
navigator.webdriver | undefined | true |
navigator.plugins | 至少几个插件 | 可能为空 |
navigator.languages | ['zh-CN', 'zh'] | 可能缺失 |
| 浏览器窗口特征 | 正常 | 可能过于规整 |
6.2 终极反检测配置
from selenium.webdriver.chrome.options import Options
def create_stealth_driver():
"""
创建“隐身模式”的Chrome浏览器,最大限度避免被检测
"""
chrome_options = Options()
# 1. 基础反检测参数
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])
chrome_options.add_experimental_option('useAutomationExtension', False)
# 2. 伪装User-Agent
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
# 3. 设置窗口大小(模拟真实设备)
chrome_options.add_argument("--window-size=1920,1080")
# 4. 禁用自动化特征(可选,降低被检测风险)
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
# 5. 禁用图片加载(提升速度,减少特征)
prefs = {"profile.managed_default_content_settings.images": 2}
chrome_options.add_experimental_option("prefs", prefs)
# 创建浏览器实例
driver = webdriver.Chrome(options=chrome_options)
# 6. 额外JS注入:修改webdriver属性
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
Object.defineProperty(navigator, 'languages', {
get: () => ['zh-CN', 'zh']
});
"""
})
return driver
6.3 实战经验:被拦截了怎么办?
当你发现Selenium无论如何都过不了验证时,有个反直觉但极有效的技巧——手动访问一次。
真实案例:一位开发者用Selenium爬取Gitee,运行一周后突然被“安全验证”拦截。尝试了所有反检测配置都无效。最后他发现:手动用浏览器访问一次,完成验证后,Selenium就恢复正常了!
原因分析:网站的反爬机制是针对“访问环境”而非“工具本身”的。当你手动完成验证后,环境被标记为“安全”,自动化工具也随之解锁。
6.4 其他反爬应对策略
- 使用代理IP轮换:避免同一个IP高频访问
- 设置随机延迟:模拟人类操作节奏,不要固定间隔
- 模拟真实鼠标移动:使用
ActionChains模拟不规则的鼠标轨迹 - 处理验证码:接入OCR或第三方打码平台
- 遵守robots.txt:尊重网站规则,避免法律风险
七、性能优化:让Selenium飞起来
Selenium最大的痛点就是慢。以下优化技巧能显著提升效率:
7.1 无头模式
chrome_options.add_argument("--headless") # 不显示浏览器窗口
无头模式可以节省大量资源,速度提升30%-50%。
7.2 禁用不必要的资源加载
prefs = {
"profile.default_content_setting_values.images": 2, # 禁用图片
"profile.default_content_setting_values.css": 2, # 禁用CSS
"profile.default_content_setting_values.javascript": 0 # 不禁用JS,否则失去意义
}
chrome_options.add_experimental_option("prefs", prefs)
7.3 使用显式等待替代sleep
# 不推荐
time.sleep(5)
# 推荐
wait.until(EC.presence_of_element_located((By.ID, "target")))
显式等待在元素出现后立即继续执行,比固定等待更高效。
7.4 合理设置页面加载策略
chrome_options.add_argument("--page-load-strategy=eager")
# eager: 等待DOM就绪,不等待图片等资源加载完成
# normal: 默认,等待全部资源加载
# none: 不等待
八、Selenium vs 其他动态爬取方案
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Selenium | 功能全面,社区成熟,调试直观 | 速度慢,资源消耗大 | 需要复杂交互的网站 |
| Playwright | 现代API,跨浏览器,速度更快 | 相对较新 | 新项目首选 |
| Pyppeteer | Puppeteer的Python版,异步支持 | 学习曲线稍陡 | 需要精细控制的场景 |
| Splash | 可部署为服务,适合分布式 | 配置复杂 | Scrapy项目集成 |
| 直接调API | 极快,资源消耗最小 | 需要逆向分析接口 | 能找到API的情况 |
九、总结:什么时候用Selenium?
适合场景
- 页面内容完全由JS渲染,HTML源码为空
- 需要模拟用户交互(点击、滚动、输入)
- 处理“加载更多”或无限滚动
- 需要登录或有复杂操作流程
- 页面结构复杂,用CSS/XPath更容易定位
不适合场景
- 简单的静态页面(用requests+BeautifulSoup更快)
- 大规模数据采集(Selenium太慢)
- 需要极高频率的采集(容易被封)
最后的话
Selenium是一把双刃剑:它能让你拿到几乎任何网页的数据,但也容易被网站识别和拦截。掌握本文介绍的核心用法、等待机制、反检测技巧,再配合合理的爬取策略,你就能在动态网页的海洋里游刃有余。
记住:技术本身没有善恶,但使用技术的方式决定了一切。请尊重网站的robots.txt,控制合理的爬取频率,让我们的爬虫更优雅、更可持续。

35

被折叠的 条评论
为什么被折叠?



