如何使用JavaScript从*.CSV文件读取数据?

255

我的CSV数据看起来像这样:

heading1,heading2,heading3,heading4,heading5
value1_1,value2_1,value3_1,value4_1,value5_1
value1_2,value2_2,value3_2,value4_2,value5_2
...

如何使用JavaScript读取此数据并将其转换为以下数组:

[
    heading1: value1_1,
    heading2: value2_1,
    heading3: value3_1,
    heading4: value4_1
    heading5: value5_1
],[
    heading1: value1_2,
    heading2: value2_2,
    heading3: value3_2,
    heading4: value4_2,
    heading5: value5_2
]
....

我尝试了这段代码但没有运气:

<script type="text/javascript">
    var allText =[];
    var allTextLines = [];
    var Lines = [];

    var txtFile = new XMLHttpRequest();
    txtFile.open("GET", "file://d:/data.txt", true);
    txtFile.onreadystatechange = function()
    {
        allText = txtFile.responseText;
        allTextLines = allText.split(/\r\n|\n/);
    };

    document.write(allTextLines);
    document.write(allText);
    document.write(txtFile);
</script>

1
是的,我提前知道有五个字段。 - Mahesh Thumar
1
下一个问题是:解决方案中允许使用jQuery吗?您使用了标签,但您的示例代码是纯JavaScript。 - Blazemonger
3
我认为在XMLHttpRequest中,不允许使用 file://... - Noel Llevares
如果是这样的话,那么始终可以使用新的HTML5文件API。虽然它还没有在所有浏览器中得到支持,但在支持它的浏览器中,实现起来并不困难。 - Evan Plaice
txtFile.send() 缺失。 - Amit Prabhu Parrikar
显示剩余3条评论
16个回答

241

无需编写自己的代码...

jQuery-CSV库有一个名为$.csv.toObjects(csv)的函数,可以自动进行映射。

注意:该库旨在处理符合RFC 4180标准的任何CSV数据,包括大多数“简单”解决方案忽略的所有恶劣情况。

正如@Blazemonger已经指出的那样,首先您需要添加换行符来使数据成为有效的CSV。

使用以下数据集:

heading1,heading2,heading3,heading4,heading5
value1_1,value2_1,value3_1,value4_1,value5_1
value1_2,value2_2,value3_2,value4_2,value5_2

使用以下代码:

var data = $.csv.toObjects(csv):

保存在"data"中的输出将是:

[
  { heading1:"value1_1",heading2:"value2_1",heading3:"value3_1",heading4:"value4_1",heading5:"value5_1" } 
  { heading1:"value1_2",heading2:"value2_2",heading3:"value3_2",heading4:"value4_2",heading5:"value5_2" }
]
注意:从技术上讲,您编写的键值映射方式是无效的JavaScript。包含键值对的对象应该用括号括起来。
如果您想自己尝试一下,建议您查看“toObjects()”选项卡下的基本使用演示
声明:我是jQuery-CSV的原作者。
更新:
编辑以使用op提供的数据集,并包含一个链接到可以测试数据有效性的演示。
更新2:
由于Google Code已关闭,jquery-csv已迁移到GitHub

4
IOW,“toObject”可以被理解为“toJSON”,对吗?另外,调用toObjects(csv)后面的冒号是打错了吗?换句话说,那不应该是一个分号吗? - B. Clay Shannon-B. Crow Raven
11
很棒的库。顺便提一下,传递的参数 csv 是一个 csv 字符串 - 读取文本文件以获取csv字符串。 - callmekatootie
4
如何使用这个库来从csv文件中读取数据? - Richa Sinha
1
@RichaSinha 通过HTML5文件API或AJAX将文件读入文本缓冲区。然后将字符串缓冲区传递到解析器中。它会输出一个数据数组作为结果。请参阅项目页面以获取示例。 - Evan Plaice
1
@GreySage 理解,jquery-csv 实际上并不需要 jquery。它只是一组附加的实用函数,为了保持一致性而附加到 jquery 命名空间中。最初的意图是扩展 jquery(伪单子)对象模型。也许有一天如果 JavaScript 添加了 :: 运算符,那将成为现实,但现在该项目范围已冻结维护模式。 - Evan Plaice
显示剩余8条评论

