Day 6: 动态页面爬取
版本基线:本文基于 Python 3.12+,requests 2.x / Scrapy 2.x / Selenium 4.x,更新于 2026-09。
学习目标
- 掌握 Selenium 的基本使用
- 学会使用 Playwright 爬取动态页面
- 了解无头浏览器模式
- 掌握反检测技术
技术原理
6.1 动态页面特点
现代网页大量使用 JavaScript 动态加载内容:
常见动态加载方式:
- AJAX 请求
- WebSocket
- DOM 操作
- 单页应用(SPA)
传统爬虫的局限:
- requests 只能获取初始 HTML
- 无法执行 JavaScript
- 无法获取动态加载的内容
6.2 浏览器自动化原理
浏览器自动化工具通过控制真实浏览器来获取动态内容:
工作流程:
- 启动浏览器实例
- 访问目标 URL
- 等待页面加载完成
- 执行 JavaScript
- 获取渲染后的 DOM
- 提取数据
6.3 Selenium 简介
Selenium 是最流行的浏览器自动化工具,支持多种浏览器和编程语言。
主要特点:
- 支持 Chrome、Firefox、Safari 等
- 支持多种编程语言
- 模拟真实用户操作
- 支持无头模式
6.4 Playwright 简介
Playwright 是微软开发的现代化浏览器自动化工具。
主要特点:
- 更快的执行速度
- 更好的稳定性
- 自动等待机制
- 支持多浏览器
案例
案例1:Selenium 基础使用
驱动管理:Selenium Manager(Selenium 4.6+)
Selenium 4.6 起内置 Selenium Manager:webdriver.Chrome() 会自动检测浏览器版本并下载、管理匹配的驱动,无需再手动下载 ChromeDriver/geckodriver(旧的 chromedriver.chromium.org 手动下载流程已被 Chrome for Testing 取代)。只需 pip install selenium 并确保本机装有浏览器即可。
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
# 创建 Chrome 浏览器实例(Selenium 4.6+ 自动管理驱动,无需手动下载)
driver = webdriver.Chrome()
try:
# 访问网页
driver.get('https://example.com')
# 等待元素加载
wait = WebDriverWait(driver, 10)
element = wait.until(
EC.presence_of_element_located((By.TAG_NAME, 'h1'))
)
# 获取标题
title = driver.title
print(f'页面标题: {title}')
# 获取元素文本
h1_text = driver.find_element(By.TAG_NAME, 'h1').text
print(f'H1内容: {h1_text}')
finally:
# 关闭浏览器
driver.quit()案例2:Playwright 基础使用
from playwright.sync_api import sync_playwright
# 使用 Playwright
with sync_playwright() as p:
# 启动浏览器
browser = p.chromium.launch(headless=False)
# 创建页面
page = browser.new_page()
# 访问网页
page.goto('https://example.com')
# 等待元素
page.wait_for_selector('h1')
# 获取标题
title = page.title()
print(f'页面标题: {title}')
# 获取元素文本
h1_text = page.locator('h1').text_content()
print(f'H1内容: {h1_text}')
# 关闭浏览器
browser.close()应用场景
1. 单页应用爬取
- React、Vue、Angular 应用
- 动态加载的内容
2. 需要登录的网站
- 模拟登录流程
- 维护登录状态
3. 交互式页面
- 需要点击、滚动
- 表单提交
4. 反爬严格的网站
- 需要执行 JavaScript
- 检测浏览器环境
代码案例
案例3:Selenium 等待策略
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.common.exceptions import TimeoutException
driver = webdriver.Chrome()
try:
driver.get('https://example.com')
# 显式等待 - 等待元素出现
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'dynamic-content'))
)
print(f'元素文本: {element.text}')
except TimeoutException:
print('等待超时')
# 等待元素可点击
button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '.load-more'))
)
button.click()
# 等待文本出现
WebDriverWait(driver, 10).until(
EC.text_to_be_present_in_element((By.ID, 'status'), '完成')
)
finally:
driver.quit()案例4:Selenium 模拟用户操作
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
import time
driver = webdriver.Chrome()
try:
driver.get('https://example.com')
# 输入文本
search_box = driver.find_element(By.ID, 'search-input')
search_box.send_keys('Python 爬虫')
# 按回车键
search_box.send_keys(Keys.RETURN)
# 等待搜索结果
time.sleep(2)
# 点击链接
link = driver.find_element(By.CSS_SELECTOR, '.result-link')
link.click()
# 鼠标悬停
element = driver.find_element(By.ID, 'menu')
actions = ActionChains(driver)
actions.move_to_element(element).perform()
# 滚动页面
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
# 截图
driver.save_screenshot('screenshot.png')
finally:
driver.quit()案例5:Playwright 自动等待
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# 访问页面
page.goto('https://example.com')
# 自动等待 - Playwright 会自动等待元素
page.locator('#dynamic-content').click()
# 等待特定状态
page.locator('.loading').wait_for(state='hidden')
# 等待网络空闲
page.wait_for_load_state('networkidle')
# 获取动态内容
content = page.locator('.dynamic-text').text_content()
print(f'动态内容: {content}')
browser.close()案例6:Playwright 处理弹窗
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# 处理对话框
page.on('dialog', lambda dialog: dialog.accept())
page.goto('https://example.com')
# 触发弹窗
page.locator('#alert-button').click()
# 处理新窗口
with page.expect_popup() as popup_info:
page.locator('#new-window-link').click()
popup = popup_info.value
print(f'新窗口标题: {popup.title()}')
browser.close()案例7:反检测技术
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
def create_stealth_driver():
"""创建反检测浏览器"""
options = Options()
# 无头模式(Chrome 109+ 推荐使用新版无头模式 --headless=new,
# 行为更接近真实浏览器,旧版 --headless 已废弃)
options.add_argument('--headless=new')
# 禁用自动化特征
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
# 设置窗口大小
options.add_argument('--window-size=1920,1080')
# 设置 User-Agent(当前主流 Chrome UA,用时请更新版本号)
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36')
# 创建驱动
driver = webdriver.Chrome(options=options)
# 修改 webdriver 属性
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
return driver
# 使用反检测驱动
driver = create_stealth_driver()
try:
driver.get('https://example.com')
print(f'页面标题: {driver.title}')
finally:
driver.quit()案例8:Playwright 反检测
from playwright.sync_api import sync_playwright
def create_stealth_browser():
"""创建反检测浏览器"""
p = sync_playwright().start()
# 启动浏览器
browser = p.chromium.launch(
headless=False,
args=[
'--disable-blink-features=AutomationControlled',
]
)
# 创建上下文
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
# 当前主流 Chrome UA,用时请更新版本号
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
)
# 添加脚本隐藏自动化特征
context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
""")
return p, browser, context
# 使用反检测浏览器
p, browser, context = create_stealth_browser()
try:
page = context.new_page()
page.goto('https://example.com')
print(f'页面标题: {page.title()}')
finally:
browser.close()
p.stop()2026 反检测现状
上面两例的“手工隐身”手段(隐藏 navigator.webdriver、excludeSwitches 等基于 CDP/启动参数的补丁)对新一代指纹检测(TLS 指纹、Canvas/WebGL 指纹、行为分析)基本失效,只能对付最基础的检测。当前社区主流的反检测方案:
- Selenium:undetected-chromedriver ——
pip install undetected-chromedriver,用uc.Chrome()替换webdriver.Chrome()即可,自动修补自动化特征; - Playwright:patchright —— Playwright 的反检测分支,API 与 Playwright 兼容,
pip install patchright后执行patchright install chromium;或 playwright-stealth —— 在现有 Playwright 代码上应用一组隐身补丁脚本。
反检测只是降低被识别的概率,并非“百毒不侵”。请把功夫优先花在降低频率、使用官方 API 与合规采集上。
案例9:Playwright 异步 API
现代爬虫普遍基于异步编排(配合 Day 7 的 asyncio),Playwright 原生提供 async API:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# 以 toscrape 的 JS 渲染版为例:页面内容由 JavaScript 生成,
# requests 拿不到数据,必须用真实浏览器渲染
await page.goto('https://quotes.toscrape.com/js/')
# 异步等待元素出现
await page.wait_for_selector('div.quote')
# 异步提取所有名言文本
quotes = await page.locator('div.quote span.text').all_text_contents()
for q in quotes[:3]:
print(q)
await browser.close()
asyncio.run(main())案例10:Playwright 网络拦截与接口抓取
现代网页的数据大多来自 XHR/fetch 接口。拦截请求、直接抓接口返回 是 Playwright 最常用的能力,往往比解析渲染后的 DOM 更稳定:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# 1) route 拦截:命中 **/api/** 的请求可修改、放行或中断
def handle_route(route):
# route.abort() # 中断请求(如屏蔽广告/图片提速)
# route.continue_(headers={**route.request.headers, 'X-Tag': 'crawler'}) # 修改请求头
route.continue_() # 放行
page.route('**/api/**', handle_route)
# 2) expect_response:等待某个接口的响应并直接读取返回内容
with page.expect_response(
lambda r: '/api/quotes' in r.url
) as response_info:
# quotes.toscrape.com/scroll/ 通过 /api/quotes?page=N 接口加载名言
page.goto('https://quotes.toscrape.com/scroll/')
data = response_info.value.json()
print(f'接口返回 {len(data["quotes"])} 条, has_next: {data["has_next"]}')
print(data['quotes'][0]['text'])
browser.close()案例11:实际爬虫示例
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
import json
import time
class DynamicSpider:
"""动态页面爬虫"""
def __init__(self, headless=True):
self.options = webdriver.ChromeOptions()
if headless:
# Chrome 109+ 推荐新版无头模式
self.options.add_argument('--headless=new')
self.driver = webdriver.Chrome(options=self.options)
self.wait = WebDriverWait(self.driver, 10)
def crawl(self, url):
"""爬取页面"""
self.driver.get(url)
# 等待内容加载
self.wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, '.content'))
)
# 提取数据
data = self.extract_data()
return data
def extract_data(self):
"""提取数据"""
items = []
# 查找所有商品
products = self.driver.find_elements(By.CSS_SELECTOR, '.product-item')
for product in products:
item = {
'name': product.find_element(By.CSS_SELECTOR, '.name').text,
'price': product.find_element(By.CSS_SELECTOR, '.price').text,
}
items.append(item)
return items
def scroll_to_bottom(self):
"""滚动到页面底部"""
self.driver.execute_script(
'window.scrollTo(0, document.body.scrollHeight)'
)
time.sleep(2)
def close(self):
"""关闭浏览器"""
self.driver.quit()
# 使用示例
spider = DynamicSpider(headless=False)
try:
url = 'https://example.com/products'
data = spider.crawl(url)
print(json.dumps(data, ensure_ascii=False, indent=2))
finally:
spider.close()课后练习
练习1:爬取动态加载的评论
使用 Selenium 爬取需要滚动加载的评论数据。
练习2:模拟登录
使用 Playwright 模拟登录网站并获取登录后的数据。
练习3:处理验证码
研究常见的验证码类型和处理方法。
常见问题
Q1: Selenium 和 Playwright 该选哪个?
A: 新项目推荐 Playwright,性能更好、更稳定。已有 Selenium 项目可以继续使用。
Q2: 无头模式和有头模式有什么区别?
A: 无头模式不显示浏览器界面,速度更快,适合服务器环境。有头模式可以看到浏览器操作,适合调试。
Q3: 如何提高动态页面爬取速度?
A: 使用无头模式、减少等待时间、禁用图片加载、使用多线程等。