Python爬虫实战:小红书数据采集工具xhs-skill核心原理与应用
1. 项目概述一个面向小红书内容创作的效率工具箱如果你在小红书平台进行内容创作无论是个人博主、品牌运营还是MCN机构大概率都遇到过这样的场景为了追踪某个话题的热度需要手动搜索、截图、整理数据为了分析竞品账号的爆款规律得一条条翻看笔记用Excel记录发布时间、点赞数、互动量或者当你需要批量下载自己或他人的笔记素材用于复盘时只能一张张手动保存效率极低。这些繁琐、重复且耗时的工作正是“PengJiyuan/xhs-skill”这个开源项目试图解决的问题。简单来说xhs-skill是一个基于Python开发的、专门针对小红书平台的数据采集与处理工具包。它不是一个官方API而是通过模拟浏览器行为或解析网页数据的方式为创作者和运营者提供了一套自动化能力。其核心价值在于将那些需要人工重复操作的“体力活”自动化把时间还给创作者让他们能更专注于内容策略、创意构思和粉丝互动本身。这个项目在GitHub上开源意味着任何具备基础Python知识的开发者或技术型运营都可以免费使用、学习甚至参与改进。从技术角度看它触及了现代内容运营中的一个核心痛点数据驱动决策。在信息过载的时代凭感觉创作已经行不通了。你需要知道什么话题在升温、什么封面更吸引点击、什么时间段发布互动更高。xhs-skill提供的数据抓取能力正是为了获取这些决策所需的一手原材料。它适合有一定技术背景的内容创作者、社交媒体运营人员、数据分析师以及对爬虫技术如何应用于实际业务场景感兴趣的开发者。接下来我将为你深度拆解这个项目的设计思路、核心功能、实现细节以及在实际使用中会遇到的各种“坑”和应对技巧。2. 核心功能模块与设计思路拆解xhs-skill作为一个工具集其功能模块的设计紧密围绕小红书内容运营的实际工作流展开。它不是一个大而全的“机器人”而是由多个相对独立、可组合的“技能”组成。理解每个模块的设计初衷能帮助我们在使用时做出更合适的选择和配置。2.1 笔记数据采集模块从单篇到批量的内容抓取这是工具最基础也是最核心的功能。其设计目标很明确以尽可能稳定、高效的方式获取小红书笔记的详细信息。这包括笔记的正文内容、发布者信息、互动数据点赞、收藏、评论、转发、话题标签以及多媒体资源图片、视频的链接。为什么选择模拟请求而非官方API这是由平台策略决定的。小红书并未向普通开发者开放内容抓取的官方接口要获取数据通常只有两种途径一是人工查看网页或App二是通过技术手段模拟这种查看行为。xhs-skill采用了后者具体技术栈通常涉及requests库发送HTTP请求配合BeautifulSoup或lxml解析HTML或者更高级地使用Selenium或Playwright来模拟真实浏览器以应对复杂的JavaScript渲染页面。设计思路在于模拟一个普通用户的访问行为从服务器响应的HTML代码中提取出结构化的数据。这里有一个关键考量抗反爬策略。平台为了防止数据被大规模抓取会设置诸多障碍如请求频率限制、验证码、参数签名等。因此这个模块的设计必须包含请求头User-Agent, Cookie等的随机化与模拟、请求间隔sleep的合理设置、以及可能遇到的错误重试机制。开发者需要在这些“对抗”中找到一个平衡点既要拿到数据又不能因请求过于频繁而导致IP被封禁。这部分的代码实现最能体现项目的实用性和健壮性。2.2 搜索与话题监控模块捕捉内容趋势的雷达单纯抓取单篇笔记意义有限运营更需要的是趋势和热点。因此搜索功能模块应运而生。你可以通过关键词、指定话题如“#OOTD”来搜索相关笔记并按照“最新”、“最热”等排序方式获取结果列表。这个模块的设计难点在于处理分页和结果去重。小红书的搜索结果是动态加载的可能涉及滚动翻页或点击“加载更多”。工具需要模拟这一交互过程并确保在翻页过程中不遗漏、不重复抓取笔记。此外搜索结果往往包含广告或推荐内容如何精准地筛选出目标笔记也需要在数据解析逻辑上下功夫。对于话题监控可以将其视为一个定时触发的自动化搜索任务定期抓取特定话题下的新内容用于分析话题热度的变化趋势和新兴内容形式。2.3 用户主页分析模块竞品调研与账号诊断分析竞品账号或行业标杆是内容运营的必修课。用户主页模块允许你输入一个博主的主页链接批量抓取其发布的所有笔记的元数据标题、互动量、发布时间等。这为账号内容分析提供了数据基础。设计这个模块时性能与合规性是需要重点考虑的。一个万粉博主的笔记量可能成百上千一次性抓取所有历史笔记会给目标服务器带来巨大压力也极易触发反爬机制。因此合理的实现方案是提供“抓取最近N篇”的选项并强制设置较长的请求间隔。同时必须强调这个功能应仅用于公开数据的分析学习绝对禁止用于恶意爬取、骚扰用户或侵犯隐私等用途这也是开源项目需要倡导的负责任的技术使用理念。2.4 多媒体资源下载模块素材归档与内容复用笔记中的图片和视频是宝贵的创作素材。手动下载效率低下尤其是当需要批量保存时。下载模块的设计目标是将笔记中的图片、视频链接解析出来并批量下载到本地。技术实现上它依赖于数据采集模块获取到的多媒体URL。难点在于第一这些URL可能是临时的、有过期时间的需要尽快处理第二小红书对图片可能有防盗链处理需要携带正确的Referer等请求头信息才能成功下载第三视频可能被切片或加密需要更复杂的流媒体下载逻辑。一个健壮的下载模块会处理好这些异常情况并提供断点续传、并发下载控制并发数避免被封等高级功能以提升下载效率和成功率。3. 环境搭建与核心依赖解析要运行或二次开发xhs-skill首先需要搭建合适的Python环境。这里我假设你已经在本地安装了Python建议3.8及以上版本。接下来我们从依赖库入手看看这个项目背后都用了哪些“武器”。3.1 核心依赖库及其作用通常这类项目的requirements.txt文件或setup.py会列出以下关键依赖requests: HTTP请求库的绝对主力。用于向小红书服务器发送GET/POST请求获取网页HTML或接口数据。它是所有数据获取的起点。BeautifulSoup4或lxml: HTML/XML解析器。当requests拿到网页源码一堆HTML标签后需要这些库来解析文档结构并按照CSS选择器或XPath路径精准地定位和提取我们需要的数据比如笔记标题所在的h1标签或者点赞数所在的某个span。Selenium或Playwright: 浏览器自动化工具。这是应对复杂情况的“重型武器”。如果目标页面数据是通过JavaScript动态渲染的即直接请求HTML看不到数据需要浏览器执行JS后才能生成那么简单的requestsBeautifulSoup组合就失效了。此时需要Selenium或Playwright来启动一个真正的浏览器如Chrome模拟用户操作等待页面加载完成再获取渲染后的完整HTML。它们的优点是能处理几乎所有网站缺点是速度慢、资源消耗大。pandas: 数据处理与分析库。虽然不是爬虫必需但在xhs-skill这类项目中非常常见。它用于将抓取到的结构化数据列表、字典方便地转换为DataFrame进而可以轻松地进行数据清洗、筛选、排序并导出为Excel或CSV文件极大方便了后续分析。aiohttp/httpx(可选): 异步HTTP客户端。当需要进行大规模并发抓取时传统的同步请求requests会因等待服务器响应而浪费大量时间。异步库允许你在等待一个请求响应的同时发起新的请求从而成倍提升抓取效率。但异步编程复杂度较高通常用于对性能有极致要求的场景。Pillow(可选): 图像处理库。如果项目包含图片水印识别、简单处理或缩略图生成等功能可能会用到它。注意依赖库的版本兼容性是个暗坑。特别是Selenium需要与浏览器驱动如chromedriver版本匹配。建议使用pip安装时严格按照项目文档指定的版本号或使用虚拟环境隔离项目。3.2 配置与初始化实战假设我们已经克隆了项目代码安装好依赖后第一步通常是进行一些必要的配置。这可能包括设置请求头Headers: 这是模仿浏览器的关键。你需要准备一个看起来像来自真实浏览器的User-Agent字符串。此外可能还需要配置Cookie。重要提示Cookie包含你的登录态切勿将包含个人登录Cookie的代码公开分享存在账号安全风险。对于公开数据抓取有时不需要登录态。配置代理IPProxy: 如果你需要大规模、高频次抓取使用代理IP池来轮换请求源IP是避免被封禁的常见手段。配置方式通常是在requests的请求参数中传入proxies字典。设置延迟Delay: 在连续请求之间插入随机延时如time.sleep(random.uniform(1, 3))是基本的礼貌也是对目标服务器的保护能有效降低被封风险。一个典型的初始化代码片段可能如下所示import requests import time import random from bs4 import BeautifulSoup class XiaoHongShuSpider: def __init__(self): self.session requests.Session() # 设置一个常见的浏览器User-Agent self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } self.session.headers.update(self.headers) # 基础延迟配置 self.base_delay (1, 3) # 随机延迟1-3秒 def _request_with_delay(self, url): 带延迟的请求函数 time.sleep(random.uniform(*self.base_delay)) try: response self.session.get(url) response.raise_for_status() # 检查HTTP错误 return response.text except requests.exceptions.RequestException as e: print(f请求失败: {url}, 错误: {e}) return None # 使用示例 spider XiaoHongShuSpider() html spider._request_with_delay(https://www.xiaohongshu.com/explore/某个笔记ID) if html: soup BeautifulSoup(html, html.parser) # 接下来使用soup解析数据...4. 核心功能实现细节与代码剖析理解了设计思路和环境配置后我们深入到具体功能的实现层面。这里我会结合常见的实现模式和xhs-skill可能采用的方法解析关键代码逻辑。4.1 解析单篇笔记定位数据与应对页面结构变化抓取单篇笔记的URL获取其详情是其他所有功能的基础。核心步骤是请求URL - 解析HTML - 提取字段。难点在于数据定位。小红书的前端页面结构可能随时调整今天能用的CSS选择器明天可能就失效了。因此代码不能写死要有一定的容错和适应性。def parse_note_detail(html_content): 解析笔记详情页HTML提取关键信息。 注意此处的选择器是示例实际需要根据当时页面结构调整。 soup BeautifulSoup(html_content, html.parser) note_info {} # 1. 提取标题 - 尝试多种可能的选择器 title_elem soup.select_one(h1[class*title], div[class*title] span) note_info[title] title_elem.get_text(stripTrue) if title_elem else # 2. 提取作者信息 author_elem soup.select_one(a[class*author-name], div[class*nickname]) note_info[author] author_elem.get_text(stripTrue) if author_elem else # 作者主页链接 author_link author_elem.get(href) if author_elem else note_info[author_url] fhttps://www.xiaohongshu.com{author_link} if author_link.startswith(/) else author_link # 3. 提取互动数据点赞、收藏、评论 - 通常这些数据在特定的meta标签或script中 # 方法A从HTML标签中找不稳定 like_elem soup.find(span, stringlambda text: 点赞 in text if text else False) # 方法B更可靠很多现代网站将数据放在script typeapplication/ldjson的JSON-LD中或初始状态变量window.__INITIAL_STATE__里。 # 这里演示从script中提取JSON import json import re script_tags soup.find_all(script) for script in script_tags: if script.string and __INITIAL_STATE__ in script.string: # 使用正则表达式提取JSON对象 match re.search(rwindow\.__INITIAL_STATE__\s*\s*({.*?});, script.string, re.DOTALL) if match: try: initial_state json.loads(match.group(1)) # 这是一个巨大的嵌套对象需要你仔细分析其结构找到笔记数据 # 例如initial_state[note][noteDetailMap][note_id][note] # 这里只是示例实际路径需要自行探索 note_data initial_state.get(note, {}).get(noteDetailMap, {}) # 假设我们能拿到note_id这里简化处理 for key, detail in note_data.items(): note_info[likes] detail.get(likes, 0) note_info[collects] detail.get(collects, 0) note_info[comments] detail.get(comments, 0) note_info[share] detail.get(share, 0) break except json.JSONDecodeError as e: print(f解析JSON-LD失败: {e}) break # 4. 提取正文内容 # 正文可能分布在多个段落标签中 content_elems soup.select(div[class*content] p, div[class*desc]) note_info[content] \n.join([elem.get_text(stripTrue) for elem in content_elems]) # 5. 提取图片和视频链接 media_links [] # 图片 for img in soup.select(img[src*xhs]): # 选择包含xhs域名特征的图片 src img.get(src) if src and src not in media_links: media_links.append(src) # 视频 - 可能是一个video标签或者背景图 for video in soup.select(video source): src video.get(src) if src and src not in media_links: media_links.append(src) note_info[media_urls] media_links # 6. 提取话题标签 tags [] for tag_elem in soup.select(a[href*/search_result?], div[class*tag]): tag_text tag_elem.get_text(stripTrue) if tag_text.startswith(#): tags.append(tag_text) note_info[hashtags] tags return note_info实操心得优先从window.__INITIAL_STATE__或类似的内嵌JSON中提取数据。这是最稳定、最结构化的方式数据通常是最全的。虽然解析嵌套JSON麻烦但一旦摸清结构后续非常稳定。准备好备用选择器。对于必须从HTML标签提取的数据准备2-3个可能的选择器路径并编写逻辑依次尝试增加代码的鲁棒性。定期检查与更新。平台前端更新是常态重要的爬虫脚本需要定期运行测试确保选择器或JSON路径依然有效。4.2 实现搜索功能处理分页与反爬搜索功能通常需要构造一个搜索URL并处理分页。小红书搜索的URL参数可能包含keyword、page、sort等。def search_notes(keyword, page1, sortgeneral): 搜索笔记 :param keyword: 搜索关键词 :param page: 页码 :param sort: 排序方式如 general (综合), time (最新) :return: 笔记ID或详情列表 # 构造搜索URL (示例实际参数需分析) base_url https://www.xiaohongshu.com/web_api/sns/v3/search/notes params { keyword: keyword, page: page, page_size: 20, # 每页数量 sort: sort, # 可能还有其他必要参数如 search_id, 需要从首次请求的Cookie或响应中获取 } headers { **self.headers, Referer: fhttps://www.xiaohongshu.com/search_result?keyword{keyword}, # 搜索接口常需要额外的认证头如 x-sign这需要逆向分析JS生成逻辑是最大难点。 } response self.session.get(base_url, paramsparams, headersheaders) if response.status_code 200: data response.json() # 解析返回的JSON提取笔记列表 notes data.get(data, {}).get(notes, []) note_list [] for note in notes: note_list.append({ note_id: note.get(id), title: note.get(title), user: note.get(user, {}).get(nickname), likes: note.get(likes), }) return note_list else: print(f搜索请求失败状态码: {response.status_code}) return []关键难点与技巧参数签名如x-sign这是最棘手的部分。平台为了反爬会对请求参数和路径用一个密钥进行加密生成一个签名放在请求头里。服务器会验证这个签名。破解这个签名通常需要逆向工程即分析其网页或移动端App的JavaScript代码找到生成签名的算法并用Python复现。这是一个技术壁垒也是xhs-skill这类项目核心价值所在。如果项目代码中包含了签名的生成逻辑那它就是“可用”的关键。分页逻辑除了基本的page参数有些接口使用“游标”cursor分页即上一页响应中会包含一个cursor字段用于获取下一页。需要根据接口实际设计来处理。频率限制搜索接口的限流通常比普通页面更严格。务必设置更长的请求间隔例如3-5秒并考虑使用代理IP。4.3 批量下载与数据存储获取到数据后有效的存储和管理至关重要。数据存储最简单的就是存为JSON或CSV文件。使用pandas可以非常优雅地处理。import pandas as pd def save_notes_to_csv(note_list, filenamexhs_notes.csv): 将笔记列表保存为CSV文件 df pd.DataFrame(note_list) # 选择要保存的列 columns_to_save [note_id, title, author, likes, collects, comments, publish_time, hashtags] df df[[col for col in columns_to_save if col in df.columns]] df.to_csv(filename, indexFalse, encodingutf-8-sig) # utf-8-sig支持Excel直接打开显示中文 print(f数据已保存至 {filename}) def save_notes_to_json(note_list, filenamexhs_notes.json): 将笔记列表保存为JSON文件 import json with open(filename, w, encodingutf-8) as f: json.dump(note_list, f, ensure_asciiFalse, indent2) print(f数据已保存至 {filename})媒体文件下载下载图片/视频时要注意网络错误和文件存储。import os from urllib.parse import urlparse def download_media(media_urls, save_dir./downloads): 下载媒体文件 if not os.path.exists(save_dir): os.makedirs(save_dir) for i, url in enumerate(media_urls): try: # 从URL中提取文件名 parsed_url urlparse(url) filename os.path.basename(parsed_url.path) if not filename: filename fmedia_{i}.jpg # 默认扩展名 # 确保文件名唯一 save_path os.path.join(save_dir, filename) counter 1 while os.path.exists(save_path): name, ext os.path.splitext(filename) save_path os.path.join(save_dir, f{name}_{counter}{ext}) counter 1 # 发送请求下载注意带上Referer等必要头信息 headers {Referer: https://www.xiaohongshu.com/} resp requests.get(url, headersheaders, streamTrue, timeout10) resp.raise_for_status() with open(save_path, wb) as f: for chunk in resp.iter_content(chunk_size8192): f.write(chunk) print(f下载成功: {save_path}) time.sleep(0.5) # 下载间隔避免过快 except Exception as e: print(f下载失败 {url}: {e})5. 高级技巧与稳定性优化实战当基本功能跑通后我们需要关注如何让爬虫更稳定、更高效、更像“人”。这是区分玩具脚本和生产级工具的关键。5.1 构建健壮的错误处理与重试机制网络请求充满不确定性必须假设任何请求都可能失败。import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_robust_session(retries3, backoff_factor0.5): 创建一个带有重试机制的稳健Session session requests.Session() # 定义重试策略 retry_strategy Retry( totalretries, # 总重试次数 backoff_factorbackoff_factor, # 退避因子延迟 backoff_factor * (2^(重试次数-1)) status_forcelist[429, 500, 502, 503, 504], # 遇到这些状态码才重试 allowed_methods[GET, POST] # 只对GET/POST方法重试 ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(http://, adapter) session.mount(https://, adapter) return session # 使用示例 session create_robust_session() try: response session.get(url, timeout10) response.raise_for_status() except requests.exceptions.RequestException as e: # 记录日志可能触发更高级别的报警或任务暂停 print(f请求最终失败: {url}, 错误: {e}) # 可以在这里将失败的任务URL加入重试队列5.2 应对验证码与登录态维护如果操作需要登录如查看某些私密账号或互动就需要处理登录和Cookie。手动登录获取Cookie最安全简单的方式是手动在浏览器登录小红书然后通过开发者工具F12 - Network复制Cookie请求头的值在代码中设置。但Cookie会过期。自动化登录不推荐通过Selenium模拟输入用户名密码登录。这涉及处理登录表单、可能出现的滑块验证码等复杂度高且账号有安全风险。平台对自动化登录检测严格极易触发风控。Cookie池与更新对于需要长期运行的任务可以维护一个Cookie池定期检查Cookie有效性失效时通过某种方式如人工介入更新。重要警告任何自动化登录行为都可能违反平台用户协议存在账号被封禁的风险。xhs-skill这类工具应主要用于抓取公开可见的数据。涉及登录的操作务必谨慎并明确知晓潜在风险。5.3 使用代理IP池应对IP封锁这是大规模爬取必须考虑的一环。当单个IP请求过快或过多时会被暂时或永久封锁。class ProxyManager: 简单的代理IP管理器示例 def __init__(self, proxy_list): self.proxies proxy_list self.current_index 0 def get_proxy(self): 获取一个代理简单轮询 proxy self.proxies[self.current_index] self.current_index (self.current_index 1) % len(self.proxies) return {http: proxy, https: proxy} def report_bad_proxy(self, proxy): 报告失效代理可从列表中移除这里简化处理 print(f代理 {proxy} 可能已失效) # 使用 proxy_manager ProxyManager([http://ip1:port, http://ip2:port]) session requests.Session() def make_request_with_proxy(url): max_retry 3 for _ in range(max_retry): proxy proxy_manager.get_proxy() try: resp session.get(url, proxiesproxy, timeout8) if resp.status_code 200: return resp else: proxy_manager.report_bad_proxy(proxy) except Exception as e: proxy_manager.report_bad_proxy(proxy) return None代理IP来源可以购买付费代理服务或自建代理服务器。免费代理通常不稳定不适合生产环境。5.4 数据去重与增量抓取对于监控类任务如监控某个话题我们只关心新内容。这就需要去重和记录抓取状态。基于唯一ID去重小红书笔记有唯一的note_id。将已抓取的ID存入集合Set或数据库每次抓取前先判断。增量抓取逻辑对于用户主页记录已抓取到的最早一篇笔记的时间或ID。下次抓取时只抓取比这个时间更新的笔记。对于搜索可以按时间排序后抓取直到遇到上次已抓取过的笔记为止。使用轻量级数据库如SQLite非常适合存储抓取状态和历史数据。import sqlite3 class CrawlStateDB: def __init__(self, db_pathcrawl_state.db): self.conn sqlite3.connect(db_path) self._init_db() def _init_db(self): cursor self.conn.cursor() # 创建表记录已抓取的笔记ID和抓取时间 cursor.execute( CREATE TABLE IF NOT EXISTS crawled_notes ( note_id TEXT PRIMARY KEY, crawled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) self.conn.commit() def is_note_crawled(self, note_id): cursor self.conn.cursor() cursor.execute(SELECT 1 FROM crawled_notes WHERE note_id ?, (note_id,)) return cursor.fetchone() is not None def mark_note_crawled(self, note_id): cursor self.conn.cursor() try: cursor.execute(INSERT INTO crawled_notes (note_id) VALUES (?), (note_id,)) self.conn.commit() except sqlite3.IntegrityError: pass # 重复插入忽略 # 在抓取循环中使用 db CrawlStateDB() for note in search_results: if not db.is_note_crawled(note[id]): # 抓取这篇新笔记的详情 detail fetch_note_detail(note[id]) process_note(detail) db.mark_note_crawled(note[id])6. 典型应用场景与数据分析示例工具是手段解决问题才是目的。下面结合几个典型场景看看如何利用xhs-skill抓取的数据进行初步分析。6.1 场景一爆款笔记内容分析目标找出某个领域如“露营装备”下近期点赞最高的50篇笔记分析其标题、封面、话题标签的共性。操作流程使用搜索功能关键词“露营装备”按“最热”排序抓取前5页约100篇笔记的元数据ID 标题 点赞 作者 话题。按点赞数降序排序取前50篇。数据分析标题词频分析使用jieba库对标题进行分词统计高频词如“干货”、“分享”、“必备”、“测评”。话题标签统计统计这50篇笔记中出现频率最高的话题标签如“#露营”、“#户外”、“#好物分享”。互动率估算如果数据中有收藏和评论可以计算点赞收藏评论/ 曝光量需估算的近似值但通常直接比较点赞数已足够。输出结果一份报告指出“露营装备”类爆款笔记常用“干货测评”、“好物分享”等标题范式并强烈关联“#露营”、“#户外生活”等标签。6.2 场景二竞品账号运营监控目标监控3个对标账号每周自动统计其新发笔记的数量、平均互动数据并发现其内容方向变化。操作流程每周一自动运行脚本抓取这3个账号主页的最新20篇笔记。与上周存储的数据进行对比识别出新发布的笔记。计算本周新笔记的平均点赞、收藏、评论数。对本周新笔记的标题和话题进行词云分析与上周对比看是否有新的关键词出现。输出结果一张周报表格清晰展示各竞品账号本周更新频率、互动表现和内容关键词变化为自身内容策略调整提供参考。6.3 场景三话题热度趋势追踪目标追踪“#沉浸式回家”这个话题看其热度是上升还是下降。操作流程每天固定时间如晚上10点抓取“#沉浸式回家”话题下按“最新”排序的前100篇笔记。记录当天抓取到的笔记总数作为日新增量的近似值、以及这些笔记的平均点赞数作为内容平均热度的近似值。将每日的数据新增笔记数、平均点赞存入时间序列数据库如InfluxDB或CSV文件。一周或一月后绘制折线图观察“日新增笔记数”和“平均点赞”两条曲线的变化趋势。输出结果一张趋势图直观显示话题的生命周期。如果新增数和平均点赞都在持续下降可能意味着话题热度已过可以考虑减少相关内容的投入。7. 常见问题、踩坑记录与排查指南在实际使用和开发类似xhs-skill的工具时你会遇到各种各样的问题。下面是我总结的一些典型“坑”及其解决方案。7.1 请求被拒绝或返回空数据现象requests直接返回403、404或者返回的HTML里没有目标数据。可能原因及排查请求头不完整检查User-Agent,Referer,Cookie是否设置正确。特别是Referer有时必须设置为小红书的域名。缺少签名参数对于搜索等接口检查请求URL或Headers中是否缺少像x-sign,x-t这样的动态签名参数。这需要逆向JS。如果项目代码突然失效很可能是签名算法更新了。IP被限制短时间内请求过于频繁。解决方案大幅增加请求间隔例如10秒以上并使用代理IP。需要登录某些页面或接口需要登录态才能访问。检查你的Cookie是否有效。可以尝试在浏览器中访问同一链接看是否需要登录。调试技巧使用浏览器开发者工具的“网络(Network)”面板找到对应的请求仔细对比你的Python脚本发出的请求与浏览器发出的请求在Headers、Query Parameters、Form Data上有何不同。7.2 数据解析失败选择器找不到元素现象BeautifulSoup或Selenium无法通过选择器定位到元素返回None。可能原因及排查页面结构已变更这是最常见的原因。平台前端更新了。解决方案重新分析页面HTML结构更新CSS选择器或XPath。优先尝试从window.__INITIAL_STATE__等脚本标签中提取数据。页面未完全加载针对Selenium在使用find_element前使用WebDriverWait和expected_conditions等待目标元素出现。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, h1.title)) )iframe嵌套目标元素可能在iframe里面。需要先使用driver.switch_to.frame(frame_element)切换到对应的iframe中。调试技巧将抓取到的HTML保存到本地文件用浏览器打开并使用开发者工具检查元素确认你预想的选择器路径是否正确。7.3 异步处理与性能瓶颈现象抓取几百条数据速度非常慢。可能原因同步请求是串行的大部分时间在等待网络响应。解决方案使用并发/并行对于I/O密集型任务网络请求使用concurrent.futures.ThreadPoolExecutor可以实现简单的多线程并发。注意控制并发数建议在5-10之间避免对服务器造成过大压力或触发反爬。使用异步库对于高级用户使用aiohttp或httpx进行异步编程可以极大提升效率。但代码复杂度较高。优化延迟在遵守“礼貌”的前提下找到不触发反爬的最短请求间隔。7.4 法律与道德风险规避这是最重要的一部分必须时刻谨记。遵守robots.txt检查https://www.xiaohongshu.com/robots.txt尊重其中定义的爬虫规则。虽然这不是法律文件但体现了行业规范。限制抓取频率和数量不要进行毁灭性的、影响网站正常服务的抓取。设置合理的请求间隔避免在高峰时段运行。仅抓取公开数据不要尝试抓取需要登录才能访问的个人隐私数据、非公开笔记等。尊重版权与用户协议抓取到的内容尤其是图片、视频用于个人学习、研究或内部数据分析是相对安全的。但未经授权绝对禁止用于商业发布、重新分发或训练AI模型这可能侵犯著作权。明确免责声明如果你基于xhs-skill进行二次开发并分享应在代码和文档中明确说明工具仅用于技术学习和合法合规的数据分析使用者需自行承担风险。开发和使用这类工具技术只是其一更重要的是对规则和边界的把握。保持克制尊重平台才能让工具发挥长期、正面的价值。