141

注意: 在我被提醒有关逗号分隔值(CSV)文件中可能会出现的“特殊情况”,例如转义引号之前,我想出了这个解决方案。对于那些想要快速脏糙地完成任务的人,我保留了我的答案,但我推荐使用Evan's answer以获得更高的准确性。


当你的data.txt文件是一个长字符串的逗号分隔条目,并且没有换行符时,此代码将起作用:

data.txt:

 heading1,heading2,heading3,heading4,heading5,value1_1,...,value5_2

JavaScript:

$(document).ready(function() {
    $.ajax({
        type: "GET",
        url: "data.txt",
        dataType: "text",
        success: function(data) {processData(data);}
     });
});

function processData(allText) {
    var record_num = 5;  // or however many elements there are in each row
    var allTextLines = allText.split(/\r\n|\n/);
    var entries = allTextLines[0].split(',');
    var lines = [];

    var headings = entries.splice(0,record_num);
    while (entries.length>0) {
        var tarr = [];
        for (var j=0; j<record_num; j++) {
            tarr.push(headings[j]+":"+entries.shift());
        }
        lines.push(tarr);
    }
    // alert(lines);
}
以下代码可以用于“真正”的CSV文件,每个记录集之间有换行符:
data.txt:
heading1,heading2,heading3,heading4,heading5
value1_1,value2_1,value3_1,value4_1,value5_1
value1_2,value2_2,value3_2,value4_2,value5_2

JavaScript:

$(document).ready(function() {
    $.ajax({
        type: "GET",
        url: "data.txt",
        dataType: "text",
        success: function(data) {processData(data);}
     });
});

function processData(allText) {
    var allTextLines = allText.split(/\r\n|\n/);
    var headers = allTextLines[0].split(',');
    var lines = [];

    for (var i=1; i<allTextLines.length; i++) {
        var data = allTextLines[i].split(',');
        if (data.length == headers.length) {

            var tarr = [];
            for (var j=0; j<headers.length; j++) {
                tarr.push(headers[j]+":"+data[j]);
            }
            lines.push(tarr);
        }
    }
    // alert(lines);
}

http://jsfiddle.net/mblase75/dcqxr/


4
顺便说一下,这假设CSV文件确实有多行 -- 这就是 allText.split(/\r\n|\n/) 的分割基准。如果你的所有数据实际上是一个长字符串的逗号分隔数据而没有换行符,那么它不是一个真正的CSV文件。 - Blazemonger
2
嗨,我使用了这段代码:但是没有输出。只有一个空白的警告框显示出来。 我的文件看起来像这样:heading1,heading2,heading3,heading4,heading5,value1_1,value2_1,value3_1,value4_1,value5_1,value1_2,value2_2,value3_2,value4_2,value5_2 csv.html和data.txt都在同一个文件夹中。 - Mahesh Thumar
8
代码可能无法处理所有有效的IETF标准CSV文件,如果存在具有嵌入逗号、换行符或双引号的字符串,则可能会失败。例如: 1,“IETF允许使用“引号”,逗号和\n换行符” 这是允许的,因为该字符串用双引号括起来,并且双引号已经被转义。 - prototype
1
我试图从Mac读取一个.csv文件。当我将第一个分割改为var allTextLines = allText.split("\r");时,我才能让这个脚本识别换行符。之后它运行得很好!谢谢! - Joe
@Joe,我建议保持var allTextLines = allText.split(/\r\n|\n|\r/);以继续利用正则表达式功能,而不是仅适用于Mac格式换行符的预定义字符串。 - Edwin
显示剩余7条评论

102

不要根据逗号分割 -- 它对于大多数CSV文件都不起作用,而且这个问题的观看次数太多了,无法适用于问答者的输入数据。解析CSV有点可怕,因为没有真正官方标准,而且许多分隔文本编写者不考虑边缘情况。

这个问题很旧了,但我相信现在有更好的解决方案,即Papa Parse。这是一个由我和贡献者共同编写的库,用于解析CSV文本或文件。这是我所知道的唯一支持千兆字节大小文件的JS库。它还能优雅地处理格式不正确的输入。

