文章目录
day04_匹配类标签
一、性别标签开发(掌握)
1、需求分析
1- 性别的四级标签ID=4
2- 读取标签配置数据,获得性别的四级标签rule规则
inType=Hive##nodes=up01:9083##table=dwd.dwd_mem_member_union_i##selectFields=zt_id,sex##range=all
业务数据存储介质类型inType: Hive数仓
业务数据存储介质连接信息nodes: up01:9083
业务数据具体存储的库和表信息table: dwd.dwd_mem_member_union_i
计算性别标签涉及的业务数据字段selectFields: zt_id,sex
计算性别标签涉及的业务数据范围range: all计算分析所有的数据
3- 对rule规则进行解析,得到类的实例对象
4- 根据解析后的rule规则,读取对应的业务数据
zt_id,sex
139040,2
196941,1
202025,0
5- 从标签配置数据中获取五级标签配置数据,pid=四级标签ID
id,name,rule
5,男,1
6,女,2
7,未知,0
6- 开始Spark代码,将业务数据与五级标签配置数据进行关联,给用户打上性别五级标签
7- 将标签结果数据输出到ElasticSearch的user_profile_tags索引中
如何给用户打上性别五级标签:
2、代码开发
from pyspark.sql import SparkSession
import os
import pyspark.sql.functions as F
from tags.utils.rule_parse_util import RuleParse
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'
if __name__ == '__main__':
# 1- 创建SparkSession对象
spark = SparkSession.builder\
.appName("sex_tag_v1")\
.master("local[*]") \
.config("spark.sql.shuffle.partitions",3)\
.config("spark.sql.warehouse.dir", "hdfs://up01:8020/user/hive/warehouse") \
.config("hive.metastore.uris", "thrift://up01:9083") \
.enableHiveSupport()\
.getOrCreate()
# 2- 性别的四级标签ID
four_tag_id = 4
# 3- 读取标签配置表数据
all_tag_df = spark.read.jdbc(
url="jdbc:mysql://192.168.88.166:3306/tags_info?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false",
table="tbl_basic_tag",
sql={'user': 'root', 'password': '123456'}
)
# 4- 读取对应的性别四级标签配置中的rule规则内容
rule_str = all_tag_df.where(f"id={four_tag_id}").first().rule
# 5- 解析rule规则,得到实例对象
rule_obj = RuleParse.parse_rule(rule_str)
# 6- 根据解析好的rule规则,读取对应的业务数据
hive_sql = "select " + rule_obj.selectFields + " from " + rule_obj.table + " where 1=1 "
business_df = spark.sql(hive_sql)
# 7- 根据四级标签ID,得到五级标签配置数据
five_tag_df = all_tag_df.where(f"pid={four_tag_id}").select("id","rule")
# 8- 将业务数据与五级标签配置数据进行关联,给用户打上五级标签
result_df = business_df.join(five_tag_df,how="left",on=business_df["sex"]==five_tag_df["rule"])\
.select(
business_df["zt_id"].alias("user_id"),
five_tag_df["id"].alias("tags_id_times")
)
result_df.show()
result_df.printSchema()
# 9- 将结果数据输出到ElasticSearch中
result_df.write.format("es").mode("append") \
.option("es.nodes", "192.168.88.166:9200") \
.option("es.resource", "user_profile_tags") \
.option("es.mapping.id", "user_id") \
.option("es.write.operation", "upsert") \
.save()
# 10- 释放资源
spark.stop()
运行结果截图:
二、Python基础回顾(复习)
1、闭包和装饰器
闭包的语法要求(必须要满足):
1- 函数嵌套定义
2- 外部函数返回内部函数的名称
举例:
def outer_func():
def inner_func():
pass
return inner_func
装饰器:
1- 作用:在不改变原始函数内容和函数调用的基础上,对函数功能进行增强
2- 格式要求:装饰器是一个特殊的闭包。在满足闭包语法的基础上,还需要在外部函数的形参这个地方,有且只能有一个形参,该形参用来接收被增强/修饰的函数
举例:
def outer_func(old_func):
def inner_func():
# 增强代码
result = old_func()
# 增强代码
return result
return inner_func
3- 通用装饰器
def outer_func(old_func):
def inner_func(*args, **kwargs):
# 增强代码
result = old_func(*args, **kwargs)
# 增强代码
return result
return inner_func
4- 装饰器本身需要参数
def wrapper_func(装饰器自己需要用的参数):
def outer_func(old_func):
def inner_func(*args, **kwargs):
# 增强代码
result = old_func(*args, **kwargs)
# 增强代码
return result
return inner_func
return outer_func
案例代码:
import time
def wrapper(nums):
def outer_fun(fun_name):
def inner_fun(*args,**kwargs):
start_time = time.time()
result = fun_name(*args,**kwargs)
cost_time = time.time() - start_time
print("函数运行耗时(秒)",round(cost_time,nums))
return result
return inner_fun
return outer_fun
@wrapper(3)
def myfun(start,end):
result = 0
for i in range(start,end):
result += i
return result
if __name__ == '__main__':
print(myfun(1, 1000000))
2、容器数据类型
- List
- 元素可以重复
- 数据有序。写入顺序和输出顺序是一致
- 数据可以修改
- 数据类型不要求统一。但是实际使用一般存放相同类型的数据
- 支持索引。也就是支持for和while循环遍历
- Tuple
- 数据可以重复
- 数据有序。写入顺序和输出顺序是一致
- 数据不可以修改
- 数据类型不要求统一。但是实际使用一般存放相同类型的数据
- 支持索引。也就是支持for和while循环遍历
- Dict字典
- key不可以重复,value没有任何要求
- 可以通过key来修改对应value的数据内容
- 不可变数据类型可以作为key。value没有任何要求
- 不支持索引。支持for循环遍历,但是不支持while循环
- Set集合
- 元素不可以重复。会自动进行数据去重
- 元素是无须的
- 数据可以修改
- 数据类型不要求统一。但是实际使用一般存放相同类型的数据
- 不支持索引。支持for循环遍历,但是不支持while循环
- string
- 数据可以重复
- 数据有序。写入顺序和输出顺序是一致
- 数据不可以修改
- 支持索引。也就是支持for和while循环遍历
- 可变和不可变数据类型:在内存地址值不变的情况下,是否允许修改对应的数据内容
- 可变数据类型:List、Set、Dict
- 不可变数据类型:int、float、bool、string、Tuple
3、面向对象
1- 面向对象和面向过程的区别
1.1- 编码方式: 拿到需求以后,面向过程需要将每一步都分析出来然后再写代码;面向对象先根据需求考虑需要涉及到多少个类,接着在写代码的过程中逐步细化类内部的细节
1.2- 适用场景:
中大型项目: 面向对象
中小型项目: 面向过程
2- 方法和属性的区别
方法: 类具备的功能。例如:吃喝拉撒
属性: 类具备的特征。例如:高矮胖瘦
3- 面向对象中方法的分类
3.1- 实例方法: 第一个参数是self,不需要我们传值,由Python底层自动进行传参。在类的内部一般是通过self调用;类的外部是通过类的实例对象进行调用。
3.2- 魔法方法: Python内部定义好的,并且方法名称的前后有两个下划线。魔法方法一般会被自动调用
__init__: 用来对类的实例对象进行初始化操作,在创建类的实例对象的时候自动被调用
__str__: 通过print打印实例对象的时候自动被调用。必须要有返回值,返回值类型必须是string
__del__: 程序运行结束或者del删除对象的时候被自动调用。一般是用来资源回收
3.3- 类方法: 定义类方法的时候需要在上面增加@classmethod装饰器。第一个参数是cls,不需要我们传值,由Python底层自动进行传参。类的内部和外部使用【类名称.类方法】。
3.4- 静态方法: 定义静态方法的时候需要在上面增加@staticmethod装饰器。类的内部和外部使用【类名称.类方法】。
4- 权限
4.1- 私有权限: 在方法和属性的名称前面加两个下划线就能够变成私有权限
4.1.1- 分类: 私有方法和私有属性
4.1.2- 访问:
a、类的内部:self.私有方法、self.私有属性
b、类的外部:正常情况外部不能使用。但是可以通过【实例名称._类名__私有方法/属性】暴力访问
4.2- 公共权限: 方法和属性正常取名,前面没有两个下划线
4.2.1- 分类: 公共方法和公共属性
4.2.2- 访问:
a、类的内部:self.公共方法、self.公共属性
b、类的外部:实例名称.方法/属性
5- 属性
5.1- 实例属性: 属于每个实例对象。在__init__魔法方法中定义和初始化
5.2- 类属性: 属于类的,每个实例对象都共有的属性。在类的里面方法的外面定义。
6- 继承
6.1- 继承的分类:
6.1.1- 单继承: class B(A)
6.1.2- 多继承: class C(A,B)
6.1.3- 多层继承: class B(A)、class C(B)
6.2- 子类能够继承父类的公共方法。继承以后能够重写父类的方法
class Test:
def __init__(self):
self.__name = "张三"
def __fun(self):
print("这是私有方法")
if __name__ == '__main__':
obj = Test()
# 类的外部不能直接方法私有权限
# print(obj.__name)
# print(obj.__fun())
# 但是可以通过暴力的形式方法,语法:类的实例对象名称._类名称__私有属性/私有方法
print(obj._Test__name)
obj._Test__fun()
三、匹配类标签代码重构(掌握)
1、为什么要重构
一般在大公司中,会有高级开发/架构师级别的人在项目开发初始阶段,从上帝视角对整个项目进行规划,抽取封装一些公共代码,形成整个项目的大体框架,如项目中的公共模块,工具类等……或者在项目开发初始阶段没有做合理的系统的规划,只是完成了基本的功能。
重构的主要目标和原则包括:
1- 提高代码的可读性: 通过重构,代码的逻辑和结构变得更加直观,方便其他开发人员理解和维护。
2- 减少代码重复: 通过识别和消除重复的代码段,减少冗余代码,使代码更加简洁。
3- 提高代码的可维护性: 重构后的代码结构更加清晰,便于修改和扩展,从而减少维护成本。
4- 提高代码的性能: 虽然重构的主要目的是改善代码质量,但有时通过优化代码结构也能提升性能。
重构思路
标签实施流程:
一样 1- 创建Spark执行环境,也就是创建SparkSession顶级对象
一样 2- 读取标签配置数据: 从MySQL表中读取4级和5级标签配置内容
一样 3- 解析rule规则: 从4级标签中获取rule规则,并且进行规则解析,解析得到业务数据存储位置信息
一样 4- 读取Hive中的业务数据: 根据rule规则读取Hive中存储的业务数据
一样 5- 过滤5级标签: 从4级和5级标签内容中过滤出5级标签内容,也就是pid=4级标签的ID
不一样 6- 标签计算: 给用户打上5级标签。也就是将业务数据和5级标签关联起来
一样 7- 结果数据存储: 将用户标签结果数据存储到ES中
一样 8- 释放资源
2、基类代码
- 面向过程
from pyspark.sql import SparkSession
import os
import pyspark.sql.functions as F
from tags.utils.rule_parse_util import RuleParse
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'
# 1- 创建SparkSession对象
def create_spark(app_name,partitions):
spark = SparkSession.builder \
.appName(app_name) \
.master("local[*]") \
.config("spark.sql.shuffle.partitions", partitions) \
.config("spark.sql.warehouse.dir", "hdfs://up01:8020/user/hive/warehouse") \
.config("hive.metastore.uris", "thrift://up01:9083") \
.enableHiveSupport() \
.getOrCreate()
return spark
# 3- 读取标签配置表数据
def read_all_tag(spark):
all_tag_df = spark.read.jdbc(
url="jdbc:mysql://192.168.88.166:3306/tags_info?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false",
table="tbl_basic_tag",
sql={'user': 'root', 'password': '123456'}
)
return all_tag_df
# 4- 读取对应的性别四级标签配置中的rule规则内容
# 5- 解析rule规则,得到实例对象
def read_and_parse_rule(all_tag_df,four_tag_id):
rule_str = all_tag_df.where(f"id={four_tag_id}").first().rule
rule_obj = RuleParse.parse_rule(rule_str)
return rule_obj
# 6- 根据解析好的rule规则,读取对应的业务数据
def read_hive_data(rule_obj,spark):
hive_sql = "select " + rule_obj.selectFields + " from " + rule_obj.table + " where 1=1 "
business_df = spark.sql(hive_sql)
return business_df
# 7- 根据四级标签ID,得到五级标签配置数据
def read_five_tag(all_tag_df,four_tag_id):
five_tag_df = all_tag_df.where(f"pid={four_tag_id}").select("id", "rule")
return five_tag_df
# 9- 将结果数据输出到ElasticSearch中
def write_2_es(result_df):
result_df.write.format("es").mode("append") \
.option("es.nodes", "192.168.88.166:9200") \
.option("es.resource", "user_profile_tags") \
.option("es.mapping.id", "user_id") \
.option("es.write.operation", "upsert") \
.save()
if __name__ == '__main__':
# 1- 创建SparkSession对象
spark = create_spark("sex_tag_v1",3)
# 2- 性别的四级标签ID
four_tag_id = 4
# 3- 读取标签配置表数据
all_tag_df = read_all_tag(spark)
# 4- 读取对应的性别四级标签配置中的rule规则内容
# 5- 解析rule规则,得到实例对象
rule_obj = read_and_parse_rule(all_tag_df,four_tag_id)
# 6- 根据解析好的rule规则,读取对应的业务数据
business_df = read_hive_data(rule_obj,spark)
# 7- 根据四级标签ID,得到五级标签配置数据
five_tag_df = read_five_tag(all_tag_df,four_tag_id)
# 8- 将业务数据与五级标签配置数据进行关联,给用户打上五级标签
result_df = business_df.join(five_tag_df,how="left",on=business_df["sex"]==five_tag_df["rule"])\
.select(
business_df["zt_id"].alias("user_id"),
five_tag_df["id"].alias("tags_id_times")
)
result_df.show()
result_df.printSchema()
# 9- 将结果数据输出到ElasticSearch中
write_2_es(result_df)
# 10- 释放资源
spark.stop()
- 面向对象
from pyspark.sql import SparkSession
import os
import pyspark.sql.functions as F
from tags.utils.rule_parse_util import RuleParse
import abc
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'
# metaclass=abc.ABCMeta这种写法不是继承的意思,而是表示该类是一个抽象类
class AbstractTagBase(metaclass=abc.ABCMeta):
# 1- 创建SparkSession对象
def create_spark(self, app_name, partitions):
spark = SparkSession.builder \
.appName(app_name) \
.master("local[*]") \
.config("spark.sql.shuffle.partitions", partitions) \
.config("spark.sql.warehouse.dir", "hdfs://up01:8020/user/hive/warehouse") \
.config("hive.metastore.uris", "thrift://up01:9083") \
.enableHiveSupport() \
.getOrCreate()
return spark
# 3- 读取标签配置表数据
def read_all_tag(self, spark):
all_tag_df = spark.read.jdbc(
url="jdbc:mysql://192.168.88.166:3306/tags_info?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false",
table="tbl_basic_tag",
sql={'user': 'root', 'password': '123456'}
)
return all_tag_df
# 4- 读取对应的性别四级标签配置中的rule规则内容
# 5- 解析rule规则,得到实例对象
def read_and_parse_rule(self, all_tag_df, four_tag_id):
rule_str = all_tag_df.where(f"id={four_tag_id}").first().rule
rule_obj = RuleParse.parse_rule(rule_str)
return rule_obj
# 6- 根据解析好的rule规则,读取对应的业务数据
def read_hive_data(self, rule_obj, spark):
hive_sql = "select " + rule_obj.selectFields + " from " + rule_obj.table + " where 1=1 "
business_df = spark.sql(hive_sql)
return business_df
# 7- 根据四级标签ID,得到五级标签配置数据
def read_five_tag(self, all_tag_df, four_tag_id):
five_tag_df = all_tag_df.where(f"pid={four_tag_id}").select("id", "rule")
return five_tag_df
# 8- 打标签的抽象方法,由子类继承以后进行具体实现
"""
@abc.abstractmethod标记一个方法为抽象方法。能够实现强制要求子类进行方法的具体实现
"""
@abc.abstractmethod
def mark_tag(self,business_df, five_tag_df):
pass
# 9- 将结果数据输出到ElasticSearch中
def write_2_es(self, result_df):
result_df.write.format("es").mode("append") \
.option("es.nodes", "192.168.88.166:9200") \
.option("es.resource", "user_profile_tags") \
.option("es.mapping.id", "user_id") \
.option("es.write.operation", "upsert") \
.save()
def execute(self,app_name,partitions,four_tag_id):
# 1- 创建SparkSession对象
spark = self.create_spark(app_name, partitions)
# 3- 读取标签配置表数据
all_tag_df = self.read_all_tag(spark)
# 4- 读取对应的性别四级标签配置中的rule规则内容
# 5- 解析rule规则,得到实例对象
rule_obj = self.read_and_parse_rule(all_tag_df, four_tag_id)
# 6- 根据解析好的rule规则,读取对应的业务数据
business_df = self.read_hive_data(rule_obj, spark)
# 7- 根据四级标签ID,得到五级标签配置数据
five_tag_df = self.read_five_tag(all_tag_df, four_tag_id)
# 8- 将业务数据与五级标签配置数据进行关联,给用户打上五级标签
result_df = self.mark_tag(business_df,five_tag_df)
# 9- 将结果数据输出到ElasticSearch中
self.write_2_es(result_df)
# 10- 释放资源
spark.stop()
3、重构代码
3.1 年龄段标签重构
import os
import pyspark.sql.functions as F
from tags.base.abstract_tag_base import AbstractTagBase
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'
class AgeTag(AbstractTagBase):
def mark_tag(self,business_df, five_tag_df):
# 8.1- 将业务数据中的birthday_date删除中横杠
new_business_df = business_df.withColumn("birthday_date", F.regexp_replace("birthday_date", "-", ""))
# new_business_df.show()
# 8.2- 将五级标签配置数据中的rule规则按照中横杠切分得到start和end
new_five_tag_df = five_tag_df.select(
"id",
F.split("rule", "-")[0].alias("start"),
F.split("rule", "-")[1].alias("end")
)
# new_five_tag_df.show()
# 8.3- 将处理后的数据进行join关联,同时进行数据过滤,给用户打上五级标签
result_df = new_business_df.join(new_five_tag_df).where(
"birthday_date between start and end"
).select(
new_business_df["zt_id"].alias("user_id"),
new_five_tag_df["id"].alias("tags_id_times")
)
return result_df
if __name__ == '__main__':
tag_obj = AgeTag()
tag_obj.execute("age_tag",3,15)
3.2 性别标签重构
import os
from tags.base.abstract_tag_base import AbstractTagBase
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'
class SexTag(AbstractTagBase):
def mark_tag(self,business_df, five_tag_df):
result_df = business_df.join(five_tag_df, how="left", on=business_df["sex"] == five_tag_df["rule"]) \
.select(
business_df["zt_id"].alias("user_id"),
five_tag_df["id"].alias("tags_id_times")
)
return result_df
if __name__ == '__main__':
tag_obj = SexTag()
tag_obj.execute("sex_tag",3,4)
1、开发国籍、政治面貌、婚姻状况等标签
需要注意的是数据的处理:国籍的数据是中文,跟标签的rule数据类型不同;婚姻状况数据中有为空的需要处理,可以作为未婚来处理;政治面貌数据中有4种类型“1团员、2党员、3群众、4其他党派”,其中团员属于标签中的群众。
2、尝试进行新旧标签合并功能的开发
1- 在父类代码中将之前存储的用户画像标签结果数据读取出来
2- 要基于pandas写UDF实现合并新旧标签的功能
3-将合并后的结果输出到ES中