Spark SQL进阶:解锁大数据处理的新姿势

news2025/6/2 8:02:47

目录

一、Spark SQL,为何进阶?

二、进阶特性深剖析

2.1 窗口函数:数据洞察的新视角

2.2 高级聚合:挖掘数据深度价值

2.3 自定义函数(UDF 和 UDTF):拓展功能边界

三、性能优化实战

3.1 数据分区与缓存策略

3.2 解决数据倾斜问题

3.3 合理配置 Spark 参数

四、实际项目案例

4.1 项目背景与数据介绍

4.2 Spark SQL 进阶应用

4.3 优化过程与效果展示

五、总结与展望


一、Spark SQL,为何进阶?

在大数据的广袤领域中,数据量正以惊人的速度增长,处理需求也变得日益复杂。想象一下,一家超大型电商企业,每天要处理数以亿计的订单数据、用户浏览记录以及商品信息。这些数据不仅规模庞大,而且结构复杂,有结构化的订单表格数据,也有非结构化的用户评价文本。企业需要从这些数据中快速分析出销售趋势、用户偏好,以便及时调整营销策略和商品库存。

在这样的大数据处理场景下,基础的 Spark SQL 功能渐渐有些力不从心。从性能层面来看,当数据量达到 PB 级,简单的查询操作也可能变得异常缓慢。比如对全量用户数据进行多表关联查询,基础的 Spark SQL 配置可能会因为内存不足或资源分配不合理,导致任务长时间运行甚至失败。而且在面对复杂查询时,像涉及多层嵌套子查询、窗口函数与复杂聚合函数组合使用的场景,基础功能很难高效地生成最优执行计划。这就好比驾驶一辆普通轿车在崎岖的山路上行驶,动力和操控都难以满足需求,所以进阶学习 Spark SQL 迫在眉睫。

二、进阶特性深剖析

2.1 窗口函数:数据洞察的新视角

窗口函数,在 Spark SQL 中是一个强大的工具,它为数据分析提供了全新的视角。与普通聚合函数不同,窗口函数可以在不改变数据行数的前提下,对数据进行基于“窗口”的计算。简单来说,窗口就是一个数据的子集,这个子集可以是按照某一列进行分区后的一组数据,也可以是按照一定顺序排列的连续数据行。

以电商领域中计算用户在一段时间内的累计消费金额为例。假设有一个包含用户 ID、消费日期和消费金额的订单表,使用窗口函数,我们可以轻松地计算出每个用户在每天的累计消费金额。在 Spark SQL 中,实现代码如下:

SELECT
  user_id,
  order_date,
  amount,
  SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS cumulative_amount
FROM
  orders;

在这段代码中,SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) 就是窗口函数的应用。PARTITION BY user_id 表示按照用户 ID 进行分区,每个用户的数据会被划分到不同的窗口中;ORDER BY order_date 则是在每个分区内按照消费日期进行排序。这样,SUM(amount) 就会在每个用户的分区内,按照日期顺序累计计算消费金额。窗口函数的优势在于,它能在保留原始数据行的基础上,进行复杂的计算,比如计算移动平均值、排名等,这为深入的数据洞察提供了便利。

2.2 高级聚合:挖掘数据深度价值

在数据分析中,普通的聚合函数,如 SUM、AVG、COUNT 等,虽然能满足一些基本的统计需求,但在面对复杂的多维数据分析时,往往显得力不从心。比如,当我们想要从多个维度对数据进行聚合分析时,普通聚合函数就需要编写大量的重复代码,而且效率较低。

这时,GROUPING SETS、CUBE、ROLLUP 等高级聚合操作就派上了用场。GROUPING SETS 允许我们在一次查询中指定多个聚合维度。比如在电商数据分析中,我们既想按商品类别统计销售额,又想按销售地区统计销售额,使用 GROUPING SETS 可以这样实现:

SELECT
  product_category,
  sales_region,
  SUM(sales_amount) AS total_sales
FROM
  sales_data
GROUP BY
  GROUPING SETS ((product_category), (sales_region));

CUBE 操作则更为强大,它会生成所有可能的维度组合的聚合结果。例如:

