Scrapy爬取Ajax动态加载页面三种实用方法

Ajax(Asynchronous JavaScript and XML)动态加载技术广泛应用于现代网站开发中,它能实现页面局部刷新、提升用户体验,但也给网络爬虫带来了挑战 ------ 传统 Scrapy 爬虫只能抓取页面初始加载的 HTML 内容,无法直接获取通过 Ajax 异步请求加载的数据。本文将详细介绍三种实用且高效的 Scrapy 爬取 Ajax 动态加载页面的方法,涵盖从简单到复杂的场景,帮助你轻松应对各类动态页面爬取需求。

方法一:直接分析 Ajax 请求,构造 Request 爬取(最推荐、最高效)

这是爬取 Ajax 动态页面的首选方法,也是效率最高、资源消耗最低的方式。核心思路是:找到 Ajax 异步请求的真实接口,直接用 Scrapy 构造对应的 Request 请求获取数据,无需渲染整个页面。

操作步骤

  1. 开发者工具抓包,定位 Ajax 请求打开目标网站,使用浏览器(Chrome/Firefox)的开发者工具(F12),切换到「Network」(网络)面板,勾选「XHR」(XML HttpRequest,Ajax 请求通常归类于此)。然后触发页面的动态加载行为(如滚动页面、点击分页、下拉刷新等),此时开发者工具会捕获到对应的 Ajax 请求。

  2. 分析 Ajax 请求的关键信息选中捕获到的 Ajax 请求,查看其「Request URL」(请求地址)、「Request Method」(请求方法,通常是 GET 或 POST)、「Request Headers」(请求头,需注意 User-Agent、Referer、Cookie 等必要字段)、「Query String Parameters」(GET 请求的参数)或「Form Data」(POST 请求的参数)。关键要点:

    • 确认返回数据格式:多数 Ajax 请求返回 JSON 格式,少数返回 XML,这两种格式都比 HTML 更易解析。
    • 识别分页 / 翻页参数:如page(页码)、limit(每页数据量)、offset(偏移量)、timestamp(时间戳)等,用于构造多页请求。
    • 检查是否有加密参数:如signtoken等,若有则需进一步分析加密逻辑(本文暂不涉及复杂加密场景)。
  3. Scrapy 中构造 Request 请求,解析返回数据 在 Scrapy 的爬虫文件中,直接使用scrapy.Request()scrapy.FormRequest()构造请求,指向分析得到的 Ajax 接口,然后在parse方法中解析返回的 JSON/XML 数据。

代码示例

python

运行

复制代码
import scrapy
import json

class AjaxDirectSpider(scrapy.Spider):
    name = 'ajax_direct'
    allowed_domains = ['xxx.com']  # 替换为目标网站域名
    # 初始请求:直接使用分析得到的Ajax接口(以GET请求为例)
    start_urls = ['https://xxx.com/api/get_data?page=1&limit=20']

    def start_requests(self):
        # 构造多页请求(示例:爬取前10页)
        for page in range(1, 11):
            ajax_url = f'https://xxx.com/api/get_data?page={page}&limit=20'
            # 构造请求,可添加必要请求头
            yield scrapy.Request(
                url=ajax_url,
                method='GET',
                headers={
                    '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',
                    'Referer': 'https://xxx.com/',  # 必要时添加Referer
                },
                callback=self.parse_ajax_data,
                meta={'page': page}  # 传递页码元数据,方便后续跟踪
            )

    def parse_ajax_data(self, response):
        # 解析JSON格式返回数据
        try:
            data = json.loads(response.text)
            # 提取有效数据(根据实际接口返回结构调整)
            result_list = data.get('data', {}).get('list', [])
            for item in result_list:
                yield {
                    'title': item.get('title', ''),
                    'content': item.get('content', ''),
                    'create_time': item.get('create_time', ''),
                    'page': response.meta.get('page', 1)
                }
        except json.JSONDecodeError as e:
            self.logger.error(f'JSON解析失败:{e},响应内容:{response.text}')

优点与适用场景

  • 优点:效率极高(无需渲染页面,直接获取结构化数据)、资源消耗低、解析简单(JSON/XML 结构化数据,无需 XPath/CSS 选择器)。
  • 适用场景:绝大多数 Ajax 动态加载页面,尤其是返回 JSON/XML 格式数据的接口,无复杂 JS 加密逻辑的场景。

