如何在PHP中输出一个Excel可以正确读取的UTF-8格式的CSV文件?

225

我有一个非常简单的东西,只是以CSV格式输出一些内容,但它必须是UTF-8编码。我在TextEdit、TextMate或Dreamweaver中打开此文件时,它会正确显示UTF-8字符,但如果我在Excel中打开它,它会显示像íÄ这样的奇怪字符。以下是我文档头部所写的内容:

header("content-type:application/csv;charset=UTF-8");
header("Content-Disposition:attachment;filename=\"CHS.csv\"");

除了 Excel(Mac,2008)无法正确导入之外,所有这些似乎都有所作用。Excel中没有选项可以让我“以UTF-8格式打开”或任何其他选项,所以我有点烦恼。

我似乎找不到任何清晰的解决方案,尽管很多人都遇到了同样的问题。我看到的最多的是包含BOM,但我无法确定如何做到这一点。正如您在上面看到的,我只是“echo”这些数据,我没有写任何文件。如果需要,我可以这样做,只是因为目前似乎没有必要。有帮助吗?

更新:我尝试将 BOM 作为 echo pack("CCC", 0xef, 0xbb, 0xbf); 传递,这仅仅是从一个试图检测 BOM 的网站上获取的。但是,当它导入时,Excel只是将这三个字符附加到第一个单元格,并仍然会弄乱特殊字符。


2
Excel没有提供调整输入文件字符集的选项吗?你确定100%吗?我手边没有副本,所以无法尝试,但我想象中一定有一个下拉框。 - Pekka
这是在 Mac 上的 Excel - 它似乎比 PC 上的 Excel 有更多限制。在“打开”对话框中根本没有下拉菜单,只能选择要打开的文件类型。我已经到处找了,如果它存在的话,那就很难发现。我可以说有 98% 的把握。 - Ben Saufley
微软办公套件还是开源办公套件? - ajreal
微软在这方面更好,我找不到任何方法让OpenOffice检测字符集,甚至是BOM。真可惜。 - Ciantic
BOM对于Mac版的Microsoft Excel 2008也没有影响。 - Ben Saufley
26个回答

337
我有相同(或类似)的问题。
在我的情况下,如果我在输出中添加一个BOM,它就可以工作。
header('Content-type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename=Customers_Export.csv');
echo "\xEF\xBB\xBF"; // UTF-8 BOM

我相信这是一个相当丑陋的黑客方法,但对我来说有效,至少对于 Excel 2007 Windows。不确定它是否适用于 Mac。


7
很抱歉,我无法担任翻译任务。我只能使用中文回答您的问题和提供相关信息,但我可以尝试用简洁明了的语言回答您的问题。有什么我可以帮助您的吗? - davidtbernal
2
这是正确的。至少,我尝试过像上面的例子一样在开头加上BOM,但实际上只显示了一些奇怪的字符,并且继续显示特殊字符错误(在Excel 2011中测试)。在所有设置中,这似乎对我最有效:https://dev59.com/AnVC5IYBdhLWcg3w4Vb6#1648671(使用PHP)。 - kasimir
4
我一直收到关于这个问题的警报,因为很多其他人也遇到了同样的问题。我希望我能将其标记为正确答案,因为它得到了很多赞同。不幸的是,我终于尝试了这个解决方案(很久以前就放弃了那个项目),但在 Excel 2008 for Mac 上对我没有用。很高兴它对你有用。我已经点赞了这个解决方案。但它并不能解决 Excel for Mac 2008 的问题。如果有人在 Mac 上运气好,请务必让我(以及显然是所有人)知道。 - Ben Saufley
我遇到了这个问题..所以我尝试了这个解决方案。我丢掉了BOM,在记事本中它可以工作,但在Excel中却出错了。所以我又把它放回去了 咧嘴微笑 - Damon
2
我认为不应该使用Content-Encoding标头来告诉字符集。这更多是关于压缩的:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding - Márton Tamás
显示剩余11条评论

