SheetJS 教程

SheetJS 导入数据

🎉摘要:本教程详细讲解如何使用SheetJS库将政府机构发布的Excel官方数据(如联邦学生资助组合数据)转换为JSON格式。内容涵盖从文件下载、解析工作簿、探索工作表结构,到提取和处理合并单元格数据,最终生成结构化JSON对象并展示在网页表格中的完整流程。适合需要处理Excel数据的JavaScript开发者参考。

许多政府机构通过 Excel 方式发布官方数据和统计信息。使用 SheetJS 库有助于将这些文件转换为有用的信息,如将 Excel 数据转换成 JSON 格式的数据,方便程序使用。

本示例的目标是处理来自 XLS 工作表的联邦学生资助组合数据。我们将从美国教育部下载并解析一个 Excel。原始数据解析完成后,我们将提取未偿还的总金额,并将数据显示在表格中。

以下序列图展示了该过程:

SheetJS 导入数据

下载文件

原始数据可在一个 XLS 工作簿中获取,文件地址 https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls。如下图:

image.png

注意,SheetJS 支持多种旧版和现代格式,确保历史数据不会在时间的长河中丢失。

这里将介绍如何使用 fetch 下载文件,用法十分简单,代码如下:

// 文件地址
const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls"; 
// file 是一个 ArrayBuffer 存放文件数据
const file = await (await fetch(url)).arrayBuffer();

文件数据存储在一个 ArrayBuffer 中。

解析文件

有了文件数据后,XLSX.read 会解析该文件并生成一个 SheetJS 工作簿(workbook)对象,如下:

const workbook = XLSX.read(file);

上面 workbook 对象遵循“通用电子表格格式”,这是一种用于工作簿、工作表、单元格和电子表格功能的内存中的模式。

探索数据集

实际使用中的电子表格采用了许多不同且不一致的格式。要确定如何处理这些数据前,最好先检查文件,查看文件的格式。

列出工作表名称

正如在“工作簿对象”页面中所解释的,SheetNames 属性是工作簿中工作表名称的列表。

下面代码展示了工作表名称的有序列表:

示例:

<table>
    <thead>
        <tr>
            <th>SheetName</th>
        </tr>
    </thead>
    <tbody id="tbody"></tbody>
</table>

<script>
(async() => {
    // 下载文件数据
    const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
    const file = await (await fetch(url)).arrayBuffer();

    // 解析工作簿
    const workbook = XLSX.read(file);
    workbook.SheetNames.forEach(item => {
        // 创建tr元素
        const row = document.createElement("TR");
        // td数据填充到tr中
        row.innerHTML = `<td>${item}</td>`;
        tbody.appendChild(row);
    });
})();
</script>

看看效果👉自己动手试一试 »

运行上面示例,将输出 sheetjs_PortfolioSummary.xls 表格中的所有工作表名称,如下图:

image.png

检查工作表数据

工作簿(workbook)对象的 Sheets 属性是一个对象,其键是工作表名称,值是工作表对象。例如,可以通过检索 SheetNames 并使用该名称检索 Sheets 来获取第一个工作表,例如:

// workbook.SheetNames[0] 获取第一个工作表的名称,PortfolioSummary
var sheet_name = workbook.SheetNames[0];
// 根据工作表名称从 Sheets 中获取工作表对象
var first_sheet = workbook.Sheets[sheet_name];

注意:可以直接检查工作表对象,但强烈建议使用实用函数来提取相关数据。

预览HTML

sheet_to_html 函数用于从工作表对象生成 HTML 表格。

以下实时示例展示了表格中数据的前 20 行:

示例:

<div id="app"></div>

<script>
    (async() => {
        // 下载文件数据
        const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
        const file = await (await fetch(url)).arrayBuffer();

        // 解析工作簿
        const workbook = XLSX.read(file);
        // 获取第一个工作表的名称,PortfolioSummary
        var sheet_name = workbook.SheetNames[0];
        // 根据工作表名称从 Sheets 中获取工作表对象
        var first_sheet = workbook.Sheets[sheet_name];
        // 将工作表对象转换为 HTML 表格字符串
        const table = XLSX.utils.sheet_to_html(first_sheet);
        document.getElementById('app').innerHTML = table;
    })();
