1 概述

1.1 背景介绍

随着电商行业的蓬勃发展,海量交易数据不断产生。了解哪些品类在市场中最受消费者青睐,成为商家优化库存、制定营销策略以及平台规划布局的关键依据。

1.2 适用对象

  • 企业
  • 个人开发者
  • 高校学生(有一定代码基础)

1.3 案例时间

本案例总时长预计30分钟。

1.4 案例流程

1.4.png
说明:
① 用户登录云主机;
② 打开CodeArts IDE获取项目代码;
③ 进行环境准备,配置JDK17;
④ 启动项目的前后端代码,在浏览器中进行页面结果展示。

1.5 资源总览

资源名称 规格 单价(元) 时长(h)
JDK jdk17 免费 0.5
开发者空间 - 云主机 鲲鹏通用计算增强型 kc2 | 4vCPUs | 8G | Ubuntu 免费 0.5

大数据带你挖掘电商Top10热门品类 👈👈👈体验完整版案例,点击这里。

2 操作步骤

2.1 数据格式简介

本案例的数据是采集电商网站的用户行为数据,主要包含用户的4种行为:搜索、点击、下单和支付。数据格式说明如下:
(1)数据采用下划线分割字段;
(2)每一行表示用户的一个行为,所以每一行只能是4种行为中的一种;
(3)如果搜索关键字是null,表示这次不是搜索;
(4)如果点击的品类id和产品id是-1表示这次不是点击;
(5)下单行为来说一次可以下单多个产品,所以品类id和产品id都是多个,id之间使用逗号分割。如果本次不是下单行为,则他们相关数据用null来表示;
(6)支付行为和下单行为类似。
2.1.png

2.2 需求分析

Top10热门品类,是指产品的分类。分别统计每个品类点击的次数,下单的次数和支付的次数。先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下单数;下单数再相同,就比较支付数。
计算结果格式类似:

智能手机 ,点击品类书,下单品类数,支付品类数
宠物玩具 ,点击品类书,下单品类数,支付品类数
休闲食品,点击品类书,下单品类数,支付品类数

2.3 环境准备

2.3.1 获取项目代码

1.拉取前端代码

git clone https://gitcode.com/CaseDeveloper/E-Commerce-commerce-top10-Web.git

前端部署参考案例:在云主机上进行电商项目VUE前端部署
2.拉取后端代码

git clone https://gitcode.com/CaseDeveloper/E-Commerce-commerce-top10.git

后端部署参考案例:基于云主机的CodeArts IDE运行Java电商项目

2.3.2 编写代码

1.打开CodeArts IDE for Java
2.2-1.png
2.打开工程E-Commerce-commerce-top10
ScreenShot_20250106144313.PNG
3.打开项目代码

  • 在工程src下datas文件夹中user_visit_action.txt是需要用到的数据。
    ScreenShot_20250106144424.PNG
  • 在utils下Top10.java 是实现代码。
    2.3-4.png
    Top10.java代码如下:
package org.example.utils;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.util.AccumulatorV2;
import org.example.Entity.BusinessData;
import org.example.Entity.CategoryCountInfo;
import org.example.Entity.ProductIdToNameMapEnum;
import org.example.Entity.UserVisitAction;
import scala.Tuple2;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//自定义AccumulatorV2实现类,用于累加计算品类相关的点击、下单、支付次数
class CategoryCountAccumulator extends AccumulatorV2<UserVisitAction, Map<Tuple2<String, String>, Long>> implements Serializable{

    private Map<Tuple2<String, String>, Long> map1 = new HashMap<>();

    // 判断是否为初始状态(即map为空)
    @Override
    public boolean isZero() {
        return map1.isEmpty();
    }

    // 复制累加器实例
    @Override
    public AccumulatorV2<UserVisitAction, Map<Tuple2<String, String>, Long>> copy() {
        CategoryCountAccumulator newACC = new CategoryCountAccumulator();
        newACC.map1 = new HashMap<>(this.map1);
        return newACC;
    }

    // 重置累加器,清空内部存储的map数据
    @Override
    public void reset() {
        map1.clear();
    }

