全部
常见问题
产品动态
精选推荐
功能建议

分析中 已回复 待规划 {{opt.name}}
分析中 已回复 待规划
小红书笔记详情接口测试实战指南

管理 管理 编辑 删除

一、小红书笔记详情接口概述

1.1 接口功能

小红书笔记详情接口用于获取指定笔记的完整信息,包括:

数据维度包含字段
基础信息笔记ID、标题、正文内容、发布时间、修改时间
多媒体内容封面图、图片列表、视频地址、视频时长
作者信息用户ID、昵称、头像、粉丝数、关注数、笔记数
互动数据点赞数、收藏数、评论数、分享数、浏览量
标签话题标签列表、@用户、关联话题、IP属地
位置信息地理位置名称、经纬度
商品信息关联商品、购买链接(带货笔记)
相关推荐相关笔记列表

1.2 接口特点

重要提示:小红书并未公开对外提供正式的开放 API,官方接口主要面向合作品牌方和广告主开放。 开发者通常通过以下方式获取数据:

  1. 小红书开放平台(需企业认证申请)
  2. 第三方数据服务(聚合 API)
  3. RPA/逆向工程(合规风险较高)


二、接口结构与请求规范

2.1 请求方式与地址测试


请求方式: HTTP GET / POST
请求地址: https://www.xiaohongshu.com/api/sns/v1/note/{note_id}/detail
          或第三方聚合接口: https://api.example.com/xhs/note/detail
          

2.2 请求参数


参数名类型必选说明
note_idString笔记唯一标识(24位十六进制字符串)
access_tokenString条件OAuth2.0 授权令牌(官方接口)
api_keyString条件第三方接口密钥
sourceString来源标识(web/app)
timestampLong时间戳(防重放攻击)

2.3 响应数据结构


{
  "code": 0,
  "msg": "success",
  "data": {
    "note_id": "649c46ab000000002702ad36",
    "title": "夏日必备的5款防晒霜测评",
    "content": "夏天快到了,给大家分享几款我常用的防晒霜...",
    "content_rich": [
      {"type": "text", "text": "夏天快到了..."},
      {"type": "image", "url": "https://sns-img-hw.xhscdn.com/xxx.jpg", "width": 1080, "height": 1440},
      {"type": "video", "url": "https://sns-video-hw.xhscdn.com/xxx.mp4", "duration": 156}
    ],
    "tags": ["防晒霜", "夏日护肤", "美妆测评"],
    "at_users": [{"user_id": "5123456789", "name": "美妆达人"}],
    "location": {"name": "上海市", "longitude": 121.47, "latitude": 31.23},
    "statistics": {
      "like_count": 12563,
      "collect_count": 8952,
      "comment_count": 1256,
      "share_count": 325,
      "view_count": 156890
    },
    "author": {
      "user_id": "612345678",
      "name": "护肤小能手",
      "avatar": "https://sns-avatar.xhscdn.com/avatar.jpg",
      "follower_count": 56800,
      "following_count": 320,
      "note_count": 128
    },
    "create_time": 1625097600,
    "update_time": 1625100800,
    "related_notes": []
  }
}


三、测试环境搭建

3.1 安装依赖


pip install requests
pip install pytest
pip install pytest-html        # 测试报告生成
pip install allure-pytest      # Allure 测试报告
pip install python-dotenv      # 环境变量管理
pip install loguru             # 日志记录

3.2 项目结构


xhs_api_test/
├── config/
│   └── config.py              # 配置文件
├── testcases/
│   ├── test_note_detail.py    # 笔记详情接口测试
│   └── conftest.py            # pytest  fixtures
├── utils/
│   ├── api_client.py          # API 请求封装
│   ├── data_generator.py      # 测试数据生成
│   └── assertions.py          # 断言工具
├── reports/                   # 测试报告目录
├── data/                      # 测试数据文件
└── pytest.ini                 # pytest 配置


四、核心代码实现

4.1 API 客户端封装


# utils/api_client.py
import requests
import time
import hashlib
import json
from typing import Dict, Optional
from loguru import logger


