引言

在内容电商蓬勃发展的今天,某生活方式社区已成为国内最具影响力的“种草”平台之一。其用户生成的笔记内容——从美妆测评、旅行攻略到家居好物、穿搭分享——蕴含着巨大的商业洞察价值。对于品牌方、市场研究者和内容创作者而言,理解热门品类的趋势走向,几乎等同于掌握了流量的密码。

比如,你想知道“冲锋衣”这个品类在平台上近三个月的热度变化,或者想分析“露营装备”相关笔记的互动规律。手动去一条条翻看显然不现实。这时候,自动化采集笔记数据就成了刚需。

然而,爬取该平台的笔记数据难度极高。其Web端所有核心数据(如搜索结果、评论列表)均通过加密API返回,需逆向还原x-s/x-t动态签名逻辑,否则将返回401或空响应体。签名密钥每5分钟轮换,必须实时提取JS上下文中的加密函数。更麻烦的是,它不封IP——它悄悄把你的设备ID加入灰名单,后续所有请求的响应体里都混入不可见的干扰字符,导致JSON解析直接报错。

本文将从零开始,系统介绍如何爬取该平台的笔记数据,并以此为基础分析热门品类趋势。文章将涵盖反爬机制剖析、采集方案选型、代码实现以及数据应用。

一、为什么某平台笔记爬取如此困难?

在动手写代码之前,先搞清楚一个问题:这个平台到底是如何识别爬虫的?

1.1 设备指纹:三重嵌套的隐性绑定

该平台的设备指纹不是简单拼接IMEI、Android ID和MAC地址,而是采用三层嵌套结构

  • 基础层(静态) :device_id由特定文件内容决定,算法为MD5(IMEI + Build.SERIAL + Build.BOARD + Build.MANUFACTURER).substring(0,16)。Android 10+环境下Build.SERIAL已被废弃,客户端会fallback到反射调用,若失败则用随机UUID替代——这意味着模拟器环境必须手动注入合法值,否则device_id每次重启都会变化。

  • 行为层(半动态) :x-b3-traceid并非全随机。通过抓包对比真实App请求发现,其前8位固定为device_id的MD5前8位,后8位为当前毫秒时间戳的十六进制表示。这使得服务端可瞬间关联设备与请求时间窗口。

  • 环境层(强动态) :x-salt字段每30分钟刷新一次,值为SHA256(device_id + current_timestamp/1800 + 固定盐值)。同一设备在30分钟内所有请求的salt完全相同,但跨块时必须更新。

1.2 Cookie、请求头与X-S签名强绑定

该平台Web端接口的风控机制核心在于:Cookie、Request Header与X-S签名强绑定。简单的签名生成往往无法通过校验,因为服务端会校验签名的生成环境(Cookie、Canvas指纹等)是否与请求发起者一致。

签名(x-sign)不是对URL或Body的简单哈希,而是对标准化请求上下文的HMAC-SHA256计算。

1.3 行为分析与风控联动

该平台的风控系统不仅检测单次请求的合法性,还会分析用户行为的连贯性。它会检测鼠标移动轨迹、点击延迟、滚动速度等行为特征是否符合真人操作。甚至在App端,它融合了设备指纹、行为轨迹、动态密钥、多层签名与实时风控的完整体系。

现实情况是:即使配置了所有反检测措施,该平台目前的风控依然非常强,这是一个持续的对抗过程,没有完全可靠的程序化解决方案。

二、采集方案选型:两条路怎么走?

面对如此严密的防护体系,目前主流的采集方案有两条路径。

2.1 路径一:浏览器自动化(Playwright方案)

核心思路:用真实的浏览器模拟真人操作,让浏览器自己完成所有签名计算和加密请求,然后通过拦截网络响应来获取数据。

推荐工具:Playwright + playwright-stealth + browserforge

