PySpark 完整教程:从环境搭建到大数据实战(附可运行代码)

4 阅读18分钟

在大数据处理、海量数据分析、分布式计算场景中,PySpark 是最受欢迎的工具之一。它将 Python 的简洁易用与 Apache Spark 的分布式计算能力相结合,无需深入掌握 Scala 即可快速上手大数据处理,广泛应用于数据清洗、数据分析、机器学习、日志挖掘等领域。

一、前置准备:环境搭建与安装验证

PySpark 依赖 Java 环境和 Apache Spark 核心包,搭建过程需遵循「先依赖后核心」的顺序,支持 Windows、Linux、Mac 三大平台,以下步骤详细且可落地,新手可直接跟随操作。

1. 核心依赖与版本匹配(避坑关键)

PySpark 对 Java 版本有严格要求,推荐使用 Java 8(OpenJDK 8 或 Oracle JDK 8),高版本 Java(如 Java 17)可能与低版本 Spark 不兼容,具体版本对应关系如下:

  • Spark 3.0+ 支持 Java 8/11
  • Spark 2.x 仅支持 Java 8

本文选用Spark 3.5.0 + Java 8 + Python 3.8+,该组合稳定且兼容性强,适合入门与生产环境。

2. 分步安装流程(三大平台全覆盖)

(1)第一步:安装 Java 8 并配置环境变量

  • Windows 系统

    1. 下载 OpenJDK 8(无需注册,推荐):前往 Adoptium 官网,下载对应 Windows 64 位的 .msi 安装包。
    2. 运行安装包,默认下一步安装,记录安装路径(如 C:\Program Files\Eclipse Adoptium\jdk8u402-b06)。
    3. 配置环境变量:
      • 新建系统变量 JAVA_HOME,值为上述安装路径。
      • 编辑系统变量 Path,添加 %JAVA_HOME%\bin
    4. 验证:打开命令提示符,输入 java -version,若返回 Java 8 版本信息,说明安装成功。
  • Linux/Mac 系统

    1. Linux(Ubuntu/Debian):通过 apt 直接安装
      sudo apt update
      sudo apt install openjdk-8-jdk
      
    2. Mac 系统:通过 Homebrew 直接安装
      brew install adoptopenjdk8
      
    3. 配置环境变量(Linux/Mac 通用):
      • 编辑 ~/.bashrc~/.zshrc(根据终端类型选择)
        vi ~/.bashrc
        
      • 添加以下内容(需替换为实际的 Java 安装路径,可通过 update-alternatives --config java 查找)
        export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
        export PATH=$JAVA_HOME/bin:$PATH
        
      • 生效配置:source ~/.bashrc
    4. 验证:输入 java -version,返回 Java 8 版本信息即成功。

(2)第二步:下载并配置 Apache Spark

  1. 下载 Spark 核心包:前往 Apache Spark 官网,选择「Spark 3.5.0」,「Package Type」选择「Pre-built for Apache Hadoop 3.3 and later」,点击下载对应的 .tgz 压缩包(Windows 也下载该包,无需单独下载 Windows 版本)。
  2. 解压压缩包:
    • Windows:将压缩包解压到自定义路径(如 D:\Software\spark-3.5.0-bin-hadoop3.3,路径建议无中文、无空格)。
    • Linux/Mac:将压缩包解压到 /usr/local/~/Software/ 目录,示例命令:
      tar -zxvf spark-3.5.0-bin-hadoop3.3.tgz -C /usr/local/
      
  3. 配置 Spark 环境变量:
    • Windows:
      1. 新建系统变量 SPARK_HOME,值为 Spark 解压路径(如 D:\Software\spark-3.5.0-bin-hadoop3.3)。
      2. 编辑系统变量 Path,添加 %SPARK_HOME%\bin%SPARK_HOME%\python\pyspark
    • Linux/Mac:
      1. 编辑 ~/.bashrc~/.zshrc,添加以下内容(替换为实际解压路径):
      export SPARK_HOME=/usr/local/spark-3.5.0-bin-hadoop3.3
      export PATH=$SPARK_HOME/bin:$SPARK_HOME/python/pyspark:$PATH
      
      1. 生效配置:source ~/.bashrc

(3)第三步:安装 PySpark 包

打开命令提示符/终端,输入以下 pip 命令直接安装,会自动匹配对应版本的依赖包:

pip install pyspark

3. 验证安装成功(两种方式)

