如何将 JavaScript 数组信息导出为 CSV(客户端)?

778

我知道有很多类似的问题,但我需要使用JavaScript来完成。我正在使用Dojo 1.8,并且已经将所有属性信息存储在一个数组中,它看起来像这样:

[["name1", "city_name1", ...]["name2", "city_name2", ...]]

你有什么想法可以在客户端将此导出为CSV吗?

31个回答

1277

您可以使用本地JavaScript完成此操作。您需要将数据解析为正确的CSV格式,如下所示(假设您正在使用一个数组来存储数据,就像您在问题中描述的那样):

const rows = [
    ["name1", "city1", "some other info"],
    ["name2", "city2", "more info"]
];

let csvContent = "data:text/csv;charset=utf-8,";

rows.forEach(function(rowArray) {
    let row = rowArray.join(",");
    csvContent += row + "\r\n";
});

或使用较短的方式(使用箭头函数):

const rows = [
    ["name1", "city1", "some other info"],
    ["name2", "city2", "more info"]
];

let csvContent = "data:text/csv;charset=utf-8," 
    + rows.map(e => e.join(",")).join("\n");

接下来,您可以使用JavaScript的window.openencodeURI函数来下载CSV文件,方法如下:

var encodedUri = encodeURI(csvContent);
window.open(encodedUri);

编辑:

如果你想给你的文件命名,你需要采取一些不同的方法,因为使用window.open方法访问数据URI时不支持这样做。为了实现这一点,你可以创建一个隐藏的<a> DOM节点,并设置它的download属性,如下所示:

var encodedUri = encodeURI(csvContent);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "my_data.csv");
document.body.appendChild(link); // Required for FF

link.click(); // This will download the data file named "my_data.csv".

6
据我所知,使用 window.open 没有办法做到这一点。但是,你可以创建一个隐藏的链接,将其中的 download 属性设置为你想要的文件名。然后点击这个链接就会下载以你想要的文件名命名的文件。我会将这个方法加入我的答案中。 - Default
14
我不得不添加 document.body.appendChild(link); 才能在火狐浏览器中获得完全的支持。 - Hardbyte
29
这个答案是错误的:它在数据为[["Hello, world"]]的情况下会失败。这将输出两列,而实际上应该只输出一列。 - aredridel
27
对于大约7000行的内容,这个功能很好用。但是开始出现了这个错误: NETWORK_INVALID_REQUEST。还有其他人遇到这个问题吗? encodeURIComponent() 函数是否有任何数据上限或其他限制? 我正在使用Chrome浏览器。 - Abhidemon
35
答案是对于那么大的内容,您必须使用blob类型,然后它就可以正常工作,例如:blob = new Blob([csvContent],{type:“text / csv”}); href = window.URL.createObjectURL(blob); 更多细节请参见:https://dev59.com/nGIk5IYBdhLWcg3wYtP7#19328891 - mdubez
显示剩余34条评论

358

根据上面的答案,我创建了这个函数,并在IE 11、Chrome 36和Firefox 29上进行了测试。