目前社区已经有成熟的实现方案。例如,有开源项目基于Playwright实现了具备双层反检测能力(playwright-stealth环境级 + browserforge指纹级)的采集工具,支持关键词搜索采集(瀑布流自动滚动加载)、笔记详情采集(标题、正文、互动数据、标签、图片/视频)以及评论采集。登录状态可持久化(扫码登录后自动保存,下次启动免登录)。

另一方案则通过操控真实浏览器绕过反爬检测,扫码登录一次后session持久化,所有内容获取通过浏览器内建签名(window._webmsxyw)透明完成,无需逆向工程

优点

  • 无需逆向签名算法,维护成本相对较低

  • 签名算法更新时不受影响

  • 真实浏览器环境,行为更接近真人

缺点

  • 效率较低,每个请求需启动浏览器实例

  • 资源消耗大(内存、CPU)

  • 大规模并发困难

2.2 路径二:API逆向(纯算法签名方案)

核心思路:逆向出x-s/x-t签名生成算法,在代码中动态计算合法的请求头,直接调用API接口。

该平台Web端所有核心数据均通过加密API返回,需逆向还原x-s/x-t动态签名逻辑。签名密钥每5分钟轮换,必须实时提取JS上下文中的加密函数。

社区已有采用Pure Algorithm(纯算法签名) 策略的项目:使用Docker容器化的undetected-chromedriver获取合法的访客Cookie;纯Rust完成QR码创建、轮询、登录确认;Python Agent提供签名算法服务。浏览器仅在首次获取访客Cookie和登录同步时运行,日常请求完全由后端处理。

优点

  • 效率极高,无需加载浏览器

  • 可大规模并发采集

  • 资源消耗低

缺点

  • 逆向门槛高,需要逆向工程能力

  • 签名算法更新时需重新逆向

  • 设备指纹模拟难度大

2.3 选型建议

对于大多数开发者而言,路径一(Playwright浏览器自动化)是更务实的选择。原因有三:

  1. 无需逆向工程:签名算法由浏览器原生完成,平台更新时不受影响

  2. 社区方案成熟:已有多个开源项目可供参考和复用

  3. 维护成本可控:主要工作是应对风控策略调整,而非逆向算法

本文后续代码将基于Playwright方案展开。

三、代码实现:从登录到采集

3.1 环境准备

pip install playwright pandas openpyxl
playwright install chromium

3.2 登录与Session持久化

from playwright.sync_api import sync_playwright
import json
import time

class XHSScraper:
    def __init__(self, headless=False, use_proxy=False):
        self.headless = headless
        self.use_proxy = use_proxy
        self.browser = None
        self.context = None
        self.page = None
        
        # 站大爷隧道代理配置
        if use_proxy:
            self.proxy_config = {
                "server": "http://隧道代理地址:端口"
            }
        else:
            self.proxy_config = None
    
    def login(self, session_dir="browser_data"):
        """扫码登录并持久化session"""
        with sync_playwright() as p:
            # 启动浏览器(非无头模式更接近真实用户)
            browser = p.chromium.launch(
                headless=self.headless,
                proxy=self.proxy_config
            )
            
            # 创建上下文,加载已保存的session
            context = browser.new_context(
                user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0",
                viewport={"width": 1440, "height": 900},
                locale="zh-CN"
            )
            
            # 注入反检测脚本
            context.add_init_script("""
                Object.defineProperty(navigator, 'webdriver', {
                    get: () => undefined
                });
                // 隐藏自动化特征
                window.chrome = { runtime: {} };
            """)
            
            page = context.new_page()
            page.goto("https://www.xiaohongshu.com")
            
            print("请用手机扫码登录...")
            # 等待用户手动扫码登录
            page.wait_for_url("https://www.xiaohongshu.com/explore*", timeout=60000)
            
            # 保存session状态
            context.storage_state(path=f"{session_dir}/state.json")
            print(f"登录成功,session已保存到 {session_dir}/state.json")
            
            browser.close()
    
    def load_session(self, session_dir="browser_data"):
        """加载已保存的session"""
        with sync_playwright() as p:
            self.browser = p.chromium.launch(
                headless=self.headless,
                proxy=self.proxy_config
            )
            self.context = self.browser.new_context(
                storage_state=f"{session_dir}/state.json",
                viewport={"width": 1440, "height": 900}
            )
            self.page = self.context.new_page()
            return self.page
    
    def close(self):
        if self.browser:
            self.browser.close()

