Python3 爬虫教程:Ajax 数据爬取实战

开篇小提示:如果你练过爬虫,大概率遇到过「直接用 requests 爬页面,HTML里全是骨架没有数据」的情况——这几乎就是Ajax动态加载的典型信号。这篇我们用 崔庆才老师的免费靶站 spa1 走一遍完整流程,接口规则清晰,没有复杂反爬,很适合入门巩固。


1. 准备工作

在开始前,请确保你的环境满足以下要求:

  • 已安装 Python 3.6+
  • 已通过 pip install requests pymongo 安装依赖库
  • 本地已启动 MongoDB 服务(用于最后数据存储,没有的话可以只跑到爬取打印环节)
  • 对浏览器开发者工具(F12)的 Network 面板有基本了解

2. 目标网站分析

我们的靶站地址是:https://spa1.scrape.center/

2.1 网站核心特点

✅ 数据完全通过 Ajax 异步加载
✅ 页面内容由前端 JS 渲染,HTML 仅含基础布局
✅ 支持分页浏览(共10页左右,每页10部电影)
✅ 点击卡片可跳转至详情页,详情页数据也是 Ajax 接口

2.2 需要爬取的字段

从列表页和详情页我们可以提取完整的电影信息:

  • 电影名称
  • 封面图片链接
  • 类别标签(如「剧情」「悬疑」)
  • 上映日期
  • 综合评分
  • 剧情简介

3. 初步验证:静态HTML抓不到数据

先写个最简单的 requests 测试,看看直接爬页面会拿到什么:

import requests

url = 'https://spa1.scrape.center/'
response = requests.get(url)
print(response.text[:500])  # 只打印前500字,避免太长

运行后你会发现,HTML 里只有 <div id="app"></div> 这种容器标签,连「电影」「评分」这些关键词都找不到——完全符合我们的预期!接下来就得靠开发者工具找Ajax接口了。


4. 爬取列表页

4.1 分析列表页的Ajax接口

打开浏览器按 F12,切换到 Network 面板,记得勾选 Preserve Log(翻页后不会清空请求记录),再筛选 XHR/Fetch(只看Ajax类请求)。

现在点击页面的「第2页」「第3页」,观察 Network 里新增的请求,会发现它们的 URL 格式高度统一:

https://spa1.scrape.center/api/movie/?limit=10&offset=0
https://spa1.scrape.center/api/movie/?limit=10&offset=10
https://spa1.scrape.center/api/movie/?limit=10&offset=20

参数解析

  • limit: 固定为10,指每页返回的电影数量
  • offset: 数据偏移量,第1页是0,第2页是10,第n页就是 10*(n-1)

4.2 封装列表页爬取函数

为了代码复用性,我们先写一个通用的接口爬取函数,再专门写列表页的调用函数:

import requests
import logging

# 配置日志,方便追踪爬取进度和错误
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s: %(message)s'
)

# 全局常量
INDEX_URL = 'https://spa1.scrape.center/api/movie/?limit={limit}&offset={offset}'
LIMIT = 10  # 固定每页10条数据

def scrape_api(url):
    """通用的Ajax接口爬取函数:返回JSON数据或None"""
    logging.info('正在爬取接口:%s', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.json()  # 直接返回解析后的JSON字典
        logging.error('爬取失败:接口 %s 返回状态码 %d', url, response.status_code)
    except requests.RequestException:
        logging.error('爬取出错:接口 %s 发生请求异常', url, exc_info=True)
    return None

def scrape_index(page):
    """爬取指定页的电影列表"""
    url = INDEX_URL.format(limit=LIMIT, offset=LIMIT * (page - 1))
    return scrape_api(url)

5. 爬取详情页

5.1 分析详情页的Ajax接口

在列表页随便点击一部电影的卡片,进入详情页后同样筛选 XHR/Fetch,会发现一条新的类似请求:

https://spa1.scrape.center/api/movie/1/

很明显,这里的 1 是电影的唯一ID——这个ID在列表页的JSON数据里已经有了(字段名就是 id),所以我们不用解析HTML跳转链接,直接拿列表页的ID去拼接接口即可。

5.2 封装详情页爬取函数

复用刚才的 scrape_api 函数,很快就能写完:

DETAIL_URL = 'https://spa1.scrape.center/api/movie/{id}/'

def scrape_detail(movie_id):
    """爬取指定ID的电影详情"""
    url = DETAIL_URL.format(id=movie_id)
    return scrape_api(url)

6. 数据存储:存入MongoDB

靶站的接口返回了完整的JSON数据,我们可以直接用 MongoDB 这种文档型数据库存储,不用额外处理字段映射(非常方便!)。

6.1 配置MongoDB连接

先导入 pymongo 并连接本地数据库:

import pymongo

# MongoDB全局常量
MONGO_URI = 'mongodb://localhost:27017'
MONGO_DB = 'movies_spa1'
MONGO_COLLECTION = 'movies'

# 建立连接并获取集合
client = pymongo.MongoClient(MONGO_URI)
db = client[MONGO_DB]
collection = db[MONGO_COLLECTION]

6.2 封装数据保存函数

update_one 配合 upsert=True,可以实现数据去重更新:如果数据库里已经有同名电影,就更新它的信息;如果没有,就新增一条。

def save_data(data):
    """将电影数据存入MongoDB,同名电影去重更新"""
    if not data:
        return
    collection.update_one(
        {'name': data.get('name')},  # 用电影名作为去重条件
        {'$set': data},               # 只更新/设置传入的字段
        upsert=True                    # 没有匹配的记录则新增
    )
    logging.info('电影《%s》已保存成功!', data.get('name'))

7. 完整爬取流程

最后把所有函数串起来,写一个主函数,爬取前10页的所有电影:

def main():
    for page in range(1, 11):  # 爬取第1到第10页
        index_data = scrape_index(page)
        if not index_data or not index_data.get('results'):
            continue
        # 遍历当前页的每部电影,获取ID后爬详情、存库
        for item in index_data['results']:
            movie_id = item.get('id')
            detail_data = scrape_detail(movie_id)
            save_data(detail_data)

if __name__ == '__main__':
    main()

8. 现代爬虫技术改进建议(简单版)

这篇是入门实战,代码比较基础,如果你想提升效率和稳定性,可以试试这些小优化:

  1. 异步爬取:用 aiohttp 替代 requests,多任务并发,速度会快很多
  2. 错误重试:给 scrape_apitenacity 库的重试装饰器,解决网络波动问题
  3. 反爬基础:添加随机 User-Agenttime.sleep() 模拟人类操作
  4. 数据校验:保存前检查必填字段(如 name id)是否存在,避免脏数据

9. 总结

通过这篇实战,我们掌握了 Ajax动态加载数据的爬虫完整流程

  1. 先用静态HTML测试,验证数据是否动态加载
  2. 用浏览器开发者工具找列表页和详情页的Ajax接口
  3. 分析接口参数,封装通用爬取函数
  4. 串起流程、加日志、存数据库

完整代码可以直接复制运行,别忘了先启动MongoDB哦!