150

引用一位微软支持工程师的话:

目前,Mac版本的Excel不支持UTF-8。

更新,2017年:这适用于Office 2016之前的所有Mac版Microsoft Excel。较新版本(来自Office 365)现在支持UTF-8。

为了输出Excel在Windows和OS X上都能成功读取的UTF-8内容,您需要做两件事:

  1. Make sure that you convert your UTF-8 CSV text to UTF-16LE

    mb_convert_encoding($csv, 'UTF-16LE', 'UTF-8');
    
  2. Make sure that you add the UTF-16LE byte order mark to the start of the file

    chr(255) . chr(254)
    
下一个问题只会在OS X上使用Excel时出现(而不是Windows),当查看一个带有逗号分隔值的CSV文件时,Excel将只呈现一行,并将所有文本和逗号放在第一行中。
避免这种情况的方法是使用制表符作为分隔值。
我使用了PHP评论中的此函数(使用制表符“\t”而不是逗号),它在OS X和Windows Excel上完美运行。
请注意,为了修复最后一行为空列的问题,我确实必须更改代码行。
    $field_cnt = count($fields);

    $field_cnt = count($fields)-1;

作为本页面其他评论所说,像OpenOffice Calc、苹果自带的Numbers和Google Doc的电子表格等其他电子表格应用程序对于带逗号的UTF-8文件没有任何问题。
请参见此问题中的表格,了解Unicode CSV文件在Excel中的工作情况。
作为一个额外提示,我想补充一下,如果你正在使用Composer,你应该考虑将League\Csv添加到requires中。League\Csv拥有非常好的API来构建CSV文件
要使用League\Csv来创建CSV文件,请查看此示例

2
别笑我笨,但是我无法让这个工作起来。我使用了mb_convert_encoding函数将我的数据转换了一下,并在文件内容之前输出了BOM,但是当我在Excel中打开文件时,只能看到一行中文字符。目前我仍然使用逗号而不是制表符,但是应该不会导致我所见到的问题。 - John Rix
2
这个解决方案也为我在Mac和Windows上解决了UTF8问题。我还发现,在CSV文件中添加一行sep=;sep=,可以告诉Excel如何分隔CSV,列将再次正确创建。 - Luc Wollants
5
我发现如果我只用BOM开始字符串,然后在另一行添加"sep =,\n",再在另一行添加数据,结果的东西会变得混乱。如果我把这三个部分都写在一行里($csv = chr(255) . chr (254) / * BOM * / 。"sep=,\n".mb_convert_encoding($csv,'UTF-16LE','UTF-8');),则结果就没问题了。现在Mac上看起来很好,但我没有Windows进行测试。 - tremby
4
对我来说唯一有效的方法。微软的不一致性让我惊讶不已。保存普通csv文件时使用“,”作为分隔符,但如果有BOM,则出于某种原因使用“\t”进行保存。.csv中的“C”代表“逗号”,该死的。这真的不难做到。 - Phil
1
在我的Excel Mac 2011中,在文件开头放置chr(255).chr(254)会将所有字符转换为中文(或韩文,无法真正区分)。对我有效的唯一解决方案是使用iconv。 - Christer Fernstrom
显示剩余7条评论

46

在我的情况下,以下方法非常有效,可以使带有UTF-8字符的CSV文件在Excel中正确显示。

$out = fopen('php://output', 'w');
fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($out, $some_csv_strings);

0xEF 0xBB 0xBF BOM头将让Excel知道正确的编码方式。


4
你太牛了,兄弟!<3 - Adrien G
fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF)); <-- 这正是我所需要的。它非常有效。 - Alex
谢谢,伙计。这个解决方案很好用,而其他的解决方案则不行。 - spetsnaz

38

以下是我如何实现此功能(促使浏览器下载 CSV 文件)的方法:

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=file.csv');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
echo "\xEF\xBB\xBF"; // UTF-8 BOM
echo $csv_file_content;
exit();

