ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PySpark 入门实战:从 DataFrame 到 SQL、DSL 与窗口函数

PySpark 入门实战:从 DataFrame 到 SQL、DSL 与窗口函数

1. 引言

PySpark 是 Spark 的 Python 接口,它让我们可以用熟悉的 Python 语法操作分布式数据集。本文从一个最简单的 JSON 读取示例出发,逐步带你掌握 DataFrame 的创建、SQL 查询、DSL 风格操作、窗口函数以及炸裂函数等核心用法。无论你是刚接触大数据的新手,还是想快速回顾 PySpark 常用 API 的开发者,这篇文章都能给你一份可直接运行的实战参考。

2. 环境准备与第一个 DataFrame

在开始之前,请确保你的环境中已经安装好 PySpark,并准备好一份测试用的 JSON 数据文件。下面是最基础的读取与展示代码:

from pyspark.sql import SparkSession if __name__ == '__main__': # 创建环境 spark = SparkSession.builder.master("local[*]").appName("第一个DF").getOrCreate() # 读取数据 dataFrame = spark.read.json(r"C:\pythoncode\shixun\pyspark_code\day03\data\students.json") # 打印数据 dataFrame.show() # 关闭环境 spark.stop()

很多初学者会混淆print(dataFrame)和dataFrame.show()的区别。这里需要特别说明:

  • print(dataFrame):只是 Python 层面的打印。DataFrame 在未触发行动算子前,只是一个“逻辑计划”,数据并没有真正被拉取到你的驱动程序(Driver)内存里。print 打印出来的是“对象地址”,而不是数据。
  • dataFrame.show():这是一个 Action。它不仅会把数据从各个分布式节点拉取汇总到 Driver 端,还会对数据进行格式化排版。如果数据量特别大,show() 也只会安全地取前 20 条,不会把内存撑爆。

简单来说,show() 是专门为 DataFrame 设计的“格式化展示”方法,而 print 只是 Python 对象的默认字符串表示。

3. 临时视图与 SQL 风格查询

Spark SQL 允许我们用熟悉的 SQL 语法来查询 DataFrame。但 SQL 是字符串,字符串本身不知道怎么操作对象,只能通过“表名”去找数据。因此,我们需要先把 DataFrame 注册成一张临时表:

from pyspark.sql import SparkSession if __name__ == '__main__': # 创建环境 spark = SparkSession.builder.appName("sql").master("local[*]").getOrCreate() # 读取数据 dataFrame = spark.read.json(r"C:\pythoncode\shixun\pyspark_code\day03\data\students.json") # 创建临时表:因为临时表虽然不存“数据本身”,但它存了“获取数据的路线图(逻辑计划) # 数据一直都在你的 JSON 文件里,临时表只是帮你搭了一座通向这些数据的“查询桥梁”。 dataFrame.createOrReplaceTempView('students') # 查询数据,使用 sql 风格 df = spark.sql("select * from students") df.show() # 关闭 spark.stop()

关于临时表,有几个关键点需要理解:

  • 物理位置:不在磁盘,在 Driver 端的内存里(逻辑映射)。
  • 生命周期:随着 SparkSession 的创建而存在,随着 spark.stop() 而消失。
  • 内容:存的不是数据,而是“表名 -> 数据来源(DataFrame 计算逻辑)”的对应关系。

换句话说,数据一直都在你的 JSON 文件里,临时表只是帮你搭了一座通向这些数据的“查询桥梁”。

4. DSL 风格操作

除了 SQL,PySpark 还提供了更贴近编程思维的 DSL(Domain Specific Language)风格。DSL 直接通过对象引用来操作数据,不需要建表映射:

from pyspark.sql import SparkSession if __name__ == '__main__': # 创建环境 spark = SparkSession.builder.appName('DSL').master('local[*]').getOrCreate() # 读取数据 dataFrame = spark.read.json(r"C:\pythoncode\shixun\pyspark_code\day03\data\students.json") # 查看数据结构 # dataFrame.printSchema() # 查询学生并使 age + 10 dataFrame.select("name", (dataFrame.age + 10).alias('age')).show() # alias 类似 as 起别名 # 查询 age 大于 20 的学生信息 dataFrame.where(dataFrame.age > 20).show() # 按照年龄分组,求 count dataFrame.groupby('age').count().show()

这里可以做一个对比总结:

  • SQL 是“字符串”:字符串不知道怎么操作对象,只能通过“表名”去找数据。
  • DSL 是“代码”:代码可以直接通过“变量名”操作对象。

