【用户画像(四)】封装基类分析类标签计算(销售额,支付方式)

封装基类

代码的重构

代码重构是指对现有代码进行修改和优化,以改善代码的质量、可读性、可维护性和可扩展性,而不改变代码的功能。它可以帮助开发人员更好地理解和维护代码,减少代码中的冗余、重复和复杂性,提高代码的可重用性和可扩展性,从而使代码更加健壮和可靠。重构的目的是使代码更加简洁、易于理解和修改,以提高软件开发的效率和质量。

基类的抽取

所有逻辑一样的步骤可以被每个标签计算任务使用,可以抽取为共同的步骤,注意参数是否相同

  • 1、创建spark的运行环境,逻辑一样,参数不一样:appName
  • 2、读取mysql中的标签信息(四级和五级标签的id和rule),逻辑一样,参数不一样:tag4Id
  • 3、解析四级标签的rule,逻辑一样,参数一样
  • 4、根据四级标签的rule读取es中的数据,逻辑一样,参数一样
  • 5、读取计算需要用到的五级标签的rule和id,逻辑一样,参数一样
  • 6、标签的计算,逻辑不一样,参数一样
  • 7、结果的更新,逻辑一样,参数一样
  • 8、结果的保存,逻辑一样,参数一样
"""
#TODO 0.准备Spark开发环境
#TODO 1.读取MySQL中的数据
#TODO 2.读取模型/标签相关的4级标签rule并解析--==标签id不一样==
#TODO 3.根据解析出来的rule读取ES数据
#TODO 4.读取模型/标签相关的5级标签(根据4级标签的id作为pid查询)---==标签id不一样==
#====5.根据ES数据和5级标签数据进行匹配,得出userId,tagsId---==实现代码不一样==
#TODO 6.查询elasticsearch中的oldDF
#TODO 7.合并newDF和oldDF
#TODO 8.将最终结果写到ES
"""
import os

from pyspark.sql import SparkSession, DataFrame
import pyspark.sql.functions as F
from pyspark.sql.types import StringType

from UserProfile.offline.pojo.RuleMeta import Rule4Meta

os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_HOME'] = '/root/anaconda3/envs/pyspark_env/bin/python'

def strParse(rule4_str: str):
    rule4_list = rule4_str.split("##")
    rule4_dict = {}
    for rule4_meta in rule4_list:
        rule4_dict[rule4_meta.split('=')[0]] = rule4_meta.split('=')[1]
    return rule4_dict

@F.udf
def updateTagsId(newTagsId, oldTagsId:str, tag5Ids:str):
    # new_result_df.tagsId, old_df.tagsId, new_result_df.tag5Ids
    tag5_list = tag5Ids.split(",")
    need_list = []
    for i in oldTagsId.split(","):
        if i not in tag5_list:
            need_list.append(i)
    need_list.append(newTagsId)
    return ','.join(need_list)

