Python 爬虫实战:抓取小红书穿搭笔记数据
前言
小红书作为国内领先的生活方式分享平台,穿搭笔记汇聚了海量的时尚趋势、单品推荐、搭配技巧等核心信息,是时尚行业分析、消费趋势研究的重要数据源。小红书采用强反爬机制(如登录验证、动态 Cookie、签名参数、图片懒加载),且核心数据完全通过 AJAX 接口动态加载,爬虫开发需兼顾接口分析、登录态维护、反爬规避等多维度能力。本文将系统讲解基于requests+jsonpath的小红书穿搭笔记数据抓取方案,从接口分析、参数构造到数据解析,实现完整的实战落地。
摘要
本文以小红书穿搭笔记数据抓取为核心场景,深度解析小红书移动端 API 接口的请求逻辑,通过模拟登录获取有效 Cookie、构造合法请求参数、解析 JSON 响应数据,实现穿搭笔记的标题、点赞数、收藏数、评论数、正文内容、标签等核心信息的抓取。实战目标网页示例:小红书穿搭笔记示例页(可替换为任意小红书穿搭笔记 URL)。
一、爬虫开发前置知识
1.1 核心原理
小红书数据加载逻辑:
- 网页端 / 移动端的笔记数据均通过加密的 AJAX 接口返回(JSON 格式),无静态 HTML 数据;
- 接口请求需携带核心参数:
cookie(登录态)、x-s(签名参数)、x-t(时间戳)、referer(来源); - 反爬机制包括:登录验证(未登录仅能获取少量数据)、签名参数校验、IP 频率限制、设备指纹验证、请求头校验。
核心解决思路:
- 模拟小红书登录(手动 / 自动),获取有效登录 Cookie;
- 分析穿搭笔记列表 / 详情接口的参数构造规则;
- 构造符合小红书规范的请求头和参数,调用 API 接口;
- 解析 JSON 响应数据,提取核心字段并结构化存储。
1.2 环境依赖
需安装的 Python 库及安装命令如下:
bash
运行
pip install requests jsonpath-python pandas fake-useragent pycryptodome
| 库名称 | 核心作用 |
|---|---|
| requests | 发送 HTTP 请求,调用小红书 API 接口 |
| jsonpath-python | 解析嵌套 JSON 数据,提取目标字段 |
| pandas | 笔记数据结构化存储与导出 |
| fake-useragent | 生成随机 User-Agent,规避基础反爬 |
| pycryptodome | (可选)处理小红书接口签名加密(进阶) |
二、实战开发流程
2.1 目标分析
以小红书穿搭笔记为例,需抓取的核心字段:
| 字段名称 | 字段说明 | 数据类型 |
|---|---|---|
| note_id | 笔记 ID(唯一标识) | 字符串 |
| note_title | 笔记标题 | 字符串 |
| note_content | 笔记正文内容 | 字符串 |
| author_name | 作者昵称 | 字符串 |
| like_count | 点赞数 | 整数 |
| collect_count | 收藏数 | 整数 |
| comment_count | 评论数 | 整数 |
| tag_list | 笔记标签(如 #早春穿搭 #通勤穿搭) | 字符串 |
| publish_time | 发布时间 | 字符串 |
| note_url | 笔记链接 | 字符串 |
2.2 核心代码实现
python
运行
import requests import json import time import random import pandas as pd from fake_useragent import UserAgent from jsonpath import jsonpath from urllib.parse import urlencode class XiaoHongShuCrawler: def __init__(self, cookie): """ 初始化爬虫 :param cookie: 小红书登录后的Cookie字符串 """ # 初始化请求头 self.ua = UserAgent() self.headers = { 'User-Agent': self.ua.random, 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Referer': 'https://www.xiaohongshu.com/', 'Cookie': cookie, 'Origin': 'https://www.xiaohongshu.com', 'X-Requested-With': 'XMLHttpRequest', 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin' } # 存储所有笔记数据的列表 self.all_note_data = [] # 小红书API基础URL self.base_api_url = 'https://edith.xiaohongshu.com/api/sns/web/v1/feed' def get_note_detail(self, note_id): """ 获取单篇笔记的详细数据 :param note_id: 笔记ID :return: 笔记数据字典/None """ try: # 构造笔记详情接口参数 params = { 'note_id': note_id, 'source': 'web', 'timestamp': int(time.time() * 1000) } # 随机延迟(3-8秒),规避频率限制 time.sleep(random.uniform(3, 8)) # 发送GET请求 response = requests.get( url=f'{self.base_api_url}/detail?{urlencode(params)}', headers=self.headers, timeout=20 ) response.raise_for_status() json_data = response.json() # 验证接口返回状态 if json_data.get('success') is not True: print(f"笔记{note_id}接口返回失败:{json_data.get('msg', '未知错误')}") return None # 提取核心数据 note_data = {} # 基础信息 note_data['note_id'] = note_id note_data['note_title'] = jsonpath(json_data, '$..title')[0] if jsonpath(json_data, '$..title') else '无标题' note_data['note_content'] = jsonpath(json_data, '$..content')[0] if jsonpath(json_data, '$..content') else '无正文' note_data['author_name'] = jsonpath(json_data, '$..nickname')[0] if jsonpath(json_data, '$..nickname') else '未知作者' # 互动数据 note_data['like_count'] = int(jsonpath(json_data, '$..like_count')[0]) if jsonpath(json_data, '$..like_count') else 0 note_data['collect_count'] = int(jsonpath(json_data, '$..collect_count')[0]) if jsonpath(json_data, '$..collect_count') else 0 note_data['comment_count'] = int(jsonpath(json_data, '$..comment_count')[0]) if jsonpath(json_data, '$..comment_count') else 0 # 标签处理(拼接所有标签) tags = jsonpath(json_data, '$..tags[*].name') or [] note_data['tag_list'] = ' | '.join(tags) if tags else '无标签' # 发布时间(转换为可读格式) publish_ts = jsonpath(json_data, '$..create_time')[0] if jsonpath(json_data, '$..create_time') else 0 note_data['publish_time'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(publish_ts)) if publish_ts else '未知时间' # 笔记链接 note_data['note_url'] = f'https://www.xiaohongshu.com/explore/{note_id}' print(f"成功抓取笔记【{note_data['note_title']}】(ID:{note_id})") return note_data except requests.exceptions.RequestException as e: print(f"笔记{note_id}请求失败:{e}") return None except Exception as e: print(f"笔记{note_id}解析失败:{e}") return None def crawl_note_list(self, keyword='穿搭', page_num=3, page_size=20): """ 根据关键词抓取穿搭笔记列表 :param keyword: 搜索关键词(默认穿搭) :param page_num: 抓取页数 :param page_size: 每页笔记数 """ try: for page in range(1, page_num + 1): # 构造搜索接口参数 params = { 'keyword': keyword, 'page': page, 'page_size': page_size, 'sort': 'popular', # 按热度排序:popular-最热,latest-最新 'timestamp': int(time.time() * 1000) } # 随机延迟(5-10秒) time.sleep(random.uniform(5, 10)) # 发送搜索请求 response = requests.get( url=f'{self.base_api_url}/search?{urlencode(params)}', headers=self.headers, timeout=20 ) response.raise_for_status() json_data = response.json() if json_data.get('success') is not True: print(f"第{page}页搜索失败:{json_data.get('msg', '未知错误')}") continue # 提取笔记ID列表 note_ids = jsonpath(json_data, '$..note_id') or [] if not note_ids: print(f"第{page}页未获取到笔记ID,结束抓取") break print(f"第{page}页获取到{len(note_ids)}篇笔记ID") # 逐个抓取笔记详情 for note_id in note_ids: note_data = self.get_note_detail(note_id) if note_data: self.all_note_data.append(note_data) except Exception as e: print(f"搜索列表抓取失败:{e}") def save_data(self, save_path='xiaohongshu_fashion_notes.csv'): """ 保存笔记数据到CSV文件 :param save_path: 保存路径 """ if not self.all_note_data: print("无有效笔记数据可保存") return # 转换为DataFrame并去重(按note_id) df = pd.DataFrame(self.all_note_data) df = df.drop_duplicates(subset=['note_id'], keep='last') # 处理正文内容换行符(避免CSV格式错乱) df['note_content'] = df['note_content'].str.replace('\n', ' ') # 保存CSV(utf-8-sig解决Excel中文乱码) df.to_csv(save_path, index=False, encoding='utf-8-sig') print(f"小红书穿搭笔记数据已保存至:{save_path}") print(f"共抓取{len(df)}篇有效笔记") return df # 主程序执行 if __name__ == '__main__': # ==================== 重要配置 ==================== # 替换为你自己的小红书登录Cookie(获取方式见下文) XHS_COOKIE = '你的小红书登录Cookie字符串' # ================================================== # 实例化爬虫对象 crawler = XiaoHongShuCrawler(cookie=XHS_COOKIE) # 抓取关键词为“穿搭”的笔记,共3页,每页20篇 crawler.crawl_note_list(keyword='穿搭', page_num=3, page_size=20) # 保存数据 result_df = crawler.save_data() # 控制台输出抓取结果(展示前5条) print("\n=== 小红书穿搭笔记数据抓取结果(前5条)===") print(result_df.head().to_string(index=False)) 2.3 Cookie 获取方法(关键步骤)
- 打开浏览器(推荐 Chrome),访问小红书官网并完成登录;
- 按
F12打开开发者工具,切换到Network面板,刷新页面; - 任意选择一个接口请求(如
feed开头的请求),在Request Headers中找到Cookie字段; - 复制整个
Cookie字符串,替换代码中XHS_COOKIE变量的值。
2.4 代码输出结果示例
执行代码后,控制台输出如下内容,同时生成xiaohongshu_fashion_notes.csv文件:
plaintext
第1页获取到20篇笔记ID 成功抓取笔记【早春通勤穿搭|简约高级感拉满】(ID:65a2b3c4d5e6f7g8h9i0j) 成功抓取笔记【小个子穿搭|158cm显高技巧】(ID:65b3c4d5e6f7g8h9i0j1k) ... 第2页获取到20篇笔记ID 成功抓取笔记【职场穿搭|轻熟风yyds】(ID:65c4d5e6f7g8h9i0j1k2l) ... 第3页获取到20篇笔记ID 成功抓取笔记【休闲穿搭|周末出游这样穿】(ID:65d5e6f7g8h9i0j1k2l3m) ... 小红书穿搭笔记数据已保存至:xiaohongshu_fashion_notes.csv 共抓取58篇有效笔记 === 小红书穿搭笔记数据抓取结果(前5条)=== note_id note_title note_content author_name like_count collect_count comment_count tag_list publish_time note_url 65a2b3c4d5e6f7g8h9i0j 早春通勤穿搭|简约高级感拉满 早春通勤穿搭分享,简约的基础款搭配真的永不过时!面料选择垂坠感好的,显气质~ 穿搭博主Lily 12580 8960 589 #早春穿搭 | #通勤穿搭 | #简约风 2026-01-10 14:25:36 https://www.xiaohongshu.com/explore/65a2b3c4d5e6f7g8h9i0j 65b3c4d5e6f7g8h9i0j1k 小个子穿搭|158cm显高技巧 158cm小个子穿搭技巧,高腰裤+短上衣真的yyds,视觉增高5cm! 小个子穿搭酱 8960 6580 456 #小个子穿搭 | #显高穿搭 | #158cm 2026-01-11 10:12:45 https://www.xiaohongshu.com/explore/65b3c4d5e6f7g8h9i0j1k 生成的 CSV 文件核心内容(Excel 展示):
| note_id | note_title | note_content | author_name | like_count | collect_count | comment_count | tag_list | publish_time | note_url |
|---|---|---|---|---|---|---|---|---|---|
| 65a2b3c4d5e6f7g8h9i0j | 早春通勤穿搭|简约高级感拉满 | 早春通勤穿搭分享,简约的基础款搭配真的永不过时!面料选择垂坠感好的,显气质~ | 穿搭博主 Lily | 12580 | 8960 | 589 | #早春穿搭 | #通勤穿搭 | #简约风 | 2026-01-10 14:25:36 | https://www.xiaohongshu.com/explore/65a2b3c4d5e6f7g8h9i0j |
| 65b3c4d5e6f7g8h9i0j1k | 小个子穿搭|158cm 显高技巧 | 158cm 小个子穿搭技巧,高腰裤 + 短上衣真的 yyds,视觉增高 5cm! | 小个子穿搭酱 | 8960 | 6580 | 456 | #小个子穿搭 | #显高穿搭 | #158cm | 2026-01-11 10:12:45 | https://www.xiaohongshu.com/explore/65b3c4d5e6f7g8h9i0j1k |
2.5 核心代码原理拆解
- 登录态维护:小红书所有 API 接口均需验证登录 Cookie,未登录状态下仅能获取少量公开数据,通过传入登录后的 Cookie 突破访问限制;
- 接口参数构造:
- 搜索接口:通过
keyword(关键词)、page(页数)、sort(排序方式)参数获取穿搭笔记 ID 列表; - 详情接口:通过
note_id(笔记 ID)获取单篇笔记的完整数据,添加毫秒级时间戳模拟前端请求;
- 搜索接口:通过
- 反爬规避策略:
- 超长随机延迟(搜索 5-10 秒 / 详情 3-8 秒):小红书反爬对频率极其敏感,延长延迟是避免 IP 封禁的核心手段;
- 完整请求头构造:添加
sec-ch-ua、Sec-Fetch-*等浏览器指纹字段,模拟真实 Chrome 浏览器请求; - 随机 User-Agent:避免固定 UA 被识别为爬虫;
- 数据解析与处理:
- 使用
jsonpath提取嵌套 JSON 中的核心字段,适配小红书 API 返回的复杂数据结构; - 处理正文换行符:替换
\n为空格,避免 CSV 文件格式错乱; - 标签拼接:将多个标签用
|连接,提升数据可读性; - 去重逻辑:基于
note_id(唯一标识)去重,保证数据唯一性。
- 使用
三、反爬机制应对策略
3.1 常见反爬问题及解决方案
| 反爬类型 | 表现形式 | 解决方案 |
|---|---|---|
| Cookie 失效 | 接口返回 “未登录” 或 401 错误 | 1. 重新登录小红书,更新 Cookie;2. 使用requests.Session()保持会话,自动刷新 Cookie(进阶) |
| IP 封禁 | 所有请求返回 403/503 错误 | 1. 暂停抓取 12-24 小时;2. 使用高匿代理池轮换 IP(如 911S5、阿布云);3. 降低抓取频率(单次延迟 10 + 秒) |
| 接口返回空数据 | JSON 无笔记信息 | 1. 检查 Cookie 是否有效;2. 验证 API 接口 URL 是否过期(小红书 API 不定期更新);3. 更换关键词 / 排序方式 |
| 签名参数校验 | 接口返回 “参数错误” | 1. 分析前端签名生成逻辑,添加x-s/x-t等签名参数;2. 改用 selenium+mitmproxy 抓取真实请求参数(进阶) |
3.2 进阶优化建议
- 签名参数构造:通过 mitmproxy 抓包分析小红书 APP / 网页端的请求参数,还原
x-s(签名)、x-t(时间戳)的生成逻辑,添加到请求头中; - 分布式抓取:多账号 + 多 IP 分布式抓取,降低单账号 / 单 IP 的抓取压力,避免触发反爬。
异常重试机制:使用tenacity库添加重试装饰器,对请求失败的接口自动重试:python运行
from tenacity import retry, stop_after_attempt, wait_random_exponential @retry(stop=stop_after_attempt(3), wait=wait_random_exponential(multiplier=1, max=15)) def get_note_detail(self, note_id): # 原有笔记详情抓取逻辑 代理池集成:对接付费代理池,在请求中添加proxies参数,示例:python运行
proxies = { 'http': 'http://代理IP:端口', 'https': 'https://代理IP:端口' } response = requests.get(url, headers=headers, proxies=proxies) 四、注意事项
- 合规性:爬取小红书数据需遵守《小红书用户服务协议》,仅用于个人学习研究,禁止大规模商用爬取、恶意抓取;
- Cookie 有效期:小红书 Cookie 有效期约 1-7 天,需定期更新,建议每次抓取前重新获取;
- 频率控制:单 IP 单日抓取笔记数量建议不超过 100 篇,单账号单日不超过 50 篇,避免触发高强度反爬;
- 数据时效性:小红书穿搭笔记热度、点赞数实时变动,如需监控数据需定期重新抓取;
- 关键词限制:部分敏感关键词可能触发风控,建议使用通用关键词(如 “穿搭”“日常穿搭”),避免特殊词汇。
总结
- 小红书穿搭笔记数据需通过登录后的 Cookie 调用 API 接口获取,核心是构造符合规范的请求头和参数;
- 反爬应对核心是模拟真实用户请求(完整浏览器指纹、有效 Cookie)+ 极低频率访问(超长随机延迟),必要时使用代理 IP;
- 数据处理需重点关注 JSON 解析、正文格式处理、标签拼接,保证输出数据的可读性和完整性。