3.3 搜索并采集笔记

def search_notes(self, keyword, max_count=50):
    """搜索关键词并采集笔记"""
    if not self.page:
        self.load_session()
    
    # 访问搜索页面
    search_url = f"https://www.xiaohongshu.com/search?keyword={keyword}"
    self.page.goto(search_url)
    time.sleep(3)
    
    notes = []
    last_height = 0
    
    while len(notes) < max_count:
        # 滚动加载更多
        self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
        time.sleep(2)
        
        # 提取笔记卡片
        cards = self.page.query_selector_all("section.note-item, div.note-item")
        for card in cards:
            try:
                title = card.query_selector("a.title, .title").inner_text() if card.query_selector("a.title, .title") else ""
                link = card.query_selector("a").get_attribute("href") if card.query_selector("a") else ""
                note_id = link.split("/")[-1] if link else ""
                
                # 提取互动数据
                like_text = card.query_selector(".like-wrapper .count, .like-count").inner_text() if card.query_selector(".like-wrapper .count, .like-count") else "0"
                
                notes.append({
                    "note_id": note_id,
                    "title": title,
                    "link": f"https://www.xiaohongshu.com{link}" if link else "",
                    "likes": self._parse_count(like_text)
                })
            except Exception as e:
                continue
        
        # 检查是否到达底部
        new_height = self.page.evaluate("document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height
        
        # 随机延迟
        time.sleep(2)
    
    return notes[:max_count]

def get_note_detail(self, note_url):
    """获取笔记详情"""
    if not self.page:
        self.load_session()
    
    self.page.goto(note_url)
    time.sleep(3)
    
    # 拦截响应,获取API数据
    detail_data = {}
    
    def handle_response(response):
        if "/api/sns/web/v1/feed" in response.url or "note_detail" in response.url:
            try:
                data = response.json()
                detail_data.update(data)
            except:
                pass
    
    self.page.on("response", handle_response)
    self.page.reload()
    time.sleep(2)
    
    # 解析页面提取信息
    try:
        title = self.page.query_selector(".title, .note-title").inner_text() if self.page.query_selector(".title, .note-title") else ""
        content = self.page.query_selector(".content, .note-content").inner_text() if self.page.query_selector(".content, .note-content") else ""
        author = self.page.query_selector(".author-name").inner_text() if self.page.query_selector(".author-name") else ""
        
        return {
            "title": title,
            "content": content,
            "author": author,
            "raw_data": detail_data
        }
    except Exception as e:
        return {"error": str(e)}

3.4 数据采集的完整流程

# 使用示例
if __name__ == "__main__":
    scraper = XHSScraper(headless=False, use_proxy=True)
    
    # 首次使用:扫码登录
    # scraper.login()
    
    # 后续使用:加载已保存的session
    scraper.load_session()
    
    # 搜索多个关键词
    keywords = ["冲锋衣", "露营装备", "防晒衣", "户外徒步"]
    all_notes = []
    
    for keyword in keywords:
        print(f"正在搜索: {keyword}")
        notes = scraper.search_notes(keyword, max_count=30)
        for note in notes:
            note["keyword"] = keyword
        all_notes.extend(notes)
        time.sleep(5)  # 关键词之间间隔
    
    # 保存数据
    import pandas as pd
    df = pd.DataFrame(all_notes)
    df.to_csv("xiaohongshu_notes.csv", index=False, encoding="utf-8-sig")
    print(f"共采集 {len(all_notes)} 条笔记")
    
    scraper.close()

四、隧道代理:突破IP封禁的关键

4.1 为什么需要隧道代理?

即使你完美实现了浏览器自动化,当采集规模达到一定量级时,IP封禁依然不可避免。该平台对IP的检测极为严格,单一IP高频请求、多账号同IP、IP段集中,轻则限流、验证码轰炸,重则账号封禁、数据爬取失败。

该平台2026年的风控核心是IP + 设备 + 行为三重校验,对代理IP有明确的硬性要求:

  • 高匿名性:必须是高匿代理,普通匿名/透明代理极易触发风控

  • IP纯净度:无历史违规记录、非机房集中IP段,数据中心IP易被标记为机器流量

  • 稳定性:24小时持续可用率≥90%

  • 地域分散:覆盖全国主要城市,避免同地域集中请求

4.2 隧道代理的工作原理

隧道代理是传统代理的升级方案。你不需要手动维护IP池,只需配置好接入信息。每次发送请求时,隧道代理会自动把流量引导至不同的出口IP。

站大爷隧道代理为例,实测数据显示:

指标实测数据
初始可用率99.3%
24小时持续可用率99%
晚高峰可用率96.9%
异常响应占比1.8%
平均响应时间65ms
匿名性高匿

实测表现显示:几乎不触发验证码,多账号无关联,晚高峰依旧稳定,长期采集几乎零报错。

站大爷隧道代理的核心优势还包括:

  • 自动切换无感知:每次请求自动更换出口IP

  • 覆盖范围广泛:覆盖全国99%地域

  • 主备双隧道:持续更新IP池,稳定性有保障

  • 灵活配置:支持精细化的IP轮换周期自定义

4.3 在Playwright中集成隧道代理

from playwright.sync_api import sync_playwright

# 站大爷隧道代理配置
PROXY_CONFIG = {
    "server": "http://隧道代理地址:端口"
}

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False,
        proxy=PROXY_CONFIG  # 所有流量通过隧道代理
    )
    # 后续正常使用browser...

