典型症状:旺季曲线把"没增长"演成了"暴涨"
去年双十一复盘那天,我干过一件很蠢的事。我把一个关键词的百度指数月度数据直接连线,看到 11 月比 10 月高出六成,当场在会上说"这个品类今年旺季彻底爆发了"。老板拿这个结论去排了下一季度预算。三个月后对账,这个品类的实际销售额只比上季度多了 3%,连渠道库存都没压下去。
问题不在数据,在我。搜索指数的季节性本来就极强,我却把季节性当成了增长。指数数据做季节性分析这件事,正确顺序是:先对齐时间尺度,再核对指数口径,最后才轮到分解模型。跳过前两步直接分解,出来的成分图再漂亮也是错的。下面按我当时踩坑的顺序,把排查路径完整走一遍。
排查路径(从外到内)
我没有一上来就打开 pandas,而是先排掉三层外部因素,因为它们比模型更容易出错:
第一层,时间尺度。同一份数据,日粒度看是"周一暴涨",周粒度看是"周末回落",月粒度看什么都没了。搜索指数里藏着周节律、节假日错位、大促脉冲三套周期,粒度选错,波形就是噪声。我当时的表是把日数据直接按月求平均,正好把双 11 那几天的脉冲摊平,峰值看起来"温和",反而掩盖了真实强度。
第二层,口径。百度指数不是搜索量,是搜索频次的加权和再指数化,数值本身只有趋势意义,不能跨词、跨源比大小。这一层很容易被忽略,站里那篇百度指数到底怎么算的把四个核心逻辑讲得比我清楚,我建议先读它再动手。同一份序列里如果混了 PC 指数和移动指数,季节性形状会直接变形。
第三层,才是分解模型。只有前两层都排干净,模型输出的趋势项才可信。
根因定位
把上面三层过完,我定位到三个真正的根因。
根因一:环比天然含季节性。这是我犯的原始错误。任何带强年周期的东西,环比都必然在旺季为正、在淡季为负,跟经营好坏无关。站里有篇文章叫毕业论文实操:用百度指数挖出财税 SaaS 的"季节金矿",讲的就是同一个坑的反面用法——它承认季节是"金矿",但前提是你先把季节从趋势里择出来,而不是把季节当趋势。我当时连"季节"这个词都没意识到。
根因二:数据没对齐日历。月粒度里 2 月只有 28 天,7 月有 31 天,不做日均归一化,2 月天然低一截。周粒度如果不把起止对齐到周一,相邻两周会各自截断,波峰波谷整体错位一格。这类误差不大,但会被后面的移动平均放大成假的"趋势拐点"。
根因三:序列太短就上分解。月度数据做分解,至少要两个完整周期也就是 24 个月,最好 36 个月以上。周期不够时,中心化移动平均在序列两端取不到对称窗口,趋势项的头尾全是估计值,看起来平滑,其实不可用。我们站里那份 358 城市词 × 362 城市、2011–2025 的百度指数日均数据集之所以好用,就是因为它的长度足够撑起分解,短序列根本谈不上季节性建模。还有一篇 百度搜索指数数据分析报告(2015–2026)也可以当参照,看别人的长序列是怎么摊开来讲的。
修复动作
定位完根因,我的修复分五步,每步都有具体取数动作。
第一步,先算同比,再看环比。同比能消掉年周期,是最便宜的季节性处理。但要留意特殊年份——2020 年前后大量线下品类经历断崖与反弹,同比基数会失真,这时候要么换基期,要么显式把这些月份标记成结构断点,而不是硬算。
第二步,做 12 期中心化移动平均去趋势。因为周期是偶数 12,直接移动平均会落在两个时点中间,必须再做一次 2 期平均才对齐到整数月。Excel 里可以手写 SUM 减两头的公式,pandas 里一行 series.rolling(12, center=True).mean() 就够。做完这一步,序列两端各会损失半年数据,这是方法本身的代价,不是数据缺失。
第三步,定加法还是乘法。判据只有一个:季节性波动的幅度是不是随序列水平一起变大。如果后几年旺季峰谷明显比前几年大,用乘法;如果峰谷高度一直差不多,用加法。乘法模型等价于对 log(y) 做加法分解,所以拿不准时可以先把数据取对数看方差是否变稳。
第四步,上 STL。传统分解把季节项冻结成一条固定的周期形状,真实业务里季节性会缓慢漂移,比如某个品类逐年"夏天越来越旺",固定季节项根本抓不到。STL 用局部加权回归,季节项可以随时间变形状,而且能对异常值降权。statsmodels 里的用法是 STL(series, period=12, robust=True).fit(),robust=True 是关键——它会把双 11 那种一次性脉冲压下去、丢进残差,而不是让它把趋势项拽偏。参数细节(seasonal 需为奇数、trend 一般约为 season 的 1.5 倍)以官方文档最新说明为准,不同版本默认值有调整。
第五步,用残差抓真异常。趋势和季节都剥掉之后,残差就是"说不清的那部分"。把残差标准化,|z| > 3 的点才值得单独看。这一步比直接盯原始曲线报警靠谱得多。
而这一切的前提,是你能拿到足够长、足够干净、字段对齐的原始序列。采集端是我最花时间的地方:站里那篇 C# WinForm 从抓包到批量采集里有个实测数字我一直记着——3 个 Cookie 轮询,批量采集 500 个关键词,成功率从 62% 提到 97%。另一篇 百度指数批量采集实战则把量级说透了:4000 只股票乘 300 多个地市就是 120 万次请求,硬刷不现实,必须先在取样策略上做减法。这两篇说穿了同一件事——季节性分析的质量上限,在采集阶段就定死了。
怎么防止再犯
复盘之后我给自己定了四条规矩,都是减法。
口径必须写在图标题里。到底用的是哪套指数、什么粒度、哪个地域,写清楚,否则三个月后自己都说不清这张图能不能跟另一张比。
分解结果要做滚动验证,不要一次静态拟合。用 60 期窗口滚动重算,看残差误差是不是在涨;如果涨得很快,说明周期选错或者序列里混了结构断点。
明确说不该用哪种做法。数据不到两个完整周期,不做 STL;季节性幅度不随水平放大的,不硬套乘法;X-13 那种要统计底子的重型季节调整,业务节奏跟不上的就别上。站里那篇 FFmpeg 直播流录制自动化里有句话我很认同——取舍要果断,场景简单、试错成本低的时候,只留最必要的步骤,其余等真出问题再加,"够用就停"。
最后一条是留证。采集时间戳、导出批次、字段口径都存下来。站里讲采集留证的 小红书笔记采集实操有段话说得很实在:真正做起来才发现三个绕不过去的问题,其中一个就是事后无法复现。季节性分析最怕的就是这个——半年后要复核结论,数据已经变了。
用我们自己的工具把这条链路跑通
理论上讲完了,讲讲我实际怎么落地。原始指数序列我用站内的百度指数批量采集工具配合指数采集 API 接口导出,可以直接拿到多年日度数据,再按上面的粒度做归一化;需要突破百度指数"逗号最多 5 个词、加号最多 3 个词"的查询限制、或者要多年日度一次导出成 Excel 的,走站里那套批量导出的路子更省事,具体用法在工具页里有说明。导出的 CSV 直接喂给 pandas,seasonal_decompose 和 STL 都能跑,不用再做格式转换。需要长期序列做回归和分解的,前面提到的两个数据集也可以直接当底稿。
常见问题
Q:一定要用 STL 吗,传统 decompose 是不是过时了?不是。传统分解跑得快、好解释,序列短、季节性稳定的时候够用。STL 的价值在于两点:季节形状会漂移、序列里有异常值。这两点不成立,就没必要上 STL。
Q:加了 robust=True 之后,双 11 那个峰值去哪了?进了残差项,没有消失。它的作用是别让一个孤立的极端点把整条趋势线拽偏,而不是把异常抹掉。你想专门研究大促脉冲,应该去看残差,而不是看趋势。
Q:百度指数和 Google Trends 能放在一起比数值吗?不能,只能比趋势形状。百度指数是绝对口径的加权和,Google Trends 是除以各组最高点、缩放到 0–100 的相对值,换一个对比词,整条曲线的高度就变。跨源比数值是错的。
Q:我只有 18 个月的数据,能做季节性分解吗?不建议。中心化移动平均在两端取不到对称窗口,趋势项头尾不可用,季节指数也会因为样本不足而不稳。至少凑够 24 个月,36 个月以上更放心。
Q:指数为 0 是不是说明这段时间完全没搜索?不代表。指数是模型归零的结果,搜索量偏低时会被归零显示,它和"没人搜"是两件事。看到 0 先去核对原始口径,别直接当断点处理。