1GB文件在1分钟内解析:

Parsed 1 GB file in 1 minute

(更新:使用Papa Parse 4,相同的文件在Firefox中只需约30秒。 Papa Parse 4 现在是浏览器中已知的最快速的CSV解析器。)

解析文本非常容易:

var data = Papa.parse(csvString);

解析文件也很容易:

Papa.parse(file, {
    complete: function(results) {
        console.log(results);
    }
});

流式传输文件类似(这里有一个流式传输远程文件的示例):

Papa.parse("http://example.com/bigfoo.csv", {
    download: true,
    step: function(row) {
        console.log("Row:", row.data);
    },
    complete: function() {
        console.log("All done!");
    }
});

如果您的网页在解析时出现锁定,Papa可以使用Web Worker使您的网站保持响应。

Papa可以自动检测分隔符并将值与标题列匹配,如果存在标题行。 它还可以将数字值转换为实际的数字类型。它适当地解析换行符、引号和其他奇怪的情况,甚至可以尽可能地处理格式不正确的输入。 我从现有库中汲取灵感来制作Papa,因此对其他JS实现表示赞扬。


做得好,Papa Parse。有一天我想详细研究一下,看看你是如何处理大文件和流的。我很高兴看到其他开发人员编写了功能齐全的解析器,接替了jquery-csv的工作。 - Evan Plaice
3
谢谢。你可能会喜欢我昨晚在本地聚会上的这个演示文稿:https://docs.google.com/presentation/d/1bmK96ETMtUHG3LFU2sN05ztrdLDq-5WJwJOJttQ515Y/edit?usp=sharing - Matt
1
@ Matt,你的演示非常棒,更加易于理解地描述了Papa Parse。 - siva
@Matt 有趣的演示,我希望我能亲自观看。我有很多问题。主要的一个是,在考虑到内存限制的情况下,如果解析一个1GB的CSV文件后,你会怎么处理结果。由于客户端无法显示所有数据(即由于内存限制),你会将其流式传输到不同的格式、过滤/显示数据子集等吗? - Evan Plaice
1
@Malky.Kid 这不是有效的 CSV(即非定界值中的空格不好)。 MS Excel 的 CSV 格式实现很糟糕。 如果您仍然可以访问源文件,则应该有一个选项可以启用引号定界符。 一旦您这样做,您的数据应该可以与任何 csv 解析器一起使用。 - Evan Plaice
显示剩余4条评论

15

我正在使用 d3.js 来解析csv文件。非常容易使用。 这里是文档

步骤:

  • npm install d3-request

使用Es6;

import { csv } from 'd3-request';
import url from 'path/to/data.csv';

csv(url, function(err, data) {
 console.log(data);
})
请参阅文档获取更多信息。 更新 - d3-request已被弃用。 您可以使用d3-fetch

8
这是一个JavaScript函数,用于解析CSV数据,并考虑到引号内的逗号。
// Parse a CSV row, accounting for commas inside quotes                   
function parse(row){
  var insideQuote = false,                                             
      entries = [],                                                    
      entry = [];
  row.split('').forEach(function (character) {                         
    if(character === '"') {
      insideQuote = !insideQuote;                                      
    } else {
      if(character == "," && !insideQuote) {                           
        entries.push(entry.join(''));                                  
        entry = [];                                                    
      } else {
        entry.push(character);                                         
      }                                                                
    }                                                                  
  });
  entries.push(entry.join(''));                                        
  return entries;                                                      
}

以下是使用该函数解析类似于以下CSV文件的示例:

"foo, the column",bar
2,3
"4, the value",5

转换为数组:

// csv could contain the content read from a csv file
var csv = '"foo, the column",bar\n2,3\n"4, the value",5',

    // Split the input into lines
    lines = csv.split('\n'),

    // Extract column names from the first line
    columnNamesLine = lines[0],
    columnNames = parse(columnNamesLine),

    // Extract data from subsequent lines
    dataLines = lines.slice(1),
    data = dataLines.map(parse);