function exportToCsv(filename, rows) {
    var processRow = function (row) {
        var finalVal = '';
        for (var j = 0; j < row.length; j++) {
            var innerValue = row[j] === null ? '' : row[j].toString();
            if (row[j] instanceof Date) {
                innerValue = row[j].toLocaleString();
            };
            var result = innerValue.replace(/"/g, '""');
            if (result.search(/("|,|\n)/g) >= 0)
                result = '"' + result + '"';
            if (j > 0)
                finalVal += ',';
            finalVal += result;
        }
        return finalVal + '\n';
    };

    var csvFile = '';
    for (var i = 0; i < rows.length; i++) {
        csvFile += processRow(rows[i]);
    }

    var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
    if (navigator.msSaveBlob) { // IE 10+
        navigator.msSaveBlob(blob, filename);
    } else {
        var link = document.createElement("a");
        if (link.download !== undefined) { // feature detection
            // Browsers that support HTML5 download attribute
            var url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download", filename);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
}
例如:https://jsfiddle.net/jossef/m3rrLzk0/

9
如果link.download !== undefined不成立,可以回退到window.open - MrYellow
5
这是一段很棒的代码。你是否愿意将其授权给比SO默认的CC-BY-SA更自由的许可证呢?例如,CC0、MIT、BSD、Apache、X11等等... http://meta.stackexchange.com/questions/12527/do-i-have-to-worry-about-copyright-issues-for-code-posted-on-stack-overflow - joseph_morris
1
我一直在许多Web应用程序中使用这种方法来实现Excel导出。但是Chrome 43+现在已将DOM属性移动到原型链中。在link.style.visibility='hidden'处会抛出异常,因为DOM属性是只读的。更多细节可以在http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype的“在严格模式下写入只读属性将抛出错误”部分找到。 - Blaise
3
到目前为止,这个回答是最好的一个。它包括了带有特殊字符和括号的情况。 - Vladimir Kostov
5
我使用了这个答案的下载部分,在Chrome上表现良好,谢谢! - Liran H
显示剩余12条评论

120
一个极简但功能齐全的解决方案 :)
/** Convert a 2D array into a CSV string
 */
function arrayToCsv(data){
  return data.map(row =>
    row
    .map(String)  // convert every value to String
    .map(v => v.replaceAll('"', '""'))  // escape double colons
    .map(v => `"${v}"`)  // quote it
    .join(',')  // comma-separated
  ).join('\r\n');  // rows starting on new lines
}

例子:

let csv = arrayToCsv([
  [1, '2', '"3"'],
  [true, null, undefined],
]);

结果:

"1","2","""3"""
"true","null","undefined"

现在将其下载为文件:


/** Download contents as a file
 * Source: https://dev59.com/questions/LWUp5IYBdhLWcg3w5KvV
 */
function downloadBlob(content, filename, contentType) {
  // Create a blob
  var blob = new Blob([content], { type: contentType });
  var url = URL.createObjectURL(blob);

  // Create a link to download it
  var pom = document.createElement('a');
  pom.href = url;
  pom.setAttribute('download', filename);
  pom.click();
}

下载:

downloadBlob(csv, 'export.csv', 'text/csv;charset=utf-8;')

保存对话框

生成的文件


16
这个回答虽然晚了,但确实是这里最好的答案。 - Garr Godfrey
3
社区应该以这个为榜样 A :-) ,分隔符、封闭符号、转义字符是通用术语,可以用来存储格式化字符。(在这里,“"”既是转义字符又是封闭符号——并不总是这样,您必须将此信息传输给CSV接收器) - jave.web
我会移除创建的锚点元素和对象URL。请参考URL: revokeObjectURL():https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL_static 和 document.body.removeChild(pom) - undefined
1
这里的答案很好,但希望能进一步改进,只有在单元格内容中包含连接字符时才添加引号。 - undefined

95

这个解决方案应该适用于 Internet Explorer 10+, Edge, 旧版和新版的 Chrome, FireFox, Safari, ++

这个被接受的答案在IE和Safari上不起作用。

// Example data given in question text
var data = [
  ['name1', 'city1', 'some other info'],
  ['name2', 'city2', 'more info']
];

// Building the CSV from the Data two-dimensional array
// Each column is separated by ";" and new line "\n" for next row
var csvContent = '';
data.forEach(function(infoArray, index) {
  dataString = infoArray.join(';');
  csvContent += index < data.length ? dataString + '\n' : dataString;
});

// The download function takes a CSV string, the filename and mimeType as parameters
// Scroll/look down at the bottom of this snippet to see how download is called
var download = function(content, fileName, mimeType) {
  var a = document.createElement('a');
  mimeType = mimeType || 'application/octet-stream';

  if (navigator.msSaveBlob) { // IE10
    navigator.msSaveBlob(new Blob([content], {
      type: mimeType
    }), fileName);
  } else if (URL && 'download' in a) { //html5 A[download]
    a.href = URL.createObjectURL(new Blob([content], {
      type: mimeType
    }));
    a.setAttribute('download', fileName);
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  } else {
    location.href = 'data:application/octet-stream,' + encodeURIComponent(content); // only this mime type is supported
  }
}

download(csvContent, 'dowload.csv', 'text/csv;encoding:utf-8');

运行此代码片段将会下载模拟数据为 csv 文件

感谢 dandavis https://dev59.com/GnHYa4cB1Zd3GeqPPLwL#16377813


1
(至少,在没有“setTimeout”的情况下,HTML5代码)可以正常运行。 - StubbornShowaGuy
适用于最新的Chrome,IE和Firefox浏览器。谢谢! - walla
这是唯一真正的跨浏览器解决方案。请注意,它适用于Safari 10.10和移动版Safari。但是,iframe部分可以被替换为只需使用location.href = ... - Dan
2
注意:函数中有一个拼写错误,实际上应该是 URL.createObjectURL(以 URL 结尾,而不是 Url)。 - Nathan Hinchey
@Santosh,这远远超出了这个的范畴;xlsx是一种压缩的XML样式文档,而不是“逗号分隔值”(或在这种情况下,分号分隔值)。请查看https://github.com/egeriis/zipcelx或类似内容。 - mix3d
显示剩余5条评论

43

在Chrome 35更新中,下载属性的行为发生了变化。

https://code.google.com/p/chromium/issues/detail?id=373182

要在Chrome中使用,请使用以下内容:

var pom = document.createElement('a');
var csvContent=csv; //here we load our csv data 
var blob = new Blob([csvContent],{type: 'text/csv;charset=utf-8;'});
var url = URL.createObjectURL(blob);
pom.href = url;
pom.setAttribute('download', 'foo.csv');
pom.click();

2
你也可以查看这个链接:https://github.com/mholt/PapaParse/issues/175#issuecomment-201308792 - Gabriel
完美运行! - Tarilonte

42

人们正在尝试创建自己的csv字符串,但在一些边缘情况下失败,例如特殊字符,这肯定是一个已经解决的问题,对吧?

papaparse - 用于JSON到CSV编码。使用Papa.unparse()

import Papa from "papaparse";

const downloadCSV = (args) => {  

  let filename = args.filename || 'export.csv';
  let columns = args.columns || null;

  let csv = Papa.unparse({ data: args.data, fields: columns})
  if (csv == null) return;

  var blob = new Blob([csv]);
  if (window.navigator.msSaveOrOpenBlob)  // IE hack; see http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
      window.navigator.msSaveBlob(blob, args.filename);
  else
  {
      var a = window.document.createElement("a");
      a.href = window.URL.createObjectURL(blob, {type: "text/plain"});
      a.download = filename;
      document.body.appendChild(a);
      a.click();  // IE: "Access is denied"; see: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access
      document.body.removeChild(a);
  }

}

使用示例

downloadCSV({ 
  filename: "filename.csv",
  data: [{"a": "1", "b": "2"}],
  columns: ["a","b"]
});

https://github.com/mholt/PapaParse/issues/175 - 有关浏览器支持讨论,请查看此评论。


1
我添加了一个简单的答案,其中使用了Papa ParseFileSaver.js来进行下载。如果您认为这是更好的方法,请随意更新或复制。 - totalhack
你有哪些特殊字符的例子会破坏简单的用 " 替换为 "" 的方法吗?解析 CSV 是棘手的,应该处理引号内的换行符、引号内的逗号等。但生成 CSV 相对来说比较简单。 - Garr Godfrey
PapaParse有许多设置,用于处理特殊字符/转义字符等内容。我认为这是大多数情况下的正确解决方案,因为CSV是一个非常棘手的格式,有许多需要注意的地方,而那些天真搜索的人可能/可能不会意识到。快速的本地JS解决方案很酷,但不应该成为首选答案或推荐解决方案。 - Taha Attari

39

我来这里是想找更符合RFC 4180标准的实现,但我没有找到,所以为了满足自己的需求,我做了一个(可能效率不高)的实现。我想与大家分享。

var content = [['1st title', '2nd title', '3rd title', 'another title'], ['a a a', 'bb\nb', 'cc,c', 'dd"d'], ['www', 'xxx', 'yyy', 'zzz']];

var finalVal = '';

for (var i = 0; i < content.length; i++) {
    var value = content[i];

    for (var j = 0; j < value.length; j++) {
        var innerValue =  value[j]===null?'':value[j].toString();
        var result = innerValue.replace(/"/g, '""');
        if (result.search(/("|,|\n)/g) >= 0)
            result = '"' + result + '"';
        if (j > 0)
            finalVal += ',';
        finalVal += result;
    }

    finalVal += '\n';
}

console.log(finalVal);

var download = document.getElementById('download');
download.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(finalVal));
download.setAttribute('download', 'test.csv');

希望这能帮助未来的某个人。这个方法同时结合了CSV的编码和下载功能。在我的jsfiddle示例中,你可以下载文件(假设使用HTML 5浏览器),或者在控制台中查看输出。

更新:

Chrome现在似乎失去了命名文件的功能。我不确定发生了什么或如何修复它,但每当我使用这段代码(包括jsfiddle),下载的文件现在都被命名为download.csv


很好的发现,Chris。我没有用数字数据测试它 :) - Uxonith
我不确定最后一个空值检查是否是必要的行为。空值与空字符串非常不同。如果要实现这个功能,我建议使用自定义的空值(例如:'[[NULL]]')。可能也需要对未定义的情况进行异常处理,但我建议不要将空值替换为空字符串。 - Uxonith
我已经测试过了,发现你是正确的。这似乎在Chrome和Opera中有效。Safari只是打开一个带有内容的页面。Internet Explorer...好吧,它就是IE。对于我的情况,我将在服务器端生成我的CSV并以这种方式提供服务,可惜了。 - Uxonith

36

@Default的解决方案在Chrome上完美运行(非常感谢!),但我在IE上遇到了问题。

这是一个解决方案(适用于IE10):

var csvContent=data; //here we load our csv data 
var blob = new Blob([csvContent],{
    type: "text/csv;charset=utf-8;"
});

navigator.msSaveBlob(blob, "filename.csv")

1
不适用于Chrome。即使在测试之前,前缀“ms”也很明显 :) 希望对于WebKit有类似的东西。 - Sergey Sob
msSaveBlob - 已弃用的功能 - degr

25

适用于所有语言的工作

        function convertToCsv(fName, rows) {
        var csv = '';
        for (var i = 0; i < rows.length; i++) {
            var row = rows[i];
            for (var j = 0; j < row.length; j++) {
                var val = row[j] === null ? '' : row[j].toString();
                val = val.replace(/\t/gi, " ");
                if (j > 0)
                    csv += '\t';
                csv += val;
            }
            csv += '\n';
        }

        // for UTF-16
        var cCode, bArr = [];
        bArr.push(255, 254);
        for (var i = 0; i < csv.length; ++i) {
            cCode = csv.charCodeAt(i);
            bArr.push(cCode & 0xff);
            bArr.push(cCode / 256 >>> 0);
        }

        var blob = new Blob([new Uint8Array(bArr)], { type: 'text/csv;charset=UTF-16LE;' });
        if (navigator.msSaveBlob) {
            navigator.msSaveBlob(blob, fName);
        } else {
            var link = document.createElement("a");
            if (link.download !== undefined) {
                var url = window.URL.createObjectURL(blob);
                link.setAttribute("href", url);
                link.setAttribute("download", fName);
                link.style.visibility = 'hidden';
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                window.URL.revokeObjectURL(url);
            }
        }
    }



    convertToCsv('download.csv', [
        ['Order', 'Language'],
        ['1', 'English'],
        ['2', 'Español'],
        ['3', 'Français'],
        ['4', 'Português'],
        ['5', 'čeština'],
        ['6', 'Slovenščina'],
        ['7', 'Tiếng Việt'],
        ['8', 'Türkçe'],
        ['9', 'Norsk bokmål'],
        ['10', 'Ελληνικά'],
        ['11', 'беларускі'],
        ['12', 'русский'],
        ['13', 'Українська'],
        ['14', 'հայերեն'],
        ['15', 'עִברִית'],
        ['16', 'اردو'],
        ['17', 'नेपाली'],
        ['18', 'हिंदी'],
        ['19', 'ไทย'],
        ['20', 'ქართული'],
        ['21', '中国'],
        ['22', '한국어'],
        ['23', '日本語'],
    ])

1
请问您能帮我理解一下这个UTF-16代码块是什么,以及它在这里的用途是什么吗? - Mar1009
嗨Mar1009。这对于某些语言是必需的。例如,西里尔字母表。 - Serdar Didan
如果下载的数据稍微大一点,那么 window.URL.revokeObjectURL(url); 将会导致网络错误。将其包装在 setTimeout() 中可以解决这个问题,参见这里 - cdauth
对于 Excel 365 商业应用程序,我不得不删除 BOM 标记 bArr.push(255, 254),因为 Excel 无法识别列。没有 BOM,Unicode 和列都可以被正确识别。我想知道其他版本的行为如何。 - tequilacat

20

您可以使用以下代码将数组导出为CSV文件,使用Javascript。

这也处理特殊字符部分。

var arrayContent = [["Séjour 1, é,í,ú,ü,ű"],["Séjour 2, é,í,ú,ü,ű"]];
var csvContent = arrayContent.join("\n");
var link = window.document.createElement("a");
link.setAttribute("href", "data:text/csv;charset=utf-8,%EF%BB%BF" + encodeURI(csvContent));
link.setAttribute("download", "upload_data.csv");
link.click(); 

这里 是可工作的 jsfiddle 链接。


这应该被标记为正确答案:简短、简明扼要,没有特殊情况。我所做的唯一修改是将内部的回车换行符替换为空格。 - undefined

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