导读 introduction
在作弊手段日益隐蔽和复杂的背景下,单纯依赖在线或实时风控已难以满足深度治理需求。本文系统介绍了一套基于 Spark 的配置化离线反作弊挖掘框架,重点解析其 Extract、Accumulate、Join、Policy 四大核心模块,以及"视图构建""动态 SQL 生成""多阶特征计算""滑动窗口"等关键能力。该框架支持全量历史重算与大规模 Shuffle 计算,通过高度配置化设计,将字段抽取、特征定义、策略判定彻底从代码中解耦,实现策略快速迭代与低成本上线。同时结合数据倾斜治理、列裁剪优化等工程实践,大幅提升稳定性与性能,成为风控体系的重要计算底座。
01 简介
在互联网业务高速发展的大背景下,作弊手段层出不穷,从恶意点击、流量造假,到批量刷单、黑产"薅羊毛",手法不断翻新、隐蔽性持续增强。这些行为不仅侵蚀了平台的公平秩序,更直接带来显著的经济损失,并严重损害广告主利益和普通用户的体验与信任。因此,全方位、持续演进的反作弊能力已成为互联网产品生态稳定运行的关键基石。
百度基于以上问题构建了一套系统化的企业级反作弊系统,根据时效性和业务需求分为三类:在线反作弊、实时反作弊与离线反作弊。这三类反作弊能力相互补充,共同构建起完整的风控防线,但在防护策略、检测深度和业务价值上各有侧重。
在线反作弊主要负责毫秒级别的请求风险判定,适用于简单规则和轻量级指标,例如从请求头部字段、访问频率等维度快速判断风险,并结合 Redis 等缓存计算实现即时响应。这类机制非常适合于即时性要求极高的场景,例如登录请求拦截或简单阈值规则拦截,但受限于可实时访问的数据维度较少。
实时反作弊 在此基础上,通过流式计算分析序列行为、业务上下文和多维特征,在秒级甚至分钟级实现更加精准的策略判定。实时系统能够响应更复杂的行为模式,例如账户连续异常操作、设备跨地域跳变等行为,兼具时效性与一定程度的特征深度,是在线与离线反作弊之间的关键桥梁。具体介绍见基于Flink的配置化实时反作弊系统
然而,在整个百度反作弊体系中,离线反作弊系统的战略价值与日俱上,是构建高精度模型、深度分析行为模式和提升整体风控能力的"底座" 。
与在线和实时系统相比,离线反作弊不受时效性的约束,可以充分利用完整历史数据进行大规模的批量分析与深度挖掘。其价值主要体现在以下几个方面:
- 全面的数据视图:离线系统可以访问业务全量日志、用户历史轨迹、跨周期行为等丰富维度的数据,这些数据在在线场景中往往无法实时获取或难以完成整合。
- 深度行为建模:通过对长期行为序列的分析,可以发现复杂的作弊模式,例如跨账号关联、长期周期异常趋势、人机行为判别等,这些模式在短周期内往往难以捕捉。
- 特征工程与策略优化:离线挖掘计算出的高维特征是构建机器学习模型的基础,也是实时风控策略得以优化的重要来源。无论是统计类指标、聚合行为分布还是时序特征,这些信号都能够显著提升模型精度。
- 黑产库与历史知识积累:离线分析能够构建不断增长的"黑产行为库"和风险特征库,支持跨业务线共享和复用。这种长期积累的"经验库"是在线/实时系统难以替代的。
正因如此,百度在反作弊领域投入多年经验,构建了高效的离线挖掘框架,用于批量处理用户行为日志、提取高维特征、训练模型并验证策略,为线上策略提供长期优化与精准判定的动力支持,使整套反作弊体系具备更强的防护能力和持续学习能力。本文介绍该离线挖掘框架的整体架构和设计亮点,并深度解读特征计算链路、性能优化实践以及配置化模块化能力,展示其在刷量识别、账号行为分析、广告作弊治理等场景中的工程价值。
02 离线挖掘框架解决的核心问题
2.1 成本和实现平衡
流式实现特征计算往往需要更高的计算成本,而对于大部分反作弊策略的实现并不需要极高的时效性要求,离线挖掘框架恰恰是解决流式运行高成本,高压力和运行时效进行平衡的媒介,小时级别的产出已可满足大部分业务需求。
2.2 全量历史重算能力和大规模Shuffle
离线的核心优势是:强全量能力 + 强历史回溯能力 + 强复杂聚合能力。
全量历史重算能力:
- 可以直接扫描全量历史数据(天级、月级、年级)
- 支持特征逻辑变更后的全量重算
- 支持复杂回溯计算
大规模Shuffle:
- 可以做大规模 Shuffle
- 支持复杂 SQL(多层嵌套、窗口、分组)
- 支持大表与大表 Join
2.3 多场景数据源和输出灵活对接
离线数据往往面临各种数据格式、表等复杂多样的数据源及灵活多变的输出格式。
- 数据源类型:目前我们的框架现有数据源支持Turing表, UDW(hive)表, AFS(Parquet, CSV, Txt, PB)文件、用户自定义SQL等,并可以灵活增加wget接入数据源等功能。
- 输出类型:对于输出也灵活实现了Turing表, UDW(hive)表, AFS(Parquet, CSV, Txt, PB)文件等格式功能,并可以增加输出至clickhouse、doris等存储媒介便于监控分析。
- 多数据源输入:实现多种数据源同时输入解析,并支持对不同数据源分别清洗过滤,并支持对各数据源单独筛选 & 分区, 实现对不同数据的灵活操控。
03 反作弊离线挖掘框架介绍
3.1 离线挖掘整体框架
百度离线挖掘框架使用生效流程图如下:

上图展示了离线挖掘框架在整个反作弊系统中的使用流程图,即框架在反作弊流程中的使用过程:
- 用户在配置平台配置 数据源、特征、策略、输出维度等各项配置conf文件。
- 用户通过配置平台打conf包到对应afs地址, 在TDS平台中筛选集群信息、资源配置等、读取conf配置文件, 并手动调起spark任务。
- 离线挖掘框架会加载配置信息, 运行spark 任务, 任务结束后将结果输出到 AFS。
- 用户使用一脉、Jupter等写ETL 任务评估策略是否符合预期, 若符合预期, 则将特征、策略配置上线, 否则修改特征、策略配置等重新运行。
具体离线挖掘框架流程图:

上图展示了离线挖掘框架的整体流程图,分为 extractor 模块、accmulator 模块、joiner 模块、policy 模块等。
Extract (抽取)模块:
抽取(Extractor)模块是离线挖掘框架的数据入口与标准化核心,负责从原始日志或明细表中读取多源行为数据,按照既定 schema 进行字段筛选、类型转换、脏数据过滤和统一格式映射,将分散、异构的原始数据加工为结构清晰、字段规范、可计算的标准行为数据集;同时结合配置文件(如特征或字典配置)完成基础标签补充与维度对齐,为后续的视图构建与聚合计算提供稳定、统一的数据基础。

这张图展示了抽取模块实现的功能:
- 输入数据:对原始输入数据源进行解析(包括Hive表,PB日志,parquet数据解析等)
- 解析特征配置文件:特征fea_001类型为segment(统计数据),维度为query,条件为:app_id=5&&city='北京',即统计符合条件在app_id=5&&city='北京'的每个query的数量。同理特征fea_002为统计符合条件product_id不空的clkip的数量。
- 自定义字段:用户可以根据udf函数自定义所需要的字段。
- 结果数据:从日志中解析抽取出所有特征中所需要的字段,以图中示例结果为:fea_id,log_timestamp,query,app_id,agent_id,baiduid,product_id,...,其中log_timestamp为必输出数据。
除了 spark sql 支持的所有原生 functions 之外,结合业务实际使用场景,还支持了 多个自定义数据处理算子,并支持用户自定义udf扩展。

