十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

小红书搜索笔记采集实战:从关键词驱动到结构化数据链路

小红书搜索笔记采集实战:从关键词驱动到结构化数据链路 小红书做过热门内容采集的人应该不少但能把“关键词搜索→笔记采集→结构化存储”这条链路讲明白的文章确实不多。这个项目就是围绕“搜索关键词驱动”来做的目标是输入一个或一批关键词自动从小红书搜索接口拉取热门笔记再把标题、点赞数、收藏数、笔记链接这些字段整理成结构化数据方便后续做选题分析、竞品监控或内容趋势观察。适合想入门Python爬虫的开发者也适合运营、产品经理这类需要数据支撑的非技术同学参考。先说清楚我这个项目没有碰任何绕过签名的黑科技也没有伪造设备指纹那套操作。主体思路是用requests模拟浏览器请求配合合理的请求频率和合规的数据使用方式重点放在“如何把采集链路跑通”和“如何把数据洗干净”这两件事上。实际开发中小红书的x-s签名确实是一道坎但早期验证思路时可以先从Web端接口入手控制好采集频率和单次请求量很多场景根本走不到需要逆向签名的地步。1. 项目整体设计与思路拆解1.1 为什么选择“搜索关键词驱动”而不是采集首页推荐流最开始我其实想直接采集首页推荐笔记因为入口简单打开首页就能看到大量笔记数据。但实际操作下来发现两个问题第一首页推荐流是千人千面的同一个账号看到的内容和另一个账号完全不同采集出来的数据没有对比价值第二推荐流没有明确的边界翻到后面会出现大量重复内容去重成本很高。换成关键词驱动之后整个思路就清晰多了。搜索接口的本质是“给定一个query返回与它相关的笔记列表”这就像在数据库里做了一次检索结果天然是按相关度和热度排序的。运营同学想知道“近期什么护肤成分讨论度高”直接搜“烟酰胺”或“A醇”拿到的就是这部分内容数据可解释性强也不用担心首页推荐流那种黑盒逻辑。搜索关键词驱动还有一个好处任务可以拆分。我一个关键词一个关键词地跑每个关键词的采集结果独立存储互不干扰。采集完“Python学习”就接着采“程序员副业”后续做词频分析、热度对比时数据边界都是清晰的不会混在一起。1.2 技术选型为什么用requests而不是scrapy这个项目我特意没用scrapy这类重型框架原因很简单小红书这种数据规模的项目用requests写脚本已经完全够用没必要引入框架层的复杂度。requests的优点是轻、直接、调试方便。我可以在Jupyter Notebook里一步步测试接口、看返回JSON结构改一行代码就能换一个参数重跑这种“即改即跑”的体验对探索性采集非常友好。scrapy虽然提供了并发、管道、中间件这些现成能力但学习成本高而且这个项目的核心难点不在并发而在接口分析和数据清洗框架带来的收益很有限。另一个我考虑的点是项目可维护性。爬虫代码写得越简单后续调整就越容易。小红书的风控策略不是一成不变的今天能用的接口参数明天可能就变了。一个清晰简单的request脚本改起来比翻scrapy的中间件和信号量快得多。1.3 整体架构三个模块一条链路这个项目的架构分三层每层只负责一件事这样出了问题我能很快锁定位置。第一层是搜索采集模块负责构造请求参数、发送HTTP请求、拿到JSON响应。这一层最关键的是参数构造和请求头模拟任何一点做得不像浏览器都会被风控挡住。第二层是数据解析模块负责从JSON里提取笔记标题、作者、互动数据、发布时间这些字段。这一层要处理的坑最多因为小红书接口返回的数据结构嵌套很深而且字段名经常是英文缩写不抓包根本不知道含义。第三层是存储与去重模块负责把清洗后的数据写入本地文件同时做URL级别的去重。我用的存储格式是CSV加JSON Lines双份CSV方便Excel打开看JSONL方便后续用pandas做分析。链路跑起来之后就是一个“输入关键词→输出结构化表格”的流水线。我在实际使用中会预先准备一个关键词清单程序按顺序处理每个关键词采集2到3页数据控制总量在几百条级别既满足分析需求又不会给目标服务器造成压力。2. 核心细节解析与实操要点2.1 找到搜索接口从浏览器开发者工具开始做这个项目最关键的一步不是写代码而是找到搜索接口的真实地址和参数格式。我的做法是打开浏览器无痕窗口进入小红书搜索页输入一个测试关键词然后在开发者工具的Network面板里过滤XHR请求就能看到搜索接口的完整调用过程。小红书Web端的搜索接口一般是一个POST请求路径里带/api/sns/web/v1/search/notes这类标识请求体里包含了关键词、页面游标、搜索来源等参数。要注意的是不同时间点接口可能有调整我的做法是在每次写代码前先抓一次包确认不要盲信网上教程里的接口地址。有个很容易忽略的点请求头里的Referer字段必须带上而且要和搜索页面URL保持一致。小红书的服务器会校验来源Referer不对直接返回403。还有User-Agent现在用无头浏览器的默认UA很容易被识别我一般直接用自己Chrome浏览器里的完整UA字符串复制出来用。2.2 理解请求参数关键词、分页与排序逻辑搜索接口的核心参数就几个但含义一定要搞明白。keyword是搜索词这个不用多说page和page_size控制分页小红书通常一页返回20条左右sort是排序方式一般有综合排序和最新排序两个选项note_type可以指定只搜索图文或只搜索视频。让我踩过坑的是分页逻辑。小红书搜索接口的分页不是简单的page1而是基于cursor这个游标参数。第一次请求时cursor是空的响应里会返回一个cursor字段下一次请求把这个值填进去才能拿到下一页。这种做法比页码制复杂但好处是能避免翻页过程中新增数据导致的重复或遗漏。排序逻辑也需要提前想清楚。如果做热点监控用综合排序比较合理因为综合排序的权重更多偏向近期热度和交互质量如果做竞品分析可能更关心最新发布的笔记这时候应该用最新排序。我项目中同时跑了两种排序方便对比同一个关键词在不同排序方式下的内容差异。2.3 解析响应JSON从嵌套结构里提取关键字段搜索接口返回的JSON结构非常庞大里面包含了推荐位、广告位、搜索结果等多个区块真正的笔记数据在data下的items数组里。每个item又嵌套着note_card这个对象笔记标题、作者、互动数据都在这里面。实际提取时我总结了一套字段映射表这是项目中最值钱的部分实际字段含义从哪个对象取note_id笔记唯一IDnote_card.note_iddisplay_title笔记标题note_card.display_titleinteract_info.liked_count点赞数note_card.interact_infointeract_info.collected_count收藏数note_card.interact_infointeract_info.comment_count评论数note_card.interact_infouser.nickname作者昵称note_card.userdisplay_notebook_info所属合集note_card.display_notebook_infocover封面图信息note_card.covertype笔记类型图文/视频note_card.type这些字段名在不同接口版本里可能会有变化我在代码里加了容错处理get不到字段就返回默认值确保单条数据解析失败不会影响整个采集流程。2.4 请求头与登录态做到什么程度才合理这一块我要多说两句。小红书的搜索接口在未登录状态下也能访问但能拿到的数据量和翻页深度都有限。我在实际测试中发现未登录状态下翻到第二页就可能触发验证而且返回的笔记列表里会混入大量低质量内容数据的可靠度不高。登录之后情况会好很多。登录后接口会带上cookie里的临时凭证我能正常翻到比较深的页码。但我不建议在项目里维护复杂的登录状态管理更稳妥的做法是手动在浏览器里登录小红书然后把cookie字符串复制到配置文件中程序启动时读取这个cookie去请求接口。不过cookie是会过期的一般几小时到几天不等。我在实际使用中做了个简单的cookie过期检测——如果响应里出现登录失效的标志或触发验证码程序会主动停下来提醒我去更新cookie再继续而不是傻傻地用失效状态重试一堆请求那样只会更快触发封禁。3. 实操过程与核心流程实现3.1 环境准备Python与依赖库安装这个项目的环境要求非常简单Python 3.8以上版本就可以我的开发机用的是Python 3.10。如果是从零开始需要先确认本机Python环境已经配好终端里执行python --version能看到版本号即可。依赖库有三件套requests用来发HTTP请求pandas用来做数据处理和CSV导出retry用来做请求失败的重试。安装命令很简单pip install requests pandas retry如果你网络环境下载慢可以用国内镜像源加速效果立竿见影pip install requests pandas retry -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后建议验证一下在Python交互环境里执行import requests; print(requests.__version__)能看到版本号就说明环境没问题。3.2 核心代码实现搜索请求与响应解析搜索请求的代码不长但每一步都有讲究。先看请求构造部分import requests import json import time import pandas as pd from retry import retry 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/explore, Content-Type: application/json;charsetUTF-8, } COOKIE 这里填你浏览器里复制的cookie字符串retry装饰器是我在实际项目中加上的。小红书的接口偶尔会返回超时或5xx错误这不是风控就是纯粹的临时故障。加了重试机制后连续失败3次才会真正报错单次请求的成功率能提升不少。retry(tries3, delay2) def fetch_search_results(keyword, cursor, page_size20): url https://www.xiaohongshu.com/api/sns/web/v1/search/notes headers dict(HEADERS) headers[Cookie] COOKIE payload { keyword: keyword, page: 1, page_size: page_size, search_id: , sort: general, note_type: 0, cursor: cursor, } resp requests.post(url, jsonpayload, headersheaders, timeout10) if resp.status_code ! 200: raise Exception(fHTTP {resp.status_code}) data resp.json() return data注意这里的payload是JSON格式requests的jsonpayload参数会自动做序列化并设置正确的Content-Type比手动转字符串再传data要省事。然后是解析函数。这个函数做过一次重构最初是硬编码下标后来发现接口返回的items里会混入一些非笔记类型的数据比如recommend_query推荐词、广告卡片类型判断成了必须做的步骤def parse_items(data): results [] items data.get(data, {}).get(items, []) for item in items: if item.get(model_type) ! note: continue card item.get(note_card, {}) note_id card.get(note_id, ) title card.get(display_title, ).strip() interact card.get(interact_info, {}) liked_count interact.get(liked_count, 0) collected_count interact.get(collected_count, 0) comment_count interact.get(comment_count, 0) user_info card.get(user, {}) nickname user_info.get(nickname, ) results.append({ note_id: note_id, title: title, nickname: nickname, liked_count: liked_count, collected_count: collected_count, comment_count: comment_count, url: fhttps://www.xiaohongshu.com/explore/{note_id}, }) return resultsdisplay_title有时候会带前后空格甚至个别笔记的标题是空字符串我在保存前做了strip()处理后续统计时就不会出现一堆看起来一样的“空标题”数据。3.3 翻页采集与数据落盘翻页的核心是拿到上一轮的cursor值。响应JSON里的data.cursor就是下一轮的入场券代码实现如下def search_all_pages(keyword, max_pages3): all_records [] cursor for page in range(1, max_pages 1): data fetch_search_results(keyword, cursorcursor) records parse_items(data) all_records.extend(records) has_more data.get(data, {}).get(has_more, False) if not has_more: break cursor data[data][cursor] time.sleep(3 random.random() * 2) return all_records这里time.sleep的设计是经验之谈。不加间隔连翻多页很容易触发接口限流间隔太固定又可能被风控判断为机器行为。我使用3到5秒的随机间隔既不会太磨蹭也让请求节奏更接近自然用户。数据落盘我用pandas一次性完成简洁可靠def save_to_file(records, keyword): clean_keyword keyword.replace(/, _).replace(:, _) filename fresult_{clean_keyword}_{time.strftime(%Y%m%d_%H%M%S)}.csv if records: df pd.DataFrame(records) df df.drop_duplicates(subsetnote_id, keepfirst) df.to_csv(filename, indexFalse, encodingutf-8-sig) print(f已保存 {len(df)} 条数据到 {filename})这里有两个细节值得提示文件名里做了非法字符替换避免关键词里带斜杠导致保存失败编码用了utf-8-sig这样Excel直接打开CSV不会出现中文乱码。3.4 并发设计串行真的够用吗搜索类采集我坚持用串行加随机延迟没有上并发。原因有两条。第一搜索接口本身是低频、轻量的请求一个关键词翻3页也就几十次请求串行完成只要十几秒并发带来的时间收益微乎其微。第二小红书对搜索接口的风控比其他接口更敏感。同一个session下短时间内高频请求几乎必然触发验证。我测试过用ThreadPoolExecutor开5个线程同时请求不同关键词结果跑了不到一分钟就有两个线程收到验证码响应。数据没采到几条反而把cookie搭进去了。如果你确实有大批量关键词要采集更稳妥的并发方案是“分布式限速”用一台机器、多个账号轮询账号与账号之间的请求间隔保持在几十秒以上。但这是工程层面的复杂度了个人项目和中小团队用不上。3.5 图片下载怎么做到干净不违规部分场景下需要把笔记配图也保存下来比如做竞品视觉分析。图片链接在小红书响应JSON里是cover对象下的url_pre或url_default字段不过这些链接都已经过CDN签名直接下载就能用。我在实际下载过程中总结出一个关键点小红书的图片URL通常会带!开头的缩放参数比如!web_note_cover直接下载会拿到压缩后的缩略图。去掉这个参数、改回原始地址拿到的才是原图。import requests import os def download_image(img_url, save_path): clean_url img_url.split(!)[0] # 去掉缩放参数 headers {User-Agent: HEADERS[User-Agent]} resp requests.get(clean_url, headersheaders, timeout15) if resp.status_code 200: with open(save_path, wb) as f: f.write(resp.content)下载图片的请求频率也要克制我一般每下载一张就休息0.5到1秒同时限制单次任务下载总量不超过100张。小红书对图片CDN的访问限制相对宽松但大量高频下载同样会拖慢整个采集流程。3.6 增量更新让数据保持新鲜内容采集做完一次就完事的情况很少真实项目里更多是“今天搜一遍下周再搜一遍对比热度变化”。为此我在脚本里加了简易的增量更新逻辑。具体做法是先加载已采集过的CSV文件拿到所有note_id作为去重集合。新一轮采集时遇到重复ID就直接跳过只保留新增的笔记。这样即使同一个关键词反复采集最终表里每条笔记只有一行记录。配合“采集时间戳”字段我还能做热度增长趋势分析——同一篇笔记今天的点赞数和上周的相比涨了多少一眼就能看出来。增量更新代码实现也很简单def load_existing_ids(csv_path): if not os.path.exists(csv_path): return set() df pd.read_csv(csv_path) return set(df[note_id].tolist()) existing_ids load_existing_ids(result_关键词.csv) # 采集时过滤 new_records [r for r in records if r[note_id] not in existing_ids]4. 常见问题与排查技巧实录4.1 请求返回401或验证码先查cookie时效这是我最常遇到的问题。搜索接口返回401、重定向到登录页或直接弹出验证码90%的情况是cookie过期或失效了。我的排查顺序是先检查cookie里的关键字段是否存在、过期时间是否临近然后重新打开浏览器复制一份新cookie替换等一段时间再跑。还有一个小技巧cookie里通常会包含多个账号相关的字段如果你只复制了一部分cookie导致请求异常可以试试把浏览器里对应域名的完整cookie全部复制出来不要自己手动裁剪拼接完整复制成功率更高。4.2 翻页采到一半返回重复数据明明没到最后一页却出现大量重复笔记这是搜索接口的常见行为。热搜词的搜索结果本来就变动频繁翻页过程中排在前面的笔记可能被新发布的笔记顶下去导致page2里出现page1已经出现过的内容。另一个可能原因是cursor使用不当比如在请求参数里既传了page又传了cursor接口对这两者的处理逻辑冲突。我最终的解法是“以去重代翻页”不依赖接口的翻页边界而是在解析阶段持续做note_id去重连续两页的新增比例低于某个阈值比如10%就认为数据已经在收敛主动停止翻页。这个方法比傻傻翻完固定页数要聪明得多。4.3 解析出来标题是乱码或大量空值标题乱码一般是编码问题。requests在解析响应时默认用utf-8如果接口在某些情况下返回了其他编码就会出现乱码。解决办法是在拿到响应后先打印一下resp.encoding和resp.apparent_encoding确认实际编码再手动指定resp.encoding utf-8。大量空值通常发生在接口字段名变更之后。这种情况没法通过代码一次修复我的经验是在项目里加“字段名拓扑校验”——如果连续10条笔记的标题字段都为空程序就报警或自动停止提醒我该抓包检查接口结构了。4.4 代理要不要用一个务实的建议很多爬虫教程会推荐用代理池但对小红书搜索采集这个场景我的建议是能用直连就别用代理尤其是免费代理。免费代理的IP质量普遍偏差有的是已经被各大平台风控标记的用这种IP请求小红书成功率比直连还低。有的会无响应或者返回乱码排查问题的时间比省下来的请求时间多得多。高匿、稳定的付费代理可以提升成功率但在没有明确风控预警的情况下这不是必须的支出。真正要做的是控制请求频率、管理好cookie有效期、合理设置重试。做好这三件事比几千个代理IP都管用。5. 合规边界与长期稳定意识5.1 什么数据能采什么数据绝不要碰爬虫能做和该做是两回事这个项目的边界意识我从第一天就在强调。搜索笔记公开列表里的标题、互动数据、作者昵称这些是用户公开发布、平台对外展示的信息做聚合分析是常规操作。但笔记正文内容、评论区内容总量有限实时性要求高且在平台规则里面向的是登录用户的全量内容抓取存储边界更泛。除非有正当的科研理由并完成合规评审这类数据不建议采集。更不能碰的是用户手机号、邮箱等未公开的隐私信息付费内容以及任何需要逆向签名算法才能拿到的后端数据。这些行为不仅违反平台规则还可能触碰法律红线。我的判断标准很简单——如果某个数据需要绕过安全机制才能拿到那就是不该碰的数据。5.2 采集频率的多与少怎么拿捏平台风控的逻辑不是“你不能拿数据”而是“你的行为不能像一个爬虫”。这个“像”字背后有一套复杂的判定逻辑请求频率、时间分布、行为序列、账号历史等都会纳入判断。我的经验是给采集任务设定一个“静默频率”单账号平均每分钟不超过10次请求单次任务持续时间不超过10分钟采集完成后至少休息15分钟再进行下一批。听起来很保守但这就是真实个人项目的安全节奏。另外还有一些操作上的细节不在凌晨三四点跑采集任务避免时间模式异常不连续翻页超过5页因为正常用户很少会翻那么深不在短时间内反复搜索同一个关键词这明显不是人类行为。5.3 项目能走多远从搜索爬虫到内容分析爬虫本身不是目的数据背后的洞察才是价值所在。这个项目跑通后我自然延伸出了几个应用方向。一个是趋势预警对一批核心关键词做每日定时采集计算内容数量的环比增长率当某个关键词的搜索笔记量突然放大时系统自动标记为“潜在热点”。对做内容运营的人来说这个信号价值很高。另一个是竞品内容拆解采集竞品品牌词的搜索结果分析他们发布笔记的高频标题句式、互动数据中位数、发布时间偏好。这些信息能从公开数据中提炼出不错的运营节奏参考。还有一个方向是数据集沉淀把采集的历史数据积累成结构化数据集后续做NLP训练或图表分析时直接用。我实际用这批数据做过一个简单的关键词共现矩阵效果不错能直观看出哪些搜索词经常同时出现在同一批笔记标题里。6. 实操环境与扩展建议6.1 本地开发环境配置备忘如果你是第一次在本地搭Python环境这里列一份直接的配置顺序跟着做就能少踩很多坑。先装Python官方安装包装完记得勾选“Add Python to PATH”这样终端才能直接识别。然后顺手解决包管理问题我习惯用pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple设置全局镜像后续装包速度会快很多。爬虫开发建议装VS Code搭配Python插件调试断点比“print大法”直观。再做一步可选操作安装Postman用来单独测试接口参数和响应结构思路比改代码调试敏捷得多。6.2 从脚本到定时任务的演进思路采集脚本跑通后我会把它注册成系统定时任务实现无人值守的数据更新。Linux用crontabWindows用任务计划程序配置好Python解释器的完整路径和脚本绝对路径设好执行频率就算完成。这里有一个细节定时任务环境下Python的sys.path和交互环境可能不一样脚本里如果用了相对路径找文件很容易找不到。我的习惯是在脚本开头把工作目录切换到脚本所在目录import os os.chdir(os.path.dirname(os.path.abspath(__file__)))这样做的好处是这个脚本挪到任何机器上只要文件依赖完整就能直接跑通不会因为工作目录不同导致数据文件存取异常。6.3 数据可视化让采集结果会说话数据采集只是第一步没有可视化的数据表格说服力有限。我后面补了一个简易的可视化脚本用matplotlib和wordcloud生成词频图和热词云。词云对内容运营特别有用一张图就能看出某个领域的高频词分布。做的时候注意中文显示问题需要下载一个中文字体并指定给matplotlib否则图上全是方块。这个坑我已经替各位踩过了代码里直接指定字体路径就行import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows plt.rcParams[axes.unicode_minus] False7. 关于这个项目的最终心得做了这么多期采集项目小红书搜索采集给我的最大感受是它不考验你的爬虫技术有多深而是考验你对接口的理解够不够细、对风控的敬畏够不够足。一个参数名变了可能导致整个流程崩掉一次频率没控制住可能让cookie直接失效。这个项目教会我的是“慢即是快”克制比激进更能走得远。如果你也想复现这个项目我的建议是从一个你熟悉的关键词开始跑通全流程后再逐步扩展关键词数量和采集维度。把这篇文章里的代码和思路搭起来你手上就有一份能为内容决策提供依据的数据源了。后续无论是做趋势观察、竞品拆解还是数据集沉淀起点都是这份干净、可追溯的采集工程。最后再分享一个小技巧。我把所有采集过的原始JSON都保留了备份不只在CSV里存提取后的字段。原因很简单CSV是给人类看的JSON是给机器看的。将来想重新解析新字段、做新维度的分析原始JSON里什么都有不用重新采集一遍。这是我踩过“删了原始数据、后面想加字段却没有”的坑之后才养成的习惯。
返回列表