class XHSApiClient:
    """
    小红书笔记详情 API 客户端
    支持官方接口和第三方聚合接口
    """
    
    def __init__(self, base_url: str, api_key: Optional[str] = None, 
                 api_secret: Optional[str] = None):
        self.base_url = base_url.rstrip('/')
        self.api_key = api_key
        self.api_secret = api_secret
        self.session = requests.Session()
        self.session.headers.update({
            "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",
            "Accept": "application/json",
            "Accept-Language": "zh-CN,zh;q=0.9",
            "Referer": "https://www.xiaohongshu.com/"
        })
        
    def _generate_sign(self, params: Dict) -> str:
        """生成请求签名(第三方接口使用)"""
        if not self.api_secret:
            return ""
            
        sorted_params = sorted(params.items())
        param_str = "".join([f"{k}{v}" for k, v in sorted_params])
        sign_str = f"{self.api_secret}{param_str}{self.api_secret}"
        return hashlib.md5(sign_str.encode()).hexdigest().upper()
    
    def get_note_detail(self, note_id: str, extra_params: Optional[Dict] = None) -> Dict:
        """
        获取笔记详情
        
        Args:
            note_id: 笔记 ID
            extra_params: 额外参数
        
        Returns:
            API 响应字典
        """
        url = f"{self.base_url}/api/sns/v1/note/{note_id}/detail"
        
        params = {
            "source": "web",
            "timestamp": int(time.time())
        }
        
        if self.api_key:
            params["api_key"] = self.api_key
            params["sign"] = self._generate_sign(params)
            
        if extra_params:
            params.update(extra_params)
        
        try:
            logger.info(f"请求笔记详情 | note_id: {note_id}")
            response = self.session.get(url, params=params, timeout=30)
            response.raise_for_status()
            
            result = response.json()
            logger.info(f"响应状态: code={result.get('code')}, msg={result.get('msg')}")
            return result
            
        except requests.exceptions.Timeout:
            logger.error(f"请求超时 | note_id: {note_id}")
            return {"code": -1, "msg": "请求超时", "data": None}
        except requests.exceptions.HTTPError as e:
            logger.error(f"HTTP 错误 | status: {e.response.status_code}")
            return {"code": e.response.status_code, "msg": str(e), "data": None}
        except requests.exceptions.RequestException as e:
            logger.error(f"请求异常: {e}")
            return {"code": -2, "msg": str(e), "data": None}
    
    def get_note_detail_batch(self, note_ids: list) -> Dict[str, Dict]:
        """批量获取笔记详情"""
        results = {}
        for note_id in note_ids:
            results[note_id] = self.get_note_detail(note_id)
            time.sleep(1)  # 限流控制
        return results
        

4.2 测试用例设计


# testcases/test_note_detail.py
import pytest
import allure
from utils.api_client import XHSApiClient