方法二:使用 Scrapy-Splash 渲染 JavaScript 页面

当 Ajax 请求逻辑复杂、难以直接分析真实接口(如包含加密参数、动态生成请求地址),或者页面依赖大量 JS 渲染才能显示完整数据时,可使用Scrapy-Splash------ 它是 Scrapy 与 Splash(一个轻量级的 JavaScript 渲染引擎)的集成插件,能够模拟浏览器渲染页面,获取 JS 执行后的完整 HTML 内容。

前置准备

  1. 安装 Splash:推荐使用 Docker 部署(最简单快捷),执行命令:docker run -p 8050:8050 scrapinghub/splash,部署完成后可通过http://localhost:8050访问 Splash 控制台。
  2. 安装 Scrapy-Splash:在 Python 环境中执行命令:pip install scrapy-splash
  3. 配置 Scrapy 项目:在项目的settings.py中添加以下配置:

python

运行

复制代码
# 启用Splash中间件
SPLASH_URL = 'http://localhost:8050'  # 本地Splash服务地址(Docker部署)
DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
# 启用Splash的DUPEFILTER
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
# 启用Splash的缓存后端(可选)
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'

操作步骤与代码示例

核心思路:使用scrapy_splash.SplashRequest()替代传统的scrapy.Request(),指定渲染参数,获取渲染后的完整页面,再用常规 XPath/CSS 选择器解析数据。

python

运行

复制代码
import scrapy
from scrapy_splash import SplashRequest

class AjaxSplashSpider(scrapy.Spider):
    name = 'ajax_splash'
    allowed_domains = ['xxx.com']  # 替换为目标动态页面域名
    start_urls = ['https://xxx.com/dynamic_page']  # 目标动态页面地址

    def start_requests(self):
        for url in self.start_urls:
            # 构造SplashRequest,请求渲染页面
            yield SplashRequest(
                url=url,
                callback=self.parse_splash_page,
                # 渲染参数:wait指定等待JS执行的时间(秒),可根据页面加载速度调整
                args={
                    'wait': 3,  # 等待3秒,确保JS加载完成、Ajax请求执行完毕
                    'timeout': 10,  # 渲染超时时间
                    'html': 1,  # 返回渲染后的HTML内容
                },
                # 可选:设置请求头
                headers={
                    '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',
                },
                # 可选:开启缓存(避免重复渲染)
                cache_args=['wait', 'timeout']
            )

    def parse_splash_page(self, response):
        # 此时response为JS渲染后的完整HTML,可使用常规XPath/CSS解析
        # 示例:提取页面中的文章列表
        article_list = response.xpath('//div[@class="article-item"]')
        for article in article_list:
            yield {
                'title': article.xpath('.//h3/text()').extract_first(default=''),
                'author': article.xpath('.//span[@class="author"]/text()').extract_first(default=''),
                'publish_time': article.xpath('.//span[@class="publish-time"]/text()').extract_first(default=''),
                'url': article.xpath('.//a/@href').extract_first(default='')
            }

        # 分页爬取(示例:提取下一页链接并构造SplashRequest)
        next_page_url = response.xpath('//a[@class="next-page"]/@href').extract_first(default='')
        if next_page_url:
            yield SplashRequest(
                url=next_page_url,
                callback=self.parse_splash_page,
                args={'wait': 3, 'timeout': 10}
            )

优点与适用场景

  • 优点:无需分析复杂 Ajax 接口,直接渲染完整页面,使用门槛低(熟悉常规 Scrapy 解析即可),支持绝大多数 JS 渲染场景。
  • 适用场景:Ajax 接口逻辑复杂、难以抓包分析,或页面依赖 JS 渲染完整 DOM 结构的场景;不追求极致爬取效率,允许一定资源消耗的场景。

注意事项

  • Splash 服务需要单独部署(Docker 最便捷),占用一定服务器 / 本地资源。
  • 渲染速度比直接请求 Ajax 接口慢,批量爬取时效率较低。
  • 可通过调整wait参数优化渲染效果(过短可能导致数据未加载完成,过长浪费时间)。

方法三:使用 Scrapy 结合 Selenium/Playwright 模拟浏览器行为