它唯一解决了在 Mac 上按空格键时 CSV 预览中的 UTF8 编码问题..但在 Excel Mac 2008 中没有解决...不知道为什么


1
UTF-8 BOM 的代码行帮我成功地将数据导出为 xls 格式,真是太棒了! - Mladen Janjetovic
9
在寻找将内容直接以utf8格式输出到浏览器的方法时,我看到了这篇文章。以下是我的解决方案,感谢这篇文章提供字节顺序标记(BOM)和二进制传输头: $outstream = fopen( "php://output", 'w' ); fputs( $outstream, "\xEF\xBB\xBF" ); foreach ( $export as $fields ) { fputcsv( $outstream, $fields ); } fclose( $outstream ); - Will B.
笔误 - 删除双重 )) - sitilge
我测试了所有的解决方案,但只有@Jazzer的一个是有效的。 - Vlado

24
我刚刚处理了同样的问题,并提出了两个解决方案。
  1. Use the PHPExcel class as suggested by bpeterson76.

    • Using this class generates the most widely compatible file, I was able to generate a file from UTF-8 encoded data that opened fine in Excel 2008 Mac, Excel 2007 Windows, and Google Docs.
    • The biggest problem with using PHPExcel is that it's slow and uses a lot of memory, which isn't an issue for reasonably sized files, but if your Excel/CSV file has hundreds or thousands of rows, this library becomes unusable.
    • Here is a PHP method that will take some TSV data and output an Excel file to the browser, note that it uses the Excel5 Writer, which means the file should be compatible with older versions of Excel, but I no longer have access to any, so I cannot test them.

      function excel_export($tsv_data, $filename) {
          $export_data = preg_split("/\n/", $tsv_data);
          foreach($export_data as &$row) {
              $row = preg_split("/\t/", $row);
          }
      
          include("includes/PHPExcel.php");
          include('includes/PHPExcel/Writer/Excel5.php');
      
          $objPHPExcel = new PHPExcel();          
      
          $objPHPExcel->setActiveSheetIndex(0);
          $sheet = $objPHPExcel->getActiveSheet();
          $row = '1';
          $col = "A";
          foreach($export_data as $row_cells) {
              if(!is_array($row_cells)) { continue; }
                  foreach($row_cells as $cell) {
                      $sheet->setCellValue($col.$row, $cell);
                      $col++;
                  }
              $row += 1;
              $col = "A";
          }
      
          $objWriter = new PHPExcel_Writer_Excel5($objPHPExcel);
          header('Content-Type: application/vnd.ms-excel');
          header('Content-Disposition: attachment;filename="'.$filename.'.xls"');
          header('Cache-Control: max-age=0');
          $objWriter->save('php://output');   
          exit;   
      }
      
  2. Because of the efficiency issues with PHPExcel, I also had to figure out how to generate a UTF-8 & Excel compatible CSV or TSV file.

    • The best I could come up with was a file that was compatible with Excel 2008 Mac, and Excel 2007 PC, but not Google Docs, which is good enough for my application.
    • I found the solution here, specifically, this answer, but you should also read the accepted answer as it explains the problem.
    • Here is the PHP code I used, note that I am using tsv data (tabs as delimiters instead of commas):

      header ( 'HTTP/1.1 200 OK' );
      header ( 'Date: ' . date ( 'D M j G:i:s T Y' ) );
      header ( 'Last-Modified: ' . date ( 'D M j G:i:s T Y' ) );
      header ( 'Content-Type: application/vnd.ms-excel') ;
      header ( 'Content-Disposition: attachment;filename=export.csv' );
      print chr(255) . chr(254) . mb_convert_encoding($tsv_data, 'UTF-16LE', 'UTF-8');
      exit;
      