如果你使用的是requests库进行API调用:

import requests

PROXIES = {
    "http": "http://隧道代理地址:端口",
    "https": "https://隧道代理地址:端口"
}

def fetch_with_tunnel(url, headers, retry=3):
    for i in range(retry):
        try:
            response = requests.get(
                url,
                headers=headers,
                proxies=PROXIES,
                timeout=15
            )
            if response.status_code == 200:
                return response.json()
            # 遇到403/429,隧道代理自动切换IP
            if response.status_code in [403, 429]:
                print(f"请求被拒绝,隧道代理自动切换IP重试...")
                continue
        except Exception as e:
            print(f"请求异常: {e}")
            continue
    return None

选型建议:如果你是刚做该平台爬虫,建议先用站大爷免费版测通逻辑,正式跑量直接切换付费隧道代理,一步到位少踩坑。

五、从笔记数据到热门品类趋势分析

5.1 数据采集的核心维度

采集到的笔记数据可以从以下几个维度进行分析:

数据维度分析价值
笔记数量反映品类的讨论热度
互动数据(点赞/收藏/评论)衡量内容的用户共鸣度
发布时间分析趋势的时间变化
关键词/标签识别细分方向
作者画像了解内容生产者的特征

5.2 趋势分析的方法论

第一步:关键词体系构建

选择目标品类下的核心关键词和长尾关键词。例如“户外”品类可以包含:冲锋衣、徒步鞋、露营装备、登山杖等。

第二步:时间序列采集

定时(如每天)采集各关键词下的笔记数据,构建时间序列。通过观察笔记数量和互动数据的变化,可以识别品类的“爆发期”和“平稳期”。

第三步:品类对比分析

将多个品类的数据进行横向对比,识别增长最快的品类。根据平台数据,2025年热门品类涵盖“冲锋衣、防晒衣、水壶、喂食器、睡袋、护目镜、定位器”等。休闲零食(29.73%)、鲜果(22.26%)、冲饮品(21.23%)也是热门品类。

