PHP使用fgetcsv处理大型CSV文件

6
使用fgetcsv,我是否可以进行破坏性读取,这样我读取并处理过的行将被丢弃,因此如果我在第一次通过时没有全部读取完整个文件,我可以回来继续从上次离开的地方开始,而不会出现脚本超时的问题? 额外详细信息: 我每天从供应商那里得到一个200mb的.gz文件。当我解压缩文件时,它变成了一个1.5gb的.csv文件,其中包含近500,000行和20-25个字段。我需要将这些信息读入MySQL数据库,最好使用PHP,这样我就可以在我的Web托管提供商上安排CRON每天运行脚本。
由于托管提供商设置了服务器的硬限制时间为180秒,并且单个脚本的最大内存利用限制为128mb,因此我无法更改这些限制。
我的想法是使用fgetcsv函数从.csv文件中获取信息,但是由于3分钟的限制,我预计需要对文件进行多次传递,所以我认为在处理文件时逐步减少文件大小会很好,这样我就不需要花费时间跳过之前已经处理过的行。

1
请发布一些代码。否则,帮助您优化代码是不可能的。 - Cobra_Fast
2
你不能只保存已经解析的行数吗? - Tomasz Kapłoński
“破坏性读取”需要您重写整个文件以删除所有内容。这是代价高昂的,您不想要那样! - deceze
@moby04 对不起,我对fgetcsv函数还很陌生,有没有办法让我在后续执行中跳过x行? - Robert82
@Robert82:考虑到该函数使用相同的文件处理程序,您可以轻松地使用其他文件函数,如fseek... - Tomasz Kapłoński
3个回答

18

从您的问题描述来看,您似乎需要更换主机。在有时限的情况下处理2GB的文件并不是一个非常好的环境。话虽如此,从文件中删除已读行甚至更加不明智,因为您需要将已经读取的部分之外的整个2GB重写到磁盘中,这非常昂贵。

假设您保存了已经处理过的行数,您可以通过以下方式跳过这些行:

$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));
}

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


非常棒的解决方案,非常整洁和优雅。帮我度过了这个紧急关头。 - Robert82
在黑客马拉松期间救了我一次,谢谢。 - ChristoKiwi

5

在读取流时,逐行阅读并将每一行插入数据库(或相应处理),可以在某种程度上避免超时和内存错误。这样,在每次迭代中,只有单行保存在内存中。请注意,不要尝试将大型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);
}

1
这与我想的类似,但是在3分钟超时的情况下,我不指望能够一次性读完整个文件。有没有办法进行第二遍扫描,并且只是“跳转”到特定行?比如第一次完成了125,000行,第二次可以从第125,001行开始吗? - Robert82

1
我认为更好的解决方案是追踪每个读取的记录的文件位置(使用ftell),并将其与已读取的数据一起存储 - 然后,如果需要恢复,则只需fseek到最后位置。不断倒回和写入打开的文件流将极其低效。
您可以尝试使用mysql的read file函数直接加载文件(这可能会快得多),尽管我过去曾遇到过问题,并最终编写了自己的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的内存限制不应该存在问题。


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