Accmulate (聚合)模块:
Accumulator(聚合)模块是整个系统的"计算引擎",负责将海量的原始日志转化为具有统计意义的反作弊特征。基于指定维度和时间窗口对行为数据进行结构化聚合计算,将原始事件流转化为可用于策略判断和模型输入的指标特征。它支持多种聚合算子(如 count、sum、distinct 等)、条件过滤统计以及多维度分组能力,并通过状态管理机制维护窗口内历史数据,实现连续、可配置的特征生成。从工程视角来看,Accumulate 本质上是一个配置驱动的多维度窗口化统计计算模块,是连接原始行为数据与风险决策逻辑之间的关键桥梁。
以下是该模块的详细执行流程及功能解析:

核心流程图解析
- 数据准备:接收来自 Extractor 的标准化数据,并根据 feature.yml 加载特征定义。
- 视图构建:这是 Themis 框架的特色,通过 View 和 DataView 概念,将数据按不同的维度(如 baiduid、IP、cookie)进行切分。
- 动态 SQL 生成:框架不会硬编码聚合逻辑,而是根据配置动态拼接 Spark SQL 语句(如 SUM、COUNT、DISTINCT)。
- 时间窗口:根据配置文件中的配置的时间窗口进行划分时间段
关键技术特性
- 视图构建 :视图构建,将同一批行为数据转换为带有"统计主体标识"的统一结构,从而支持多维度特征的动态聚合,是面向特征计算的维度抽象层。
在反作弊或行为分析场景中,同一条行为数据可以被多种"主体"统计,例如一条登录行为:
user_id,device_id,ip,cookie,ts
这条数据可以:统计到 user 维度、统计到 device 维度、统计到 ip 维度、统计到 cookie 维度,如果直接写 SQL 聚合,你需要:group by user_id,group by device_id,group by ip,... 。随着维度增加,代码会爆炸式增长。于是框架引入一个抽象,先构建一个逻辑视图,再根据视图去做聚合。
视图构建做了三件事:
- 维度声明:将原始数据按指定字段组合成不同"统计视角",这相当于提前确定这个特征是围绕谁统计的?
- 维度映射:对应维度,记录对应的必要值,例如:(IP具体值,特征id)。
- 维度参与聚合:不同统计维度通过 view_name / view_value 实现逻辑隔离。
- 多阶特征计算:随着市场作弊手段的不断提高,普通的一阶策略已经无法识别潜藏的作弊数据,需要更高阶如三阶特征的策略来判定,并便于后期策略的多指标分析。
逻辑: 有些计费名(cntname)下不同的广告位区别很大,需要先算个tu维度的特征,然后tu维度又要先算下面的异常用户占比,就有了这个三阶特征。
例如:
- 第一层为sn维度的普通比例特征,sn维度ip去重个数除以点击量的比例。
- 第二层为tu维度,第一层的比例特征大于0.8的sn对应点击占tu全量点击的比例。
- 第三层为计费名维度,第二层的比例特征大于0.4的tu对应点击占计费名全量点击的比例。
策略依赖的最终特征为计费名维度异常tu点击的比例,即第三层特征。
- 数据倾斜治理:在聚合过程中,框架会根据配置文件设定开启/不开启识别热点 Key(如超大流量的 IP),广播热点数据,防止任务长尾,具体见4.2。
目前框架能够实现通用特征算子的新增和管理,目前已经支持的抽象化通用特征算子有以下 14 种:

图中时间窗口windows逻辑解释:
在配置文件feature.yaml 中每个特征配置的字段

支持大数据处理中经典的滚动窗口和滑动窗口模式。
- 滚动窗口定义:滚动窗口将每个元素指定给指定窗口大小的窗口。滚动窗口具有固定大小,且不重叠。例如,指定一个大小为 5 分钟的滚动窗口。在这种情况下,将每隔 5 分钟开启一个新的窗口,其中每一条数都会划分到唯一一个 5 分钟的窗口中,如下图所示。

