Python爬虫实战⑤|抓取动态网页,AJAX与API接口解析


author: 专注Python实战,分享爬虫与数据分析干货
title: Python爬虫实战⑤|抓取动态网页,AJAX与API接口解析
update: 2026-04-26
tags: Python,爬虫,动态网页,AJAX,API,JSON,异步加载

作者:专注Python实战,分享爬虫与数据分析干货
更新时间:2026年4月
适合人群:已掌握基础爬虫、想突破动态网页抓取的开发者


前言:页面数据看不到?因为它是动态加载的

你一定遇到过这种情况——

  • 浏览器能看到数据,但requests抓下来是空的
  • 网页源码里没有你要的内容,但页面上明明显示着
  • 点击"加载更多"后URL不变,但内容增加了

这不是bug,是AJAX动态加载。 数据不在HTML里,而是通过JavaScript额外请求API获取。

学会抓动态网页,你的爬虫能力直接翻倍。


一、什么是AJAX动态加载?

1.1 传统网页 vs 动态网页

传统网页(静态):

浏览器请求 → 服务器返回完整HTML → 浏览器渲染显示

数据就在HTML源码里,requests直接能拿到。

动态网页(AJAX):

浏览器请求HTML → HTML是空的/半空的
            ↓
浏览器执行JS → JS发起AJAX请求API → 服务器返回JSON数据 → JS填充到页面

数据在API接口的JSON响应里,requests抓HTML拿不到数据。

1.2 怎么判断是不是动态网页?

import requests
from bs4 import BeautifulSoup

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

url = "https://example.com/dynamic-page"
response = requests.get(url, headers=headers, timeout=10)
soup = BeautifulSoup(response.text, "html.parser")

# 如果页面上的数据在HTML里找不到,就是动态加载
content = soup.find("div", class_="data-list")
if content:
    print("静态网页,数据在HTML里")
else:
    print("动态网页!需要找API接口")

二、用浏览器开发者工具找API接口

2.1 开发者工具打开方式

  • Chrome/Edge:按F12,或右键→检查
  • 切换到 Network(网络)标签
  • 勾选 “Preserve log”(保留日志)
  • 选择 XHR 或 Fetch 过滤器

2.2 找到API请求的步骤

  1. 打开目标网页
  2. F12 → Network → XHR
  3. 在网页上触发数据加载(刷新/翻页/滚动/点击)
  4. 观察Network里新出现的请求
  5. 点击请求 → Preview/Response 查看返回数据
  6. 点击 Headers 查看请求URL、方法、参数

2.3 用代码模拟API请求

找到API接口后,直接用requests请求JSON数据:

import requests
import json

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://example.com/",
    "Accept": "application/json, text/plain, */*",
}

# API接口URL(从开发者工具中获取)
api_url = "https://example.com/api/data/list"

# 请求参数
params = {
    "page": 1,
    "size": 20,
    "keyword": "Python",
}

response = requests.get(api_url, headers=headers, params=params, timeout=10)
data = response.json()

print(f"状态: {data.get('status', 'unknown')}")
print(f"数据条数: {len(data.get('data', {}).get('list', []))}")

# 提取具体字段
for item in data.get("data", {}).get("list", []):
    print(f"  标题: {item.get('title', '')}")
    print(f"  价格: {item.get('price', '')}")
    print(f"  链接: {item.get('url', '')}")
    print()

三、常见AJAX接口格式与解析

3.1 GET请求 + URL参数

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://example.com/",
}

# 翻页接口
for page in range(1, 6):
    api_url = "https://example.com/api/articles"
    params = {
        "page": page,
        "per_page": 20,
        "category": "tech",
    }
    response = requests.get(api_url, headers=headers, params=params, timeout=10)
    data = response.json()

    articles = data.get("data", {}).get("articles", [])
    print(f"第{page}页: {len(articles)} 篇文章")
    for article in articles:
        print(f"  - {article['title']}")

