动态网页抓取全攻略:Selenium与Requests破解JS渲染与AJAX请求

动态网页(基于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
:支持无头模式、显式等待,稳定性提升;
webdriver-manager
:自动下载匹配浏览器版本的驱动(Chrome/Firefox),无需手动配置;
undetected-chromedriver
:规避Selenium的webdriver特征检测(反爬常用手段);
jsonpath-ng
:快速提取JSON数据(AJAX接口返回常用)。

三、方案一: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版本、驱动路径),
undetected-chromedriver
是更好的选择——它会修改Chrome的指纹特征,让网站无法识别是自动化工具:


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

Method
(GET/POST)、
Headers

Form Data/Query Params
,用于构造Requests请求。

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
(表单格式)或
Request Body
(JSON格式)中:

情况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”(源代码)中,搜索加密参数名(如
sign
),找到生成该参数的JS函数;分析加密算法:常见加密算法为MD5、SHA-1、HMAC,或自定义拼接+哈希;Python复现加密:将JS加密逻辑用Python重写,生成合法参数。

示例:破解“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
等(从开发者工具复制真实请求头);随机请求头:维护多个
User-Agent
,每次请求随机切换;Cookie持久化:用
requests.Session()
保存Cookie,模拟用户会话,避免每次请求都是新会话。

6.2 控制请求频率(避免IP封禁)

单IP请求间隔控制在1-3秒(AJAX接口)或3-5秒(Selenium);批量抓取时,使用
time.sleep(random.uniform(1, 3))
设置随机延迟;大规模抓取时,搭配IP池(高匿名代理),避免单IP高频请求。

6.3 规避Selenium检测(强反爬场景)


undetected-chromedriver
替代默认驱动;禁用Chrome的自动化扩展(
--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抓取为空→ 用显式等待核心元素,而非固定
time.sleep
;检查是否为JS渲染未完成;AJAX接口返回403→ 携带完整请求头(尤其是
Referer

Origin
);检查Cookie是否有效;Selenium被检测→ 用
undetected-chromedriver
;执行JS隐藏
webdriver
特征;AJAX参数加密难破解→ 用
PyExecJS
直接调用网站的JS加密函数(避免手动复现);滚动加载未获取全部数据→ Selenium模拟滚动到底部后,用显式等待新元素出现;Cookie过期→ 定期重新登录获取Cookie;监控接口响应状态码(401/403);Selenium速度慢→ 启用无头模式、禁用图片加载;优先用Requests抓AJAX接口;接口参数动态变化→ 分析参数生成逻辑(如时间戳
timestamp
=当前时间戳);IP被封禁→ 搭配IP池;控制请求频率;用家庭IP而非数据中心IP;动态渲染页面找不到元素→ 检查元素是否在iframe中(需
driver.switch_to.frame()
切换);用XPath/CSS选择器的相对路径。

九、总结与进阶方向

动态网页抓取的核心是“先分析场景,再选择工具”:

接口清晰→ 用Requests(高效);需模拟操作/JS渲染复杂→ 用Selenium(稳定);复杂场景→ 混合使用(Selenium处理登录,Requests抓接口)。

进阶方向:

Playwright替代Selenium:微软开发的新一代自动化工具,比Selenium更轻量、反爬友好、API更简洁;异步Requests:用
aiohttp
替代
requests
,提升AJAX接口的并发抓取效率;自动化接口分析:用
mitmproxy
拦截浏览器请求,自动提取AJAX接口和参数;机器学习破解反爬:用模型识别滑动验证、图文验证(如
ddddocr
识别验证码)。

如果在实战中遇到接口加密、Selenium反检测、AJAX参数动态变化等问题,欢迎留言交流!

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...