第一部分:生成CSV文件,导出/下载文件
0、什么是CSV文件。
CSV(Comma-Separated Values),即逗号分隔值(有时也称为字符分隔值,因为分隔字符也可以不是逗号)。CSV文件是以CSV格式存储表格数据的纯文本文件。
注:Excel可以直接打开CSV文件, 可以另存为Excel文件扩展名xls(Excel 03之前的版本), xlsx(Excel 07之后的版本)。所以一般导出Excel数据的需求,直接导出CSV比较快捷。
1、PHP后台导出CSV文件,使用PHP官方自带函数fputcsv。
$lists = array ( array('aaa', 'bbb', 'ccc'), array('123', '456', '789'), array('"xxx"', '"yyy"') ); $fp = fopen('file.csv', 'w'); foreach ($lists as $list) { fputcsv($fp, $list); } fclose($fp); # 导出的结果cat file.csv aaa,bbb,ccc 123,456,789 """xxx""","""yyy"""
以上是PHP官方fputcsv()的例子,引用这个例子主要是为了说明为啥生成的第三行每列是6个双引号。
首先我们需要明白CSV目前还没有严格的标准,但有一些基本规范,看完下面三条规范你就完全明白上面的显示方式了, 更多规范请参考CSV维基百科。
-
- 任何字段都可以被包裹(使用双引号字符)。
"1997","Ford","E350"
-
- 包含换行符、双引号和/或逗号的字段应当被包裹。(否则,文件很可能不能被正确处理)。
1997,Ford,E350,"Super, luxurious truck"
-
- 每个被嵌入的双引号字符必须被表示为两个双引号字符, 同时必须被双引号包裹。
1997,Ford,E350,"Super, ""luxurious"" truck"
2、PHP后台导出CSV文件,使用PHP官方自带函数file_put_contents。
$lists = array ( array('aaa', 'bbb', 'ccc'), array('123', '456', '789'), array('"xxx"', '"yyy"', '"zzz"'), ); $file_name = 'file.csv'; foreach ($lists as $list) { $row = implode(',', $list); file_put_contents($file_name, $row . PHP_EOL, FILE_APPEND | LOCK_EX); }
3、PHP后台导出CSV文件,使用PHP官方自带函数fwrite/fputs。
fputs是fwrite的别名,在逻辑上还是有差异,一个是写入,一个是添加。
$lists = array ( array('aaa', 'bbb', 'ccc'), array('123', '456', '789'), array('"xxx"', '"yyy"', '"zzz"'), ); // 写入文件 $fp = fopen('file.csv', 'w'); foreach ($lists as $list) { $row = implode(',', $list); fwrite($fp, $row . PHP_EOL); } fclose($fp); // 向文件添加内容 $fp = fopen('file.csv', 'a'); foreach ($lists as $list) { $row = implode(',', $list); fputs($fp, $row . PHP_EOL); } fclose($fp);
注意:
使用file_put_contents,每次都会在写入时先打开文件,写入结束后在关闭文件。然而使用fwrite,仅仅需要打开一次文件即可,因此使用fwrite效率更高。效率高的原因参考《PHP写日志fwrite和file_put_contents的区别》
4、浏览器请求下载CSV文件
// 以下是一个API的主体内容 $file_name = 'uids' . '_' . date('Ymd') . '.csv'; header('Content-type: application/octet-stream'); header('Content-Transfer-Encoding: binary'); header("Content-Disposition: attachment; filename=" . $file_name); $uids = []; // from db echo 'uid' . "\r\n"; foreach ($uids as $uid) { echo $uid . "\r\n"; ob_flush(); flush(); }
测试验证,将以上代码存到download.php文件中,在该文件目录下启动PHP内置服务:
php -S localhost:8088
浏览器地址栏输入:localhost:8088/download.php将获取下载文件
第二部分: Excel打开CSV文件常见问题总结
一、导出的CSV文件,使用Excel打开,无法自动分列(没有按逗号分隔成多列)。
- 解决方法一:使用Excel分列功能
a、选择一整列 b、在头部菜单中选择【数据】 c、【数据】菜单展开选择【分列】的功能
- 解决方法二:Mac系统下,修改系统语言。
系统偏好设置-> 语言和地区->然后地区栏选“中国”之外的某个地方,如香港。
- 解决方法三:如果之前使用fputcsv生成的数据,可以改为file_put_contents或fwrite试试。
测试发现fputcsv生成的CSV格式如果包含纯数字列,无法分列,file_put_contents或fwrite可以
例:
aaa,bbb,ccc 123,456,789 xxx,yyy,zzz
二、Windows下Excel打开CSV文件中文乱码。
BOM(byte order mark)是放在一段字节流开头的Unicode字符,字符编码内容是U+FEFF, 但他是不可见的,像是一串魔法数字,可以用了做一些标记。所以用来表示字节顺序或编码方式。在UTF-16和UTF-32中BOM是必须的,在字节流开头加上BOM用来表示编码和字节序。在UTF-8中BOM不是必须的,同时UTF-8是以一个字节为单位处理的,不存在字节序的问题(为什么UTF-8不存在字节序的问题),但是有时候需要在UTF-8编码的字节流开头加上标示编码方式的标记,即在BOM中加入编码EF BB BF即可(编码EF BB BF一般也就称为UTF-8 BOM头)。(FEFF不好理解,可参考:BOM In HTML)
乱码原因(参考: BOM头是什么)
Windows下的Excel打开CSV文件,会检查BOM,获取文件编码方式,如果没有获取到就按默认编码方式打开,当打开文件的编码方式和文件内容的编码方式不一致的时候,乱码大戏就上演了。而一般在Linux系统或Mac OS中生成的文件默认都是UTF-8编码,这也是乱码频发的原因。(Windows下的记事本打开就不会有问题,Excel估计是一个Bug, WPS打开测试一般正常,估计是微软修复了)。
乱码解决方法:
0、生成文件加上BOM解决乱码
// 说明,转换后Windows下打开不乱码,Mac下Excel打开乱码 function export_csv($data, $file_name) { try { $fp = fopen($file_name, 'w'); // 两种方式效果一样 fwrite($fp, $bom = "\xEF\xBB\xBF"); fwrite($fp, $bom = (chr(0xEF) . chr(0xBB) . chr(0xBF))); foreach ($data as $list) { fputcsv($fp, $list); } fclose($fp); } catch (Exception $e) { var_dump($e); return false; } return true; } $lists = array ( array('aaa', 'bbb', 'ccc'), array('123', '456', '789'), array('"xxx"', '"yyy"', '"zzz"'), array('春晚', '北京', '前程似锦') ); export_csv($lists, 'log.csv');
Tip: 思考这种方式Mac下为啥还是乱码
1、转换编码方式(UTF-8转GBK)解决乱码:
// 说明,转换后Windows下,Mac下Excel打开都不会乱码 function export_csv($data, $file_name) { foreach ($data as $field) { // 解决中文乱码 $row = iconv('UTF-8', 'GBK', implode(',', $field)); // $row = implode(',', $field); file_put_contents($file_name, $row . PHP_EOL, FILE_APPEND | LOCK_EX); } return true; } $lists = array ( array('aaa', 'bbb', 'ccc'), array('123', '456', '789'), array('"xxx"', '"yyy"', '"zzz"'), array('春晚', '北京', '前程似锦') ); export_csv($lists, 'log.csv');
提示:Mac下文件编码方式查看: vim 打开文件 :set fileencoding查看(file命令貌似不行):
2、Windows下手动转换
在Windows下用WPS打开乱码的CSV文件,另存为xls格式
3、Mac OS下手动转换
利用Mac自动的工具Automator生成一个服务,手动转换即可(亲测有效).
Mac Excel打开文件全是乱码,原因和解决办法是什么
Tip: 还有一种救急方法,在Mac上用Numbers打开CSV, 另存为Excel, 测试发现Mac和Windows下打开都没有乱码。
4、浏览器下载CSV文件stackoverflow完整例子《Export to CSV via PHP》(可能需要翻墙)
/** * 浏览器下载csv文件(加BOM) * @author salmonl * @date 2019-03-01 */ class Tool_Csv { /** * 导出UTF-8编码的csv文件(加BOM) * @param $array * @param array $filename * @param $filename */ public static function export(&$array, $fieldsName = [], $filename = null) { !$filename && $filename = 'data_export'; self::download_send_headers($filename . date('Y-m-d') . ".csv"); echo self::array2csv($array, $filename); } /** * send header * @param $string */ public static function download_send_headers($filename) { // disable caching $now = gmdate("D, d M Y H:i:s"); header("Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate"); header("Last-Modified: {$now} GMT"); // force download header("Content-Type: application/force-download"); header("Content-Type: application/octet-stream"); header("Content-Type: application/download"); // disposition / encoding on response body header("Content-Disposition: attachment;filename={$filename}"); header("Content-Transfer-Encoding: binary"); } /** * array to csv * @param $string */ private static function array2csv(array &$array, $fields_name = []) { if (count($array) == 0) { return null; } ob_start(); $df = fopen("php://output", 'w'); fwrite($df, $bom = "\xEF\xBB\xBF"); if ($fields_name) { fputcsv($df, $fields_name); } else { fputcsv($df, array_keys(reset($array))); } foreach ($array as $row) { fputcsv($df, $row); } fclose($df); return ob_get_clean(); } }