PHP 在一个巨大的 csv 文件上使用 fgetcsv


PHP Using fgetcsv on a huge csv file

使用 fgetcsv ,我是否可以以某种方式进行破坏性读取,其中我读取和处理的行将被丢弃,因此如果我在第一次传递中没有完成整个文件,我可以回来并在the script timed out之前从我离开的地方继续

其他详细信息:

我从供应商那里收到每日产品提要,该文件为 200mb .gz文件。 当我解压缩文件时,它变成了一个 1.5gb 的.csv,有近 500,000 行和 20 - 25 个字段。 我需要将此信息读取到 MySQL 数据库中,最好是使用 PHP,以便我可以安排 CRON 每天在我的网络托管服务提供商处运行脚本。

托管服务提供商将服务器上的硬超时设置为 180 秒,任何单个脚本的最大内存利用率限制为 128mb。 这些限制是我无法改变的。

我的想法是使用 fgetcsv 函数从.csv中获取信息,但由于 3 分钟超时,我预计必须在文件中进行多次传递,我认为在处理文件时缩减文件会很好,这样我就不需要花费周期跳过在上一次传递中已经处理过的行。

从您的问题描述来看,听起来确实需要切换主机。处理具有硬时间限制的 2 GB 文件不是一个非常有建设性的环境。话虽如此,从文件中删除读取行的建设性甚至更低,因为您必须将整个 2 GB 重写到磁盘减去您已经读取的部分,这非常昂贵。

假设您保存了已处理的行数,则可以跳过如下行:

$alreadyProcessed = 42; // for example
$i = 0;
while ($row = fgetcsv($fileHandle)) {
    if ($i++ < $alreadyProcessed) {
        continue;
    }
    ...
}

但是,这意味着您每次浏览整个 2 GB 文件时都会从头开始读取它,这本身已经需要一段时间,并且每次重新开始时,您都可以处理越来越少的行。

这里最好的解决方案是记住文件指针的当前位置ftell是您要查找的函数:

$lastPosition = file_get_contents('last_position.txt');
$fh = fopen('my.csv', 'r');
fseek($fh, $lastPosition);
while ($row = fgetcsv($fh)) {
    ...
    file_put_contents('last_position.txt', ftell($fh));
}

这使您可以直接跳回到上次的位置并继续阅读。您显然希望在此处添加大量错误处理,因此无论您的脚本在哪个点中断,您都不会处于不一致的状态。

在像 Stream 一样读取时,可以在一定程度上避免超时和内存错误。通过逐行读取,然后将每一行插入数据库(或相应地处理)。这样,每次迭代时内存中仅保留一行。请注意,不要尝试将巨大的csv文件加载到数组中,这确实会消耗大量内存。

if(($handle = fopen("yourHugeCSV.csv", 'r')) !== false)
{
    // Get the first row (Header)
    $header = fgetcsv($handle);
    // loop through the file line-by-line
    while(($data = fgetcsv($handle)) !== false)
    {
        // Process Your Data
        unset($data);
    }
    fclose($handle);
}
我认为

更好的解决方案(连续倒带并写入打开的文件流在效率上是非常低效的)是跟踪读取的每条记录的文件位置(使用 ftell)并将其与您读取的数据一起存储 - 然后,如果您必须恢复,那么只需搜索到最后一个位置。

您可以尝试使用 mysql 的读取文件功能直接加载文件(这可能会快得多),尽管我过去遇到过这个问题并最终编写了自己的 php 代码。

托管服务提供商将服务器上的硬超时设置为 180 秒,任何单个脚本的最大内存利用率限制为 128mb。这些限制是我无法改变的。

你试过什么?

内存可以通过 php.ini 文件以外的其他方式限制,但我无法想象有人如何真正阻止您使用不同的执行时间(即使禁用ini_set,您也可以从命令行运行 php -d max_execution_time=3000/your/script.php 或 php -c/path/to/custom/inifile/your/script.php

除非您尝试将整个数据文件放入内存中,否则内存限制为 128Mb 应该没有问题