(1)方式一:运行 pyspark shell

打开命令提示符/终端,直接输入 pyspark,若出现 Spark 启动界面(包含 Spark 版本、Java 版本信息,以及 >>> 提示符),说明安装成功。

  • 退出 shell:输入 exit() 或按 Ctrl+D

(2)方式二:运行 Python 脚本验证

创建一个名为 pyspark_hello.py 的脚本,写入以下代码:

# 验证 PySpark 安装成功
from pyspark.sql import SparkSession

# 创建 Spark 会话(核心入口)
spark = SparkSession.builder \
    .appName("PySpark_Hello_World") \
    .master("local[*]")  # 本地模式,使用所有可用核心
    .getOrCreate()

# 打印 Spark 版本信息
print(f"PySpark 版本:{spark.version}")

# 停止 Spark 会话
spark.stop()

运行脚本:python pyspark_hello.py,若成功打印 PySpark 版本信息,无报错,说明环境搭建完成。

4. 核心概念:PySpark 运行模式说明

  • 本地模式(local[*]):本文所有实操均使用该模式,无需搭建集群,适合开发、测试、小规模数据处理,local[*] 表示使用本地所有可用 CPU 核心。
  • 集群模式(Standalone/YARN/Mesos):适合生产环境的海量数据处理,需额外搭建 Spark 集群或集成 Hadoop YARN,后续进阶可深入学习。

二、核心基础:PySpark 核心架构与数据结构

在进行实操前,先明确 PySpark 的核心架构与两大核心数据结构,避免后续操作只知其然不知其所以然。

1. PySpark 核心架构(分布式计算的核心)

PySpark 基于 Apache Spark 架构,核心分为「Driver 端」和「Executor 端」:

  • Driver 端:运行 Python 脚本的主节点,负责创建 Spark 会话、提交任务、协调资源,本文中即本地运行脚本的电脑。
  • Executor 端:负责执行具体的分布式计算任务,每个 Executor 对应一个计算节点,本地模式下 Executor 运行在本地进程中。
  • 核心入口SparkSession(新版 PySpark 推荐),替代了传统的 SparkContextSQLContext,统一了结构化数据、非结构化数据的处理入口。

2. 两大核心数据结构:RDD vs DataFrame

PySpark 提供两种核心数据结构,分别适用于不同场景,两者的核心差异如下:

数据结构核心特点适用场景效率操作方式
RDD(弹性分布式数据集)底层分布式数据结构,无结构化约束,支持复杂数据类型非结构化数据处理、复杂算法实现、底层分布式操作较低(无优化)转换操作(map/filter)+ 行动操作(count/collect)
DataFrame(结构化数据框)高层结构化数据结构,类似 Pandas DataFrame,带列名和数据类型结构化数据处理、数据清洗、数据分析、Spark SQL较高(支持 Catalyst 优化器)面向列的操作、SQL 查询

实际工作中优先使用 DataFrame,效率更高、操作更简洁,RDD 仅在处理复杂非结构化数据(如文本、图片)时使用。

三、核心实操1:RDD 基础操作(创建、转换、行动)

RDD 是 PySpark 的底层数据结构,理解 RDD 的操作逻辑是掌握 PySpark 分布式计算的基础。RDD 操作分为两类:转换操作(懒执行,不立即返回结果)行动操作(立即执行,返回结果到 Driver 端)

1. RDD 核心操作函数与语法

(1)创建 RDD(两种常用方式)

  • 并行化本地集合:spark.sparkContext.parallelize(collection, numSlices=None)
    • collection:本地列表、元组等可迭代对象。
    • numSlices:分区数,默认根据本地核心数自动分配。
  • 读取外部文件:spark.sparkContext.textFile(file_path)
    • file_path:本地文件路径或 HDFS 路径,支持 .txt 等文本文件。

(2)转换操作(常用)

函数功能说明示例
map(func)对 RDD 中每个元素应用 func 函数,返回新 RDDrdd.map(lambda x: x*2)
filter(func)筛选出 func 返回 True 的元素,返回新 RDDrdd.filter(lambda x: x > 10)
flatMap(func)map 再扁平化,将嵌套迭代对象展开rdd.flatMap(lambda x: x.split(" "))
reduceByKey(func)对 Key-Value 类型 RDD 按 Key 分组,对 Value 应用 func 聚合rdd.reduceByKey(lambda a, b: a + b)

