【问题标题】:PHP Using fgetcsv on a huge csv filePHP 在一个巨大的 csv 文件上使用 fgetcsv
【发布时间】:2013-10-22 10:59:07
【问题描述】:

使用fgetcsv,我能否以某种方式破坏性读取我已读取和处理的行将被丢弃所以如果我没有在第一遍中通过整个文件,我可以回来继续the script timed out之前停下的地方吗?

其他详情:

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

我在服务器上设置了一个硬超时,由托管服务提供商设置为 180 秒,并且任何单个脚本的最大内存使用限制为 128mb。我无法更改这些限制。

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

【问题讨论】:

  • 请贴一些代码。否则无法帮助您优化代码。
  • 不能只保存已经解析的行数吗?
  • “破坏性读取”将要求您重写整个文件以删除要删除的所有内容。那太贵了,你不想这样!
  • @moby04 请原谅,我是 fgetcsv 函数的新手,有没有办法让我在后续执行时跳过 x 行?
  • @Robert82:鉴于此函数使用相同的文件处理程序运行,您可以轻松使用其他文件函数,如 fseek...

标签: php fgetcsv


【解决方案1】:

从您的问题描述看来,您确实需要切换主机。处理具有硬时间限制的 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));
}

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

【讨论】:

  • 很好的解决方案,非常简洁优雅。让我度过了难关。
  • 在黑客日期间救了我。谢谢。
【解决方案2】:

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

【讨论】:

  • 这与我的想法相似,但有 3 分钟的超时,我不希望能够一次读取整个文件。有没有办法在第二遍中返回,然后“跳”到特定的行?假设我第一次完成了 125,000 行,我可以第二次从第 125,001 行开始吗?
【解决方案3】:

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

您可以尝试使用 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 的内存限制应该没有问题

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-08
    • 2021-05-29
    • 2013-11-07
    • 1970-01-01
    • 2012-10-27
    • 2020-07-09
    相关资源
    最近更新 更多