1.データの準備
このプロジェクトのデータは、eコマースWebサイトからのユーザー行動データの収集であり、主に4種類のユーザー行動(検索、クリック、注文、支払い)が含まれています。
データ形式
- データは_splitフィールドを使用します。
- 各行はユーザーの行動を表すため、各行は4つの行動のうちの1つにしかなれません。
- 検索キーワードがnullの場合は、今回は検索ではないことを意味します。
- クリックされたカテゴリIDと製品IDが-1の場合、今回はクリックされていないことを意味します。
- 注文動作に関しては、一度に複数の商品を注文できるため、カテゴリIDと商品IDは両方とも複数であり、IDはカンマで区切られます。これが注文でない場合、それらの関連データはnullで表されます。
- 支払いの動作は、注文の動作と似ています。
データセットのダウンロード
リンク:https
://pan.baidu.com/s/1ZLhYdXz1Foi6MpeUBFGCnQ抽出コード:12lt
需要1:人気のある上位10のカテゴリ
ニーズの声明
カテゴリとは、製品の分類を指します。大規模なeコマースWebサイトには、複数のカテゴリがあります。プロジェクトには1つのカテゴリしかありません。企業によって、人気の定義が異なる場合があります。人気のあるカテゴリは、各カテゴリのクリック数、注文数、支払い額に応じてカウントされます。
このプロジェクトの要件は次のように最適化されています:クリック数に応じた最初のランク、および上位ランク。クリック数が同じ場合は注文数を比較します。注文数が再び同じ場合は、支払い回数を比較します。
需要分析
- 操作が「クリック」、「注文」、「支払い」の3つのタイプのどれであるかを判別します。
- カテゴリと操作カテゴリを(カテゴリ、(クリック数、注文数、支払い数))にまとめると、メタグループをオブジェクトとして組み立てることができます。
- キーカテゴリに従って、3つの操作の総数を集計します。
- トップ10を並べ替えます。
コード:
データ情報をカプセル化するサンプルクラスを作成します
// 样例类可以自动生成apply方法和unapply()方法
case class UserVisitAction(date: String, //用户点击行为的日期
user_id: Long, //用户的ID
session_id: String, //Session的ID
page_id: Long, //某个页面的ID
action_time: String, //动作的时间点
search_keyword: String, //用户搜索的关键词
click_category_id: Long, //某一个商品品类的ID
click_product_id: Long, //某一个商品的ID
order_category_ids: String, //一次订单中所有品类的ID集合
order_product_ids: String, //一次订单中所有商品的ID集合
pay_category_ids: String, //一次支付中所有品类的ID集合
pay_product_ids: String, //一次支付中所有商品的ID集合
city_id: Long) //城市 id
// 定义样例类存储商品品类及对应的操作次数
// 样例类的属性默认使用val修饰,不可重新赋值
case class CategoryCountInfo(categoryId: String, //品类id
var clickCount: Long, //点击次数
var orderCount: Long, //订单次数
var payCount: Long) //支付次数
1つのコードを要求する
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
val sc = new SparkContext(conf)
val dataRDD: RDD[String] = sc.textFile("D:\\学习资料\\spark\\spark\\2.资料\\spark-core数据\\user_visit_action.txt")
// 封装对象
val userActionRDD: RDD[UserVisitAction] = dataRDD.map {
line => {
val lineSplit: Array[String] = line.split("_")
// 封装对象
UserVisitAction(
lineSplit(0),
lineSplit(1).toLong,
lineSplit(2),
lineSplit(3).toLong,
lineSplit(4),
lineSplit(5),
lineSplit(6).toLong,
lineSplit(7).toLong,
lineSplit(8),
lineSplit(9),
lineSplit(10),
lineSplit(11),
lineSplit(12).toLong
)
}
}
// 转换数据格式
val cateInfoRDD: RDD[(String, CategoryCountInfo)] = userActionRDD.flatMap {
userAction => {
// 判断操作类型
if (userAction.click_category_id != -1) {
// 转换数据格式 (品类, CategoryCountInfo对象)
List((userAction.click_category_id.toString,
CategoryCountInfo(userAction.click_category_id.toString, 1, 0, 0)))
} else if (userAction.order_category_ids != "null") {
// 处理order_category_ids
val cateIds: Array[String] = userAction.order_category_ids.split(",")
// ListBuffer存储多个对象
val list = new mutable.ListBuffer[(String, CategoryCountInfo)]()
for (cateId <- cateIds) {
list.append((cateId, CategoryCountInfo(cateId, 0, 1, 0)))
}
list
} else if (userAction.pay_category_ids != "null") {
val cateIds: Array[String] = userAction.pay_category_ids.split(",")
// ListBuffer存储多个对象
val list = new mutable.ListBuffer[(String, CategoryCountInfo)]()
for (cateId <- cateIds) {
list.append((cateId, CategoryCountInfo(cateId, 0, 0, 1)))
}
list
} else {
// 返回空集合
Nil
}
}
}
// 根据key聚合数据
val reduceRDD: RDD[(String, CategoryCountInfo)] = cateInfoRDD.reduceByKey(
(cate1, cate2) => {
cate1.clickCount = cate1.clickCount + cate2.clickCount
cate1.orderCount = cate1.orderCount + cate2.orderCount
cate1.payCount = cate1.payCount + cate2.payCount
cate1
}
)
// 去掉多余的key,转换格式
val cateCountRDD: RDD[CategoryCountInfo] = reduceRDD.map {
_._2
}
// 排序取前10
val resArr: Array[CategoryCountInfo] = cateCountRDD.sortBy(
// 元组可以按照元素的顺序来依次排序
cate =>{
(cate.clickCount,cate.orderCount,cate.payCount)},
// 倒序
false
).take(10)
sc.stop()
需要2:人気のあるトップ10カテゴリの各カテゴリのアクティブセッション統計トップ10
ニーズの声明
上位10のカテゴリについて、各カテゴリの上位10クリックのsessionIdを取得します。(注:ここでは、注文と支払いの数ではなく、クリック数のみに焦点を当てています。)
top10カテゴリの場合、各カテゴリは、クリック数でトップ10にランク付けされるsessionIdを取得する必要があります。この関数を使用すると、特定のユーザーグループに最も関心のあるカテゴリと、各カテゴリの最も一般的なユーザーのセッション動作を確認できます。
需要分析
- 需要1から人気のあるトップ10カテゴリのIDを取得します。
- データをフィルタリングすると、値は上位10の人気のあるカテゴリIDと対応するクリックログを保持します。
- データ形式を変換します(カテゴリid_session、1)。
- 上記のデータを集計し、各カテゴリのセッションクリック数をカウントします。
- データ形式(カテゴリID、(セッション、カウント))を変換します。
- カテゴリ別にグループ化します。
- 各グループの上位10を取得して、逆の順序で並べ替えます。
コード
- 要件2は、要件1の結果に依存する必要があります。
// =======================================上面是需求1=============================================
// 取出top10的商品id
val top10Ids: Array[String] = resArr.map(_.categoryId)
// top10Ids要发送到每个task,可以做广播变量优化
val broadcast: Broadcast[Array[String]] = sc.broadcast(top10Ids)
// 过滤数据,只保留top10 id对应的点击数据
val filterRDD: RDD[UserVisitAction] = userActionRDD.filter(
// 注意click_category_id的类型是Long类型,需要转换为String类型
datas => {
if (datas.click_category_id != -1) {
broadcast.value.contains(datas.click_category_id.toString)
} else {
false
}
}
)
// 转换格式 (品类id_session, 1)
val cateIdAndSession1: RDD[(String, Int)] = filterRDD.map(
datas => {
(datas.click_category_id + "_" + datas.session_id, 1)
}
)
// 按照相同的key聚合 (品类id_session, count)
val cateIdAndSessionCount: RDD[(String, Int)] = cateIdAndSession1.reduceByKey(_ + _)
// 转换数据格式 (品类id, (session, count))
val cateIdAndSessionCount2: RDD[(String, (String, Int))] = cateIdAndSessionCount.map {
case (idAndSession, count) => {
val split: Array[String] = idAndSession.split("_")
(split(0), (split(1), count))
}
}
// 按照品类分组
val cateGroupRDD: RDD[(String, Iterable[(String, Int)])] = cateIdAndSessionCount2.groupByKey()
// 倒序排序,取前10
val res2RDD: RDD[(String, List[(String, Int)])] = cateGroupRDD.mapValues(
datas => {
val list: List[(String, Int)] = datas.toList
// 排序
list.sortWith(_._2 > _._2)
}.take(10)
)
res2RDD.foreach(println)
sc.stop()
要件3:ページの単一ジャンプ率の統計
ニーズの声明
ページのシングルジャンプコンバージョン率を計算します。たとえば、セッション中にユーザーがアクセスしたページパス3、5、7、9、10、21のように、ページのシングルジャンプのコンバージョン率を計算すると、ページ3がページにジャンプします。 5および一度呼び出されるシングルジャンプ、7-9はシングルジャンプとも呼ばれ、シングルジャンプレートはページクリックの確率をカウントすることです。
例:シングルジャンプ率3〜5を計算し、最初に対象セッションの3ページの訪問数(PV)をAとして取得し、次に対象セッションの3ページへの訪問数(PV)を取得して次に5ページに移動します。回数はBで、B / Aは3〜5のページシングルジャンプ率です。
需要分析
- 最初に各ページへの訪問数を数えることができます
->最初にデータ形式を変換します:(ページID、1)
->次にページIDに従って集計し、各ページへの訪問の総数を見つけます(ページID、カウント)
->次にページA->ページB->ページC->ページD ...シングルジャンプ時間をカウントします - セッションIDとページID(セッションID-ページID、アクセス時間)に従ってユーザーアクセス時間を並べ替えます
- データ形式を変換し、セッションIDでグループ化します
-> session1:ページA->ページB->ページC-
>セッション2:->ページA->ページB->ページC - 各グループのアクセスシーケンスのコレクションリスト(各ユーザーのページアクセスシーケンス)を返します
->ページアクセスシーケンス:A-> B-> C-> D-> E-
>コレクションテールでジッパーを作成します:B-> C-> D-> E
->(A、B)(B、C)(C、D)(D、E) - ジッパーによって形成されたセット内の各要素が表示される回数、つまり、各ページのシングルホップの数を見つけます((A、B)、カウント)
- 最後に、ページあたりのシングルジャンプの数/ページあたりの合計回数=ページのシングルジャンプ率を使用します
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
val sc = new SparkContext(conf)
val dataRDD: RDD[String] = sc.textFile("D:\\学习资料\\spark\\spark\\2.资料\\spark-core数据\\user_visit_action.txt")
// 封装对象
val userVisitActionRDD: RDD[UserVisitAction] = dataRDD.map {
line => {
val lineSplit: Array[String] = line.split("_")
// 封装对象
UserVisitAction(
lineSplit(0),
lineSplit(1).toLong,
lineSplit(2),
lineSplit(3).toLong,
lineSplit(4),
lineSplit(5),
lineSplit(6).toLong,
lineSplit(7).toLong,
lineSplit(8),
lineSplit(9),
lineSplit(10),
lineSplit(11),
lineSplit(12).toLong
)
}
}
// 先统计每个页面的访问次数
// (页面id,1) -> (页面id,count)
val pageAnd1: RDD[(Long, Long)] = userVisitActionRDD.map(
action => {
(action.page_id, 1L)
}
)
// 按照pageId聚合,每个页面的访问次数, 转为map字典,方便使用
val pageAndCount: Map[Long, Long] = pageAnd1.reduceByKey(_ + _).collect().toMap
// 根据sessionid和pageid,按照时间排序
val sessionAndPage: RDD[(String, String)] = userVisitActionRDD.map(
action => {
// 转换格式 (sessionid_pageid, 时间)
(action.session_id + "_" + action.page_id, action.action_time)
}
)
// 按照sessionid分组,按照时间正序排序
// 转换格式 (sessionid, (pageid,时间))
val sessionPageTime: RDD[(String, (String, String))] = sessionAndPage.map {
case (session_page, time) => {
val split: Array[String] = session_page.split("_")
(split(0), (split(1), time))
}
}
// 按照sessionid分组,每个用户的访问页面和时间
val sessionGroup: RDD[(String, Iterable[(String, String)])] = sessionPageTime.groupByKey()
// 转换格式,舍弃sessionid Iterable(pageid,时间)
val pageTime: RDD[Iterable[(String, String)]] = sessionGroup.map(_._2)
// 按照时间正序排序 Iterable(pageid,时间)
val pageSortRDD: RDD[Iterable[(String, String)]] = pageTime.map(
data => {
data.toList.sortWith(_._2 < _._2)
}
)
//转换格式,舍弃时间 Iterable(pageid)
val pageRDD: RDD[List[String]] = pageSortRDD.map(
datas => {
datas.toList.map(_._1)
}
)
// 拉链操作,组成((A,B),1) 的形式,方便计算从A到B的跳转数
val zipRDD: RDD[((String, String), Long)] = pageRDD.flatMap(
pageList => {
pageList.zip(pageList.tail).map((_, 1L))
}
)
val breakCount: RDD[((String, String), Long)] = zipRDD.reduceByKey(_ + _)
// breakCount.take(3).foreach(println)
// 计算A到B的跳转数
// 跳转数 / 页面访问数
val resRDD: RDD[String] = breakCount.map {
case (breakPage, count) => {
val sum: Long = pageAndCount.getOrElse(breakPage._1.toLong, 1)
val res: Double = count * 1.0 / sum
breakPage + ":" + res
}
}
resRDD.foreach(println)
//breakRDD.take(3).foreach(println)
sc.stop()