当页面不仅依赖 Ajax 动态加载,还包含复杂的用户交互逻辑(如需要登录、滑动验证、点击按钮才能触发数据加载),或者 Splash 无法满足渲染需求时,可使用 Selenium(支持 Chrome/Firefox 等浏览器)或 Playwright(新一代浏览器自动化工具,支持多浏览器)与 Scrapy 结合,模拟真实浏览器的操作行为,获取完整数据。

本文以Selenium为例进行演示(Playwright 用法类似,核心逻辑一致)。

前置准备

  1. 安装 Selenium:pip install selenium
  2. 下载对应浏览器的 WebDriver:如 Chrome 浏览器需下载 ChromeDriver(版本需与本地 Chrome 浏览器匹配),并配置环境变量(或在代码中指定 WebDriver 路径)。
  3. (可选)安装浏览器自动化相关依赖:确保本地安装了 Chrome/Firefox 浏览器。

核心实现思路

  1. 自定义 Scrapy 下载中间件,在下载环节使用 Selenium 驱动浏览器打开目标页面,模拟浏览器行为(等待加载、点击、滚动等),获取渲染后的完整 HTML。
  2. Scrapy 爬虫发送常规请求,经过自定义中间件处理后,获取浏览器渲染后的页面内容,再进行常规解析。

代码示例

步骤 1:自定义 Selenium 下载中间件(在项目的middlewares.py中)

python

运行

复制代码
from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time

class SeleniumAjaxMiddleware:
    """自定义Selenium下载中间件,渲染JS动态页面"""
    def __init__(self):
        # 配置ChromeOptions,启用无头模式(无浏览器界面,节省资源)
        chrome_options = Options()
        chrome_options.add_argument('--headless=new')  # Chrome 112+版本的无头模式参数
        chrome_options.add_argument('--disable-gpu')
        chrome_options.add_argument('--no-sandbox')
        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')
        
        # 初始化Chrome WebDriver(指定ChromeDriver路径,若已配置环境变量可省略executable_path)
        self.driver = webdriver.Chrome(options=chrome_options)
        # 设置页面加载超时时间
        self.driver.set_page_load_timeout(30)
        self.wait = WebDriverWait(self.driver, 10)  # 显式等待对象,超时时间10秒

    def process_request(self, request, spider):
        """处理请求,使用Selenium渲染页面"""
        try:
            spider.logger.info(f'使用Selenium渲染页面:{request.url}')
            # 驱动浏览器打开目标页面
            self.driver.get(request.url)
            
            # 模拟浏览器行为:等待关键元素加载(确保Ajax数据加载完成)
            # 示例:等待文章列表元素加载,可根据目标页面调整
            self.wait.until(
                EC.presence_of_element_located((By.XPATH, '//div[@class="article-item"]'))
            )
            
            # 可选:模拟滚动页面,触发更多Ajax数据加载(如无限滚动页面)
            # self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            # time.sleep(2)  # 等待滚动后的数据加载
            
            # 获取渲染后的完整HTML内容
            page_source = self.driver.page_source
            # 构造HtmlResponse返回给爬虫解析
            return HtmlResponse(
                url=request.url,
                body=page_source.encode('utf-8'),
                encoding='utf-8',
                request=request
            )
        except Exception as e:
            spider.logger.error(f'Selenium渲染页面失败:{e}')
            return None

    def __del__(self):
        """爬虫关闭时,关闭WebDriver"""
        self.driver.quit()
步骤 2:配置中间件(在settings.py中)

python

运行

复制代码
# 启用自定义Selenium中间件
DOWNLOADER_MIDDLEWARES = {
    'your_project_name.middlewares.SeleniumAjaxMiddleware': 543,  # 替换为你的项目名称
}
步骤 3:编写爬虫(常规 Scrapy 爬虫,无需修改核心逻辑)

python

运行

复制代码
import scrapy

