详细讲解在Node.js环境下批量读取本地多个Excel文件数据的方法,涵盖文件遍历、格式过滤、工作表解析、结构化数据合并及异常处理等关键操作。
Node.js环境下怎么批量读取本地Excel文件数据
时间:2026-10-30
浏览:529
文章目录
假设你正在处理企业内部分散在各个部门的报表:同一个目录下存放着数十个甚至上百个结构相似的 Excel 文件(例如各网点的日销售记录或月度统计表),如果手动逐个打开并复制粘贴数据,不仅耗时繁琐,还极易出现漏读或错行。在 Node.js 环境下,利用内置的文件系统模块结合成熟的 Excel 解析库,可以通过编写脚本自动扫描指定目录、筛选合法的 Excel 文件、逐个提取工作表中的单元格数据,并最终汇总为结构化的 JSON 数组供后续分析或入库使用。
一、准备工作与依赖安装
在 Node.js 中,Node 原生标准库提供了文件与目录操作的基础能力(例如 fs 与 path 模块),但并没有内置 Excel 二进制文件(.xlsx / .xls)的解析能力。社区中最常用且兼容性最好的是 xlsx(即 SheetJS)。它无需依赖本地的 Python、C++ 编译环境或 Office 办公软件,纯 JavaScript 即可完成跨平台解析。
首先在项目工作目录中初始化 Node.js 工程,并安装所需要的解析依赖:
# 1. 初始化项目 package.json
npm init -y
# 2. 安装 SheetJS 解析核心库
npm install xlsx
如果业务项目使用 ES 模块(在 package.json 中配置了 "type": "module"),可以使用 import 语法;若使用 CommonJS 规范,则通过 const XLSX = require('xlsx') 引入。下文示例均采用通用的 CommonJS 规范编写,保证在任何标准的 Node.js 16+ 环境中开箱即用。
二、核心操作步骤与实现过程
批量读取本地 Excel 文件可以拆解为五个循序渐进的操作环节:定位目录、规则过滤、加载工作簿、按行提取以及集合聚合。每个步骤各自承担单一职责,确保整个处理流程稳定、透明。
1. 遍历目录并过滤有效文件
在扫描目标目录时,不能简单将所有文件都交给 Excel 库处理,需要注意两个典型过滤条件:
- 扩展名匹配:通常只筛选
.xlsx和.xls后缀的文件,忽略.csv、.txt或其它杂质文件。 - 忽略临时锁定文件:当有人在 Windows 或 macOS 上用 Excel 打开某个表格时,Office 会在同一目录下自动生成形如
~$文件名.xlsx的隐藏临时锁定文件。这类文件体积极小且没有完整结构,一旦尝试读取便会抛出损坏错误,因此必须通过!file.startsWith('~$')明确剔除。
2. 单文件解析与数据抽取
获取到合法文件的完整绝对路径后,调用 xlsx.readFile(filePath) 读取该工作簿。一个工作簿可能包含多个 Sheet,通常情况下可以通过 workbook.SheetNames[0] 读取第一个工作表,也可以根据需求循环遍历所有 Sheet。
获得具体的 Sheet 对象后,使用 xlsx.utils.sheet_to_json(worksheet) 即可将以单元格坐标为索引的网格数据快速转化为 JavaScript 数组对象。
三、完整可运行代码示例
下面是一个标准的批处理脚本 batchReadExcel.js。该脚本通过异步遍历目录、单文件异常隔离以及来源元数据注入,确保即使某一个 Excel 文件损坏或格式有误,也不会导致整个任务崩溃中断。
const fs = require('fs');
const path = require('path');
const XLSX = require('xlsx');
/**
* 批量读取指定目录下的所有 Excel 文件数据
* @param {string} targetDir - 存放 Excel 文件的目标目录
* @returns {Array<Object>} 汇总后的数据行数组
*/
function batchReadExcelFiles(targetDir) {
// 1. 校验输入目录有效性
if (!fs.existsSync(targetDir)) {
console.error(`[错误] 目标目录不存在: ${targetDir}`);
return [];
}
// 2. 获取目录内全部文件并进行规则过滤
const allEntries = fs.readdirSync(targetDir);
const excelFiles = allEntries.filter((file) => {
const ext = path.extname(file).toLowerCase();
const isExcel = ext === '.xlsx' || ext === '.xls';
const isTempLockFile = file.startsWith('~$'); // 过滤 Office 临时隐藏文件
return isExcel && !isTempLockFile;
});
console.log(`[扫描完成] 在目录中找到 ${excelFiles.length} 个有效的 Excel 文件。`);
const aggregatedData = [];
const failureList = [];
// 3. 逐个文件进行安全解析
for (const fileName of excelFiles) {
const fullPath = path.join(targetDir, fileName);
try {
// 读取工作簿对象
const workbook = XLSX.readFile(fullPath, {
cellDates: true, // 保持日期对象解析,避免转换为纯浮点天数
});
// 默认读取第一个工作表
const firstSheetName = workbook.SheetNames[0];
if (!firstSheetName) {
console.warn(`[跳过] 文件 ${fileName} 中没有检测到有效工作表。`);
continue;
}
const worksheet = workbook.Sheets[firstSheetName];
// 将表格转换为 JSON 数组(默认首行为表头属性名)
const sheetRows = XLSX.utils.sheet_to_json(worksheet, {
defval: '', // 遇到空白单元格时填充空字符串,防止属性缺失
});
// 4. 注入元信息(如来源文件名、工作表名),便于后续数据排查和分组
const enrichedRows = sheetRows.map((row) => ({
...row,
_sourceFile: fileName,
_sheetName: firstSheetName,
}));
aggregatedData.push(...enrichedRows);
console.log(`[解析成功] ${fileName} -> 提取 ${sheetRows.length} 条数据`);
} catch (err) {
// 5. 异常隔离:单个文件损坏不中断后续处理
console.error(`[解析失败] 文件 ${fileName} 读取异常:`, err.message);
failureList.push({ fileName, error: err.message });
}
}
// 汇总结果输出报告
console.log('--------------------------------------------');
console.log(`[处理结束] 累计读取成功: ${aggregatedData.length} 行数据`);
if (failureList.length > 0) {
console.warn(`[警告] 存在 ${failureList.length} 个无法解析的文件,请排查损坏情况。`);
}
return aggregatedData;
}
// 示例调用:读取当前目录下的 reports 文件夹
const targetDirectory = path.join(__dirname, 'reports');
const result = batchReadExcelFiles(targetDirectory);
// 打印前 2 条示例结果用于验证
console.log('数据前两行预览:');
console.log(result.slice(0, 2));
执行结果验证
假设在 ./reports 目录下放置了 store_beijing.xlsx 与 store_shanghai.xlsx 两个报表文件,在终端中执行 node batchReadExcel.js,控制台的示例输出如下:
[扫描完成] 在目录中找到 2 个有效的 Excel 文件。
[解析成功] store_beijing.xlsx -> 提取 120 条数据
[解析成功] store_shanghai.xlsx -> 提取 95 条数据
--------------------------------------------
[处理结束] 累计读取成功: 215 行数据
数据前两行预览:
[
{
'商品编码': 'SKU10023',
'商品名称': '超高清机械键盘',
'销量': 18,
'单价': 299,
_sourceFile: 'store_beijing.xlsx',
_sheetName: 'Sheet1'
},
{
'商品编码': 'SKU10024',
'商品名称': '人体工学鼠标',
'销量': 35,
'单价': 129,
_sourceFile: 'store_beijing.xlsx',
_sheetName: 'Sheet1'
}
]
四、实际落地中的关键注意事项
在实际生产环境中,由于 Excel 文件通常由不同人员通过不同软件生成,常常存在格式不规范、数据错位等情况,编写脚本时需重点关注以下几个问题:
sheet_to_json,容易读出纯数字。在 XLSX.readFile 时指定 cellDates: true 选项,SheetJS 会自动将其转换为 JavaScript 的原生 Date 对象。
sheet_to_json 会忽略空白单元格,这会导致不同行返回的对象属性数量不一致(缺失空列对应的键)。通过设置配置项 { defval: '' },可以强制为所有未填单元格保留空字符串或指定默认值,保持每行结构严格对齐。
xlsx.readFile 是整文件同步或内存常驻加载机制。如果待处理的 Excel 达到几百个且每个单文件超过几十兆,直接将其一次性全部装入内存可能导致 V8 引擎出现 JavaScript heap out of memory 崩溃。对于海量或超大文件,建议使用 exceljs 的流式读取接口(exceljs.stream.xlsx.WorkbookReader),或对文件列表进行分组批次处理,每处理一批及时写入数据库或本地磁盘,触发垃圾回收释放内存。
try...catch。当遇到个别加密、文件格式损坏、甚至非 Excel 重命名伪装的文件时,记录失败日志并平稳跳过,切勿让单个异常中断整个批处理任务。
五、全流程与工程实践总结
批量处理 Excel 的本质是“扫描过滤 ➔ 安全解析 ➔ 结构归一 ➔ 汇总归档”。在实现过程中,保持健壮的文件名校验、保留数据来源标记以及做好异常隔离是保证自动化任务稳定运行的核心。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。