class TagComputeBase():
    def __init__(self, appName, tag_four_id):
       self.appName = appName
       self.tag_four_id = tag_four_id
       self.url = "jdbc:mysql://192.168.88.166:3306/tfec_tags?useSSL=false&useUnicode=true&characterEncoding=utf8&user=root&password=123456"
       tableName = 'tbl_basic_tag'
       self.sql = f'select id, rule from {tableName} where id = {tag_four_id} or pid = {tag_four_id}'
       self.result_index = "tfec_userprofile_result"

    # 创建Spark开发环境并返回
    def getSparkSession(self):
        sparkSession = SparkSession.builder \
            .appName(self.appName) \
            .master("local[*]") \
            .config("spark.sql.shuffle.partitions", 20) \
            .getOrCreate()
        return sparkSession

    # 读取MySQL中的数据(传入的参数是SparkSession对象, 要使用Spark读取MySQL数据
    def getTagInfo(self, sparkSession: SparkSession):
        tag_df = sparkSession.read\
            .format('jdbc')\
            .option('url', self.url)\
            .option('query', self.sql).load()
        return tag_df

    # 获取四级标签rule的对象
    def getRule4Meta(self, tag_df: DataFrame):
        rule4_str = tag_df.rdd.map(lambda row: row.rule).collect()[0]
        rule4_dict = strParse(rule4_str)
        # 将字典传入类, 封装成对象
        rule_four_meta = Rule4Meta.dict_to_obj(rule4_dict)
        return rule_four_meta

    # 获取计算标签需要的数据
    def getComputeData(self, sparkSeession: SparkSession, rule_meta: Rule4Meta):
        es_df = sparkSeession.read\
            .format('es')\
            .option('es.nodes', rule_meta.esNodes)\
            .option('es.resource', rule_meta.esIndex)\
            .option('es.read.field.include', rule_meta.selectFields).load()
        return es_df

    # 读取五级标签的id和rule
    def getTag5Info(self, tag_df: DataFrame):
        tag5_df = tag_df.where(f'id!={self.tag_four_id}')
        return tag5_df

    # 标签计算(将两个df的数据join, 得到userId, 标签id
    def tagCompute(self, es_df: DataFrame, tag5_df: DataFrame):
        pass

    # 合并更新新老结果(使用自定义udf函数实现-还需要获取老的df标签结果
    def tagResultUpdate(self, new_result_df: DataFrame, sparkSession: SparkSession,
                  tag5_df: DataFrame, rule_meta: Rule4Meta ):
        old_df = sparkSession.read.format('es') \
            .option('es.nodes', rule_meta.esNodes) \
            .option('es.resource', self.result_index) \
            .load()
        # 获取五级标签的集合
        tag5_list = tag5_df.select(F.col('id').cast(StringType())).rdd.map(lambda row:row.id).collect()
        # 给获取的五级标签的集合放到new_result_df中
        new_rs_df = new_result_df.withColumn('tag5Ids', F.lit(','.join(tag5_list)))
        # 将新老标签的数据合并
        result_df = old_df\
            .join(new_rs_df, on=old_df.userId==new_rs_df.userId, how='left')\
            .select(new_rs_df.userId,updateTagsId(new_rs_df.tagsId, old_df.tagsId, new_rs_df.tag5Ids).alias('tagsId'))
        return result_df
    # 保存标签计算的结果
    def saveResult(self, result_df: DataFrame, rule_meta: Rule4Meta):
        result_df.write.format('es')\
            .option('es.nodes', rule_meta.esNodes)\
            .option('es.resource', self.result_index)\
            .option('es.write.operation', 'upsert') \
            .option('es.mapping.id', 'userId') \
            .option('es.mapping.name', 'userId:userId, tagsId:tagsId') \
            .mode('append') \
            .save()
    # 自动调用执行所有的流程
    def execute(self):
        # - 1、创建spark的运行环境,逻辑一样,参数不一样:appName
        sparkSession = self.getSparkSession()
        # - 2、读取mysql中的标签信息(四级和五级标签的id和rule),逻辑一样,参数不一样:tag4Id
        tag_df = self.getTagInfo(sparkSession)
        # - 3、解析四级标签的rule,逻辑一样,参数一样
        rule_meta = self.getRule4Meta(tag_df)
        # - 4、根据四级标签的rule读取es中的数据,逻辑一样,参数一样
        es_df = self.getComputeData(sparkSession, rule_meta)
        # - 5、读取计算需要用到的五级标签的rule和id,逻辑一样,参数一样
        tag5_df = self.getTag5Info(tag_df)
        # - 6、标签的计算,逻辑不一样,参数一样
        new_result_df = self.tagCompute(es_df, tag5_df)
        if new_result_df is not None:
            # - 7、结果的更新,逻辑一样,参数一样
            result_df = self.tagResultUpdate(new_result_df, sparkSession, tag5_df, rule_meta)
            # - 8、结果的保存,逻辑一样,参数一样
            self.saveResult(result_df, rule_meta)

统计类标签计算

计算最后一次消费时间距离当前时间的天数

  • 四级标签的rule-id:23
inType=Elasticsearch

esNodes=up01:9200

esIndex=tfec_tbl_orders

esType=_doc

selectFields=memberid,finishtime
  • 五级标签
+---+------+
| id|  rule|
+---+------+
| 24|   0-7|
| 25|  8-14|
  • 计算逻辑
    • 需要计算最后一次消费时间距离当前时间的天数
    • 根据memberid进行分组group by,求最大值的finishtime
    • 需要将finishtime从unix_timestamp转化yyyyMMdd的字符格式
    • 把五级标签rule切分为start,end
    • 使用F.datediff(start,end)求两个日期之间的差值
    • 由于数据比较老我们把当前日期作提前处理F.datesub(F.currentdate,1095)

最终得到两个df, 然后合并就行了

  • es_pre_df(经过处理的es中的源数据)
+------+----+
|userId|days|
+------+----+
|   898|   5|
|    29|   5|
  • tag5_pre_df最后一次消费时间距离当前时间的规则,以及标签的id
+------+-----+---+
|tagsId|start|end|
+------+-----+---+
|    24|    0|  7|
|    25|    8| 14|

消费标签计算

from pyspark.sql import DataFrame
import pyspark.sql.functions as F
from pyspark.sql.types import StringType

from UserProfile.offline.base.TagComputeBase import TagComputeBase