@allure.feature("小红书笔记详情接口")
@allure.story("基础功能测试")
class TestNoteDetail:
    
    @pytest.fixture(scope="class")
    def client(self):
        """初始化 API 客户端"""
        return XHSApiClient(
            base_url="https://www.xiaohongshu.com",
            api_key="your_api_key",
            api_secret="your_api_secret"
        )
    
    @allure.title("TC01: 获取正常笔记详情")
    @allure.description("传入有效的笔记ID,验证返回数据完整性和字段类型")
    @pytest.mark.smoke
    def test_get_note_detail_success(self, client):
        """测试正常获取笔记详情"""
        note_id = "649c46ab000000002702ad36"  # 有效笔记ID
        
        with allure.step("发送请求"):
            response = client.get_note_detail(note_id)
        
        with allure.step("验证响应状态"):
            assert response["code"] == 0, f"期望 code=0, 实际 code={response['code']}"
            assert response["msg"] == "success"
        
        with allure.step("验证数据结构"):
            data = response["data"]
            assert data is not None, "data 字段不应为空"
            
            # 验证必需字段存在
            required_fields = [
                "note_id", "title", "content", "author", 
                "statistics", "create_time"
            ]
            for field in required_fields:
                assert field in data, f"缺少必需字段: {field}"
        
        with allure.step("验证字段类型"):
            assert isinstance(data["note_id"], str)
            assert isinstance(data["title"], str)
            assert isinstance(data["statistics"]["like_count"], int)
            assert isinstance(data["statistics"]["comment_count"], int)
            assert isinstance(data["author"]["follower_count"], int)
        
        with allure.step("验证数据一致性"):
            assert data["note_id"] == note_id, "返回的 note_id 应与请求一致"
            assert data["statistics"]["like_count"] >= 0, "点赞数不应为负数"
            assert data["statistics"]["view_count"] >= data["statistics"]["like_count"], \
                "浏览量应大于等于点赞数"
    
    @allure.title("TC02: 笔记ID不存在")
    @allure.description("传入不存在的笔记ID,验证返回正确的错误信息")
    def test_note_not_found(self, client):
        """测试笔记不存在场景"""
        note_id = "000000000000000000000000"  # 不存在的ID
        
        response = client.get_note_detail(note_id)
        
        assert response["code"] != 0, "应返回非零错误码"
        assert "msg" in response, "应包含错误信息"
        assert "data" not in response or response["data"] is None
    
    @allure.title("TC03: 笔记ID格式错误")
    @allure.description("传入格式非法的笔记ID,验证参数校验")
    @pytest.mark.parametrize("invalid_id", [
        "",                           # 空字符串
        "123",                        # 过短
        "abcdefghijklmnopqrstuvwx",   # 非十六进制
        "649c46ab000000002702ad3g",   # 包含非法字符
        "649c46ab000000002702ad360",  # 过长(25位)
    ])
    def test_invalid_note_id(self, client, invalid_id):
        """测试非法笔记ID格式"""
        response = client.get_note_detail(invalid_id)
        
        assert response["code"] != 0 or response.get("data") is None, \
            f"非法ID '{invalid_id}' 应返回错误或空数据"
    
    @allure.title("TC04: 笔记ID为空")
    @allure.description("不传笔记ID,验证参数缺失处理")
    def test_missing_note_id(self, client):
        """测试缺少必要参数"""
        response = client.get_note_detail("")
        
        assert response["code"] != 0, "缺少参数应返回错误"
    
    @allure.title("TC05: 验证多媒体内容类型")
    @allure.description("分别测试图文笔记和视频笔记的内容结构")
    @pytest.mark.parametrize("note_id,expected_type", [
        ("649c46ab000000002702ad36", "image"),  # 图文笔记
        ("649c46ab000000002702ad37", "video"),  # 视频笔记
    ])
    def test_note_content_type(self, client, note_id, expected_type):
        """测试不同内容类型的笔记"""
        response = client.get_note_detail(note_id)
        
        if response["code"] != 0:
            pytest.skip("笔记可能已删除或不可访问")
        
        data = response["data"]
        content_rich = data.get("content_rich", [])
        
        if expected_type == "image":
            image_items = [item for item in content_rich if item.get("type") == "image"]
            assert len(image_items) > 0, "图文笔记应包含图片"
        elif expected_type == "video":
            video_items = [item for item in content_rich if item.get("type") == "video"]
            assert len(video_items) > 0, "视频笔记应包含视频"
            assert "duration" in video_items[0], "视频应包含时长信息"
    
    @allure.title("TC06: 验证作者信息完整性")
    @allure.description("检查作者字段的完整性和数据类型")
    def test_author_info(self, client):
        """测试作者信息"""
        note_id = "649c46ab000000002702ad36"
        response = client.get_note_detail(note_id)
        
        if response["code"] != 0:
            pytest.skip("笔记不可访问")
        
        author = response["data"]["author"]
        
        assert "user_id" in author
        assert "name" in author
        assert "avatar" in author
        assert isinstance(author["follower_count"], int)
        assert author["follower_count"] >= 0
    
    @allure.title("TC07: 验证互动数据范围")
    @allure.description("验证互动数据的合理范围")
    def test_interaction_data(self, client):
        """测试互动数据有效性"""
        note_id = "649c46ab000000002702ad36"
        response = client.get_note_detail(note_id)
        
        if response["code"] != 0:
            pytest.skip("笔记不可访问")
        
        stats = response["data"]["statistics"]
        
        # 数据范围校验
        assert stats["like_count"] >= 0, "点赞数不能为负"
        assert stats["collect_count"] >= 0, "收藏数不能为负"
        assert stats["comment_count"] >= 0, "评论数不能为负"
        assert stats["share_count"] >= 0, "分享数不能为负"
        assert stats["view_count"] >= 0, "浏览量不能为负"
        
        # 逻辑关系校验
        assert stats["view_count"] >= stats["like_count"], "浏览量应>=点赞数"
        assert stats["like_count"] >= stats["collect_count"], "点赞数通常>=收藏数"
    
    @allure.title("TC08: 验证时间戳格式")
    @allure.description("检查创建时间和修改时间的时间戳格式")
    def test_timestamp_format(self, client):
        """测试时间戳格式"""
        note_id = "649c46ab000000002702ad36"
        response = client.get_note_detail(note_id)
        
        if response["code"] != 0:
            pytest.skip("笔记不可访问")
        
        data = response["data"]
        
        assert isinstance(data["create_time"], (int, float)), "create_time 应为时间戳"
        assert data["create_time"] > 0, "时间戳应为正数"
        
        if "update_time" in data:
            assert data["update_time"] >= data["create_time"], \
                "修改时间应晚于创建时间"