- 滑动窗口定义:滑动窗口也是将元素指定给固定长度的窗口。与滚动窗口功能一样,也有窗口大小的概念。不一样的地方在于,滑动窗口有另一个参数控制窗口计算的频率(滑动窗口滑动的步长)。因此,如果滑动的步长小于窗口大小,则滑动窗口之间每个窗口是可以重叠。在这种情况下,一条数据就会分配到多个窗口当中。举例,有 10 分钟大小的窗口,滑动步长为 5 分钟。这样,每 5 分钟会划分一次窗口,这个窗口包含的数据是过去 10 分钟内的数据,如下图所示。

Join (关联)模块:
Join(关联)模块是离线挖掘框架中的数据整合层,负责将来自不同视图或不同计算阶段产出的特征结果进行按键对齐与多维关联,通过统一主键(如 user_id、device_id、ip 等)将分散的聚合结果横向拼接成完整的特征宽表;同时处理字段冲突、空值补齐和粒度对齐等问题,确保不同维度、不同时间窗口的统计指标能够在同一维度下合并输出,为后续策略判定提供结构化综合特征数据集。具体是将抽取(Extract)模块与特征计算(Accmulate)模块数据关联, 并以logid进行Group By, 得到PV粒度全量数据, 将特征计算结果拼回各日志中,得到output2 结果 (产出为: log+ feature)。

上图展示join模块的基本逻辑,即将特征聚合模块结果使用logid,拼接到原始日志中,使得抽取模块每条日志拼接到自己所命中的所有特征
- 对特征聚合模块(Accmulate)每条结果增加logid字段。
- 对特征聚合模块进行logid聚合,多个特征结果聚合到一条logid中。
- 抽取模块(Extract) 使用logid,Left join关联logid聚合后的特征聚合模块数据,得到joiner结果。
Policy (策略判定)模块:
Policy(策略判定)模块是离线挖掘框架中承接特征结果并输出最终风险结论的决策核心,负责将聚合产出的多维特征输入规则引擎或策略配置体系,根据预设阈值、组合条件与优先级逻辑进行匹配与计算,生成风险标签、命中规则、风险等级或处置建议;同时支持策略可配置化与版本管理,使风控逻辑能够在不改动底层计算代码的情况下灵活调整,实现特征到业务决策的闭环落地。该模块解析配置的策略文件policy.yaml, 根据policy_id 对 每条日志命中的features 进行策略判定, 输出最终结果,得到output3 结果 (产出为: log + feature + policy)。

这张图展示了反作弊规则的判定流程:
1.输入数据 :每条日志包含多个字段,包括基础字段 (如IP、手机号、UID等)、计算得到的特征(如统计特征fea1、fea2等)。
2.策略判定:系统基于预设的反作弊规则,对各字段、特征。例如,规则1要求【fea_001 > 100 && fea_002 < 10】,规则2要求 【IP like '192.%' && fea_002 > 100】。多个规则都会执行判定逻辑,判断是否命中。
3.结果输出 :最终的PV数据会带上反作弊命中结果。例如,在示例中,该PV数据命中了policy_002,表明该行为可能存在风险。
以上就是策略配置的所有介绍,通过配置化管理字段、特征、词表、模型和规则,反作弊系统能够快速响应业务需求,灵活调整检测逻辑。同时,配置化设计大幅降低了开发部署成本,提高了策略迭代效率。
3.2 流程汇总
以上3.1介绍了离线挖掘框架各个模块实现的功能,代码实现以scala的dataframe容器作为各个模块之间数据传输的媒介,此处以dataframe的计算步骤来汇总介绍框架是如何进行数据传输。

04 离线挖掘框架设计亮点
4.1 模块化工程架构思想
框架整个代码实现力求模块化、轻量化;便于并行开发和测试,对后期维护升级铺平渠道。