    // 处理单个分区内的数据,根据不同的业务行为(点击、下单、支付)累加相应的次数
    @Override
    public void add(UserVisitAction action) {
        // 点击行为
        if (action.getClick_category_id()!= -1) {
            Tuple2<String, String> key = new Tuple2<>(action.getClick_category_id() + "", "click");
            map1.put(key, map1.getOrDefault(key, 0L) + 1L);
        }
        // 下单行为
        else if (!"null".equals(action.getOrder_category_ids())) {
            String[] ids = action.getOrder_category_ids().split(",");
            for (String id : ids) {
                Tuple2<String, String> key = new Tuple2<>(id, "order");
                map1.put(key, map1.getOrDefault(key, 0L) + 1L);
            }
        }
        // 支付行为
        else if (!"null".equals(action.getPay_category_ids())) {
            String[] ids = action.getPay_category_ids().split(",");
            for (String id : ids) {
                Tuple2<String, String> key = new Tuple2<>(id, "pay");
                map1.put(key, map1.getOrDefault(key, 0L) + 1L);
            }
        }
    }

    // 合并不同分区的累加结果
    @Override
    public void merge(AccumulatorV2<UserVisitAction, Map<Tuple2<String, String>, Long>> other) {
        Map<Tuple2<String, String>, Long> map2 = other.value();
        for (Map.Entry<Tuple2<String, String>, Long> entry : map2.entrySet()) {
            Tuple2<String, String> k = entry.getKey();
            Long v = entry.getValue();
            map1.put(k, map1.getOrDefault(k, 0L) + v);
        }
    }

    // 获取累加器当前的值(即存储了各品类操作次数的map)
    @Override
    public Map<Tuple2<String, String>, Long> value() {
        return map1;
    }
}