3.2 POST请求 + JSON参数

import requests
import json

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Content-Type": "application/json;charset=UTF-8",
    "Referer": "https://example.com/",
}

api_url = "https://example.com/api/search"
payload = {
    "keyword": "Python爬虫",
    "page": 1,
    "pageSize": 20,
    "filters": {
        "category": "book",
        "priceRange": [0, 100]
    }
}

response = requests.post(api_url, headers=headers, json=payload, timeout=10)
data = response.json()

results = data.get("result", {}).get("items", [])
print(f"搜索到 {len(results)} 条结果")
for item in results:
    print(f"  {item['name']} - ¥{item['price']}")

3.3 需要Token认证的API

import requests

# 第1步:登录获取token
login_url = "https://example.com/api/login"
login_data = {"username": "user", "password": "pass"}
response = requests.post(login_url, json=login_data, timeout=10)
token = response.json().get("token", "")
print(f"获取Token: {token[:20]}...")

# 第2步:带token请求API
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Authorization": f"Bearer {token}",
    "Referer": "https://example.com/",
}

api_url = "https://example.com/api/user/data"
response = requests.get(api_url, headers=headers, timeout=10)
data = response.json()
print(f"用户数据: {data}")

四、动态网页实战案例

4.1 抓取新浪微博搜索结果

import requests
import json
import time
import random

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://s.weibo.com/",
    "Accept": "application/json, text/plain, */*",
    "X-Requested-With": "XMLHttpRequest",
}

keyword = "Python"
all_posts = []

for page in range(1, 6):
    api_url = "https://s.weibo.com/ajax/jsonp/search"
    params = {
        "keyword": keyword,
        "page": page,
    }

    print(f"搜索 '{keyword}' 第{page}页...", end=" ")

    try:
        response = requests.get(api_url, headers=headers, params=params, timeout=10)
        # 有些API返回JSONP格式,需要去掉回调函数包装
        text = response.text
        if text.startswith("jQuery"):
            text = text[text.index("(") + 1 : text.rindex(")")]

        data = json.loads(text)
        cards = data.get("data", {}).get("cards", [])

        for card in cards:
            mblog = card.get("mblog", {})
            if mblog:
                all_posts.append({
                    "用户": mblog.get("user", {}).get("screen_name", ""),
                    "内容": mblog.get("text", "")[:50],
                    "转发": mblog.get("reposts_count", 0),
                    "评论": mblog.get("comments_count", 0),
                    "点赞": mblog.get("attitudes_count", 0),
                })

        print(f"获取 {len(cards)} 条")
        time.sleep(random.uniform(2, 5))

    except Exception as e:
        print(f"失败: {e}")

print(f"\n共获取 {len(all_posts)} 条微博")

4.2 抓取知乎热榜

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://www.zhihu.com/hot",
}

api_url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total"
params = {"limit": 50, "desktop": "true"}

try:
    response = requests.get(api_url, headers=headers, params=params, timeout=10)
    data = response.json()

    items = data.get("data", [])
    print(f"知乎热榜共 {len(items)} 条")
    print("=" * 60)

    for i, item in enumerate(items, 1):
        target = item.get("target", {})
        title = target.get("title", "")
        hot = item.get("detail_text", "")
        print(f"{i:>2}. {title}")
        print(f"    热度: {hot}")

except Exception as e:
    print(f"抓取失败: {e}")
    print("提示:知乎API可能需要登录Cookie才能访问")

五、处理JSONP响应

有些API返回的不是标准JSON,而是JSONP(带回调函数名):

// 标准JSON
{"name": "张三", "age": 25}

// JSONP
callback({"name": "张三", "age": 25})
jQuery123456({"name": "张三", "age": 25})
import json
import re