(3)行动操作(常用)

函数功能说明示例
count()返回 RDD 中元素的个数rdd.count()
collect()返回 RDD 中所有元素(注意:数据量大时禁止使用,会撑爆 Driver 内存)rdd.collect()
first()返回 RDD 中第一个元素rdd.first()
saveAsTextFile(file_path)将 RDD 结果保存为文本文件rdd.saveAsTextFile("./rdd_result.txt")
reduce(func)对 RDD 中所有元素应用 func 进行聚合rdd.reduce(lambda a, b: a + b)

2. 完整实操代码:RDD 综合操作演示

# PySpark RDD 核心操作演示
from pyspark.sql import SparkSession

# 1. 创建 Spark 会话(Driver 端核心)
spark = SparkSession.builder \
    .appName("PySpark_RDD_Demo") \
    .master("local[2]")  # 本地模式,使用 2 个核心
    .getOrCreate()

# 2. 获取 SparkContext(创建 RDD 所需)
sc = spark.sparkContext

# 3. 方式一:从本地列表创建 RDD
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
rdd_nums = sc.parallelize(nums, numSlices=2)  # 分为 2 个分区
print("RDD 元素个数:", rdd_nums.count())
print("RDD 第一个元素:", rdd_nums.first())
print("RDD 所有元素:", rdd_nums.collect())

# 4. RDD 转换操作(map + filter)
# 步骤1:所有元素乘以 2
rdd_map = rdd_nums.map(lambda x: x * 2)
# 步骤2:筛选出大于 10 的元素
rdd_filter = rdd_map.filter(lambda x: x > 10)
print("转换后 RDD 所有元素:", rdd_filter.collect())

# 5. RDD 聚合操作(reduce + reduceByKey)
# (1)reduce 聚合:所有元素求和
sum_result = rdd_nums.reduce(lambda a, b: a + b)
print("RDD 元素求和结果:", sum_result)

# (2)reduceByKey 聚合:先创建 Key-Value 类型 RDD,再按 Key 求和
kv_data = [("a", 1), ("b", 2), ("a", 3), ("b", 4), ("c", 5)]
rdd_kv = sc.parallelize(kv_data)
rdd_kv_agg = rdd_kv.reduceByKey(lambda a, b: a + b)
print("Key-Value RDD 聚合结果:", rdd_kv_agg.collect())

# 6. 方式二:读取外部文本文件创建 RDD(创建一个 test.txt 文件放在脚本同一目录下)
try:
    rdd_text = sc.textFile("./test.txt")
    print("文本文件 RDD 前 5 行:", rdd_text.take(5))  # take(5) 返回前 5 个元素,避免数据量过大
except Exception as e:
    print("读取文本文件失败:", e)
    print("请确保脚本同一目录下存在 test.txt 文件")

# 7. 保存 RDD 结果到本地文件
rdd_filter.saveAsTextFile("./rdd_filter_result")
print("RDD 筛选结果已保存到 ./rdd_filter_result 目录")

# 8. 停止 Spark 会话,释放资源
spark.stop()

3. 运行结果说明

  1. 运行脚本后,会先创建 Spark 会话,从本地列表创建 RDD,打印 RDD 的元素个数、第一个元素和所有元素。
  2. 经过 mapfilter 转换操作后,返回所有乘以 2 且大于 10 的元素。
  3. 通过 reduce 实现元素求和,通过 reduceByKey 实现按 Key 分组聚合。
  4. 尝试读取本地 test.txt 文件(需自行创建),返回前 5 行内容。
  5. 最后将筛选后的 RDD 结果保存到 ./rdd_filter_result 目录(会生成多个分区文件和元数据文件,这是分布式存储的特性)。

4. 避坑提醒

  1. collect() 函数会将 RDD 所有数据拉取到 Driver 端,若 RDD 数据量过大(如千万级以上),会导致 Driver 内存溢出,生产环境中尽量避免使用,可使用 take(n)sample() 查看部分数据。
  2. 保存 RDD 结果时,指定的输出目录不能提前存在,否则会报错,需先删除原有目录。
  3. RDD 转换操作是「懒执行」,只有调用行动操作时,才会真正执行所有转换操作,这是 Spark 优化分布式计算的核心特性。

四、核心实操2:DataFrame 与 Spark SQL(实际工作首选)

