Ajax(Asynchronous JavaScript and XML)动态加载技术广泛应用于现代网站开发中,它能实现页面局部刷新、提升用户体验,但也给网络爬虫带来了挑战 —— 传统 Scrapy 爬虫只能抓取页面初始加载的 HTML 内容,无法直接获取通过 Ajax 异步请求加载的数据。本文将详细介绍三种实用且高效的 Scrapy 爬取 Ajax 动态加载页面的方法,涵盖从简单到复杂的场景,帮助你轻松应对各类动态页面爬取需求。
方法一:直接分析 Ajax 请求,构造 Request 爬取(最推荐、最高效)
这是爬取 Ajax 动态页面的首选方法,也是效率最高、资源消耗最低的方式。核心思路是:找到 Ajax 异步请求的真实接口,直接用 Scrapy 构造对应的 Request 请求获取数据,无需渲染整个页面。
操作步骤
开发者工具抓包,定位 Ajax 请求打开目标网站,使用浏览器(Chrome/Firefox)的开发者工具(F12),切换到「Network」(网络)面板,勾选「XHR」(XML HttpRequest,Ajax 请求通常归类于此)。然后触发页面的动态加载行为(如滚动页面、点击分页、下拉刷新等),此时开发者工具会捕获到对应的 Ajax 请求。
分析 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(时间戳)等,用于构造多页请求。 - 检查是否有加密参数:如
sign、token等,若有则需进一步分析加密逻辑(本文暂不涉及复杂加密场景)。
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 内容。
前置准备
- 安装 Splash:推荐使用 Docker 部署(最简单快捷),执行命令:
docker run -p 8050:8050 scrapinghub/splash,部署完成后可通过http://localhost:8050访问 Splash 控制台。 - 安装 Scrapy-Splash:在 Python 环境中执行命令:
pip install scrapy-splash。 - 配置 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 用法类似,核心逻辑一致)。
前置准备
- 安装 Selenium:
pip install selenium。 - 下载对应浏览器的 WebDriver:如 Chrome 浏览器需下载 ChromeDriver(版本需与本地 Chrome 浏览器匹配),并配置环境变量(或在代码中指定 WebDriver 路径)。
- (可选)安装浏览器自动化相关依赖:确保本地安装了 Chrome/Firefox 浏览器。
核心实现思路
- 自定义 Scrapy 下载中间件,在下载环节使用 Selenium 驱动浏览器打开目标页面,模拟浏览器行为(等待加载、点击、滚动等),获取渲染后的完整 HTML。
- 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 渲染场景,反爬严格 |
选型核心建议
- 优先选择方法一(直接分析 Ajax 接口):效率最高、成本最低,是爬取 Ajax 动态页面的最优解,绝大多数场景都能满足。
- 方法一无法实现时,选择方法二(Scrapy-Splash):平衡了效率与易用性,无需深入学习浏览器自动化,能应对大部分复杂 JS 渲染场景。
- 仅在极端复杂场景下(需要模拟用户交互),才选择方法三(Scrapy+Selenium/Playwright):接受其低效率与高资源消耗,换取最强的兼容性。
总结
- Scrapy 爬取 Ajax 动态页面的核心是获取 JS 执行后的完整数据,三种方法分别对应不同复杂度的场景,按需选择即可。
- 直接分析 Ajax 接口是最优解,关键在于熟练使用浏览器开发者工具抓包,识别真实请求与参数。
- Scrapy-Splash 是应对复杂 Ajax 页面的高效工具,Docker 部署简化了环境配置,适合批量爬取中等复杂度的动态页面。
- Selenium/Playwright 是最后的兜底方案,适合处理包含用户交互的极端场景,同时需注意反爬与资源消耗问题。
- 实际爬取过程中,还需结合反反爬策略(如设置合理请求间隔、更换代理 IP、模拟真实请求头),提高爬虫的稳定性与成功率。