@allure.feature("小红书笔记详情接口")
@allure.story("性能与异常测试")
class TestNoteDetailPerformance:
    
    @pytest.fixture(scope="class")
    def client(self):
        return XHSApiClient(
            base_url="https://www.xiaohongshu.com",
            api_key="your_api_key"
        )
    
    @allure.title("TC09: 接口响应时间")
    @allure.description("验证接口响应时间在可接受范围内")
    def test_response_time(self, client):
        """测试响应时间"""
        import time
        
        note_id = "649c46ab000000002702ad36"
        start = time.time()
        response = client.get_note_detail(note_id)
        elapsed = time.time() - start
        
        allure.attach(f"实际响应时间: {elapsed:.2f}s", "性能数据")
        assert elapsed < 3, f"响应时间 {elapsed:.2f}s 超过阈值 3s"
    
    @allure.title("TC10: 并发请求测试")
    @allure.description("测试接口在高并发下的稳定性")
    @pytest.mark.stress
    def test_concurrent_requests(self, client):
        """测试并发请求"""
        import threading
        
        note_ids = ["649c46ab000000002702ad36"] * 5
        results = []
        
        def fetch(note_id):
            results.append(client.get_note_detail(note_id))
        
        threads = [threading.Thread(target=fetch, args=(nid,)) for nid in note_ids]
        for t in threads:
            t.start()
        for t in threads:
            t.join()
        
        success_count = sum(1 for r in results if r.get("code") == 0)
        assert success_count >= 3, f"并发请求成功率过低: {success_count}/5"
    
    @allure.title("TC11: 限流测试")
    @allure.description("测试高频请求下的限流处理")
    @pytest.mark.stress
    def test_rate_limiting(self, client):
        """测试限流"""
        note_id = "649c46ab000000002702ad36"
        responses = []
        
        for _ in range(20):
            responses.append(client.get_note_detail(note_id))
        
        # 检查是否有 429 状态码
        rate_limited = any(r.get("code") == 429 for r in responses)
        if rate_limited:
            allure.attach("触发限流", "限流验证")
            

4.3 测试数据生成器

Python


# utils/data_generator.py
import random
import string


class NoteDataGenerator:
    """测试数据生成器"""
    
    @staticmethod
    def valid_note_id():
        """生成有效的笔记ID(24位十六进制)"""
        return ''.join(random.choices(string.hexdigits.lower(), k=24))
    
    @staticmethod
    def invalid_note_ids():
        """生成各类非法笔记ID"""
        return [
            "",                                    # 空
            "123",                                 # 过短
            "xyz",                                 # 非十六进制
            "g" * 24,                              # 非法字符
            ''.join(random.choices(string.hexdigits.lower(), k=23)),  # 23位
            ''.join(random.choices(string.hexdigits.lower(), k=25)),  # 25位
        ]
    
    @staticmethod
    def boundary_note_ids():
        """边界值测试数据"""
        return [
            "0" * 24,                              # 全0
            "f" * 24,                              # 全f
            "0" + "f" * 23,                        # 边界混合
        ]
        

4.4 Pytest 配置


# pytest.ini
[pytest]
testpaths = testcases
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short --html=reports/report.html --self-contained-html
markers =
    smoke: 冒烟测试
    regression: 回归测试
    stress: 压力测试
    slow: 慢速测试
    


五、测试执行与报告

5.1 执行测试


# 执行全部测试
pytest

# 执行冒烟测试
pytest -m smoke

# 生成 Allure 报告
pytest --alluredir=reports/allure
allure serve reports/allure

# 执行压力测试
pytest -m stress -n 4

5.2 测试报告示例


============================= test session starts ==============================
platform darwin -- Python 3.11.0, pytest-7.4.0, pluggy-1.0.0
rootdir: /Users/dev/xhs_api_test
collected 11 items