DataFrame 是 PySpark 中处理结构化数据的核心,类似 Pandas DataFrame,支持列名、数据类型约束,且自带 Catalyst 优化器,效率远高于 RDD。Spark SQL 则是基于 DataFrame 的结构化查询语言,支持标准 SQL 语法,适合数据分析人员快速上手。

1. 核心操作:创建 DataFrame(三种常用方式)

(1)方式一:从本地列表/字典创建

# 从列表创建 DataFrame
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, StringType, IntegerType

spark = SparkSession.builder.appName("Create_DataFrame_Demo").master("local[*]").getOrCreate()

# 定义数据列表
data = [("Alice", 25, "Female", "New York"),
        ("Bob", 30, "Male", "London"),
        ("Charlie", 35, "Male", "Paris"),
        ("David", 40, "Male", "Tokyo"),
        ("Eve", 45, "Female", "Sydney")]

# 方式1:直接指定列名
df1 = spark.createDataFrame(data, schema=["Name", "Age", "Gender", "City"])
print("DataFrame 1 结构:")
df1.printSchema()  # 打印数据结构(列名、数据类型)
print("DataFrame 1 前 3 行:")
df1.show(3)  # 显示前 3 行数据,默认显示 20 行

# 方式2:自定义结构化 schema(更严谨,推荐生产环境使用)
schema = StructType([
    StructField("Name", StringType(), nullable=False),  # nullable=False 表示该列不允许为空
    StructField("Age", IntegerType(), nullable=True),
    StructField("Gender", StringType(), nullable=True),
    StructField("City", StringType(), nullable=True)
])

df2 = spark.createDataFrame(data, schema=schema)
print("DataFrame 2 结构:")
df2.printSchema()
print("DataFrame 2 所有数据:")
df2.show()

spark.stop()

(2)方式二:读取外部文件(CSV/JSON/Parquet)

# 读取 CSV 文件创建 DataFrame(最常用)
from pyspark.sql import SparkSession

spark = SparkSession.builder.appName("Read_File_DataFrame").master("local[*]").getOrCreate()

# 读取 CSV 文件(带表头)
df_csv = spark.read \
    .option("header", "true")  # 第一行作为列名
    .option("inferSchema", "true")  # 自动推断数据类型(开发环境可用,生产环境推荐自定义 schema)
    .csv("./users.csv")  # 自定义 CSV 文件路径

print("CSV 文件 DataFrame 结构:")
df_csv.printSchema()
print("CSV 文件 DataFrame 前 5 行:")
df_csv.show(5)

# 读取 JSON 文件
try:
    df_json = spark.read.json("./users.json")
    print("JSON 文件 DataFrame 前 5 行:")
    df_json.show(5)
except Exception as e:
    print("读取 JSON 文件失败:", e)

spark.stop()

(3)方式三:从 Pandas DataFrame 转换

# 从 Pandas DataFrame 转换为 PySpark DataFrame
from pyspark.sql import SparkSession
import pandas as pd

spark = SparkSession.builder.appName("Pandas_To_PySpark").master("local[*]").getOrCreate()

# 创建 Pandas DataFrame
pd_df = pd.DataFrame({
    "Name": ["Alice", "Bob", "Charlie"],
    "Age": [25, 30, 35],
    "City": ["New York", "London", "Paris"]
})

# 转换为 PySpark DataFrame
ps_df = spark.createDataFrame(pd_df)
print("从 Pandas 转换的 DataFrame 结构:")
ps_df.printSchema()
print("从 Pandas 转换的 DataFrame 数据:")
ps_df.show()

spark.stop()

2. DataFrame 基础操作(筛选、排序、分组聚合)

# PySpark DataFrame 基础操作演示
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, avg, count

spark = SparkSession.builder.appName("DataFrame_Basic_Operations").master("local[*]").getOrCreate()

# 1. 创建 DataFrame
data = [("Alice", 25, "Female", "New York"),
        ("Bob", 30, "Male", "London"),
        ("Charlie", 35, "Male", "Paris"),
        ("David", 40, "Male", "Tokyo"),
        ("Eve", 45, "Female", "Sydney"),
        ("Fiona", 28, "Female", "New York")]

df = spark.createDataFrame(data, schema=["Name", "Age", "Gender", "City"])

# 2. 数据筛选:筛选出年龄大于 30 且性别为 Female 的数据
df_filtered = df.filter((col("Age") > 30) & (col("Gender") == "Female"))
print("筛选后数据:")
df_filtered.show()