def parse_jsonp(jsonp_text):
    """解析JSONP响应,提取JSON数据"""
    # 方法1:用正则提取括号内内容
    match = re.search(r"\((.+)\);?$", jsonp_text, re.DOTALL)
    if match:
        json_str = match.group(1)
        return json.loads(json_str)

    # 方法2:如果是标准JSON,直接解析
    try:
        return json.loads(jsonp_text)
    except json.JSONDecodeError:
        pass

    # 方法3:暴力去掉常见回调前缀
    for prefix in ["callback", "jQuery"]:
        if jsonp_text.startswith(prefix):
            json_str = jsonp_text[jsonp_text.index("(") + 1 : jsonp_text.rindex(")")]
            return json.loads(json_str)

    raise ValueError(f"无法解析JSONP: {jsonp_text[:100]}...")

# 使用示例
jsonp_response = 'jQuery123456({"status": "ok", "data": [1, 2, 3]})'
data = parse_jsonp(jsonp_response)
print(data)  # {'status': 'ok', 'data': [1, 2, 3]}

六、无限滚动加载

很多网站(如微博、淘宝、Pinterest)用"无限滚动"代替分页:

6.1 找到滚动加载的API

初始页面:https://example.com/feed
滚动加载:https://example.com/api/feed?after=abc123&count=20
再次滚动:https://example.com/api/feed?after=def456&count=20

"after"参数通常是上一批最后一条数据的ID或时间戳。

6.2 模拟无限滚动

import requests
import time
import random

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://example.com/feed",
}

api_url = "https://example.com/api/feed"
all_items = []
after_cursor = None  # 游标,初始为None
max_rounds = 10      # 最多加载10轮

for round_num in range(1, max_rounds + 1):
    params = {"count": 20}
    if after_cursor:
        params["after"] = after_cursor

    print(f"加载第 {round_num} 轮...", end=" ")

    try:
        response = requests.get(api_url, headers=headers, params=params, timeout=10)
        data = response.json()

        items = data.get("items", [])
        if not items:
            print("没有更多数据了")
            break

        all_items.extend(items)

        # 获取下一轮的游标
        after_cursor = data.get("next_cursor") or data.get("pagination", {}).get("after")
        if not after_cursor:
            print("已到最后一页")
            break

        print(f"获取 {len(items)} 条,累计 {len(all_items)} 条")
        time.sleep(random.uniform(1, 3))

    except Exception as e:
        print(f"失败: {e}")
        break

print(f"\n共加载 {len(all_items)} 条数据")

七、知识卡

概念说明
AJAX异步JavaScript请求,页面不刷新即可获取数据
API接口后端提供的数据接口,通常返回JSON
XHRXMLHttpRequest,AJAX请求的类型
JSONP带回调函数的JSON,跨域请求用
Network面板浏览器开发者工具的网络请求监控
游标(cursor)无限滚动的分页标记,指向下一批数据
TokenAPI访问凭证,放在请求头里
X-Requested-WithAJAX请求标识,值通常为XMLHttpRequest
Referer告诉API你从哪个页面来的
Content-Type请求体格式,JSON为application/json

八、课后作业

必做题:

  1. 用浏览器开发者工具找到任意一个动态网站的API接口
  2. 用requests直接请求API,提取JSON数据
  3. 实现一个简单的动态网页爬虫(至少3页)

选做题:

  1. 写一个通用函数,自动解析JSONP响应
  2. 实现无限滚动页面的数据抓取

完成作业的同学,把运行截图发到评论区!


动态网页 = 爬虫的进阶门槛。 跨过去,90%的网站你都能爬。

本篇要点:

  • 判断静态/动态网页
  • 用开发者工具找API接口
  • GET/POST/Token三种API请求方式
  • JSONP解析
  • 无限滚动加载处理

下一篇我们学习反爬虫应对策略——IP代理、随机延迟、UA伪装,让你不被封。

收藏 + 关注,专栏更新不迷路!

有问题欢迎评论区留言,大家一起讨论!


标签:Python | 爬虫 | 动态网页 | AJAX | API | JSON | 异步加载 | 数据抓取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川冰ICE

不求施舍,但求真心

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值