简介这套基于Python编写的小红书爬虫源码下载配置后即可运行尤其适合计算机相关专业学生作为毕业设计项目也适合爬虫入门者研究社交平台数据采集。资源共1080个文件压缩包仅2.55MB主要包含781个js文件、68个ts文件、63个json配置、54个md说明文档及10个py核心脚本js与ts多用于前端辅助或工具库py文件则承担搜索、首页、用户资料等核心爬取逻辑。目前已有1873人学习下载。项目内文件分类清晰除核心Python模块外还提供README说明、gitignore环境配置以及search.py、home.py、one.py、profile.py等功能模块search.py负责搜索请求与结果解析home.py获取首页信息one.py针对单个帖子或用户详情profile.py处理用户资料通过实操可掌握网络请求、HTML/JSON解析、数据存储等关键技能同时了解反爬与合规采集注意事项为后续大数据分析或算法研究打下基础。1. 小红书爬虫源码能直接跑吗登录态、签名与请求频率才是真门槛那句“小红书爬虫源码Python 写下载就能跑可做毕业设计”是资源描述里最常见的话。实际拆开源码看这个包要解决的核心问题有三个Web 端登录态Cookie的传递、请求签名参数x-s / x-t的生成、以及请求频率的控制。跑通基础流程不难Python 的 requests 库直接发 HTTP 请求接口返回 JSON解析字段就能落盘真正难的是连续跑几百条请求不触发限流、不返回空 items。这个包把几件事封装成了清晰的模块config 管配置、sign 管签名、parser 管解析。适合想用真实平台数据做内容分析的人、想完整走一遍“请求→签名→解析→落盘”工程链路的 Python 开发者以及正在找爬虫方向毕业设计项目的学生。下面按先看结构、再复现流程、最后讲避坑来拆。2. 拆开源码看门道目录结构、请求链路与签名封装2.1 目录结构与入口文件拿到源码包先不要急着运行花十分钟把目录结构过一遍后面排错会快很多。典型的小红书爬虫项目会按“入口、配置、签名、采集、解析、输出”六个职责拆分文件这个包也不例外。文件职责使用频率main.py命令行入口接收关键词与页数参数每次启动config.py维护 Cookie、请求头、请求间隔、超时与重试次数必须改sign.py生成 x-s 签名参数内部调用crawler.py请求发送与响应预处理核心parser.py把 JSON 解析成结构化字段核心output/存放 JSON 与 CSV 结果文件自动生成main.py 只做顺序调度读取参数、加载配置、初始化会话、调用采集函数、触发结果落盘不做业务清洗。运行方式一般是这样python main.py --keyword 露营 --pages 5 --output csv--keyword是搜索关键词--pages控制翻页深度--output指定 csv 或 json 两种结果格式。这里的参数对应 config.py 里同名变量命令行优先级高于配置文件。注意 main.py 刻意保持短小因为做毕业设计答辩演示时评委大概率会现场改参数重跑入口越简单翻车概率越低。crawler.py 是整个包的心脏所有 HTTP 请求都从这里发出。它依赖两个外部模块requests 负责传输sign.py 负责注入签名。如果你后续要加“收藏笔记”或“关注用户”这类扩展也是在这个文件里加函数保持现有请求模式不变即可。2.2 请求链路里最关键的三个参数小红书 Web 端的数据接口和普通网站不一样它不依赖复杂的验证码而是靠请求头里的组合参数来判断请求是否来自真实浏览器。这套组合参数里优先级最高的是以下三个。Cookie 是第一道门槛。浏览器登录后服务端会下发一组 Web 端会话 Cookie里面携带用户身份与设备指纹信息。爬虫请求必须在请求头里带上这份 Cookie否则接口会返回 460 状态码或者 items 为空。这份 Cookie 从哪里来在浏览器里登录小红书网页版打开开发者工具在 Network 面板复制任意接口请求头里的 Cookie 字段贴进 config.py 即可。签名参数是第二道门槛也是这个源码包最核心的封装点。Web 端对写操作和部分读操作会校验动态签名校验不通过返回 406。签名由 sign.py 统一生成内部逻辑大致是对请求路径按规则排序、拼接固定盐值、再结合 x-t 时间戳做摘要计算。这个包把算法细节封装在 sign.py 内部调用方只需要传入路径和参数就能拿到签名结果。请求头是第三道门槛。User-Agent 决定服务端把请求识别成什么浏览器Referer 必须保持https://www.xiaohongshu.com/域名Origin 用于跨域判断。三者不一致比如 Referer 为空、Origin 写成了移动端域名就可能触发风控。# config.py 中维护的请求头模板 HEADERS { 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, Referer: https://www.xiaohongshu.com/, Origin: https://www.xiaohongshu.com, }这里有个实操细节User-Agent 里的版本号不必和你本机浏览器完全一致但要保持它是主流浏览器较新版本。有些反爬策略会维护一个旧版本 UA 黑名单用太老的 Chrome 或者 Firefox UA 容易被直接过滤。2.3 签名模块的正确使用方式sign.py 是包里最不应该改动的文件。它的对外接口很简单传入完整的请求路径、GET 参数、毫秒级时间戳返回 x-s 字段值。内部实现的复杂度包括排序规则、拼接顺序、摘要算法版本都被封装在_sign_impl私有函数里。# sign.py 对外暴露的签名入口 def generate_sign(session, path, params, ts): # 源码内部会结合会话状态、路径和参数做组合计算 # 返回一个字符串直接放入请求头 x-s 字段 return _sign_impl(session, path, params, ts)调用方需要做三件事构造 path、构造 params、取当前毫秒时间戳。常见的错误是直接复制浏览器 Network 面板里的 x-s 值粘贴到代码中固定使用这个值在几分钟内就会失效。正确做法是每次请求前重新调用 generate_sign。另一个容易被忽略的点x-t 时间戳必须和签名计算时使用的时间戳一致。如果你在代码里先取了一次时间戳给签名用又在发送请求前重新取了一次给请求头用两个值不同服务端会判定签名非法。签名模块使用边界很清晰不做外部依赖、不读文件、不连数据库纯函数设计。这意味着你可以在多线程场景下重复调用只要 session 状态稳定结果就是可重复的。这个包能稳定跑依赖的正是签名模块的“无状态”特性。2.4 配置文件的维护习惯config.py 是唯一需要你经常手动改的文件。除了 Cookie 和 HEADERS请求间隔与重试参数也在这里。请求间隔直接决定触发限流的概率。参数建议值说明request_interval3~5 秒两次请求之间的固定间隔retry_times3单次请求失败后的重试次数timeout10连接超时与读取超时共用page_size20每页笔记数量Web 端固定 20请求间隔是这套参数里最玄学的部分。间隔太小几百条请求后大概率触发频率限制间隔太大采集效率又无法接受。折中方案是固定间隔 3 秒遇到返回空数组时自动退避到 8 秒再继续。config.py 里可以预留一个fail_backoff参数用于初始化限流后的临时降速。3. 跑通核心流程搜索、详情、评论三条链路复现3.1 导入登录态并初始化请求会话第一步是把 config.py 里的 Cookie 加载进一个 requests.Session 实例。Session 会自动维持请求头、Cookie 和 TCP 连接池的状态是爬虫工程里的标准做法比每次请求手动传 headers 可靠得多。# crawler.py 中初始化会话的代码 import requests from config import COOKIE, HEADERS def create_session() - requests.Session: session requests.Session() session.headers.update(HEADERS) session.cookies.update(_parse_cookie(COOKIE)) return session def _parse_cookie(cookie_str: str) - dict: # 把 k1v1; k2v2 格式的字符串拆成字典 pairs cookie_str.split(; ) return {item.split(, 1)[0]: item.split(, 1)[1] for item in pairs if in item}这里用session.cookies.update而不是手动向 headers 里塞 Cookie 字段原因在于 requests 库的 Cookie 解析逻辑和 HTTP 规范一致。手动塞字符串最常踩的坑是 Cookie 里带符号时被切错位置或者长尾 Cookie 出现编码问题。第一个函数创建会话第二个函数解析字符串。两张代码合在一起构成了整个爬虫所有请求的基座。建议你把它写在一个独立模块里不要让其他文件直接 import requests保持依赖单向流动。3.2 按关键词搜索笔记并解析列表字段搜索接口是采集链路的第一站。构造请求参数时要遵守接口的字段命名keyword 是关键词page 从 1 开始page_size 固定 20sort 支持 general、time_desc 这些排序方式一般业务场景用 general 最合适。# crawler.py 中搜索笔记的完整函数 import time from sign import generate_sign def fetch_search_notes(session, keyword, page1, page_size20): path /api/sns/web/v1/search/notes params { keyword: keyword, page: page, page_size: page_size, sort: general, } ts int(time.time() * 1000) session.headers[x-t] str(ts) session.headers[x-s] generate_sign(session, path, params, ts) url https://edith.xiaohongshu.com path resp session.get(url, paramsparams, timeout10) data resp.json().get(data, {}) items data.get(items, []) return [_parse_note_card(item) for item in items]这段代码的关键点是顺序先算时间戳再把时间和签名写入请求头最后发送请求。反过来操作x-t 已经发送了才计算签名或者签名用的时间戳和 x-t 不一致都会触发签名校验失败。url 拼接用的是edith.xiaohongshu.com这个 Web 端接口域名它和页面域名不同这是正常现象不要改成www.xiaohongshu.com。返回结构里每个元素是 note_card 容器里面嵌套了正文摘要、作者信息、互动数据。解析时要兼容两种笔记类型图文笔记和视频笔记。图文笔记的标题叫display_title视频笔记则是display_title加note_type区分。不要直接取title字段它的在部分接口版本中不存在。# parser.py 中解析 note_card 的字段映射 def _parse_note_card(item): note item.get(note_card, {}) interact note.get(interact_info, {}) user note.get(user, {}) return { note_id: note.get(note_id), note_type: note.get(type, ), # normal 为图文video 为视频 title: note.get(display_title, ), desc: note.get(desc, ), user_id: user.get(user_id), nickname: user.get(nickname), liked_count: interact.get(liked_count, 0), collected_count: interact.get(collected_count, 0), comment_count: interact.get(comment_count, 0), }看一眼note.get(type)的值能避免把视频笔记的封面链接当成正文图片解析。真实采集时图文笔记的 desc 通常有两三百字视频笔记的 desc 普遍更短这个差异不影响字段解析但会影响后续清洗时是否保留正文。3.3 进入详情页采集正文与评论搜索列表只给摘要拿不到全部正文和评论。要补齐这两块需要再进两个接口笔记详情接口拿到完整正文、点赞收藏数、图片列表评论接口拿到完整评论楼层。详情接口用 source_note_id 参数定位笔记评论接口用 cursor 做游标翻页。# 拉取笔记详情 def fetch_note_detail(session, note_id): path /api/sns/web/v1/feed params {source_note_id: note_id, note_id: note_id} ts int(time.time() * 1000) session.headers[x-t] str(ts) session.headers[x-s] generate_sign(session, path, params, ts) resp session.get( https://edith.xiaohongshu.com path, paramsparams, timeout10 ) note resp.json().get(data, {}).get(items, [])[0] return { note_id: note_id, full_desc: note.get(desc, ), image_urls: [img.get(url_default, ) for img in note.get(image_list, [])], like_count: note.get(interact_info, {}).get(liked_count), collected_count: note.get(interact_info, {}).get(collected_count), }详情接口是单条数据不存在分页问题但要处理一条异常当笔记被删除或设为私密时items 列表是空的直接取[0]会抛 IndexError。稳妥写法是先用if not items: return None做判空再由上层调用决定是跳过还是重试。评论接口的分页方式和搜索接口不一样它不用页码用游标。游标机制本身很直观第一页传空 cursor接口返回data.cursor和data.has_more下一页把返回的 cursor 再传进去直到 has_more 为 false。# 拉取笔记评论返回评论列表和下一页游标 def fetch_comments(session, note_id, cursor): path /api/sns/web/v2/comment/page params { note_id: note_id, cursor: cursor, top_comment_id: , } ts int(time.time() * 1000) session.headers[x-t] str(ts) session.headers[x-s] generate_sign(session, path, params, ts) resp session.get( https://edith.xiaohongshu.com path, paramsparams, timeout10 ) payload resp.json().get(data, {}) comments payload.get(comments, []) return comments, payload.get(cursor, ), payload.get(has_more, False)评论字段里有用户昵称、评论内容、点赞数、发布时间还有一个sub_comment_count表示二级评论数量。需要做评论树展示的话可以按parent_comment_id字段重建父子关系只做统计的话忽略这个字段只汇总一二级评论总数即可。3.4 结果落盘JSON 和 CSV 两种格式的取舍采集完的原始数据先统一存成 JSON再做一次清洗导出 CSV。这个顺序不要反因为 CSV 的字段结构是固定的一旦清洗规则写错了数据就丢了JSON 保留原始结构可以反复加工。# output/writer.py 中两个写文件的函数 import csv, json, pathlib def save_json(data, filepath): pathlib.Path(filepath).parent.mkdir(parentsTrue, exist_okTrue) with open(filepath, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def save_csv(rows, filepath, fieldnames): pathlib.Path(filepath).parent.mkdir(parentsTrue, exist_okTrue) with open(filepath, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(rows)CSV 文件落地时一定要用utf-8-sig编码而不是utf-8。原因很现实Excel 默认用 GBK 读取无 BOM 的 UTF-8 文件中文标题会全部变成乱码。utf-8-sig会在文件头写入 BOM 标记Excel 自动识别编码这是用 Python 导出中文 CSV 最容易忽略的细节。JSON 落盘时ensure_asciiFalse也是必需的否则中文会被转成 \uXXXX 序列文件能打开但没法直接阅读后续调试也不方便。4. 避坑排查签名失效、字段错位与限流的三类高频问题4.1 返回空数组或提示“操作过于频繁”现象程序刚跑完第一页从第二页开始返回的 items 列表为空接口状态码仍是 200但 body 里没有数据。跑得更久一点会直接在下一次请求返回包含“操作过于频繁”的提示。原因Web 端对单 IP 的请求频率有窗口限制。这个限制不是一个固定次数而是“单位时间内的请求密度”短时间集中发 30 个请求就可能触发间隔均匀的 100 个请求反而没触发。用多线程无间隔采集最容易踩这个坑。解决把请求间隔从 1 秒调到 3~5 秒走完一个笔记详情后额外加 0.5 秒。遇到 items 为空时将当前线程休眠 8 秒再做下一次请求。这套组合策略在多数场景下能稳定跑完千条级别任务。注意不要靠改 Cookie 里的设备号来绕限流这会让两个账号都进入风控得不偿失。4.2 签名校验失败接口返回 406现象程序运行一两个小时后开始抛 406错误信息里有 signature 相关关键词。重启程序后恢复正常但跑一段时间再次出现。原因签名参数 x-s 是动态值依赖 x-t 时间戳。代码里如果把请求头 x-s 固定写死或者在一个长时间运行的进程里复用同一个时间戳就会触发服务端签名校验失败。另一种情况是系统时间偏差笔记本手动改了时区导致时间戳与服务端差值超过阈值。解决每次请求前重新调用 generate_sign并确保 x-s 计算时传入的时间戳与请求头 x-t 字段完全一致。对于系统时间偏差手动校准到自动同步时间误差控制在两秒内。规避做法是把时间获取统一封装只在一处取毫秒时间戳避免各处调用产生微小偏差。4.3 Cookie 失效接口返回 400 或跳登录现象采集运行到中段所有接口同时开始返回 400响应体里 user 字段全部为 null之后请求重定向到登录页域名。原因Web 端会话有有效期限制通常几小时到一天不等。长时间高频请求会缩短这个有效期。某些 Cookie 字段只在特定域名下生效跨域携带反而触发风控。解决失效后没有别的办法只能重新打开浏览器登录复制新 Cookie 替换 config.py 中的值。这里的有效做法是维护两个 Cookie 位config.py 里存当前使用的值另存一份备用值。正式采集前先用搜索接口做一次健康检查返回空 items 或 400 就立刻切换备用 Cookie而不是等程序抛异常。4.4 字段解析错位正文截断与图片链接丢失现象解析结果里 title 和 desc 时而完整时而被截断图片链接数组一会是正常 URL一会变成短链占位符。同一份代码跑出来的数据字段数量不稳定。原因笔记类型差异。视频笔记的 desc 长度上限比图文笔记小图片列表也是一个空数组。解析代码如果只用一套字段映射处理两种类型就会出现错位。另一个原因是接口对部分图片 URL 做了防盗链替换数据里的链接是临时签名 URL有有效期。解决解析函数里用note_type字段分支处理图文笔记读取完整 desc 和 image_list视频笔记只读 desc 不读图片列表。图片链接失效是平台行为采集后尽快本地化存储图片不要在数据库里长期保存临时 URL。4.5 进程假死没有报错也没有输出现象采集进程运行一段时间后不再输出日志CPU 占用接近 0CtrlC 无法立刻中断像是卡死状态。原因requests 请求没有设置读取超时或 timeout 只给了连接阶段。服务端收到大量请求后主动断开 TCP 连接客户端仍在等待响应数据造成永久阻塞。解决timeout 参数同时指定 connect 和 read 两个阈值比如timeout(5, 10)代表连接 5 秒超时、读取 10 秒超时。配合循环内streamFalse确保响应体被完整读入内存不会因为迭代器未消费导致连接不释放。进程假死出现一次后建议在采集任务外层加总超时比如单条笔记从请求到解析完成不超过 30 秒超出直接放弃并记录日志。5. 让采集结果真正能交付清洗去重、增量采集与答辩演示5.1 字段规整从原始数据到干净表搜索接口拿到的数据里有大量冗余字段正文摘要和详情页正文还会重复。清洗规则必须包含三件事去重、去掉多余空白字符、统一时间格式。去重依据是 note_id如果同一篇图文被不同关键词重复搜到note_id 相同直接跳过后续详情请求能省下不少请求量。# 按 note_id 去重省掉重复的详情请求 def deduplicate(notes, seen): result [] for note in notes: if note[note_id] not in seen: seen.add(note[note_id]) result.append(note) return result详情页的 desc 字段里会混着换行符和 \u3000 全角空格用正则会做一次规整。时间字段做一个判断是毫秒时间戳则转成YYYY-MM-DD HH:MM:SS是字符串则统一到同一种格式避免答辩演示时看到两种时间格式混在一张表里。5.2 增量采集的思路毕业设计项目如果只跑一次数据是静态的。想展示“持续采集”的能力就加增量逻辑每次采集前先读本地已有结果里最新的发布时间只采集晚于这个时间的笔记。注意搜索接口本身只支持按时间倒序不支持从指定时间点开始所以处理方式是翻页采集过程中发现当前页所有笔记的发布时间都早于上次采集时间就提前结束采集。5.3 Excel 导出与答辩演示清洗后的数据用 pandas 导出 Excel 表格输出到output/result.xlsx。答辩现场演示两个内容就够了一是命令行启动采集过程展示日志输出二是采集完成后打开 Excel展示笔记标题、作者、点赞数、收藏数、评论数、发布时间六列数据。再加一个简单统计按关键词统计笔记数量、按作者统计笔记数量数据体量不大时直接用 pandas 的value_counts生成两张图表即可。上次帮别人调这个项目时正好遇到评论区里同一条评论被去重删掉但爬虫在删掉之前已经把它的子评论记录了一起整理。意识到问题的那一刻我才重新把“清洗”放在“采集”前面去设计。从那以后我每次跑采集任务前都会强制过一遍 note_id 去重逻辑并在写完 CSV 后随机抽三条数据核对字段完整性确认无误后才算任务结束。希望这篇拆解能让你少走这几步弯路也把你自己的爬虫项目做得更稳妥。本文还有配套的精品资源点击获取