# 3. 数据排序:按年龄降序排序,再按姓名升序排序
df_sorted = df.sort(col("Age").desc(), col("Name").asc())
print("排序后数据:")
df_sorted.show()

# 4. 分组聚合:按城市分组,统计每个城市的人数和平均年龄
df_grouped = df.groupBy("City") \
    .agg(
        count("Name").alias("Person_Count"),  # 统计人数,并重命名列
        avg("Age").alias("Average_Age")  # 计算平均年龄,并重命名列
    )
print("分组聚合后数据:")
df_grouped.show()

# 5. 数据选择:只选择 Name 和 Age 两列
df_selected = df.select("Name", "Age")
print("选择指定列数据:")
df_selected.show(3)

# 6. 缺失值处理(填充/删除)
# 先添加一条含缺失值的数据
from pyspark.sql import Row
df_with_null = df.union(spark.createDataFrame([Row(Name="Grace", Age=None, Gender="Female", City="London")]))
print("含缺失值的数据:")
df_with_null.show()

# 填充缺失值(将 Age 列的 null 填充为 0)
df_fill_null = df_with_null.fillna({"Age": 0})
print("填充缺失值后的数据:")
df_fill_null.show()

# 删除含缺失值的行
df_drop_null = df_with_null.dropna(subset=["Age"])
print("删除缺失值后的数据:")
df_drop_null.show()

spark.stop()

3. Spark SQL 操作(标准 SQL 语法)

# PySpark Spark SQL 操作演示
from pyspark.sql import SparkSession

spark = SparkSession.builder.appName("Spark_SQL_Demo").master("local[*]").getOrCreate()

# 1. 创建 DataFrame
data = [("Alice", 25, "Female", "New York"),
        ("Bob", 30, "Male", "London"),
        ("Charlie", 35, "Male", "Paris")]

df = spark.createDataFrame(data, schema=["Name", "Age", "Gender", "City"])

# 2. 创建临时视图(支持 SQL 查询,视图仅在当前 Spark 会话中有效)
df.createOrReplaceTempView("user_info")  # createOrReplaceTempView:若视图已存在则替换

# 3. 执行标准 SQL 查询
# 示例1:查询所有数据
sql_result1 = spark.sql("SELECT * FROM user_info")
print("SQL 查询所有数据:")
sql_result1.show()

# 示例2:查询年龄大于 28 的男性用户
sql_result2 = spark.sql("SELECT Name, Age, City FROM user_info WHERE Age > 28 AND Gender = 'Male'")
print("SQL 查询年龄大于 28 的男性用户:")
sql_result2.show()

# 示例3:按城市分组,统计人数和平均年龄
sql_result3 = spark.sql("""
    SELECT City, COUNT(Name) AS Person_Count, AVG(Age) AS Average_Age
    FROM user_info
    GROUP BY City
""")
print("SQL 分组聚合结果:")
sql_result3.show()

# 4. 创建全局临时视图(跨 Spark 会话有效,需通过 global_temp. 前缀访问)
df.createGlobalTempView("global_user_info")

# 5. 访问全局临时视图
sql_result4 = spark.sql("SELECT * FROM global_temp.global_user_info")
print("访问全局临时视图数据:")
sql_result4.show()

spark.stop()

4. 运行结果说明

  1. DataFrame 操作中,通过 filter 实现数据筛选,sort 实现排序,groupBy 结合 agg 实现分组聚合,支持缺失值的填充与删除。
  2. Spark SQL 操作中,先将 DataFrame 注册为临时视图,再通过 spark.sql() 执行标准 SQL 语句,结果返回为 DataFrame,支持后续进一步处理。
  3. 临时视图(createOrReplaceTempView)仅在当前 Spark 会话中有效,全局临时视图(createGlobalTempView)跨会话有效,需通过 global_temp. 前缀访问。

5. 核心优势总结

  1. DataFrame 支持面向列的操作,语法更简洁,比 RDD 更易上手,适合大多数结构化数据处理场景。
  2. Spark SQL 支持标准 SQL 语法,降低了数据分析人员的学习成本,可直接复用传统数据库的查询经验。
  3. DataFrame 自带 Catalyst 优化器,会自动优化查询计划,提升分布式计算效率,无需手动优化。

五、综合实战:用户行为数据大数据分析

结合前面的知识点,实现一个完整的 PySpark 大数据分析案例:用户电商行为数据清洗与分析,流程包括「数据读取→数据清洗→数据分析→结果保存→结果可视化」。