// Prints ["foo, the column","bar"]
console.log(JSON.stringify(columnNames));

// Prints [["2","3"],["4, the value","5"]]
console.log(JSON.stringify(data));

以下是如何将数据转换为对象,例如 D3 的 csv 解析器(它是一个可靠的第三方解决方案):

var dataObjects = data.map(function (arr) {
  var dataObject = {};
  columnNames.forEach(function(columnName, i){
    dataObject[columnName] = arr[i];
  });
  return dataObject;
});

// Prints [{"foo":"2","bar":"3"},{"foo":"4","bar":"5"}]
console.log(JSON.stringify(dataObjects));

这是这段代码的有效示例
祝使用愉快!--Curran

正是我要找的,比我写的简单得多。谢谢。 我将其转换为CodePen上的这个函数:https://codepen.io/rgraph/pen/NWBwbWp?editors=1010 - Richard

6
您可以使用PapaParse来帮助。 https://www.papaparse.com/ 这里是一个CodePen。 https://codepen.io/sandro-wiggers/pen/VxrxNJ
Papa.parse(e, {
            header:true,
            before: function(file, inputElem){ console.log('Attempting to Parse...')},
            error: function(err, file, inputElem, reason){ console.log(err); },
            complete: function(results, file){ $.PAYLOAD = results; }
        });

4
如果你不想使用Ajax解决这个问题,可以使用FileReader() Web API示例实现:
  1. 选择 .csv 文件
  2. 查看输出结果

function readSingleFile(e) {
  var file = e.target.files[0];
  if (!file) {
    return;
  }

  var reader = new FileReader();
  reader.onload = function(e) {
    var contents = e.target.result;
    displayContents(contents);
    displayParsed(contents);
  };
  reader.readAsText(file);
}

function displayContents(contents) {
  var element = document.getElementById('file-content');
  element.textContent = contents;
}

function displayParsed(contents) {
  const element = document.getElementById('file-parsed');
  const json = contents.split(',');
  element.textContent = JSON.stringify(json);
}

document.getElementById('file-input').addEventListener('change', readSingleFile, false);
<input type="file" id="file-input" />

<h3>Raw contents of the file:</h3>
<pre id="file-content">No data yet.</pre>

<h3>Parsed file contents:</h3>
<pre id="file-parsed">No data yet.</pre>


4

这是一个老问题,在2022年有很多方法可以实现。首先,我认为D3是数据操作的最佳选择之一。它是开源且免费使用的,而且它是模块化的,所以我们可以只导入fetch模块

这里是一个基本示例。我们将使用传统模式,因此我将导入整个D3库。现在,让我们调用d3.csv函数,就完成了。这个函数内部调用了fetch方法,因此它可以打开dataURL、url、文件、blob等等。

const fileInput = document.getElementById('csv')
const outElement = document.getElementById('out')
const previewCSVData = async dataurl => {
  const d = await d3.csv(dataurl)
  console.log({
    d
  })
  outElement.textContent = d.columns
}

const readFile = e => {
  const file = fileInput.files[0]
  const reader = new FileReader()
  reader.onload = () => {
    const dataUrl = reader.result;
    previewCSVData(dataUrl)
  }
  reader.readAsDataURL(file)
}

fileInput.onchange = readFile
<script type="text/javascript" src="https://unpkg.com/d3@7.6.1/dist/d3.min.js"></script>
<div>
  <p>Select local CSV File:</p>
  <input id="csv" type="file" accept=".csv">
</div>
<pre id="out"><p>File headers will appear here</p></pre>

如果我们不想使用任何库,只想使用简单的JavaScript(Vanilla JS),并且我们已经成功获取了一个文件的文本内容作为"data",又不想使用d3,我们可以实现一个简单的函数,将"data"分割成"text"数组,然后提取第一行并将其分割成"headers"数组,其余的"text"将是"lines",我们将处理它。之后,我们映射每个"line"并提取其"values",从将每个"header"映射到其对应的"value[index]"值创建一个数组,再创建一个"row"对象。
注意:我们还将使用一个小技巧,即JavaScript中的数组对象也可以具有属性。因此,我们将定义一个属性"rows.headers",并将"headers"分配给它。