以上图为工程实现图,步骤解释:
-
通过TDS/spark-submit提交spark job
-
runner调用context的init()方法,进行框架配置任务初始化
-
init()过程中调用ConfLoader和DictLoader加载配置文件、词表,以及注册udf等等初始化操作
-
init()返回封装好的context对象
5、6、7、8、执行各模块,将计算结果保存至context
9.根据配置的round轮数,输出对应结果的df
从运行图可以看到,这套离线反作弊挖掘框架并不是简单的"Spark 作业集合",而是一个具备完整工程设计理念的 可编排计算引擎 。其核心设计思想体现在四个方面:统一调度中枢、数据上下文抽象、算子标准化编排、配置驱动解耦。
- 统一调度中枢:构建"作业引擎"而非脚本集合
框架以 OfflineThemisRunner 作为唯一入口,负责生命周期管理、流程调度和执行编排。所有模块均由 Runner 驱动执行,而非模块间直接调用。体现"控制流集中管理,业务逻辑分散执行"。
工程优势:
- 统一异常处理
- 执行流程清晰、可追踪
- 支持任务模板化和标准化运行
- Context 抽象:解耦控制流与数据流
整个计算链路通过 Context 进行数据承载。各算子只与 Context 交互,而不直接依赖其他算子。
工程优势:
- 消除模块间的强耦合
- 实现数据语义统一管理
- 支持中间结果复用与调试
- 允许执行顺序灵活调整
从架构角度看,Context 是框架的"数据总线",将数据流从算子依赖关系中剥离出来,使系统具备真正的模块化能力。
- 算子标准化:构建可组合的计算流水线
框架将特征计算拆分为四类标准算子:Extractor(抽取)、Accumulator(聚合)、Joiner(拼接)、Policy(过滤)
所有算子遵循统一接口规范(run(context)),输入输出标准化,将复杂业务逻辑抽象为标准化计算单元
工程价值在于:
- 新特征开发只需实现算子接口
- 降低复杂链路的维护成本
- 便于统一优化与性能调优
- 配置驱动:将策略从代码中剥离
通过配置来驱动计算流程和策略逻辑。代码负责能力,配置负责策略。
具体配置功能见4.3
4.2 运行优化
1、解决数据倾斜
在Accumulate特征聚合阶段,使用到groupby进行聚合操作,如果热key数据量大的情况下导致单个 Task 处理大量数据,即会出现严重的数据倾斜,甚至导致 OOM / 失败重算。

以上图的优化思路:采样识别 + 拆分 Join (Skew Join)
- 首先用 Spark API 的 sample() 统计左表 key 出现频次,先采样找出热点(大 Key)
- 将左表按是否热点拆分
- 将右表也对应拆分
- 对热点 Key 用广播 Join ,避免 Shuffle
- 非热点 Key 按常规 Join
- 最后union all两份数据得到最终结果
对于采样解决数据倾斜已经配置化,用户可根据实际需求自定义配置是否启动优化和采样的比例,具体见4.3
2、列裁剪优化
Join拼接模块阶段,在优化前使用炸开后的Extract数据 Left Join Agg结果(view_name,view_value,window_start<=time_col<=window_end), 获取结果数据(Joiner), 结果数据包含(neededViews + agg聚合结果)。
我们假设:
1). 抽取出的Extract中含100个neededViews字段
2). feature.yml中feaList包含了80个featureId
那么就会出现以下情况:
1). 假设某条数据命中了50个feature条件,那么这条数据的聚合结果就有50条
2). 对Extract进行爆炸,也会爆成50条
注:
1). 以上方式使用Extract Left Join Agg结果时,每条数据会被扩充几十甚至上百倍,若每条Extract数据字段较多,则会造成很大的数据冗余,这些数据并不参与计算,浪费计算资源。
2). 因此再通过此方法进行group by聚合操作,浪费了很多不必要的内存,很容易发生数据倾斜,计算速度也会很慢。