完整实战代码

# PySpark 综合实战:用户电商行为数据清洗与分析
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, count, sum, avg, datediff, current_date

# 1. 创建 Spark 会话
spark = SparkSession.builder \
    .appName("Ecommerce_User_Behavior_Analysis") \
    .master("local[*]") \
    .getOrCreate()

# 2. 步骤1:模拟生成用户行为数据(实际场景中读取外部 CSV/Parquet 文件)
user_behavior_data = [
    ("U001", "P001", "2025-12-01", "click", 1, 99.9),
    ("U001", "P001", "2025-12-02", "purchase", 1, 99.9),
    ("U002", "P002", "2025-12-01", "click", 2, 199.9),
    ("U002", "P002", "2025-12-03", "purchase", 1, 199.9),
    ("U003", "P001", "2025-12-02", "click", 1, 99.9),
    ("U003", "P003", "2025-12-04", "click", 3, 299.9),
    ("U004", None, "2025-12-01", "click", 1, None),  # 含缺失值
    ("U005", "P002", "2025-12-05", "purchase", 2, 199.9),
    ("U001", "P002", "2025-12-03", "click", 1, 199.9),
    ("U002", "P003", "2025-12-06", "click", 2, 299.9)
]

# 创建 DataFrame 并定义 schema
df_behavior = spark.createDataFrame(
    user_behavior_data,
    schema=["User_ID", "Product_ID", "Behavior_Date", "Behavior_Type", "Click_Count", "Product_Price"]
)

print("原始用户行为数据:")
df_behavior.show()
print("原始数据结构:")
df_behavior.printSchema()

# 3. 步骤2:数据清洗(处理缺失值、数据类型转换、筛选有效数据)
# 3.1 删除 Product_ID 或 Product_Price 为空的数据
df_cleaned = df_behavior.dropna(subset=["Product_ID", "Product_Price"])

# 3.2 转换数据类型(Behavior_Date 转为日期类型,Click_Count 转为整数类型)
df_cleaned = df_cleaned \
    .withColumn("Behavior_Date", col("Behavior_Date").cast("date")) \
    .withColumn("Click_Count", col("Click_Count").cast("int")) \
    .withColumn("Product_Price", col("Product_Price").cast("double"))

# 3.3 筛选有效行为类型(仅保留 click 和 purchase)
df_cleaned = df_cleaned.filter(col("Behavior_Type").isin(["click", "purchase"]))

# 3.4 计算行为距离当前日期的天数
df_cleaned = df_cleaned.withColumn("Days_Since_Behavior", datediff(current_date(), col("Behavior_Date")))

print("数据清洗后的数据:")
df_cleaned.show()

# 4. 步骤3:数据分析(核心业务指标)
# 4.1 指标1:按用户分组,统计每个用户的点击次数和购买次数
df_user_analysis = df_cleaned.groupBy("User_ID") \
    .agg(
        count(col("Behavior_Type").when(col("Behavior_Type") == "click")).alias("Total_Click_Count"),
        count(col("Behavior_Type").when(col("Behavior_Type") == "purchase")).alias("Total_Purchase_Count"),
        sum(col("Product_Price").when(col("Behavior_Type") == "purchase")).alias("Total_Purchase_Amount")
    ) \
    .fillna({"Total_Purchase_Amount": 0})  # 未购买的用户,购买金额填充为 0

print("用户点击与购买统计:")
df_user_analysis.show()

# 4.2 指标2:按商品分组,统计每个商品的点击量、购买量和销售额
df_product_analysis = df_cleaned.groupBy("Product_ID") \
    .agg(
        sum("Click_Count").alias("Total_Click_Volume"),
        count(col("Behavior_Type").when(col("Behavior_Type") == "purchase")).alias("Total_Purchase_Volume"),
        sum(col("Product_Price").when(col("Behavior_Type") == "purchase")).alias("Total_Sales_Amount")
    )

print("商品销售统计:")
df_product_analysis.show()

# 4.3 指标3:计算商品转化率(购买量 / 点击量)
df_conversion = df_product_analysis \
    .withColumn("Conversion_Rate", col("Total_Purchase_Volume") / col("Total_Click_Volume")) \
    .orderBy(col("Conversion_Rate").desc())

print("商品转化率统计(降序):")
df_conversion.show()