public class Top10 implements Serializable  {
    public static List<BusinessData> top10() {
        SparkConf conf = new SparkConf().setAppName("Top10").setMaster("local[*]");
        // 创建JavaSparkContext,该对象是提交的入口
        JavaSparkContext sc = new JavaSparkContext(conf);
        Top10 top10 = new Top10();
        List<BusinessData> sparkRun = top10.sparkRun(sc);
        // 9. 关闭连接
        sc.stop();
        return sparkRun;
    }
    public List<BusinessData>  sparkRun(JavaSparkContext sc) {

  //  public static void main(String[] args) {
        // 创建SparkConf
        
       // SparkConf conf = new SparkConf().setAppName("Top10").setMaster("local[*]");
        // 创建JavaSparkContext,该对象是提交的入口
       // JavaSparkContext sc = new JavaSparkContext(conf);

        // 后续代码部分,如数据读取、处理、累加器使用、排序取前10以及关闭连接等操作保持不变
        // 1. 读取数据
        JavaRDD<String> dataRDD = sc.textFile("src/datas/user_visit_action.txt");

        // 2. 将读到的数据进行切分,并且将切分的内容封装为UserVisitAction对象
        JavaRDD<UserVisitAction> actionRDD = dataRDD.map(line -> {
            String[] fields = line.split("_");
            return new UserVisitAction(
                    fields[0],
                    Long.parseLong(fields[1]),
                    fields[2],
                    Long.parseLong(fields[3]),
                    fields[4],
                    fields[5],
                    Long.parseLong(fields[6]),
                    Long.parseLong(fields[7]),
                    fields[8],
                    fields[9],
                    fields[10],
                    fields[11],
                    Long.parseLong(fields[12])
            );
        });

        // 3. 创建累加器并注册
        CategoryCountAccumulator acc = new CategoryCountAccumulator();
        sc.sc().register(acc, "myAcc");

        // 4. 遍历actionRDD,使用累加器进行统计
        actionRDD.foreach(new VoidFunction<UserVisitAction>() {
            @Override
            public void call(UserVisitAction action) {
                acc.add(action); 
            }
        });

       
       // 5. 获取累加器的值
        Map<Tuple2<String, String>, Long> accMap = acc.value();

        // 6. 对累加出来的数据按照类别进行分组
        Map<String, Map<Tuple2<String, String>, Long>> groupMap = new HashMap<>();
        for (Map.Entry<Tuple2<String, String>, Long> entry : accMap.entrySet()) {
            Tuple2<String, String> key = entry.getKey();
            String categoryId = key._1;
            if (!groupMap.containsKey(categoryId)) {
                groupMap.put(categoryId, new HashMap<>());
            }
            groupMap.get(categoryId).put(key, entry.getValue());
        }

        // 7. 对分组后的数据进行结构的转换为CategoryCountInfo对象
        List<CategoryCountInfo> categoryCountInfoList = new ArrayList<>();
        for (Map.Entry<String, Map<Tuple2<String, String>, Long>> entry : groupMap.entrySet()) {
            String id = entry.getKey();
            Map<Tuple2<String, String>, Long> map = entry.getValue();
            long clickCount = map.getOrDefault(new Tuple2<>(id, "click"), 0L);
            long orderCount = map.getOrDefault(new Tuple2<>(id, "order"), 0L);
            long payCount = map.getOrDefault(new Tuple2<>(id, "pay"), 0L);
            categoryCountInfoList.add(new CategoryCountInfo(id, clickCount, orderCount, payCount));
        }

        // 8. 将转换后的数据进行排序(降序)取前10名
         categoryCountInfoList.sort((left, right) -> {
            // 先按照点击次数降序比较
            int clickCountCompare = Long.compare(right.getClickCount(), left.getClickCount());
            if (clickCountCompare!= 0) {
                return clickCountCompare;
            }
            // 点击次数相同,按照订单次数降序比较
            int orderCountCompare = Long.compare(right.getOrderCount(), left.getOrderCount());
            if (orderCountCompare!= 0) {
                return orderCountCompare;
            }
            // 订单次数相同,按照支付次数降序比较
            return Long.compare(right.getPayCount(), left.getPayCount());
        });
      Map<String, String> productIdToNameMap = ProductIdToNameMapEnum.INSTANCE.getProductIdToNameMap();
       List<CategoryCountInfo> top10List = categoryCountInfoList.size() > 10? categoryCountInfoList.subList(0, 10) : categoryCountInfoList;
       List<BusinessData> businessData = new ArrayList<>();

for (CategoryCountInfo info : top10List) {
    String productName = productIdToNameMap.getOrDefault(info.getCategoryId(), info.getCategoryId());
    BusinessData data = new BusinessData();
    data.setCategory(productName);
    data.setClickCount(info.getClickCount());
    data.setOrderCount(info.getOrderCount());
    data.setPaymentCount(info.getPayCount());
    businessData.add(data);
  //  System.out.println("CategoryCountInfo(" + productName + ", " + info.getClickCount() + ", " + info.getOrderCount() + ", " + info.getPayCount() + ")");

}
         // 9. 关闭连接
      // sc.stop();
    return businessData;
   }
}  
  • entity下,CategoryCountInfo,ProductIdToNameMapEnum,UserVisitAction是实体类。
    2.3-5.png
    代码如下:
    a) CategoryCountInfo类:
package com.example.entity;
import java.io.Serializable;

public class CategoryCountInfo implements Serializable {
    private String categoryId;
    private long clickCount;
    private long orderCount;
    private long payCount;

    public CategoryCountInfo(String categoryId, long clickCount, long orderCount, long payCount) {
        this.categoryId = categoryId;
        this.clickCount = clickCount;
        this.orderCount = orderCount;
        this.payCount = payCount;
    }

    public String getCategoryId() {
        return categoryId;
    }

    public long getClickCount() {
        return clickCount;
    }

    public void setClickCount(long clickCount) {
        this.clickCount = clickCount;
    }

    public long getOrderCount() {
        return orderCount;
    }

    public void setOrderCount(long orderCount) {
        this.orderCount = orderCount;
    }

    public long getPayCount() {
        return payCount;
    }

    public void setPayCount(long payCount) {
        this.payCount = payCount;
    }

    // 重写toString方法,按照期望的格式返回对象的字符串表示
    @Override
    public String toString() {
        return "CategoryCountInfo(" +
                categoryId + ',' +
                + clickCount + ','+
                + orderCount + ','+
                + payCount +
                ')';
    }
}

b) ProductIdToNameMapEnum 类(枚举类,商品分类id和商品分类的对应关系):

package com.example.entity;

import java.util.HashMap;
import java.util.Map;

public enum ProductIdToNameMapEnum {
    INSTANCE;