SELECT
  product_category,
  sales_region,
  SUM(sales_amount) AS total_sales
FROM
  sales_data
GROUP BY
  CUBE (product_category, sales_region);

这会得到按商品类别和销售地区的所有组合的销售额统计,包括按商品类别汇总、按销售地区汇总以及两者交叉汇总。ROLLUP 操作类似于 CUBE,但它是按照指定维度的层次结构进行聚合,生成的结果是一种更有层次的汇总数据。通过这些高级聚合操作,我们可以在一次查询中获取丰富的多维数据分析结果,大大提高了数据分析的效率和深度。

2.3 自定义函数(UDF 和 UDTF):拓展功能边界

在 Spark SQL 中,虽然内置了丰富的函数,但在实际应用中,我们常常会遇到一些特殊的业务逻辑,需要自定义函数来实现。自定义函数主要包括 UDF(User-Defined Function)和 UDTF(User-Defined Table-Generating Function)。

UDF 用于对单行数据进行处理,并返回一个标量值。比如,我们有一个需求,要将用户输入的字符串转换为特定格式,如将“hello world”转换为“Hello World”(首字母大写),就可以通过自定义 UDF 来实现。在 Scala 中,实现代码如下:

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions.udf

val spark = SparkSession.builder.appName("UDFExample").master("local").getOrCreate()
val capitalizeUDF = udf((input: String) => input.capitalize)
val data = Seq(("hello world"), ("scala is awesome"))
val df = spark.createDataFrame(data).toDF("input_string")
df.select(capitalizeUDF($"input_string").alias("capitalized_string")).show()

UDTF 则用于将一行数据转换为多行数据。例如,我们有一个字段存储了用户的多个爱好,以逗号分隔,如 “reading,writing,swimming”,现在需要将每个爱好拆分成单独的行,就可以使用 UDTF。在 Spark SQL 中,可以通过 explode 函数结合自定义逻辑来实现类似 UDTF 的功能:

import org.apache.spark.sql.functions.explode

val hobbiesData = Seq(("Alice", "reading,writing,swimming"))
val hobbiesDF = spark.createDataFrame(hobbiesData).toDF("user", "hobbies")
hobbiesDF.select($"user", explode(split($"hobbies", ",")).alias("hobby")).show()

UDF 和 UDTF 极大地拓展了 Spark SQL 的功能边界,使我们能够根据具体的业务需求,灵活地进行数据处理和分析。

三、性能优化实战

3.1 数据分区与缓存策略

在大数据处理中,数据分区是提升 Spark SQL 性能的关键手段。简单来说,数据分区就是将大规模的数据集合按照特定的规则划分成多个小的子集,每个子集就是一个分区。这样做的好处在于,当进行数据处理时,不同的分区可以并行处理,大大提高了处理效率。例如,在处理一个包含海量用户行为数据的表时,我们可以根据时间(如按天、按月)或用户 ID 的哈希值等作为分区键。如果按时间分区,查询某一天的用户行为数据时,就可以直接定位到对应的分区,而无需扫描整个数据集,极大地减少了数据读取量和处理时间。

缓存策略在 Spark SQL 中也起着举足轻重的作用。对于那些经常被查询的数据,将其缓存到内存中,可以避免重复读取磁盘,从而显著提高查询速度。比如,在一个电商数据分析系统中,商品的基本信息表(如商品名称、价格、库存等)是经常被查询的。我们可以使用以下代码将该表缓存起来:

val productInfoDF = spark.sql("SELECT * FROM product_info")
productInfoDF.cache()

这样,后续对 productInfoDF 的查询就可以直接从内存中获取数据,大大缩短了查询响应时间。需要注意的是,缓存数据会占用内存资源,所以要根据集群的内存情况和数据的使用频率,合理选择需要缓存的数据。

3.2 解决数据倾斜问题

数据倾斜是在 Spark SQL 处理过程中经常遇到的一个棘手问题。它指的是在数据分布上存在严重的不均衡,导致某些任务处理的数据量远远超过其他任务。这种情况会使得整个作业的执行效率大幅下降,因为整个作业的完成时间取决于处理数据量最大的那个任务。例如,在分析电商订单数据时,假设要按地区统计订单数量,如果某个地区的订单量特别大,而其他地区订单量相对较少,就会发生数据倾斜。