</script>

看看效果👉自己动手试一试 »

运行示例,效果如下图:

image.png

查看该表格得出的要点如下:

  • 数据从第 7 行开始

  • 第 5 行和第 6 行是标题行,其中通用标题所在的单元格是合并的

  • 对于年度数据(2007-2012 年),A 列和 B 列是合并的

  • 对于季度数据(2013 年第一季度及之后),A 列存储年份。单元格可能会纵向合并以涵盖 4 个季度

提取数据

提取原始数据

sheet_to_json 函数用于从工作表对象生成数据数组。对于像这样的复杂布局,最简单的方法是生成一个“数组的数组”,其中每一行都是一个单元格值数组。截图显示了第 5~8 行:

image.png

在数组的数组中,第5行有许多空白,这些空白对应着空单元格以及合并范围内被覆盖的单元格:

// 第 5 行 —— 空白处对应没有内容的单元格
[ , , "Direct Loans", , "Federal Family Education Loans (FFEL)", , "Perkins Loans", , "Total1" ]

第 7 行包含 2007 财政年度的数据:

// 第 7 行——B 列被合并单元格覆盖了
[ 2007, , 106.8, 7, 401.9, 22.6, 8.2, 2.8, 516, 28.3 ]

如果指定了选项 header: 1,XLSX.utils.sheet_to_json() 将生成一个数组的数组:

const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const raw_data = XLSX.utils.sheet_to_json(worksheet, {header: 1});

填充合并单元格块

单元格 A13:A16 是合并的:

image.png

合并的数据仅适用于左上角单元格(A13)。数组的数组在单元格A14:A16中会有缺失值(以 null 表示):

// 第 13 行 [2013, "Q1", 508.7, 23.4, 444.9, 22.1, 8.2, 3,    961.9, 38.7] 
// 第 14 行 [null, "Q2", 553,   24.1, 437,   21.6, 8.3, 3,    998.6, 38.9] 
// 第 15 行 [null, "Q3", 569.2, 24.3, 429.5, 21.2, 8.2, 2.9, 1006.8, 38.7] 
// 第 16 行 [null, "Q4", 609.1, 25.6, 423,   20.9, 8.1, 2.9, 1040.2, 39.6]

示例:

<div id="app"></div>

<script>
    (async() => {
        // 下载文件数据
        const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
        const file = await (await fetch(url)).arrayBuffer();

        // 解析工作簿
        const workbook = XLSX.read(file);
        // 获取第一个工作表的名称,PortfolioSummary
        var sheet_name = workbook.SheetNames[0];
        // 根据工作表名称从 Sheets 中获取工作表对象
        var first_sheet = workbook.Sheets[sheet_name];

        // 将工作表对象转换为JSON数据
        const raw_data = XLSX.utils.sheet_to_json(first_sheet, {header:1});

        // 提取 Excel 的 13 到 16 行(SheetJS 的 12 到 15 行)
        const rows_13_16 = raw_data.slice(12,16);

        // 显示数据
        var htmls = [];
        rows_13_16.forEach(item => {
            htmls.push(`<p>${JSON.stringify(item)}</p>`)
        });
        document.getElementById('app').innerHTML = htmls.join('');
    })();
</script>

看看效果👉自己动手试一试 »

效果如下图:

image.png

工作表的 worksheet!merges 属性包含了工作表中的所有合并区域。可以遍历每个合并块并填充单元格,但在这种情况下,对原始数据进行后处理会更简单:

// 处理逻辑:
// 根据观察上面的输出,第一列 null 的正确填充值应该是最后一个不为 null 的值,上面是 2013
let last_year = 0; // 记录最后一个年份
// 遍历原始数据
// 如果当前行的第一列不为null,则将该列的值赋值给第一列和last_year
// 如果当前行的第一列为null,则将 last_year 的值赋值给第一列和last_year
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));

经过后处理后,这些行现在有了正确的年份字段:

// 第 13 行 [2013, "Q1", 508.7, 23.4, 444.9, 22.1, 8.2, 3,    961.9, 38.7] 
// 第 14 行 [2013, "Q2", 553,   24.1, 437,   21.6, 8.3, 3,    998.6, 38.9] 
// 第 15 行 [2013, "Q3", 569.2, 24.3, 429.5, 21.2, 8.2, 2.9, 1006.8, 38.7] 
// 第 16 行 [2013, "Q4", 609.1, 25.6, 423,   20.9, 8.1, 2.9, 1040.2, 39.6]

示例:

<div id="app"></div>

<script>
    (async() => {
        // 下载文件数据
        const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
        const file = await (await fetch(url)).arrayBuffer();

        // 解析工作簿
        const workbook = XLSX.read(file);
        // 获取第一个工作表的名称,PortfolioSummary
        var sheet_name = workbook.SheetNames[0];
        // 根据工作表名称从 Sheets 中获取工作表对象
        var first_sheet = workbook.Sheets[sheet_name];

        // 将工作表对象转换为JSON数据
        const raw_data = XLSX.utils.sheet_to_json(first_sheet, {header:1});

        // 填充年份
        var last_year = 0;
        raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));

        // 提取 Excel 的 13 到 16 行(SheetJS 的 12 到 15 行)
        const rows_13_16 = raw_data.slice(12,16);

        // 显示数据
        var htmls = [];
        rows_13_16.forEach(item => {
            htmls.push(`<p>${JSON.stringify(item)}</p>`)
        });
        document.getElementById('app').innerHTML = htmls.join('');
    })();
</script>

看看效果👉自己动手试一试 »

填充年份后效果如下图:

image.png

选择数据行

此时,每一行数据的年份都在 A 列,美元数值在 C 列。年份(行中的第一个值)将在 2007 到 2029 之间。数值(第三个值)为正数。以下函数用于测试一行数据是否符合要求:

const is_valid_row = r =>
  r[0] >= 2007 && r[0] <= 2029 // 年份(A列)在2007到2029之间
  && r[2] > 0; // 美元数值(C列)为正数

Array#filter 可以使用之前的测试来筛选出匹配的行:

// 2016年第一季度 - 1220.3美元(十亿)
{ "FY": 2016, "FQ": "Q1", "total": 1220.3 }

示例:

<div id="app"></div>

<script>
    (async() => {
        // 下载文件数据
        const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
        const file = await (await fetch(url)).arrayBuffer();

        // 解析工作簿
        const workbook = XLSX.read(file);
        // 获取第一个工作表的名称,PortfolioSummary
        var sheet_name = workbook.SheetNames[0];
        // 根据工作表名称从 Sheets 中获取工作表对象
        var first_sheet = workbook.Sheets[sheet_name];

        // 将工作表对象转换为JSON数据
        const raw_data = XLSX.utils.sheet_to_json(first_sheet, {header:1});

        // 填充年份
        var last_year = 0;
        raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));

        // 选择数据行(2007~2029年,且金额大于0)
        const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);

        // 显示数据
        var htmls = [];
        rows.forEach(item => {
            htmls.push(`<p>${JSON.stringify(item)}</p>`)
        });
        document.getElementById('app').innerHTML = htmls.join('');
    })();
</script>

看看效果👉自己动手试一试 »

生成行对象

查看标题:

image.png

所需数据位于 I 列,列索引可以使用 decode_col实用函数 来计算。例如:

示例:

<div id="app"></div>
<script>
    (async() => {
        // 下载文件数据
        const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
        const file = await (await fetch(url)).arrayBuffer();

        // 解析工作簿
        const workbook = XLSX.read(file);
        // 获取第一个工作表的名称,PortfolioSummary
        var sheet_name = workbook.SheetNames[0];
        // 根据工作表名称从 Sheets 中获取工作表对象
        var first_sheet = workbook.Sheets[sheet_name];

        // 将工作表对象转换为JSON数据
        const raw_data = XLSX.utils.sheet_to_json(first_sheet, {header:1});

        // 填充年份
        var last_year = 0;
        raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));

        // 选择数据行(2007~2029年,且金额大于0)
        const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
        
        // 使用 XLSX.utils.decode_col() 方法将列名称转换为索引
        const cols = ["A", "B", "I"];
        var data = cols.map(col => {
            return `<p>${col} => ${XLSX.utils.decode_col(col)}</p>`;
        });
        document.getElementById('app').innerHTML = data.join("");
    })();
