
简介一份可直接运行的xhs搜索笔记与评论爬虫项目面向Python爬虫开发者与小红书内容研究者重点解决小红书Web端搜索笔记与评论的数据采集问题。项目通过JS注入方式动态计算请求头中的X-s、X-s-C加密参数再借助异步请求获取接口数据并将页面出现的字段完整结构化写入本地Excel字段覆盖页面展示的各类信息整体采集速度优于纯浏览器模拟方案。压缩包共52个文件以py源码、pyc编译产物、JS脚本与ini/txt配置为主体积仅82KB部署轻量项目内部分层清晰包含基础爬虫封装、代理账号池、数据模型、数据库配置、账号配置、反检测stealth脚本、短信通知工具及测试模块并附带requirements.txt依赖清单方便直接安装与二次扩展。目前已有303人学习下载对需要小批量采集分析小红书内容或希望借鉴其签名参数处理思路的爬虫学习者而言这套项目能提供较完整的参考实现。1. 从“小红书数据抓取”说起不只是X-s参数的问题做爬虫的人最清楚小红书这类内容平台的接口防护已经不是简单的UA伪装或Cookie带上就能过的级别。真正卡住大多数人的是请求头里那两个动态参数X-s和X-s-C它们每次请求都不一样而且和服务器的校验逻辑强绑定。你手动在浏览器里复制出来的值脱离当时的环境和操作序列基本一用就废。市面上流传的很多方案是走Selenium或Playwright慢速点击效率低不说页面一改版就崩。而这个项目给了一条更务实的路线不纯靠协议逆向也不完全依赖浏览器模拟而是通过JS注入去动态计算X-s参数拿到合法的请求头再走异步请求直接打数据接口。这样既绕开了“纯协议党”的高门槛也没有“模拟点击党”的速度瓶颈算是一个很典型的折中工程方案。这套项目适合谁如果你只是需要小批量、按关键词或按笔记维度去抓搜索笔记和评论做竞品分析、舆情监控、内容选题调研那它基本开箱即用。但如果你指望靠它跑千万级数据那必须先规划好账号和IP资源否则触发风控只是时间问题。下面我会从请求签名的生成原理、项目的模块设计、数据抓取流程以及账号/IP池的配置技巧几个维度展开最后把最容易踩的坑和验证方法一并说清。2. 请求签名与异步抓取原理X-s、X-s-C 是怎么算出来的2.1 小红书接口的签名校验逻辑小红书Web端和移动端的接口签名机制并不完全相同但核心思路一致客户端在发起请求前会收集当前请求的路径、查询参数、请求体、时间戳、设备信息等通过一段加密逻辑生成签名放在请求头里随请求一起发出。服务端收到后会做同样的计算比对签名是否一致同时校验时间戳的有效窗口。如果有人直接改写请求参数而不更新签名服务端立刻就能识别出来。常见的三个关键请求头是X-s、X-s-C和X-t。X-t是时间戳X-s是签名摘要X-s-C在某些接口里表示签名算法版本或附加校验值。这三个值必须成套出现单独伪造其中一个没有意义。更麻烦的是生成签名所需的算法逻辑被打包在站点自身的JavaScript文件里而且经过混淆处理直接读源码去还原算法投入产出比很低。2.2 为什么选择JS注入而不是纯Python重写算法纯协议派的做法是逆向出签名算法用Python重写一套。但小红书的混淆和动态加载策略导致算法经常微调一旦Hash或加密逻辑变了整套代码就得重新逆向。而且签名过程中还涉及浏览器环境的一些内置对象和属性比如window、navigator、document等Node.js里如果不对这些做大量mock计算出的签名极容易校验失败。JS注入方案的做法是用一个无头浏览器或轻量级浏览器环境比如Playwright、PyMiniRacer、Js2Py等加载站点自己的JS文件然后在页面的上下文里直接调用生成签名的函数拿到真实的X-s和X-s-C再返回给Python。这样算法逻辑由站点自身维护我们只需要找准调用时机和参数格式即可。相当于我们借用浏览器的计算能力来签自己的请求头。提示这种注入方式并非完全不存在风险。注入点需要稳定签名函数可能被改挂而且页面如果检测到自动化特征会拒绝返回计算结果。2.3 项目中的异步请求设计拿到签名后后续数据获取就不需要再经过浏览器了直接用requests或httpx发异步请求即可。项目里的main.py、base_crawler.py就是干这个的。异步的好处是一次签名可以复用多次请求吗不行。每个请求的路径和参数都不同理论上签名也要重新生成。所以项目里通常的做法是维护一个“签名池”分批预生成一定数量的签名然后异步任务调度时按需取用。# 伪代码示意签名池 异步请求 import httpx import asyncio class SignPool: def __init__(self, pool_size20): self._pool asyncio.Queue(maxsizepool_size) self._count 0 async def _generate(self): # 调用注入JS后的函数返回 x_s, x_s_c, x_t x_s, x_s_c, x_t await call_js_to_sign() return {x-s: x_s, x-s-c: x_s_c, x-t: x_t} async def fill(self): while self._count self._pool.maxsize: sign await self._generate() await self._pool.put(sign) self._count 1 async def get_sign(self): sign await self._pool.get() self._count - 1 return sign async def fetch_with_sign(client, url, params): sign await sign_pool.get_sign() headers build_headers(sign) resp await client.get(url, paramsparams, headersheaders) return resp.json()这段逻辑说明了一个关键点签名池的预生成和消费者的异步拉取是解耦的。fill()负责补充get_sign()负责消费两者异步协作避免每个请求都同步等待JS注入返回。参数上pool_size决定同时存多少签名设置太大会增加JS环境压力太小会导致请求阻塞一般20到50是合理范围。3. 项目结构与核心模块拆解3.1 从代码组织看工程套路解压xhs_spider_project.zip后目录结构并不复杂关键是理解每个文件的职责。tools里存放工具函数media_platform/xhs是主逻辑base下有base_crawler.py和proxy_account_pool.pymodels里放了xiaohongshu.py的数据模型config下拆了db_config.py、account_config.py、base_config.py三个配置文件。这种分层方式很典型配置与逻辑分离数据模型独立爬虫基类统一管理请求和重试。stealth.min.js是一个反自动化检测的JS脚本用于在注入环境里隐藏webdriver痕迹。libs里的easing.py用于模拟人类操作的时间曲线虽然这个项目不走模拟点击但在某些需要滑动验证码或滚动加载的场景下合理的easing算法能降低被识别概率。3.2 配置项的设计参考打开config/base_config.py通常会看到类似下面的内容# config/base_config.py class BaseConfig: # 爬取模式search / note_detail / comment / user CRAWL_MODE search # 每批任务暂停范围秒 REQUEST_INTERVAL (0.5, 1.5) # 单个账号最大请求次数超过则切换账号 MAX_REQUESTS_PER_ACCOUNT 50 # 代理IP切换时间秒 PROXY_SWITCH_INTERVAL 300 # 数据导出路径 EXCEL_OUTPUT_PATH ./output/xhs_data.xlsx # 日志级别 LOG_LEVEL INFO # 异步并发数 CONCURRENCY 5这里的REQUEST_INTERVAL是一个区间每次请求前随机取一个延迟值避免固定间隔被服务端统计分析识别。MAX_REQUESTS_PER_ACCOUNT是账号安全阈值超过后自动换成下一个可用账号。PROXY_SWITCH_INTERVAL控制代理IP的使用时长有些代理是按请求计费有些是按时长这个参数要根据你买的代理类型来调。account_config.py则用来维护账号Cookie和登录态。如果项目支持扫码登录后自动保存Cookie则这里会有对应的持久化逻辑。需要注意Cookie中的web_session字段通常是最核心的一旦失效所有请求会返回401或412。3.3 数据模型与Excel落地models/xiaohongshu.py中定义了笔记、评论、用户三个数据类。以评论为例一般包含评论ID、用户昵称、用户头像、点赞数、评论内容、评论时间、子评论列表等字段。数据抓完后通过db.py统一写入Excel使用openpyxl或pandas均可。# models/xiaohongshu.py 简化版 from dataclasses import dataclass, field from typing import List dataclass class Comment: comment_id: str user_name: str user_id: str like_count: int content: str created_at: str sub_comments: List[Comment] field(default_factorylist) dataclass class Note: note_id: str title: str desc: str author_name: str like_count: int comment_count: int share_count: int collected_count: int comments: List[Comment] field(default_factorylist)定义数据模型的意义在于当接口返回的JSON字段名发生变化时只需要适配一处而不必在每个解析函数里修改。4. 实战从关键词到Excel的完整抓取流程4.1 第一步初始化环境与登录态作者在原项目描述里特别提到“开箱即用”意思是依赖和配置都帮你列好了。requirements.txt里主要包括httpx、openpyxl、pandas、playwright或selenium取决于JS注入的实现方式以及fake-useragent等辅助库。安装依赖后先做登录态准备。如果项目支持扫码运行一个初始化脚本浏览器弹出二维码手机端扫码后Cookie自动持久化到本地文件。# 初始化依赖 pip install -r requirements.txt # 启动登录态获取示例 python -m media_platform.xhs.main --login这条命令会打开一个浏览器窗口扫码完成后关闭。登录信息会写入browser_data目录之后爬虫运行时会直接加载这份Cookie不需要重复登录。4.2 第二步配置抓取任务假设我们要抓取“数据分析”这个关键词下最近一周的热门笔记以及每篇笔记下的热评。在config/base_config.py里设置CRAWL_MODE search KEYWORDS [数据分析] SORT_TYPE popularity # 或 time TIME_RANGE week # 可选day / week / month / 空代表不限 MAX_NOTES_PER_KEYWORD 50 COMMENTS_PER_NOTE 20这里SORT_TYPE对应接口里的排序参数popularity表示按综合热度排序time表示按发布时间排序。不同排序接口的请求路径可能不一样但签名生成逻辑是一致的。4.3 第三步异步抓取与签名复用项目核心循环分三步搜索笔记ID列表、批量获取笔记详情、分页抓取评论。每一层都是异步并发。# 伪代码示意 import asyncio async def crawl_search_results(keyword): # 1. 搜索接口 search_params { keyword: keyword, sort: SORT_TYPE, time: TIME_RANGE, page: 0, page_size: 20 } sign await sign_pool.get_sign() headers append_sign_headers(sign) data await client.get(SEARCH_API, paramssearch_params, headersheaders) note_list extract_note_ids(data) return note_list async def crawl_comments(note_id): comments [] for page in range(MAX_PAGES): params {note_id: note_id, cursor: cursor, count: 20} # 每次请求都需要新的签名 sign await sign_pool.get_sign() headers append_sign_headers(sign) resp await client.get(COMMENT_API, paramsparams, headersheaders) page_comments parse_comment_data(resp) comments.extend(page_comments) if not has_more(resp): break return comments注意comment接口的cursor参数是游标不是页码。翻页时服务端返回的cursor字段要原样传给下一次请求如果手动改成数字大概率返回空列表。这也是评论区爬取最容易踩的坑。4.4 第四步数据结构化并写入Excel抓完数据后调用db.py里的写Excel函数。以笔记和评论一对多关系为例建议按“一行笔记加其所有评论”的扁平结构输出方便后续透视分析。import pandas as pd def notes_to_excel(notes: List[Note], output_path: str): rows [] for note in notes: for c in note.comments: rows.append({ note_id: note.note_id, note_title: note.title, author: note.author_name, like_count: note.like_count, comment_user: c.user_name, comment_content: c.content, comment_likes: c.like_count, comment_time: c.created_at }) df pd.DataFrame(rows) df.to_excel(output_path, indexFalse)to_excel需要openpyxl库支持导出时指定indexFalse避免写出多余的行号。如果数据量超过6万行Excel单个sheet会有性能问题建议拆分成多个sheet或改用CSV。4.5 第五步运行与日志观察python main.py run.log 21 监控日志里如果看到[403]大量出现先检查账号Cookie是否过期如果是[412]说明签名校验失败大概率需要刷新JS注入环境如果是[406]可能是代理IP被识别换一个IP池或降低并发数。5. 账号/IP池配置与反爬规避的进阶技巧5.1 账号池的调度策略原项目里proxy_account_pool.py实现了一个池化调度器核心思路是给每个账号维护一个状态机。状态包含正常、冷却、禁用、过期四种。当一个账号累计请求数达到阈值就自动进入冷却状态冷却时间可以配置。# 伪代码账号池调度 class AccountPool: def get_available_account(self): for acc in self.accounts: if acc.status valid and acc.request_count acc.max_requests: return acc # 如果没有可用账号等待冷却时间 time.sleep(COOL_DOWN_SECONDS) self.reset_cooled_accounts() return self.get_available_account()这种做法能有效降低同一账号在短时间内的请求密度。如果你有10个账号每个账号最多请求50次那么一轮最多可发起500个请求。之后等待所有账号冷却结束再继续下一轮。常见冷却时间是5到15分钟视账号质量而定。5.2 IP池的选型与验证IP池要区分“隧道代理”和“静态代理”。隧道代理每次请求自动换IP延迟稍高但不需要自己维护IP列表静态代理需要主动切换。建议在proxy_account_pool.py里写一个探活函数定期检测IP的连通性和可用性def probe_proxy(proxy: str) - bool: test_url https://www.xiaohongshu.com proxies {http: proxy, https: proxy} try: resp requests.get(test_url, proxiesproxies, timeout5) return resp.status_code 200 except Exception: return False把探活耗时超过1秒的IP直接移出池子因为高延迟会拖慢整个异步任务的效率。5.3 反自动化检测的隐藏细节stealth.min.js在JS注入环境里做了很多浏览器指纹的伪装。如果你是自己扩展项目建议不要直接使用Playwright的默认浏览器环境而是在启动参数里关闭--disable-blink-featuresAutomationControlled同时设置真实的userAgent和viewport。# playwright 启动参数 p playwright.chromium.launch( headlessTrue, args[ --disable-blink-featuresAutomationControlled, --no-sandbox, --disable-infobars ] )另外有些检测是基于Canvas指纹和WebGL渲染结果的。如果出现请求被拦截但签名正确的情况可以尝试人工打开页面和自动化页面对比navigator.webdriver和window.chrome对象的差异。5.4 一个小而有效的验证技巧抓完数据后别急着全部写入Excel。先随机抽一条笔记去网页端人工核对标题、点赞数、评论数是否和抓取结果一致。如果评论数差很多大概率是评论区只能加载前几页需要调整cursor翻页逻辑。如果笔记字段缺失打开浏览器开发者工具对比接口返回的JSON字段名和代码里的映射关系。这种抽查方式比盯着日志看错误码更直观也更适合非大量数据的场景。本文还有配套的精品资源点击获取