const data = `heading_1,heading_2,heading_3,heading_4,heading_5
value_1_1,value_2_1,value_3_1,value_4_1,value_5_1
value_1_2,value_2_2,value_3_2,value_4_2,value_5_2
value_1_3,value_2_3,value_3_3,value_4_3,value_5_3`

const csvParser = data => {
  const text = data.split(/\r\n|\n/)
  const [first, ...lines] = text
  const headers = first.split(',')
  const rows = []
  rows.headers = headers 
  lines.map(line => {
    const values = line.split(',')
    const row = Object.fromEntries(headers.map((header, i) => [header, values[i]]))
    rows.push(row)
  })

  return rows
}

const d = csvParser(data)
// Accessing to the theaders attribute
const headers = d.headers
console.log({headers})
console.log({d})

最后,让我们使用fetch实现一个原始的JS文件加载器,并解析csv文件。

const fetchFile = async dataURL => {
  return await fetch(dataURL).then(response => response.text())
}

const csvParser = data => {
  const text = data.split(/\r\n|\n/)
  const [first, ...lines] = text
  const headers = first.split(',')
  const rows = []
  rows.headers = headers 
  lines.map(line => {
    const values = line.split(',')
    const row = Object.fromEntries(headers.map((header, i) => [header, values[i]]))
    rows.push(row)
  })

  return rows
}

const fileInput = document.getElementById('csv')
const outElement = document.getElementById('out')
const previewCSVData = async dataURL => {
  const data = await fetchFile(dataURL)
  const d = csvParser(data)
  console.log({ d })
  outElement.textContent = d.headers
}

const readFile = e => {
  const file = fileInput.files[0]
  const reader = new FileReader()
  reader.onload = () => {
    const dataURL = reader.result;
    previewCSVData(dataURL)
  }
  reader.readAsDataURL(file)
}

fileInput.onchange = readFile
<script type="text/javascript"  src="https://unpkg.com/d3@7.6.1/dist/d3.min.js"></script>
<div>
  <p>Select local CSV File:</p>
  <input id="csv" type="file" accept=".csv">
</div>
<pre id="out"><p>File contents will appear here</p></pre>

我用这个文件来测试它


3
function CSVParse(csvFile)
{
    this.rows = [];

    var fieldRegEx = new RegExp('(?:\s*"((?:""|[^"])*)"\s*|\s*((?:""|[^",\r\n])*(?:""|[^"\s,\r\n]))?\s*)(,|[\r\n]+|$)', "g");   
    var row = [];
    var currMatch = null;

    while (currMatch = fieldRegEx.exec(this.csvFile))
    {
        row.push([currMatch[1], currMatch[2]].join('')); // concatenate with potential nulls

        if (currMatch[3] != ',')
        {
            this.rows.push(row);
            row = [];
        }

        if (currMatch[3].length == 0)
            break;
    }
}

我喜欢让正则表达式尽可能多地工作。该正则表达式将所有项视为带引号或不带引号,后跟列分隔符或行分隔符。或者是文本的结尾。
这就是为什么有最后一个条件--没有它,它将是一个无限循环,因为模式可以匹配零长度字段(在csv中完全有效)。但由于$是一个零长度断言,它不会进展到非匹配并结束循环。
顺便说一下,我必须使第二个备选方案排除围绕值的引号;似乎它在我的javascript引擎上在第一个备选方案之前执行,并将引号视为未引用的值的一部分。我不会问--只是让它工作了。

很不幸,我在这个函数中陷入了一个无限循环。 - Hauke
@Hauke -- 如果你能将数据分成几列和几行,仍然产生无限循环,我会非常感激的-- 这可能会让我了解为什么我之前失败了的原因。 - Gerard ONeill

2
根据被接受的答案,我通过将此处的1更改为0使其工作:
for (var i=1; i<allTextLines.length; i++) {

更改为

for (var i=0; i<allTextLines.length; i++) {

如果一个文件只有一行连续的文本,它将计算其allTextLines.length为1。因此,如果循环从1开始,并且只要小于1就运行,它永远不会运行。这就是空白警告框的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接