testcases/test_note_detail.py::TestNoteDetail::test_get_note_detail_success PASSED [  9%]
testcases/test_note_detail.py::TestNoteDetail::test_note_not_found PASSED    [ 18%]
testcases/test_note_detail.py::TestNoteDetail::test_invalid_note_id[ ] PASSED [ 27%]
testcases/test_note_detail.py::TestNoteDetail::test_invalid_note_id[123] PASSED [ 36%]
testcases/test_note_detail.py::TestNoteDetail::test_invalid_note_id[...] PASSED [ 45%]
testcases/test_note_detail.py::TestNoteDetail::test_note_content_type PASSED [ 54%]
testcases/test_note_detail.py::TestNoteDetail::test_author_info PASSED       [ 63%]
testcases/test_note_detail.py::TestNoteDetail::test_interaction_data PASSED  [ 72%]
testcases/test_note_detail.py::TestNoteDetail::test_timestamp_format PASSED   [ 81%]
testcases/test_note_detail.py::TestNoteDetailPerformance::test_response_time PASSED [ 90%]
testcases/test_note_detail.py::TestNoteDetailPerformance::test_concurrent_requests PASSED [100%]

============================== 11 passed in 15.32s ==============================


六、常见问题与解决方案


问题原因解决方案
code: -1, msg: 请求超时网络延迟或接口响应慢增加超时时间,使用代理池
code: 403IP 被封禁或缺少权限更换 IP,检查 API Key 权限
code: 429请求频率过高触发限流降低请求频率,使用指数退避
data: null笔记已删除或私密跳过该笔记,记录日志
字段类型不匹配API 版本变更更新测试断言,关注接口文档
视频 URL 失效CDN 链接过期验证 URL 可访问性,使用备用地址


七、合规注意事项

  1. 数据使用范围:仅用于自有业务分析,不得转售或商用
  2. 隐私保护:用户个人信息需脱敏处理
  3. 频率控制:遵守平台限流策略,建议 ≤ 10 次/秒
  4. 官方授权:生产环境建议使用小红书开放平台正式接口


八、总结


测试维度覆盖内容用例数量
功能测试正常请求、参数校验、数据完整性7
异常测试非法参数、空值、越界3
性能测试响应时间、并发、限流2
数据校验字段类型、逻辑关系、范围贯穿全部
通过系统化的接口测试,可以确保小红书笔记详情接口的稳定性和数据准确性,为后续的内容分析、数据挖掘等业务场景奠定坚实基础。


{{voteData.voteSum}} 人已参与
支持
反对
请登录后查看

123c001fa85d 最后编辑于2026-06-11 18:04:50

快捷回复
回复
回复
回复({{post_count}}) {{!is_user ? '我的回复' :'全部回复'}}
排序 默认正序 回复倒序 点赞倒序

{{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level || item.bbs_level }}

作者 管理员 企业

{{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推荐': '推荐'}}
{{item.is_suggest == 1? '取消推荐': '推荐'}} 【已收集】
{{item.floor}}# 沙发 板凳 地板 {{item.floor}}# 【已收集】
{{item.user_info.title || '暂无简介'}}
附件

{{itemf.name}}

{{item.created_at}}  {{item.ip_address}}
打赏
已打赏¥{{item.reward_price}}
{{item.like_count}}
分享
{{item.showReply ? '取消回复' : '回复'}}
删除
回复
回复

{{itemc.user_info.nickname}}

{{itemc.user_name}}

回复 {{itemc.comment_user_info.nickname}}

附件

{{itemf.name}}

{{itemc.created_at}}
打赏
已打赏¥{{itemc.reward_price}}
{{itemc.like_count}}
{{itemc.showReply ? '取消回复' : '回复'}}
删除
回复
回复
收起 展开更多
查看更多
打赏
已打赏¥{{reward_price}}
19
{{like_count}}
{{collect_count}}
添加回复 ({{post_count}})

相关推荐

快速安全登录

使用微信扫码登录
回复
回复
问题:
问题自动获取的帖子内容,不准确时需要手动修改. [获取答案]
答案:
提交
bug 需求 取 消 确 定
打赏金额
当前余额:¥{{rewardUserInfo.reward_price}}
{{item.price}}元
请输入 0.1-{{reward_max_price}} 范围内的数值
打赏成功
¥{{price}}
完成 确认打赏

微信登录/注册

切换手机号登录

{{ bind_phone ? '绑定手机' : '手机登录'}}

{{codeText}}
切换微信登录/注册
暂不绑定
CRMEB客服
CRMEB咨询热线 400-8888-794

扫码领取产品资料

功能清单
思维导图
安装教程
CRMEB开源商城下载 源码下载 CRMEB帮助文档 帮助文档
返回顶部 返回顶部
CRMEB客服