数据倾斜的产生原因主要是数据本身的分布特性以及所使用的操作。以电商订单按地区分析为例,可能由于某个地区举办了大型促销活动,导致该地区订单量暴增。在进行数据聚合或连接操作时,大量相同地区的数据会被分配到同一个任务中处理,从而引发数据倾斜。

针对数据倾斜问题,有多种解决方法。一种常见的方法是扩大 shuffle 分区数,通过增加分区数量,将原本集中在少数分区的数据分散到更多的分区中,从而减轻单个任务的负担。例如,可以在 Spark 配置中设置 spark.sql.shuffle.partitions 参数来增加分区数:

spark.conf.set("spark.sql.shuffle.partitions", "400")

另一种方法是使用随机前缀。对于那些数据量特别大的键值对,在进行 shuffle 操作前,给它们的键添加一个随机前缀,使它们分散到不同的分区中。比如,对于订单量特别大的地区,在处理前给该地区的订单数据的键添加随机数字前缀,这样原本集中的键就会分散到多个分区,避免了数据过度集中在少数任务中。

3.3 合理配置 Spark 参数

在 Spark SQL 中,合理配置参数是优化性能的重要环节。spark.sql.shuffle.partitions 参数决定了 shuffle 操作时的分区数量,如前文所述,适当增加该参数的值可以缓解数据倾斜问题,但如果设置过大,也会增加任务调度和管理的开销。一般来说,需要根据数据量、集群节点数量和每个节点的资源情况来综合调整。例如,在一个拥有 10 个节点,每个节点内存为 32GB,数据量为 1TB 的集群中处理数据时,可以先尝试将 spark.sql.shuffle.partitions 设置为 200,然后根据作业执行情况和性能指标(如执行时间、资源利用率等)进行微调。

spark.sql.broadcastTimeout 参数设置了广播变量的超时时间。在进行表连接操作时,如果一个表的数据量较小,Spark 会自动将其广播到各个节点,以避免数据在节点间传输。但如果广播过程中出现网络延迟等问题,可能会导致广播超时。通过合理设置这个参数,可以确保广播操作能够顺利完成。比如,在网络状况较好的集群中,可以将该参数设置为较短的时间(如 60 秒);而在网络不稳定的环境中,则需要适当延长超时时间(如 120 秒)。

此外,还有 spark.sql.inMemoryColumnarStorage.compressed 参数,用于控制是否对内存中的列存储数据进行压缩。开启压缩可以减少内存占用,但会增加一定的压缩和解压缩开销。对于内存资源紧张的集群,开启该参数可能会显著提高内存利用率,从而提升整体性能。例如,在处理包含大量文本数据的表时,开启压缩可以有效减少内存使用,同时由于文本数据的压缩率通常较高,压缩和解压缩的性能开销相对较小,整体上能够提升作业的执行效率。

四、实际项目案例

4.1 项目背景与数据介绍

在电商行业蓬勃发展的当下,数据驱动决策成为企业发展的关键。本次分析聚焦于某电商平台,随着业务的快速扩张,平台积累了海量的数据。企业希望通过对这些数据的深入分析,挖掘用户行为模式、销售趋势等有价值的信息,从而优化营销策略、提升用户体验并增加销售额。

项目中涉及的数据主要包括用户信息、订单数据和商品数据。用户信息表包含用户 ID、注册时间、性别、年龄、地域等字段,数据规模达到千万级别,这些数据为分析用户特征和行为提供了基础。订单数据表记录了每一笔订单的详细信息,如订单 ID、用户 ID、商品 ID、订单金额、下单时间、支付方式等,每天新增数据量约百万条。商品数据表则涵盖了商品 ID、商品名称、类别、价格、库存等信息,商品种类丰富,数据量也在不断增长。这些数据具有数据量大、实时性要求高、数据关系复杂等特点,需要高效的处理和分析技术。

4.2 Spark SQL 进阶应用