</script>

看看效果👉自己动手试一试 »

所需的列如下:

描述

对象中的属性

A / 0

财政年度

FY

B / 1

财政季度(如适用)

FQ

I / 8

未偿还美元总额

total

对数据执行Array#map可以生成所需的行对象:

const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));

这将生成一个行对象数组。每个行对象将如下所示:

// 2016 Q1 - $1220.3 (billion) { "FY": 2016, "FQ": "Q1", "total": 1220.3 }

示例:

<div id="app"></div>

<script>
    (async() => {
        // 下载文件数据
        const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
        const file = await (await fetch(url)).arrayBuffer();

        // 解析工作簿
        const workbook = XLSX.read(file);
        // 获取第一个工作表的名称,PortfolioSummary
        var sheet_name = workbook.SheetNames[0];
        // 根据工作表名称从 Sheets 中获取工作表对象
        var first_sheet = workbook.Sheets[sheet_name];

        // 将工作表对象转换为JSON数据
        const raw_data = XLSX.utils.sheet_to_json(first_sheet, {header:1});

        // 填充年份
        var last_year = 0;
        raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));

        // 选择数据行(2007~2029年,且金额大于0)
        const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
        // 生成行对象(第一列、二列和九列的数据)
        const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));

        // 显示数据
        var htmls = [];
        rows.forEach(item => {
            htmls.push(`<p>${JSON.stringify(item)}</p>`)
        });
        document.getElementById('app').innerHTML = htmls.join('');
    })();
</script>

看看效果👉自己动手试一试 »


完整示例

下面是完整实例代码:

示例:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SheetJS 教程</title>
    <!-- 引入 SheetJS 库 -->
    <script src="https://www.hxstrive.com/cdn/libs/SheetJS/0.20.3/xlsx.full.min.js"></script>
    <style>
        table {
            border: 1px solid #e2e8f0;
            border-collapse: collapse;
        }
        table th, table td {
            border: 1px solid #e2e8f0;
        }
        table th {
            border-bottom: 2px solid #cbd5e1;
        }
    </style>
</head>
<body>

<table>
    <thead>
        <tr>
            <th>Fiscal Year</th>
            <th>Quarter</th>
            <th>Total (in $B)</th>
        </tr>
    </thead>
    <tbody id="tbody"></tbody>
</table>

<script>
(async() => {
    // 下载文件数据
    const url = "https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_PortfolioSummary.xls";
    const file = await (await fetch(url)).arrayBuffer();

    // 解析工作簿
    const workbook = XLSX.read(file);
    console.log("所有工作表名称:", workbook.SheetNames);

    // 获取第一个工作表
    const worksheet = workbook.Sheets[workbook.SheetNames[0]];
    // 将工作表的数据转换成JSON数据,JSON格式
    // [[第一行数据], [第二行数据], ...]
    const raw_data = XLSX.utils.sheet_to_json(worksheet, {header:1});
    console.log(raw_data)

    // 填充年份列,处理合并问题
    var last_year = 0; // 临时存放上一行的年份信息
    raw_data.forEach(row => {
        last_year = row[0] = (row[0] != null ? row[0] : last_year)
    });

    // 选择数据行,只选择2007-2029年间且有数据的行
    const rows = raw_data.filter(row => {
        return row[0] >= 2007 && row[0] <= 2029 && row[2] > 0
    });

    // 生成行对象数组,方便后续操作
    const objects = rows.map(row => ({
        FY: row[0],
        FQ: row[1],
        total: row[8]
    }));

    // 将数据行信息添加到table的tbody中
    objects.forEach(o => {
        // 创建tr元素
        const row = document.createElement("TR");
        // td数据填充到tr中
        row.innerHTML = `<td>${o.FY}</td><td>${o.FQ||""}</td><td>${o.total}</td>`;
        tbody.appendChild(row);
    });
})();
</script>
</body>
</html>

看看效果👉自己动手试一试 »

参考资料:https://docs.sheetjs.com/docs/getting-started/examples/import 

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号