SQL 作为声明式语言,必须通过字符串和元数据(Catalog)来定位数据;DSL 作为命令式/函数式语言,直接通过对象引用(Reference)来操作数据。这就是为什么一个需要建表映射,一个直接就能用。

5. 窗口函数实战

窗口函数(Window Function)是数据分析中非常强大的工具,常用于分组排名、累计求和等场景。下面我们通过一个学生成绩表来体验 row_number 的用法:

from pyspark.sql import SparkSession, DataFrame, Column, Window, functions from pyspark import SparkConf from pyspark.sql.types import StructType, StructField, StringType, IntegerType if __name__ == '__main__': spark = SparkSession.builder.master('local[*]').appName('windows').getOrCreate() # 梳理表结构 schema schema1 = StructType([ StructField(name="id", dataType=StringType(), nullable=False), StructField(name="name", dataType=StringType(), nullable=False), StructField(name="score", dataType=IntegerType(), nullable=False), StructField(name="country", dataType=StringType(), nullable=False), ]) df1 = spark.read.option("header", True).schema(schema1).csv("C:\pythoncode\shixun\pyspark_code\day03\data\student.csv") # 加个 option 是因为数据里面有表头 df1.printSchema() df1.show() # withColumn:通过添加一列或替换具有相同名称的现有列,返回一个新的 DataFrame。 df2 = df1.withColumn('rnk', functions.row_number().over( Window.partitionBy(df1.country).orderBy(functions.desc('score')) )) df2.where(df2.rnk <= 3).select('id', df2.name, df2.score, df2.country).show()

这里有几个要点:

  • 为什么 CSV 要手动指定 schema:不是“因为读了 CSV 必须写”,而是“因为 CSV 本身没结构,如果不手动写,后续的数值计算、SQL 查询就会出大问题”。读 Parquet、JSON、TXT 都不用建 schema。
  • withColumn:用于添加一列或替换具有相同名称的现有列,返回一个新的 DataFrame。
  • Window.partitionBy:按 country 分组,在每个组内按 score 降序排名。

6. 炸裂函数与 WordCount 实战

炸裂函数(explode)是处理一对多关系的利器,最常见的应用就是 WordCount。下面分别用 SQL 和 DSL 两种方式实现:

from pyspark import SparkConf from pyspark.sql import SparkSession, functions, DataFrame if __name__ == '__main__': # 创建环境 spark = SparkSession.builder.appName("func").master('local[*]').getOrCreate() # 读取数据 df1 = spark.read.text(r"C:\pythoncode\shixun\pyspark_code\day03\data\a.txt") # read.text() 这个方法的底层逻辑非常死板: # 它会把文件里的每一行都当成一个字符串, # 强行塞进一个名叫 value 的列里。 # 使用 sql(必须建表),写多行查询语句的时候要用三引号 # 在这个环境里,Spark 已经把所有内置函数(如 count、sum、split、explode) # 全部预先加载好了 df1.createTempView('t_words') spark.sql(""" select word, count(*) as c1 from (select explode(split(value, " ")) word from t_words) as t1 group by word """).show() # 使用 DSL # a.txt 里面的数据 word 不是表头 df2 = df1.withColumn('word', functions.explode(functions.split('value', " "))) df2.groupby("word").count().show()

关于value这个列名,需要特别提醒:它是.text()方法自带的固有列名。在你的 WordCount 代码里,你必须老老实实地用'value'去引出数据。在 SQL 里,value 既没有单引号也没有双引号,而在 Python DSL 里我们却经常写F.col("value")或者F.split("value", " ")。

7. 总结

本文通过五个实战示例,带你走完了 PySpark 最核心的入门路径:

  • DataFrame 基础:理解 print 与 show 的区别,掌握 JSON 读取。
  • 临时视图与 SQL:理解临时表的本质是“逻辑映射”,学会用 SQL 查询。
  • DSL 风格:对比 SQL 与 DSL 的差异,掌握 select、where、groupby 等操作。
  • 窗口函数:用 row_number 实现分组排名,理解 schema 对 CSV 的重要性。
  • 炸裂函数:用 explode 实现 WordCount,掌握 SQL 与 DSL 两种写法。

建议你亲手运行每一段代码,观察输出结果,再尝试修改参数(比如把 show() 改成 show(5),或者把 row_number 换成 rank),这样能更快建立对 PySpark 的直觉。后续可以继续深入学习 DataFrame 的 join、UDF 自定义函数、以及性能调优等内容。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进