动态网页(基于React/Vue/Angular等框架)和AJAX异步加载的数据,是爬虫开发中的高频难点——传统Requests直接抓取只能拿到静态HTML骨架,无法获取JS渲染后的内容或AJAX加载的动态数据。本文结合实战经验,从“场景区分→工具选型→核心技巧→反爬应对”四个维度,详解如何用Selenium(模拟浏览器)和Requests(直接抓接口)破解动态内容,帮你高效搞定各类动态网页抓取。
一、先分清场景:该用Selenium还是Requests?
动态网页的核心难点分两类,对应不同的解决方案,盲目选择会导致效率低下或抓取失败:
| 动态类型 | 核心特征 | 推荐工具 | 优势 | 劣势 |
|---|---|---|---|---|
| JS渲染(SPA单页应用) | 页面内容通过JS动态生成,刷新后HTML无核心数据;需模拟浏览器执行JS | Selenium/Playwright | 无需分析接口,直接获取渲染后内容;支持模拟用户操作 | 速度慢(启动浏览器耗时);资源占用高 |
| AJAX异步加载 | 页面骨架静态,核心数据(列表、详情)通过XHR/Fetch请求加载,返回JSON/HTML片段 | Requests | 速度快(直接抓接口);资源占用低;并发能力强 | 需分析接口参数;加密接口需破解签名/token |
选型原则:
优先用Requests抓AJAX接口(效率最高),仅当接口复杂(加密参数难破解)或需模拟用户操作(登录、滑动验证)时,用Selenium兜底。
二、工具准备:环境搭建与核心库安装
# 基础库(Requests+解析)
pip install requests==2.31.0 lxml==4.9.3 beautifulsoup4==4.12.3
# Selenium(模拟浏览器)
pip install selenium==4.21.0 webdriver-manager==4.0.1 undetected-chromedriver==3.5.5
# 辅助库(处理JSON/加密)
pip install jsonpath-ng==1.6.1 pycryptodome==3.20.0
关键工具说明:
:支持无头模式、显式等待,稳定性提升;
selenium 4.x:自动下载匹配浏览器版本的驱动(Chrome/Firefox),无需手动配置;
webdriver-manager:规避Selenium的webdriver特征检测(反爬常用手段);
undetected-chromedriver:快速提取JSON数据(AJAX接口返回常用)。
jsonpath-ng
三、方案一:Selenium实战——模拟浏览器破解JS渲染
Selenium的核心是“模拟真实用户操作浏览器”,让浏览器执行JS渲染页面后,再提取内容。适合处理SPA单页应用(如Vue/React项目)或需模拟操作的场景(登录、滑动、点击加载更多)。
3.1 核心配置:打造“反检测”浏览器
默认Selenium会暴露特征(如
webdriver),容易被网站识别并封禁。以下是优化后的配置:
window.navigator.webdriver=true
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
import time
def create_anti_detect_driver():
"""创建反检测的Chrome驱动"""
chrome_options = Options()
# 1. 基础优化:无头模式(不显示浏览器窗口,提升速度)
chrome_options.add_argument("--headless=new") # Selenium 4.10+推荐的无头模式
chrome_options.add_argument("--disable-gpu") # 禁用GPU加速(避免报错)
# 2. 反检测配置:去除webdriver特征
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option("useAutomationExtension", False)
# 3. 模拟真实浏览器环境
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
chrome_options.add_argument("--no-sandbox") # 禁用沙箱(Linux环境必需)
chrome_options.add_argument("--disable-dev-shm-usage") # 禁用/dev/shm(避免内存不足)
# 4. 禁用图片加载(提升速度,可选)
prefs = {"profile.managed_default_content_settings.images": 2}
chrome_options.add_experimental_option("prefs", prefs)
# 5. 启动驱动(自动匹配浏览器版本)
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=chrome_options
)
# 6. 进一步隐藏webdriver特征(执行JS代码)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
return driver
3.2 核心技巧:等待页面渲染完成(避免抓空)
JS渲染需要时间,直接提取会导致内容为空。Selenium提供三种等待方式,显式等待是首选(精准、高效):
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def extract_js_rendered_content(url):
driver = create_anti_detect_driver()
try:
driver.get(url)
# 显式等待:等待核心元素加载完成(如商品列表、正文容器)
# 示例:等待id为"content"的元素出现,最多等10秒
wait = WebDriverWait(driver, 10)
content_element = wait.until(
EC.presence_of_element_located((By.ID, "content")) # 元素存在即可
# EC.visibility_of_element_located((By.ID, "content")) # 元素可见(更严格)
)
# 若需滚动加载更多内容(如无限滚动列表)
# 模拟滚动到页面底部,等待新内容加载
for _ in range(3): # 滚动3次,加载3页数据
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 等待AJAX加载(或用显式等待新元素)
# 提取渲染后的HTML(此时包含JS生成的内容)
html = driver.page_source
# 用lxml解析内容(结构化提取)
from lxml import etree
selector = etree.HTML(html)
title = selector.xpath('//h1/text()')[0].strip() if selector.xpath('//h1/text()') else ""
content = selector.xpath('//div[@id="content"]//text()')
content = "".join(content).strip()
return {"title": title, "content": content}
finally:
driver.quit() # 必须关闭驱动,避免资源泄漏
# 测试:抓取Vue/React单页应用内容
if __name__ == "__main__":
spa_url = "https://example.com/spa-page" # 替换为真实JS渲染页面
result = extract_js_rendered_content(spa_url)
print(f"标题:{result['title']}")
print(f"正文:{result['content'][:500]}...")
3.3 模拟用户操作:破解“点击加载更多”“登录验证”
很多动态内容需要用户操作触发(如点击“加载更多”、登录后显示数据),Selenium支持完整的操作API:
示例1:点击“加载更多”按钮
def click_load_more(url):
driver = create_anti_detect_driver()
try:
driver.get(url)
wait = WebDriverWait(driver, 10)
# 循环点击“加载更多”,直到按钮消失
while True:
try:
# 等待“加载更多”按钮可点击
load_more_btn = wait.until(
EC.element_to_be_clickable((By.XPATH, '//button[contains(text(), "加载更多")]'))
)
load_more_btn.click() # 点击按钮
time.sleep(2) # 等待加载
except Exception as e:
print("无更多内容或按钮消失")
break
# 提取所有数据
html = driver.page_source
# 后续解析...
return html
finally:
driver.quit()
示例2:模拟登录(破解登录后的数据抓取)
def login_and_extract_data(login_url, target_url, username, password):
driver = create_anti_detect_driver()
try:
# 1. 访问登录页
driver.get(login_url)
wait = WebDriverWait(driver, 10)
# 2. 输入用户名和密码(根据实际页面的输入框选择器调整)
username_input = wait.until(EC.presence_of_element_located((By.ID, "username")))
password_input = wait.until(EC.presence_of_element_located((By.ID, "password")))
username_input.send_keys(username) # 输入用户名
password_input.send_keys(password) # 输入密码
# 3. 点击登录按钮
login_btn = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[@type="submit"]')))
login_btn.click()
# 4. 等待登录成功(如跳转至首页或目标页)
wait.until(EC.url_contains(target_url.split("/")[2])) # 等待域名匹配
# 5. 访问目标页,提取数据
driver.get(target_url)
html = driver.page_source
# 后续解析...
return html
finally:
driver.quit()
3.4 进阶:用undetected-chromedriver突破强反爬
如果网站用更严格的反爬(如检测Chrome版本、驱动路径),是更好的选择——它会修改Chrome的指纹特征,让网站无法识别是自动化工具:
undetected-chromedriver
import undetected_chromedriver as uc
def create_uc_driver():
"""创建undetected-chromedriver(强反爬场景)"""
options = uc.ChromeOptions()
options.add_argument("--headless=new") # 无头模式
options.add_argument("--disable-gpu")
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
# 启动驱动(自动规避检测)
driver = uc.Chrome(options=options)
return driver
# 使用方式和普通Selenium一致
driver = create_uc_driver()
driver.get("https://example.com/anti-crawler-page")
html = driver.page_source
driver.quit()
四、方案二:Requests实战——直接抓取AJAX接口(高效首选)
AJAX请求的核心是“浏览器向服务器发送异步请求,获取JSON/HTML数据后渲染到页面”。直接用Requests模拟AJAX请求,跳过浏览器渲染环节,效率是Selenium的10倍以上。
4.1 核心步骤:找到AJAX接口(开发者工具实战)
以Chrome为例,教你精准定位AJAX接口:
打开目标网页,按F12进入“开发者工具”→切换到“Network”(网络);刷新页面或触发动态加载(如滚动、点击),找到“XHR”或“Fetch”类型的请求(这些是AJAX请求);点击请求,查看“Response”(响应):若包含核心数据(如商品列表、评论),则为目标接口;查看“Headers”(请求头)和“Params”(请求参数):记录、
Request URL(GET/POST)、
Method、
Headers,用于构造Requests请求。
Form Data/Query Params
4.2 实战案例1:抓取GET类型AJAX接口(分页数据)
很多列表页的分页数据通过AJAX接口加载,参数通常为(页码)、
page(每页数量):
pageSize
import requests
import jsonpath_ng as jp
def crawl_ajax_get_api():
# 目标AJAX接口(从开发者工具复制)
api_url = "https://example.com/api/products"
# 请求头(从开发者工具复制,需携带User-Agent、Referer等)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Referer": "https://example.com/products",
"Accept": "application/json, text/plain, */*"
}
all_products = []
page = 1
while True:
# 构造请求参数(分页参数)
params = {
"page": page,
"pageSize": 20,
"sort": "sales" # 排序字段(从开发者工具复制)
}
try:
response = requests.get(
url=api_url,
headers=headers,
params=params,
timeout=10
)
# 解析JSON响应
data = response.json()
# 提取商品数据(用jsonpath快速定位,比字典索引更灵活)
products = jp.parse("$..items[*]").find(data) # 匹配所有items下的商品
products = [item.value for item in products]
if not products:
print("无更多数据")
break
all_products.extend(products)
print(f"已获取第{page}页,累计{len(all_products)}件商品")
page += 1
# 控制请求频率(避免高频封禁)
time.sleep(1)
except Exception as e:
print(f"第{page}页请求失败:{e}")
break
# 数据处理(提取核心字段)
result = []
for product in all_products:
result.append({
"id": product["id"],
"name": product["name"],
"price": product["price"],
"sales": product["salesCount"]
})
return result
# 测试:抓取AJAX分页数据
if __name__ == "__main__":
products = crawl_ajax_get_api()
print(f"最终获取{len(products)}件商品")
print(products[:3])
4.3 实战案例2:抓取POST类型AJAX接口(带表单/JSON参数)
部分AJAX接口用POST请求,参数可能在(表单格式)或
Form Data(JSON格式)中:
Request Body
情况1:Form Data格式(参数为键值对)
def crawl_ajax_post_form():
api_url = "https://example.com/api/login"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded" # Form Data格式
}
# Form Data参数(从开发者工具复制)
data = {
"username": "your_username",
"password": "your_password",
"rememberMe": "true"
}
response = requests.post(
url=api_url,
headers=headers,
data=data, # 用data参数传递Form Data
timeout=10
)
# 登录成功后,获取Cookie用于后续请求
cookies = response.cookies
print("登录成功,Cookie:", cookies)
# 后续请求携带Cookie
target_api = "https://example.com/api/user/data"
user_data = requests.get(
url=target_api,
headers=headers,
cookies=cookies,
timeout=10
).json()
return user_data
情况2:JSON格式(参数为JSON字符串)
def crawl_ajax_post_json():
api_url = "https://example.com/api/comment/list"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Content-Type": "application/json" # JSON格式
}
# JSON参数(从开发者工具复制)
json_data = {
"productId": 123456,
"page": 1,
"pageSize": 10
}
response = requests.post(
url=api_url,
headers=headers,
json=json_data, # 用json参数传递JSON数据(自动序列化)
timeout=10
)
comments = response.json()["data"]["comments"]
print(f"获取{len(comments)}条评论")
return comments
4.4 进阶:破解AJAX接口加密参数(如sign/token)
部分网站为了反爬,会对AJAX参数进行加密(如签名、
sign令牌),破解思路如下:
token
查找加密逻辑:在开发者工具的“Sources”(源代码)中,搜索加密参数名(如),找到生成该参数的JS函数;分析加密算法:常见加密算法为MD5、SHA-1、HMAC,或自定义拼接+哈希;Python复现加密:将JS加密逻辑用Python重写,生成合法参数。
sign
示例:破解“sign”参数(MD5加密)
假设JS加密逻辑为:
sign = MD5("appKey=xxx&page=1&pageSize=20&secret=xxx")
import hashlib
def generate_sign(page, page_size):
"""复现JS加密逻辑,生成sign参数"""
app_key = "xxx" # 从JS代码中找到
secret = "xxx" # 从JS代码中找到
# 拼接字符串(按JS逻辑顺序)
sign_str = f"appKey={app_key}&page={page}&pageSize={page_size}&secret={secret}"
# MD5加密
sign = hashlib.md5(sign_str.encode("utf-8")).hexdigest()
return sign
# 构造请求参数
params = {
"appKey": "xxx",
"page": 1,
"pageSize": 20,
"sign": generate_sign(1, 20) # 生成合法sign
}
# 后续用Requests请求接口...
五、混合使用:Selenium+Requests(兼顾效率与稳定性)
在复杂场景下,可结合两者优势:用Selenium处理登录、滑动验证等操作,获取Cookie/token后,用Requests抓取AJAX接口(效率更高)。
def mix_selenium_requests(login_url, target_api):
# 1. 用Selenium模拟登录,获取Cookie
driver = create_anti_detect_driver()
cookies = None
try:
driver.get(login_url)
# 模拟登录操作(输入用户名、密码、点击登录)
# ...(省略登录代码,参考3.3节)
# 获取登录后的Cookie
cookies = driver.get_cookies()
finally:
driver.quit()
# 2. 转换Cookie为Requests可用格式
cookie_dict = {cookie["name"]: cookie["value"] for cookie in cookies}
# 3. 用Requests抓取AJAX接口(携带Cookie)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
}
response = requests.get(
url=target_api,
headers=headers,
cookies=cookie_dict,
timeout=10
)
data = response.json()
return data
六、动态网页反爬应对:专属解决方案
动态网页的反爬比静态网页更严格,以下是实战验证有效的应对策略:
6.1 伪装请求特征(避免被识别为爬虫)
完整请求头:除了,还要携带
User-Agent、
Referer、
Accept等(从开发者工具复制真实请求头);随机请求头:维护多个
Origin,每次请求随机切换;Cookie持久化:用
User-Agent保存Cookie,模拟用户会话,避免每次请求都是新会话。
requests.Session()
6.2 控制请求频率(避免IP封禁)
单IP请求间隔控制在1-3秒(AJAX接口)或3-5秒(Selenium);批量抓取时,使用设置随机延迟;大规模抓取时,搭配IP池(高匿名代理),避免单IP高频请求。
time.sleep(random.uniform(1, 3))
6.3 规避Selenium检测(强反爬场景)
用替代默认驱动;禁用Chrome的自动化扩展(
undetected-chromedriver);模拟真实用户操作(如随机滚动、停留时间、点击顺序),避免机械操作。
--disable-extensions
6.4 处理动态Cookie/token
若token有过期时间,定期用Selenium重新获取;监控接口响应状态(如401 Unauthorized),触发重新登录逻辑。
七、实战案例:爬取某动态电商商品详情(完整流程)
需求:
目标网站是Vue开发的SPA单页应用(JS渲染);商品详情需登录后查看;商品评论通过AJAX加载(分页)。
完整代码:
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.options import Options
from webdriver_manager.chrome import ChromeDriverManager
import requests
import jsonpath_ng as jp
import time
# 1. Selenium模拟登录,获取Cookie
def get_login_cookies(login_url, username, password):
chrome_options = Options()
chrome_options.add_argument("--headless=new")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome(
service=webdriver.ChromeService(ChromeDriverManager().install()),
options=chrome_options
)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
try:
driver.get(login_url)
wait = WebDriverWait(driver, 10)
# 输入用户名密码
username_input = wait.until(EC.presence_of_element_located((By.ID, "username")))
password_input = wait.until(EC.presence_of_element_located((By.ID, "password")))
username_input.send_keys(username)
password_input.send_keys(password)
# 点击登录
login_btn = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[@type="submit"]')))
login_btn.click()
# 等待登录成功
wait.until(EC.url_contains("home"))
# 获取Cookie
cookies = driver.get_cookies()
return {cookie["name"]: cookie["value"] for cookie in cookies}
finally:
driver.quit()
# 2. Requests抓取商品详情(AJAX接口)
def get_product_detail(product_id, cookies):
detail_api = f"https://example.com/api/product/{product_id}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Referer": f"https://example.com/product/{product_id}"
}
response = requests.get(
url=detail_api,
headers=headers,
cookies=cookies,
timeout=10
)
data = response.json()
return {
"id": data["id"],
"name": data["name"],
"price": data["price"],
"description": data["description"],
"stock": data["stock"]
}
# 3. Requests抓取商品评论(AJAX分页)
def get_product_comments(product_id, cookies):
comment_api = "https://example.com/api/product/comments"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Referer": f"https://example.com/product/{product_id}"
}
all_comments = []
page = 1
while True:
params = {
"productId": product_id,
"page": page,
"pageSize": 20
}
response = requests.get(
url=comment_api,
headers=headers,
params=params,
cookies=cookies,
timeout=10
)
data = response.json()
comments = jp.parse("$..comments[*]").find(data)
comments = [item.value for item in comments]
if not comments:
break
all_comments.extend(comments)
page += 1
time.sleep(1)
return all_comments
# 主函数
if __name__ == "__main__":
login_url = "https://example.com/login"
username = "your_username"
password = "your_password"
product_id = 123456
# 1. 登录获取Cookie
cookies = get_login_cookies(login_url, username, password)
print("登录成功,获取Cookie")
# 2. 获取商品详情
product_detail = get_product_detail(product_id, cookies)
print("商品详情:", product_detail)
# 3. 获取商品评论
comments = get_product_comments(product_id, cookies)
print(f"获取{len(comments)}条评论")
print("第一条评论:", comments[0]["content"] if comments else "无评论")
八、避坑指南:10个高频问题解决方案
Selenium抓取为空→ 用显式等待核心元素,而非固定;检查是否为JS渲染未完成;AJAX接口返回403→ 携带完整请求头(尤其是
time.sleep、
Referer);检查Cookie是否有效;Selenium被检测→ 用
Origin;执行JS隐藏
undetected-chromedriver特征;AJAX参数加密难破解→ 用
webdriver直接调用网站的JS加密函数(避免手动复现);滚动加载未获取全部数据→ Selenium模拟滚动到底部后,用显式等待新元素出现;Cookie过期→ 定期重新登录获取Cookie;监控接口响应状态码(401/403);Selenium速度慢→ 启用无头模式、禁用图片加载;优先用Requests抓AJAX接口;接口参数动态变化→ 分析参数生成逻辑(如时间戳
PyExecJS=当前时间戳);IP被封禁→ 搭配IP池;控制请求频率;用家庭IP而非数据中心IP;动态渲染页面找不到元素→ 检查元素是否在iframe中(需
timestamp切换);用XPath/CSS选择器的相对路径。
driver.switch_to.frame()
九、总结与进阶方向
动态网页抓取的核心是“先分析场景,再选择工具”:
接口清晰→ 用Requests(高效);需模拟操作/JS渲染复杂→ 用Selenium(稳定);复杂场景→ 混合使用(Selenium处理登录,Requests抓接口)。
进阶方向:
Playwright替代Selenium:微软开发的新一代自动化工具,比Selenium更轻量、反爬友好、API更简洁;异步Requests:用替代
aiohttp,提升AJAX接口的并发抓取效率;自动化接口分析:用
requests拦截浏览器请求,自动提取AJAX接口和参数;机器学习破解反爬:用模型识别滑动验证、图文验证(如
mitmproxy识别验证码)。
ddddocr
如果在实战中遇到接口加密、Selenium反检测、AJAX参数动态变化等问题,欢迎留言交流!