# 定义性别标签计算类, 继承自标签计算基类
class ConsumptionCycleTagCompute(TagComputeBase):
    def tagCompute(self, es_df: DataFrame, tag5_df: DataFrame):
        # 需要计算最后一次消费时间距离当前时间的天数
        # es_df.show()
        # +----------+--------+
        # |finishtime|memberid|
        # +----------+--------+
        # |1594137600|     342|
        # |1594310400|     405|
        # |1593964800|     653|

        # tag5_df.show()
        # +---+------+
        # | id|  rule|
        # +---+------+
        # | 24|   0-7|
        # | 25|  8-14|
        # 求用户最大的时间戳, 也就是最近一次的消费时间
        es_pre_df = es_df.groupBy(F.col('memberid').alias('userId')).agg(
            F.max(F.col('finishtime')).alias('finishtime')
        )

        # es_pre_df.show()
        # +------+----------+
        # |userId|finishtime|
        # +------+----------+
        # |   898|1596211200|
        # |    29|1596211200|

        # 将时间戳类型的时间转换为日期格式
        date_df = es_pre_df\
            .select(F.col('userId'), F.from_unixtime(F.col('finishtime'), format='yyyy-MM-dd').alias('finishDate'))
        # date_df.show()
        # +------+----------+
        # |userId|finishDate|
        # +------+----------+
        # |   898|2020-08-01|
        # |    29|2020-08-01|
        # |    92|2020-08-01|

        # 由于消费者最后一次的消费时间跟现在距离过远, 需要将日期推到3年前-1095天,然后计算相隔天数
        date_pre_df = date_df.select('userId', F.datediff(F.date_sub(F.current_date(), 1095), F.col('finishDate')).alias('days'))
        # date_pre_df.show()
        # +------+----+
        # |userId|days|
        # +------+----+
        # |   898|   5|
        # |    29|   5|
        # 由于发现数据有点离谱, days普遍集中在5, 6 这两个数值, 所以为了严谨, 将1095改为1093使得days产生7-8,产生两个区间的标签id

        # 将tag5_df 的rule(0-7)拆分成start 和 end 这种类型的时间
        tag5_pre_df = tag5_df.select(F.col('id').alias('tagsId'), F.split('rule', '-')[0].alias('start'), F.split('rule', '-')[1].alias('end'))
        # tag5_pre_df.show()
        # +------+-----+---+
        # |tagsId|start|end|
        # +------+-----+---+
        # |    24|    0|  7|
        # |    25|    8| 14|

        # 将两个标签的数据进行合并(date_pre_df.userId不要写成date_pre_df.select(F.col('userId'))
        result_df = date_pre_df\
            .join(tag5_pre_df, on=date_pre_df.days.between(tag5_pre_df.start, tag5_pre_df.end), how='left')\
            .select(date_pre_df.userId.cast(StringType()).alias('userId'), tag5_pre_df.tagsId.cast(StringType()).alias('tagsId'))
        return result_df

        pass
if __name__ == '__main__':
    appName = '消费周期标签计算任务'
    tag4Id = 23
    # 创建标签计算对象
    consumptionCycleTagCompute = ConsumptionCycleTagCompute(appName, tag4Id)
    consumptionCycleTagCompute.execute()

支付方式标签计算

计算每个人常用的支付方式

  • 四级标签id-29
inType=Elasticsearch

esNodes=up01:9200

esIndex=tfec_tbl_orders

esType=_doc

selectFields=memberid,paymentcode
  • 五级标签
30,alipay
31,wxpay
32,chinapay
33,kjtpay
34,cod
35,other
  • 计算逻辑
    • 计算每个人常用的支付方式
    • 根据memberid、paymentcode进行分组groupby,求count(paymentcode)每个人的每个支付的次数
    • 求每个人支付方式中次数最多的支付方式
    • 使用开窗计算:
es_pre_df = es_df\
         .groupBy(F.col('memberid'), F.col('paymentcode'))\
         .agg(F.count('paymentcode').alias('cnt'))\
         .withColumn('rk', F.row_number().over(Window.partitionBy('memberid').orderBy(F.col('cnt').desc())))\
         .where('rk = 1')

计算流程

from pyspark.sql import DataFrame, Window
import pyspark.sql.functions as F
from pyspark.sql.types import StringType

from UserProfile.offline.base.TagComputeBase import TagComputeBase

# 定义性别标签计算类, 继承自标签计算基类
class PaymentTagCompute(TagComputeBase):
    def tagCompute(self, es_df: DataFrame, tag5_df: DataFrame):
        # 计算每个人常用的支付方式
        # es_df.show()
        # +--------+-----------+
        # |memberid|paymentcode|
        # +--------+-----------+
        # |     342|     alipay|
        # |     405|     alipay|
        es_pre_df = es_df\
            .groupBy(F.col('memberid'), F.col('paymentcode'))\
            .agg(F.count('paymentcode').alias('cnt'))\
            .withColumn('rk', F.row_number().over(Window.partitionBy('memberid').orderBy(F.col('cnt').desc())))\
            .where('rk = 1')

        # es_pre_df.show()
        # +--------+-----------+---+---+
        # |memberid|paymentcode|cnt| rk|
        # +--------+-----------+---+---+
        # |       4|     alipay|195|  1|
        # |      26|     alipay|176|  1|
        # |      29|     alipay|196|  1|

        # tag5_df.show()
        # +---+--------+
        # | id|    rule|
        # +---+--------+
        # | 30|  alipay|
        # | 31|   wxpay|

        # 合并两个df得到最终结果
        result_df = es_pre_df.join(tag5_df,on=es_pre_df.paymentcode==tag5_df.rule, how='left')\
            .select(es_pre_df.memberid.cast(StringType()).alias('userId'), tag5_df.id.cast(StringType()).alias('tagsId'))
        return result_df

if __name__ == '__main__':
    appName = '支付方式标签计算任务'
    tag4Id = 29
    # 创建标签计算对象
    paymentTagCompute = PaymentTagCompute(appName, tag4Id)
    paymentTagCompute.execute()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值