class AjaxSeleniumSpider(scrapy.Spider):
    name = 'ajax_selenium'
    allowed_domains = ['xxx.com']
    start_urls = ['https://xxx.com/dynamic_page']

    def parse(self, response):
        # 解析Selenium渲染后的完整HTML,用法与常规Scrapy爬虫一致
        article_list = response.xpath('//div[@class="article-item"]')
        for article in article_list:
            yield {
                'title': article.xpath('.//h3/text()').extract_first(default=''),
                'author': article.xpath('.//span[@class="author"]/text()').extract_first(default=''),
                'publish_time': article.xpath('.//span[@class="publish-time"]/text()').extract_first(default=''),
                'url': article.xpath('.//a/@href').extract_first(default='')
            }

        # 分页爬取
        next_page_url = response.xpath('//a[@class="next-page"]/@href').extract_first(default='')
        if next_page_url and next_page_url not in self.start_urls:
            yield scrapy.Request(url=next_page_url, callback=self.parse)

优点与适用场景

  • 优点:模拟真实浏览器行为,支持复杂用户交互(登录、验证、点击、滚动等),能处理几乎所有 JS 渲染场景,兼容性极强。
  • 适用场景:页面包含复杂用户交互逻辑、需要模拟浏览器操作才能触发数据加载,或 Splash 无法满足渲染需求的极端场景。

注意事项

  • 资源消耗极高(启动真实浏览器内核),爬取效率最低,不适合大规模批量爬取。
  • WebDriver 版本需与浏览器版本严格匹配,否则会出现兼容性问题。
  • 无头模式下可能被网站反爬机制识别,可根据需求关闭无头模式,或添加更多反反爬配置(如代理、Cookie 等)。

三种方法对比与选型建议

方法 效率 资源消耗 使用门槛 适用场景
直接分析 Ajax 接口 极高 极低 中等(需会抓包分析) 绝大多数 Ajax 页面,返回结构化数据,无复杂加密
Scrapy-Splash 中等 中等 较低(需部署 Splash,熟悉常规解析) Ajax 接口复杂,难以抓包,无需复杂用户交互
Scrapy+Selenium/Playwright 极低 极高 较高(需熟悉浏览器自动化) 复杂用户交互,极端 JS 渲染场景,反爬严格

选型核心建议

  1. 优先选择方法一(直接分析 Ajax 接口):效率最高、成本最低,是爬取 Ajax 动态页面的最优解,绝大多数场景都能满足。
  2. 方法一无法实现时,选择方法二(Scrapy-Splash):平衡了效率与易用性,无需深入学习浏览器自动化,能应对大部分复杂 JS 渲染场景。
  3. 仅在极端复杂场景下(需要模拟用户交互),才选择方法三(Scrapy+Selenium/Playwright):接受其低效率与高资源消耗,换取最强的兼容性。

总结

  1. Scrapy 爬取 Ajax 动态页面的核心是获取 JS 执行后的完整数据,三种方法分别对应不同复杂度的场景,按需选择即可。
  2. 直接分析 Ajax 接口是最优解,关键在于熟练使用浏览器开发者工具抓包,识别真实请求与参数。
  3. Scrapy-Splash 是应对复杂 Ajax 页面的高效工具,Docker 部署简化了环境配置,适合批量爬取中等复杂度的动态页面。
  4. Selenium/Playwright 是最后的兜底方案,适合处理包含用户交互的极端场景,同时需注意反爬与资源消耗问题。
  5. 实际爬取过程中,还需结合反反爬策略(如设置合理请求间隔、更换代理 IP、模拟真实请求头),提高爬虫的稳定性与成功率。
相关推荐
姓刘的哦2 小时前
推理PyTorch模型的方法
人工智能·pytorch·python
4***17542 小时前
Python酷库之旅-第三方库Pandas(051)
开发语言·python·pandas
__如风__2 小时前
国产海光CPU+DCU+麒麟OS上使用Docker成功部署PaddleSpeech实战
pytorch·python·paddlepaddle
何中应2 小时前
在Coze上新建一个插件
开发语言·python·ai
rgeshfgreh2 小时前
Conda降级Python版本全指南
python
前端程序猿之路2 小时前
30天大模型学习之Day3:高级 Prompt 工程
人工智能·python·学习·语言模型·大模型·prompt·ai编程
也许是_2 小时前
大模型应用技术之 Agent框架 AutoGPT
人工智能·python
The star"'2 小时前
Deepseek基础,模板引擎,prompt提示词,增强检索,智能机器人
python·机器人·云计算·prompt·easyui
我是小疯子662 小时前
Python+Copilot:从语法纠错到重构的极速开发指南
python