在分析用户购买行为时,窗口函数发挥了重要作用。我们使用窗口函数计算用户的购买频率,找出购买频率高的用户。例如,通过以下 SQL 语句可以计算每个用户在一个月内的购买次数,并按照购买次数进行排名:

SELECT
  user_id,
  COUNT(*) OVER (PARTITION BY user_id ORDER BY order_time ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS purchase_count,
  RANK() OVER (ORDER BY COUNT(*) OVER (PARTITION BY user_id ORDER BY order_time ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) DESC) AS purchase_rank
FROM
  orders
WHERE
  order_time BETWEEN '2024-01-01' AND '2024-01-31';

这样,我们可以轻松识别出那些高频购买用户,为后续的精准营销提供目标用户群体。

在对订单数据进行多维分析时,高级聚合操作成为有力工具。利用 GROUPING SETS,我们可以同时按地区和时间段统计销售额。示例代码如下:

SELECT
  sales_region,
  order_date,
  SUM(order_amount) AS total_sales
FROM
  orders
GROUP BY
  GROUPING SETS ((sales_region), (order_date), (sales_region, order_date));

这使得我们能够从多个维度全面了解销售情况,发现不同地区和时间段的销售差异,为制定销售策略提供数据支持。

对于商品描述等文本数据,我们编写了 UDF 来进行处理。比如,需要提取商品描述中的关键词,以便更好地进行商品分类和搜索。我们可以编写一个 UDF,使用自然语言处理库(如 NLTK 或 SnowNLP)来实现关键词提取。在 Scala 中,实现代码如下:

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions.udf
import org.snu.ids.ha.index.KeywordExtractor

val spark = SparkSession.builder.appName("UDFExample").master("local").getOrCreate()
val keywordExtractor = new KeywordExtractor()
val extractKeywordsUDF = udf((description: String) => {
    val keywords = keywordExtractor.extractKeyword(description, true)
    keywords.map(_._1).mkString(",")
})

val productData = Seq(("Product1", "This is a high - quality laptop with advanced features"), ("Product2", "A beautiful dress for special occasions"))
val productDF = spark.createDataFrame(productData).toDF("product_name", "description")
productDF.select($"product_name", extractKeywordsUDF($"description").alias("keywords")).show()

通过这个 UDF,我们可以从商品描述中提取出关键词,为商品的精准推荐和搜索功能提供支持。

4.3 优化过程与效果展示

在项目实施过程中,我们遇到了性能问题。随着数据量的不断增加,一些复杂查询的执行时间过长,严重影响了分析效率。例如,在进行多表关联和复杂聚合查询时,任务常常因为内存不足而失败。经过分析,我们发现主要原因是数据倾斜和资源分配不合理。

针对这些问题,我们采取了一系列优化措施。在数据分区方面,对订单表按照订单时间进行分区,这样在查询特定时间段的订单数据时,可以大大减少数据扫描范围。对于经常被查询的数据,如商品信息表,我们进行了缓存处理,将其缓存在内存中,提高查询速度。同时,我们还调整了 Spark 的相关参数,如增加 spark.sql.shuffle.partitions 的值,从默认的 200 增加到 400,以缓解数据倾斜问题;合理设置 spark.executor.memory 和 spark.executor.cores,根据集群节点的配置,将每个 Executor 的内存设置为 8GB,核心数设置为 4。

优化前后的性能对比十分显著。优化前,一个复杂的多维分析查询可能需要运行数小时,而优化后,执行时间缩短到了几十分钟,效率提升了数倍。内存使用率也得到了有效控制,任务失败率大幅降低,从原来的 10% 降低到了 1% 以内,大大提高了数据分析的效率和稳定性,为企业的决策提供了更及时、准确的数据支持。

五、总结与展望

在大数据处理的征程中,Spark SQL 进阶之路充满了探索与挑战,也收获了强大的能力与显著的成果。从窗口函数提供的独特数据洞察视角,到高级聚合操作挖掘出的数据深度价值,再到自定义函数拓展的功能边界,每一个进阶特性都为我们处理复杂数据提供了有力武器。在性能优化实战中,通过合理运用数据分区与缓存策略、解决数据倾斜问题以及精准配置 Spark 参数,我们能够让 Spark SQL 在面对海量数据时依然保持高效运行。

实际项目案例也充分证明了 Spark SQL 进阶技术的价值。在电商数据分析等复杂场景中,通过运用这些进阶技术,企业能够从海量数据中快速提取有价值的信息,为决策提供精准的数据支持,从而在激烈的市场竞争中占据优势。

展望未来,随着大数据技术的不断发展,Spark SQL 也将持续演进。数据湖与数据仓库的融合趋势将使 Spark SQL 在处理不同类型数据时更加灵活高效;在人工智能与大数据深度融合的背景下,Spark SQL 有望与机器学习、深度学习算法更紧密结合,实现更智能化的数据处理和分析。对于广大数据从业者而言,持续学习和探索 Spark SQL 的进阶技术,不仅能够提升自身在大数据领域的竞争力,还能为推动大数据技术的发展贡献力量。让我们在 Spark SQL 的进阶道路上不断前行,挖掘更多数据价值,迎接大数据时代的更多挑战与机遇。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2393710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

放假带出门的充电宝买哪种好用耐用?倍思超能充35W了解一下!

端午节的到来和毕业季的临近,让很多人开始计划出游或长途旅行。而在旅途中,一款好用耐用的充电宝可以省不少事。今天,我们就来聊聊放假带出门的充电宝买哪种好用耐用,看看为什么倍思超能充35W更适合带出门~ 一、为什么需要一款好用…

AI智能体策略FunctionCalling和ReAct有什么区别?

Dify 内置了两种 Agent 策略:Function Calling 和 ReAct,但二者有什么区别呢?在使用时又该如何选择呢?接下来我们一起来看。 1.Function Calling Function Call 会通过将用户指令映射到预定义函数或工具,LLM 先识别用…

改进自己的图片 app

1. 起因, 目的: 前面我写过一个图片 app , 最新做了些改动。 把原来的一列,改为3列, 继续使用瀑布流手机上使用,更流畅,横屏显示为2列。 2. 先看效果 3. 过程: 过程太细碎了,这里只是做一下…

Uniapp+UView+Uni-star打包小程序极简方案

一、减少主包体积 主包污染源(全局文件依赖)劲量独立导入 componentsstaticmain.jsApp.vueuni.css 分包配置缺陷,未配置manifest.json中mp-weixin节点 "usingComponents" : true,"lazyCodeLoading" : "requiredC…

算法题(159):快速幂

审题: 本题需要我们计算出(a^b)%c的值,并按照规定格式输出 思路: 方法一:暴力解法 我们直接循环b次计算出a^b,然后再取余c,从而得出最终结果 时间上:会进行2^31次,他的数量级非常大,…

【新品发布】嵌入式人工智能实验箱EDU-AIoT ELF 2正式发布

在万物互联的智能化时代,将AI算法深度植入硬件终端的技术,正悄然改变着工业物联网、智慧交通、智慧医疗等领域的创新边界。为了助力嵌入式人工智能在教育领域实现高质量发展,飞凌嵌入式旗下教育品牌ElfBoard,特别推出嵌入式人工智…

基于javaweb的SpringBoot体检管理系统设计与实现(源码+文档+部署讲解)

技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

IPD的基础理论与框架——(四)矩阵型组织:打破部门壁垒,构建高效协同的底层

在传统的组织架构中,企业多采用直线职能制,就像一座等级森严的金字塔,信息沿着垂直的层级传递,员工被划分到各个职能部门。这种架构职责清晰、分工明确,在稳定的市场环境中,能让企业高效运作,发…

小程序为什么要安装SSL安全证书

小程序需要部署SSL安全证书,这是小程序开发及运营的强制性要求,也是保障用户数据安全、提升用户体验和满足平台规范的必要措施。 一、平台强制要求 微信小程序官方规范 微信小程序明确要求所有网络请求必须通过HTTPS协议传输,服务器域名需配…

python打卡训练营打卡记录day40

知识点回顾: 彩色和灰度图片测试和训练的规范写法:封装在函数中展平操作:除第一个维度batchsize外全部展平dropout操作:训练阶段随机丢弃神经元,测试阶段eval模式关闭dropout 作业:仔细学习下测试和训练代码…

【清晰教程】利用Git工具将本地项目push上传至GitHub仓库中

Git 是一个分布式版本控制系统,由 Linus Torvalds 创建,用于有效、高速地处理从小到大的项目版本管理。GitHub 是一个基于 Git 的代码托管平台,提供了额外的协作和社交功能,使项目管理更加高效。它们为项目代码管理、团队协作和持…

20250529-C#知识:静态类、静态构造函数和拓展方法

C#知识:静态类、静态构造函数和拓展方法 静态类一般用来编写工具类 1、静态类 用static关键字修饰的类一般充当工具类只能包含静态成员,不能包含静态索引器不能被实例化静态方法只能使用静态成员非静态方法既可以使用非静态成员,也可以使用静态成员 sta…

实验设计与分析(第6版,Montgomery)第4章随机化区组,拉丁方, 及有关设计4.5节思考题4.18~4.19 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第章随机化区组&#xff0c;拉丁方&#xff0c; 及有关设计4.5节思考题4.18~4.19 R语言解题。主要涉及方差分析&#xff0c;拉丁方。 batch <- c(rep("batch1",5), rep(&quo…

【吾爱】逆向实战crackme160学习记录(一)

前言 最近想拿吾爱上的crackme程序练练手&#xff0c;发现论坛上已经有pk8900总结好的160个crackme&#xff0c;非常方便&#xff0c;而且有很多厉害的前辈已经写好经验贴和方法了&#xff0c;我这里只是做一下自己练习的记录&#xff0c;欢迎讨论学习&#xff0c;感谢吾爱论坛…

vue2 + webpack 老项目升级 node v22 + vite + vue2 实战全记录

前言 随着这些年前端技术的飞速发展&#xff0c;几年前的一些老项目在最新的环境下很可能会出现烂掉的情况。如果项目不需要升级&#xff0c;只需要把编译后的文件放在那里跑而不用管的话还好。但是&#xff0c;某一天产品跑过来给你讲要升级某一个功能&#xff0c;你不得不去…

STM32的HAL编码流程总结(上部)

目录 一、GPIO二、中断系统三、USART串口通信四、I2C通信五、定时器 一、GPIO 1.选择调试类型 在SYS中Debug选择Serial Wire模式 2.选择时钟源 在RCC中将HSE和LSH都选择为内部晶振 3.时钟树配置 4.GPIO配置 在芯片图上选择开启的引脚和其功能 配置引脚的各自属性 5.工…

深度学习|pytorch基本运算

【1】引言 pytorch是深度学习常用的包&#xff0c;顾名思义&#xff0c;就是python适用的torch包&#xff0c;在python里面使用时直接import torch就可以调用。 需要注意的是&#xff0c;pytorch包与电脑配置、python版本有很大关系&#xff0c;一定要仔细阅读安装要求、找到…

替代 WPS 的新思路?快速将 Word 转为图片 PDF

在这个数字化办公日益普及的时代&#xff0c;越来越多的人开始关注文档处理工具的功能与体验。当我们习惯了某些便捷操作时&#xff0c;却发现一些常用功能正逐渐变为付费项目——比如 WPS 中的一项实用功能也开始收费了。 这款工具最特别的地方在于&#xff0c;可以直接把 W…

【K8S】K8S基础概念

一、 K8S组件 1.1 控制平面组件 kube-apiserver&#xff1a;公开 Kubernetes HTTP API 的核心组件服务器。 etcd&#xff1a;具备一致性和高可用性的键值存储&#xff0c;用于所有 API 服务器的数据存储。 kube-scheduler&#xff1a;查找尚未绑定到节点的 Pod&#xff0c;并将…

包含Javascript的HTML静态页面调取本机摄像头

在实际业务开发中&#xff0c;需要在带有摄像头的工作机上拍摄施工现场工作过程的图片&#xff0c;然后上传到服务器备存。 这便需要编写可以运行在浏览器上的代码&#xff0c;并在代码中实现Javascript调取摄像头、截取帧保存为图片的功能。 为了使用户更快掌握JS调取摄像头…