# 5. 步骤4:保存分析结果(保存为 CSV 文件和 Parquet 文件)
# 5.1 保存用户分析结果
df_user_analysis.write \
    .option("header", "true") \
    .mode("overwrite")  # 覆盖已有文件
    .csv("./ecommerce_user_analysis.csv")

# 5.2 保存商品转化率结果(Parquet 格式,高效压缩,适合大数据存储)
df_conversion.write \
    .mode("overwrite") \
    .parquet("./ecommerce_product_conversion.parquet")

print("分析结果已保存:")
print("1. 用户分析结果:./ecommerce_user_analysis.csv")
print("2. 商品转化率结果:./ecommerce_product_conversion.parquet")

# 6. 步骤5:结果可视化(转换为 Pandas DataFrame,使用 Matplotlib 绘图)
try:
    import pandas as pd
    import matplotlib.pyplot as plt

    # 转换 PySpark DataFrame 为 Pandas DataFrame
    pd_conversion = df_conversion.toPandas()

    # 绘制商品转化率柱状图
    plt.rcParams["font.sans-serif"] = ["SimHei"]  # 支持中文显示
    plt.figure(figsize=(10, 6))
    plt.bar(pd_conversion["Product_ID"], pd_conversion["Conversion_Rate"], color="skyblue")
    plt.xlabel("商品 ID")
    plt.ylabel("转化率")
    plt.title("PySpark 商品转化率分析结果")
    plt.grid(axis="y", alpha=0.7)
    plt.savefig("./ecommerce_conversion_rate.png")
    plt.show()

    print("转化率可视化图表已保存为 ./ecommerce_conversion_rate.png")
except ImportError as e:
    print("缺少可视化依赖包:", e)
    print("请安装 pandas 和 matplotlib:pip install pandas matplotlib")
except Exception as e:
    print("可视化失败:", e)

# 7. 停止 Spark 会话
spark.stop()

运行效果说明

  1. 脚本首先模拟生成用户电商行为数据,包含点击、购买等行为,以及部分缺失值。
  2. 数据清洗阶段,处理缺失值、转换数据类型、筛选有效行为,计算行为距离当前日期的天数。
  3. 数据分析阶段,统计用户点击/购买指标、商品销售指标、商品转化率,满足电商业务的核心分析需求。
  4. 分析结果保存为 CSV 文件(易读取)和 Parquet 文件(高效压缩,适合大数据场景)。
  5. 最后将转换结果转为 Pandas DataFrame,使用 Matplotlib 绘制商品转化率柱状图,实现可视化验证。

六、常见问题与避坑指南(新手必备)

  1. 环境配置错误:Java 版本不兼容

    • 现象:运行 pyspark 或脚本时,提示「Unsupported major.minor version」或 Java 相关报错。
    • 解决:卸载高版本 Java,安装 Java 8,重新配置 JAVA_HOME 环境变量。
  2. Spark 会话创建失败:端口被占用

    • 现象:提示「Address already in use」。
    • 解决:关闭占用端口的进程,或重启电脑,也可在创建 Spark 会话时指定端口:spark.driver.port=xxxx
  3. 数据读取失败:文件路径问题

    • 现象:读取本地文件时提示「File not found」。
    • 解决:① 使用绝对路径(如 D:\ecommerce\users.csv);② 相对路径需确保脚本与文件在同一目录;③ Windows 路径使用 /\\ 转义。
  4. Driver 内存溢出:collect() 滥用

    • 现象:处理大数据时,调用 collect() 提示「OutOfMemoryError」。
    • 解决:避免使用 collect(),改用 take(n)sample() 查看部分数据,或直接在 Executor 端完成聚合后再返回结果。
  5. DataFrame 数据类型错误:无法进行聚合操作

    • 现象:执行 sum()avg() 时提示「cannot resolve 'xxx' due to data type mismatch」。
    • 解决:使用 withColumn().cast() 转换数据类型为数值类型(int/double)。
  6. 中文乱码问题:保存 CSV 文件或可视化时中文显示异常

    • 解决:① 保存 CSV 时添加编码配置:.option("encoding", "UTF-8");② 可视化时设置 Matplotlib 支持中文。
  7. PySpark 版本与 Spark 核心包版本不一致

    • 现象:运行脚本时提示「Version Mismatch」。
    • 解决:确保 pip install pyspark 的版本与下载的 Spark 核心包版本一致(如均为 3.5.0)。