以上图为列裁剪后的优化,优化思路为:
其实优化前第一步的操作就是为了将logid赋值到每一条ACC特征计算结果上,那样接下来才能进行group by logid操作。
-
我们先对抽取模块结果列裁剪logid和关联键的hash()值,和特征计算模块同样的关联键的hash()进行join。
-
再对特征计算结果进行group by logid操作,就能减轻许多计算压力。
-
最后用Extract Left Join第2步的结果即可。
综上,经过列裁剪及聚合下沉操作后,实际工程速度在列数较多场景下均提升60%以上,并有效防止OOM,降低任务失败率。
4.3 配置化
为了满足反作弊策略快速上线、精细化模拟验证和灵活联调等高频迭代需求,我们的实时反作弊系统采用了高度配置化驱动架构,并将所有配置集中托管平台上进行统一管理。
在这一体系下,策略和计算逻辑不再硬编码到程序中,而是通过规范的配置文件描述出来,从字段抽取、特征定义、规则判定到结果产出,每一个步骤都可以通过配置完成。策略开发人员只需在平台上配置好各项参数,系统即可自动生成对应的作业,并支持一键打包和上线执行,大幅缩短了业务上线周期,降低了对底层框架开发的依赖。

策略配置主要由以下几类配置模块组成:
主配置:全局环境配置,这是框架的主配置文件,定义了任务运行的基础环境和全局参数,控制任务的运行模式、资源分配和全局开关。
- 输入输出:该配置决定了框架的输入地址、输入格式、输出地址、输出格式、控制框架需要的输出阶段等,例如round1,round2,round3。
- 优化:还可在此配置中配置是否开启抽样优化及抽样的比例等。
- udf自定义函数:用户可以自定义udf函数。
字段配置:负责将各种来源、各种格式的原始日志映射为框架可识别的标准字段。我们将字段抽取逻辑进行了配置化抽象,策略开发人员使用类似于写sql的方式即可完成简单字段的etl逻辑的开发,如常见的json字段抽取,字符串处理,反作弊内部的常用UDF等,配置能覆盖大部分字段抽取。根据抽取方式不同分为:
- 基础字段:直接从原始数据流中提取的字段,例如设备 ID、用户 ID 等。
- 二次计算字段:简单的字段转换逻辑(如 IP 转地域、UA 解析)。
- 维表字段:通过查询词表映射关系获得的字段,例如黑名单匹配结果、分类标签等。
特征配置:特征是策略的重要判定依据,定义了如何从标准字段中计算出用于反作弊判定的统计特征。特征配置包括以下几个关键方面:
- 特征类型:数据的聚合方式,如sum、count、distinct等。
- 窗口信息:设置聚合特征的时间窗口范围和窗口形式,时间范围如:1 小时、1天等,窗口形式如:滑动窗口、滚动窗口等。
- 特征维度:特征的聚合维度,如用户、设备、IP 地址等。
词表配置:词表通常是历史已知的黑名单、字段映射(如ip映射城市)等固定维表信息,在数据进入引擎之前,利用词表进行初步的"脏数据"清洗或黑名单过滤,提供外部参考数据,用于过滤或打标。配置内容需包括以下几个方面:
- 词表路径:指定词表的存储位置,支持文件路径或分布式存储地址。
- 词表类型:支持多种形式的词表,包括集合(set)、键值对映射(kv)、正则表达式(regex)等。
策略配置:规则配置决定了作弊行为的最终判定规则和处置方式,组合特征,输出最终的作弊名单或风险评分:
- 策略判定阈值:定义触发策略的条件,例如基础字段匹配、词表匹配、风险评分的阈值、特征累积阈值、模型打分阈值等。
- 策略判黑等级:设定风险等级,区分低、中、高风险及对应的处置措施。
以上总结配置文件的各个功能如下:

05 总结
本文介绍了基于spark 的离线反作弊挖掘框架,围绕解决的基本问题、工程设计亮点等展开。通过特征计算和配置化管理,提升了反作弊系统的检测效率和稳定性。展望未来,离线反作弊挖掘框架将持续演进,与更多智能算法、大模型和业务系统深度融合,不断完善检测能力和可用性。借助持续优化的特征计算与策略模块,此框架将为百度生态提供更加坚实的反作弊保障。