1. 项目简介
SmartETL:一个简单实用、灵活可配、开箱即用的Python数据处理(ETL)框架,提供Wikidata / Wikipedia / GDELT等多种开源情报数据的处理流程; 支持大模型、API、常见文件、数据库等多种输入输出及转换处理,支撑各类数据集成接入、大数据处理、离线分析计算、AI智能分析、知识图谱构建等任务。项目内置50+常用流程、180+常用ETL算子、10+领域特色数据处理流程,覆盖常见数据处理需求。
项目源码已经开放在SmartETL,感兴趣的小伙伴可以直接拉取源码进行试用,并且这个项目我会持续丰富完善,也欢迎大家提出提意见或是直接提出数据处理需求,也可以一起来参与项目建设。
文本是《SmartETL:大模型赋能的开源情报数据处理框架》系列第五篇,介绍processor数据处理算子(T+L)设计。
2.Processor设计
SmartETL中数据处理(包括T和L两个阶段)节点统一设计为Processor,对应YAML流程配置文件中的processor
和nodes
默认节点类型,对应Python模块为wikidata_filter.iterator
。
采用面向对象的设计,定义了JsonIterator
这个抽象基类(abstract base class),设计如下(源码见https://github.com/ictchenbo/SmartETL/blob/main/wikidata_filter/iterator/base.py):
class JsonIterator:
"""流程处理算子(不包括数据加载)的基础接口"""
def _set(self, **kwargs):
"""设置组件参数,提供对象属性链式设置"""
for k, w in kwargs.items():
setattr(self, k, w)
return self
def _get(self, key: str):
"""获取组件参数"""
return getattr(self, key)
def on_start(self):
"""处理数据前,主要用于一些数据处理的准备工作,不应该用于具体数据处理"""
pass
def on_data(self, data: Any, *args):
"""处理数据的方法。根据实际需要重写此方法。"""
pass
def __process__(self, data: Any, *args):
"""内部调用的处理方法,先判断是否为None 否则调用on_data进行处理,普通节点的on_data方法不会接收到None"""
# print(f'{self.name}.__process__', data)
if data is not None:
if isinstance(data, Message):
if data.msg_type == 'end':
# print(f'{self.name} end')
pass
else:
self.on_data(data.data)
else:
return self.on_data(data)
def on_complete(self):
"""结束处理。主要用于数据处理结束后的清理工作,不应该用于具体数据处理"""
pass
@property
def name(self):
return self.__class__.__name__
def __str__(self):
return f"{self.name}"
__init__(self, *args, **kwargs)
构造方法,是YAML组件构造表达式中大部分情况就是对这个方法的调用,例如“Print”等价于“Print()”,就是调用“wikidata_filter.iterator.Print”class的__init__
方法。这里可以看到,如果组件的__init__
方法支持默认参数,对组件使用者将会非常便利。_set(self, **kwargs)
设置节点的属性,支持链式语法,方便在YAML组件构造表达式中调用。on_data(self, data: Any, *args)
定义数据处理方法,主要通过重写此方法实现具体数据处理逻辑。如果在数据处理流程中,某个Processor节点输出为None
,会停止消息流处理,因此不会调用此方法。__process__(self, data: Any, *args)
这是框架内部引擎直接调用的数据处理方法,与on_data
区别在于可以接收Message
类型消息和None
消息。默认处理逻辑为:如果data
为None
,则直接返回;否则如果data
为Message
类型且消息不为end
,则调用on_data
处理该消息包裹的具体消息(data.data
);否则如果data
不是Message
,则调用on_data
处理该消息;否则,直接返回。on_start(self)
节点初始化方法,比如创建数据库连接。在具体开发时,也可以在构造方法__init__
中进行节点初始化。on_complete(self)
数据处理完成后调用的方法,主要用于节点清理,比如关闭数据库连接、关闭文件等。_get(self, key: str)
获取节点的属性。@property name(self)
获取节点名称。__str__(self)
节点的字符串表示,可重写以提供更加友好的表示。
3.Processor体系
采用继承、封装和多态的面向对象设计思想进行Processor体系设计,实现大量算子的体系化组织和高效率扩展。
主要包括以下几个分支:
- 流程控制组件,目前包括
Chain
和Fork
,分别实现串行和并行处理逻辑,这是与流程执行引擎实现关系最紧密的组件,下一步扩展执行引擎、性能优化都需要从这类组件上下功夫。 Map
,实现数据的1->1转换处理。Map
自身需要提供一个mapper函数,支持行级或字段级的转换(根据是否提供key
参数确定),支持选择目标字段(target_key
,如果未提供,则与key值相同)。子类可通过实现__call__
方法,从而将自身作为mapper参数传递给Map构造方法。Map
子类主要包括:MapUtil
(通过提供任意的工具函数进行Map处理)、MapFill
(基于提供的dict进行source_key值查找并设置为target_key字段)、Format
(对字符串字段进行格式化)、FromJson
(从json字符串字段加载为json对象)、ToJson
(对任意对象序列化为json字符串)等。此外,单独提供一个Function
,可调用任意给定函数,实现数据处理。Flat
,实现数据的扁平化操作,即1->*处理模式。支持行级或字段级的Flat转换(根据是否提供key参数确定)。根据不同数据类型进行Flat,如果是数组、元组或set类型(主要应用场景),则将其每个元素作为一条单独的数据输出;如果是字典类型,则根据flat_mode配置,提供kv、key、value三种输出模式。Flat子类主要包括:FlatMap
(通过提供mapper函数获取需要Flat对象,而不是输入数据本身)、FlatProperty
(假设输入为dict,对其某个类型为dict的字段进行Flat,达到字段提升的效果)等。Filter
,实现数据的过滤,即1->0/1处理模式。对经过Filter的数据,只有符合某种条件才能输出,否则输出None
,达到数据截断或丢弃的目的。Filter主要子类包括:SimpleMatch
(基于简单属性匹配规则)、JsonPathMaptch
(基于jsonpath语法的匹配过滤)、WikidataMatch
(针对Wikidata的匹配过滤)、WhiteList
(白名单过滤机制,即仅在名单中的数据才保留)、BlackList
(黑名单过滤机制,即在名单中的数据就会被丢弃)、Distinct
(去重,基于本地缓存或基于Redis的缓存去重)、TakeN
/SkipN
/Sample
(基于采样的过滤)、FieldsExist
/FieldsNonEmpty
(字段存在/字段非空)、All
/Any
/Not
(组合过滤)等。ReduceBase
,对数据进行聚合、规约,即*->?处理模式。ReduceBase本身为抽象基类,具体功能为其子类实现,包括:Buffer
(缓冲,积攒一批数据后再集中输出)、Reduce
(规约,对一组数据根据提供的规约方法进行规约后输出)、Group
(分组,将具有相同属性的数据放在一起输出,可以看成一类特殊规约)。它们分别代表了一大类数据处理场景,其中Buffer
广泛用于数据输出(包括写文件、写数据库等)等Processor基类;Reduce
/Group
则类似SQL的group by
操作,可以紧跟Count
/Sum
/Max
/Min
/Mean
/Var
/Std
/Sample
/Head
/Tail
/OrderBy
/Distinct
等算子。DictProcessorBase
,处理dict数据的基类,子类on_data
方法的调用参数只需要考虑为dict的情况,从而减少类型检查代码。在实际应用中,为了统一,通常将处理数据都看成dict(或json对象)进行处理,因此DictProcessorBase分支将会非常庞大。包括(1)转换处理类:MapRules
(基于一种简单规则对dict/json进行转换)、MapMulti
(基于提供的mapper对多个字段进行转换处理)、MapKV
(将输入dict和K-V反转为V-K的dict);(2)选择投影类:Select
(类似SQL的select操作,支持基于点号分隔的嵌套字段)、SelectVal
(选择字段的值输出);(3)字段增删改类:AddFields
、ReplaceFields
、RenameFields
、MergeFields
、CopyFields
、InjectField
、ConcatFields
(拼接若干个字段);(4)模型调用类:LLM
(基于OpenAI兼容接口调用大模型获得模型输出结果)、Embed
(调用嵌入式模型进行嵌入向量生成);等等。- 输出/Load组件,实现数据输出到目标数据库系统,主要包括文件和数据库。其中文件内置提供了
Text
、Json
、CSV
三种格式,且支持开启gzip
压缩。数据库内置提供了MySQL
、MongoDB
、ElasticSearch
、ClickHouse
、Qdrant
、MinIO
。这类组件均继承自Buffer
组件,实现带缓冲输出。 - 业务组件,根据业务实际需要实现的组件,如
web.gdelt.Export
(实现GDELT数据下载、组合元信息)、web.polls.PollData
(实现对民调数据结构转换)、web.image.Download
(图片下载)、wikidata.*
(实现对wikidata的结构简化、信息融合、实体关系生成等)等。