我在Windows-7 Excel 2007上遇到了同样的问题,尝试了所有建议但都失败了 :( - PHP Ferrari
UTF-16LE方案是有效的。请注意正确格式化tsv文件,必须是tab分隔而不是逗号分隔。此外,请参考Marc在这里的帖子:https://dev59.com/AnVC5IYBdhLWcg3w4Vb6#1648671 - Ellert van Koperen
第二个解决方案对我有效,但是我使用了.xls作为输出文件的扩展名。 - Ghanshyam Bagul

21

我遇到了同样的问题,解决方法如下:

    // header('Content-Encoding: UTF-8'); // invalid value!
    header('Content-Type: text/csv; charset=utf-8' );
    header(sprintf( 'Content-Disposition: attachment; filename=my-csv-%s.csv', date( 'dmY-His' ) ) );
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');

    $df = fopen( 'php://output', 'w' );

    //This line is important:
    fputs( $df, "\xEF\xBB\xBF" ); // UTF-8 BOM !!!!!

    foreach ( $rows as $row ) {
        fputcsv( $df, $row );
    }
    fclose($df);
    exit();

Content-Encoding永远不应该取值UTF-8。这是无效的。https://dev59.com/uGQn5IYBdhLWcg3wCjX0#17155003 - Cheeso

13

Excel不支持UTF-8编码格式。您需要将您的UTF-8文本编码成UCS-2LE。

mb_convert_encoding($output, 'UCS-2LE', 'UTF-8');

3
建议使用 UTF-16LE 编码,这能涵盖所有已知的字符。 - Ellert van Koperen
我注意到mb_convert_encoding在我的导出的XLS文件中能够正常工作并正确显示变音符号。我使用mysql默认配置以及apache + php设置为utf-8工作。 - Junior Mayhé

8

针对此问题的跟进:

看起来问题只是出现在Mac上的Excel中。这不是我生成文件的方式有问题,因为即使从Excel生成CSV也会破坏它们。我将其保存为CSV并重新导入,所有字符都乱了。

所以...似乎没有正确的答案。感谢所有的建议。

我想说,从我所读到的所有内容来看,@Daniel Magliola关于BOM的建议可能是其他电脑的最佳答案。但它仍然不能解决我的问题。


由于UTF8在Mac和Windows的支持不一致,最好使用完全不同的编码方式。我发现cp1252通常与Microsoft的东西兼容良好。http://en.wikipedia.org/wiki/Windows-1252 - s29
1
Ben,这个问题有一个正确的答案 :) 看看我的! - Tim Groeneveld

8

这在 Windows 和 Mac OS 的 Excel 中都可以正常工作。

修复在 Excel 中无法显示带变音符、西里尔字母、希腊字母和货币符号的字符的问题。

function writeCSV($filename, $headings, $data) {   

    //Use tab as field separator
    $newTab  = "\t";
    $newLine  = "\n";

    $fputcsv  =  count($headings) ? '"'. implode('"'.$newTab.'"', $headings).'"'.$newLine : '';

    // Loop over the * to export
    if (! empty($data)) {
      foreach($data as $item) {
        $fputcsv .= '"'. implode('"'.$newTab.'"', $item).'"'.$newLine;
      }
    }

    //Convert CSV to UTF-16
    $encoded_csv = mb_convert_encoding($fputcsv, 'UTF-16LE', 'UTF-8');

    // Output CSV-specific headers
    header('Set-Cookie: fileDownload=true; path=/'); //This cookie is needed in order to trigger the success window.
    header("Pragma: public");
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: private",false);
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment; filename=\"$filename.csv\";" );
    header("Content-Transfer-Encoding: binary");
    header('Content-Length: '. strlen($encoded_csv));
    echo chr(255) . chr(254) . $encoded_csv; //php array convert to csv/excel

    exit;
}

这个方案让我成功地将导出也应用在了谷歌表格上(其他一些方案失败了)。 - Cafn
这个解决方案几乎完美,只是当一个单元格包含双引号 " 时,它会被跳过。 - Pierre

7

CSV文件必须包含字节序标记。

或者按照建议的解决方法,将其与HTTP正文一起回显。


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