5.3 数据可视化示例

import pandas as pd
import matplotlib.pyplot as plt

# 加载采集的数据
df = pd.read_csv("xiaohongshu_notes.csv")

# 按关键词统计笔记数量和平均互动
trend = df.groupby('keyword').agg({
    'note_id': 'count',
    'likes': 'mean'
}).rename(columns={'note_id': '笔记数量', 'likes': '平均点赞'})

# 绘制趋势图
fig, ax1 = plt.subplots()
ax1.bar(trend.index, trend['笔记数量'], color='skyblue')
ax1.set_xlabel('品类')
ax1.set_ylabel('笔记数量', color='skyblue')

ax2 = ax1.twinx()
ax2.plot(trend.index, trend['平均点赞'], color='red', marker='o')
ax2.set_ylabel('平均点赞', color='red')

plt.title('各品类笔记热度对比')
plt.show()

5.4 趋势发现的典型案例

根据平台2025年度报告,“谷子、拼豆、bjd(球形关节玩偶)”等兴趣在2025年强势破圈。三大年轻人兴趣趋势包括:亲手创造一个小世界获得确定性、将万物作为“嘴替”表达情感、走出门在自然中解放天性。

这些趋势的发现,正是建立在大量笔记数据的采集与分析之上。通过爬虫获取的原始数据,经过清洗、聚合、对比,最终转化为可执行的商业洞察。

六、常见问题与避坑指南

6.1 Session过期怎么办?

该平台登录态通常为数天到数周。应对策略:

  • 使用storage_state持久化session,每次启动时加载

  • 编写检测脚本,定期检查登录态有效性

  • 失效时自动触发重新登录

6.2 遇到验证码怎么处理?

  • 关闭无头模式:确保headless=False,无头模式更容易被检测

  • 降低采集频率:增加sleep间隔到5-15秒

  • 保持浏览器打开:设置不自动关闭浏览器,触发验证码时可以手动完成验证

  • 使用真实浏览器连接模式:连接你自己的真实浏览器,复用已有的登录态,避免走自动化流程被检测

6.3 采集不全怎么办?

  • 该平台搜索结果的瀑布流加载可能受限于服务端返回的数据量

  • 建议按时间分段采集(如按月份拆分)

  • 使用多个关键词交叉覆盖同一品类

6.4 签名算法更新怎么办?

如果你选择的是API逆向方案,签名算法更新时需要重新逆向。建议:

  • 优先选择Playwright浏览器自动化方案,签名由浏览器原生处理

  • 关注开源社区的更新动态

  • 建立多套备选方案

七、总结

本文从该生活方式社区的反爬机制入手,系统介绍了爬取笔记数据以分析热门品类趋势的完整方案。核心要点可以概括为:

挑战解决方案
设备指纹检测使用真实浏览器,注入反检测脚本
X-S/X-T动态签名Playwright浏览器自动化,签名由浏览器原生生成
Cookie与签名强绑定Session持久化,复用登录态
IP频率限制站大爷隧道代理,自动切换IP
数据采集效率关键词搜索 + 瀑布流滚动 + 批量导出
趋势分析时间序列采集 + 多品类对比 + 可视化

技术选型速览:推荐“Playwright浏览器自动化 + Session持久化 + 站大爷隧道代理”的组合方案。该方案无需逆向工程,签名算法更新时不受影响,维护成本相对可控。

该平台的笔记数据是一座尚未被充分开采的金矿。通过合理的采集策略和数据分析方法,我们可以将这座金矿转化为结构化的趋势洞察,为品牌决策、内容创作和市场研究提供坚实的数据基础。

最后需要提醒的是:数据采集行为应当遵循行业规范,控制请求频率以避免对目标服务器造成过载。请遵守目标网站的robots.txt协议及相关法律法规,仅将爬虫技术用于学习和研究目的。希望本文能帮助你在数据分析的道路上迈出坚实的一步。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