    private final Map<String, String> productIdToNameMap;

    ProductIdToNameMapEnum() {
        productIdToNameMap = new HashMap<>();
        productIdToNameMap.put("1", "智能手机");
        productIdToNameMap.put("2", "护肤品套装");
        productIdToNameMap.put("3", "运动鞋");
        productIdToNameMap.put("4", "平板电脑");
        productIdToNameMap.put("5", "休闲食品");
        productIdToNameMap.put("6", "智能手环");
        productIdToNameMap.put("7", "运动水杯");
        productIdToNameMap.put("8", "儿童玩具");
        productIdToNameMap.put("9", "营养补充剂");
        productIdToNameMap.put("10", "家用小电器");
        productIdToNameMap.put("11", "时尚外套");
        productIdToNameMap.put("12", "蓝牙耳机");
        productIdToNameMap.put("13", "创意家居饰品");
        productIdToNameMap.put("14", "车载香水");
        productIdToNameMap.put("15", "智能手表");
        productIdToNameMap.put("16", "美妆工具");
        productIdToNameMap.put("17", "电子游戏");
        productIdToNameMap.put("18", "户外背包");
        productIdToNameMap.put("19", "宠物玩具");
        productIdToNameMap.put("20", "健身器材");
    }

    public Map<String, String> getProductIdToNameMap() {
        return productIdToNameMap;
    }
}

c) UserVisitAction 类:

package com.example.entity;
// 用户访问动作表对应的Java类
public class UserVisitAction {
    private String date;
    private long user_id;
    private String session_id;
    private long page_id;
    private String action_time;
    private String search_keyword;
    private long click_category_id;
    private long click_product_id;
    private String order_category_ids;
    private String order_product_ids;
    private String pay_category_ids;
    private String pay_product_ids;
    private long city_id;

    public UserVisitAction(String date, long user_id, String session_id, long page_id, String action_time,
                           String search_keyword, long click_category_id, long click_product_id,
                           String order_category_ids, String order_product_ids, String pay_category_ids,
                           String pay_product_ids, long city_id) {
        this.date = date;
        this.user_id = user_id;
        this.session_id = session_id;
        this.page_id = page_id;
        this.action_time = action_time;
        this.search_keyword = search_keyword;
        this.click_category_id = click_category_id;
        this.click_product_id = click_product_id;
        this.order_category_ids = order_category_ids;
        this.order_product_ids = order_product_ids;
        this.pay_category_ids = pay_category_ids;
        this.pay_product_ids = pay_product_ids;
        this.city_id = city_id;
    }

    public String getDate() {
        return date;
    }

    public long getUser_id() {
        return user_id;
    }

    public String getSession_id() {
        return session_id;
    }

    public long getPage_id() {
        return page_id;
    }

    public String getAction_time() {
        return action_time;
    }

    public String getSearch_keyword() {
        return search_keyword;
    }

    public long getClick_category_id() {
        return click_category_id;
    }

    public long getClick_product_id() {
        return click_product_id;
    }

    public String getOrder_category_ids() {
        return order_category_ids;
    }

    public String getOrder_product_ids() {
        return order_product_ids;
    }

    public String getPay_category_ids() {
        return pay_category_ids;
    }

    public String getPay_product_ids() {
        return pay_product_ids;
    }

    public long getCity_id() {
        return city_id;
    }
}
2.3.3 编辑配置

点击右上角编辑配置。
ScreenShot_20250106145114.PNG
在VM options: 配置项,添加如下配置(jdk版本高于1.8需要添加)。

--add-opens java.base/sun.nio.ch=ALL-UNNAMED

2.3-7.png

2.4 运行代码

1.点击页面右上角的执行按钮运行后端代码。
ScreenShot_20250106145236.PNG
2.在命令行运行前端代码
在前端代码目录执行命令:

npm run dev

2.4-2.png

运行成功后显示如下:
2.4-3.png
3.打开页面http://localhost:3000/products,访问电商项目页面。
2.4-4..png
注册账号并登录,然后点击页面的“top10”商品按钮。
2.4-5.png
可以看到Top10热门的商品品类展示如下。
2.4-6.png
至此,电商top10热门品类挖掘实操全部完成。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