我从其他服务器下载CSV文件作为供应商的数据Feed。
我使用curl获取文件的内容并将其保存到名为$contents
的变量中。
我可以很好地达到那个部分,但是我尝试通过\r
和\n
进行爆炸以获得一个行数组,但它失败并出现“内存不足”错误。
我echo strlen($contents)
,它大约有3050万个字符。
我需要操纵值并将它们插入到数据库中。我需要做些什么来避免内存分配错误?
答案 0 :(得分:51)
正如其他答案所说:
CURLOPT_FILE
但是,你可能不想真正创建一个你想要处理内存中数据的文件......只要它“到达”就使用它。
一种可能的解决方案可能是定义您自己的流包装器,并使用这个,而不是真实的文件,CURLOPT_FILE
首先,请参阅:
现在,让我们举个例子。
首先,让我们创建我们的流包装类:
class MyStream {
protected $buffer;
function stream_open($path, $mode, $options, &$opened_path) {
// Has to be declared, it seems...
return true;
}
public function stream_write($data) {
// Extract the lines ; on y tests, data was 8192 bytes long ; never more
$lines = explode("\n", $data);
// The buffer contains the end of the last line from previous time
// => Is goes at the beginning of the first line we are getting this time
$lines[0] = $this->buffer . $lines[0];
// And the last line os only partial
// => save it for next time, and remove it from the list this time
$nb_lines = count($lines);
$this->buffer = $lines[$nb_lines-1];
unset($lines[$nb_lines-1]);
// Here, do your work with the lines you have in the buffer
var_dump($lines);
echo '<hr />';
return strlen($data);
}
}
我的工作是:
stream_write
接下来,我们注册此流包装器,以与伪协议“test”一起使用:
// Register the wrapper
stream_wrapper_register("test", "MyStream")
or die("Failed to register protocol");
而且,现在,我们执行卷曲请求,就像写入“真实”文件时一样,就像其他建议的答案一样:
// Open the "file"
$fp = fopen("test://MyTestVariableInMemory", "r+");
// Configuration of curl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.rue89.com/");
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 256);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FILE, $fp); // Data will be sent to our stream ;-)
curl_exec($ch);
curl_close($ch);
// Don't forget to close the "file" / stream
fclose($fp);
请注意,我们不使用真实文件,而是使用伪协议。
这样,每次有一大块数据到达时,MyStream::stream_write
方法将被调用,并且能够处理少量数据(当我测试时,我总是得到8192字节,无论我有什么价值用于CURLOPT_BUFFERSIZE
)
几点说明:
不过,我希望这会有所帮助;-)
玩得开心!
答案 1 :(得分:18)
PHP正在窒息,因为它耗尽了内存。不要让curl使用文件内容填充PHP变量,而是使用
CURLOPT_FILE
选项将文件保存到磁盘。
//pseudo, untested code to give you the idea
$fp = fopen('path/to/save/file', 'w');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_exec ($ch);
curl_close ($ch);
fclose($fp);
然后,一旦保存文件,而不是使用file
或file_get_contents
函数(将整个文件加载到内存中,再次杀死PHP),使用fopen
和{ {3}}一次读取一行文件。
答案 2 :(得分:12)
CURLOPT_WRITEFUNCTION
选项,以便CURL为每个接收到的“数据块”调用回调。具体来说,“callable”将接收两个参数,第一个参数具有调用curl对象,第二个参数具有数据块。函数应该返回strlen($data)
以便卷曲继续发送更多数据。
Callables可以是PHP中的方法。使用这一切,我开发了一个可能的解决方案,我发现前一个解决方案更具可读性(虽然Pascal Martin的响应非常好,但从那以后情况发生了变化)。为简单起见,我使用了公共属性,但我确信读者可以适应和改进代码。甚至可以在达到多行(或字节)时中止CURL请求。我希望这对其他人有用。
<?
class SplitCurlByLines {
public function curlCallback($curl, $data) {
$this->currentLine .= $data;
$lines = explode("\n", $this->currentLine);
// The last line could be unfinished. We should not
// proccess it yet.
$numLines = count($lines) - 1;
$this->currentLine = $lines[$numLines]; // Save for the next callback.
for ($i = 0; $i < $numLines; ++$i) {
$this->processLine($lines[$i]); // Do whatever you want
++$this->totalLineCount; // Statistics.
$this->totalLength += strlen($lines[$i]) + 1;
}
return strlen($data); // Ask curl for more data (!= value will stop).
}
public function processLine($str) {
// Do what ever you want (split CSV, ...).
echo $str . "\n";
}
public $currentLine = '';
public $totalLineCount = 0;
public $totalLength = 0;
} // SplitCurlByLines
// Just for testing, I will echo the content of Stackoverflow
// main page. To avoid artifacts, I will inform the browser about
// plain text MIME type, so the source code should be vissible.
Header('Content-type: text/plain');
$splitter = new SplitCurlByLines();
// Configuration of curl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://stackoverflow.com/");
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($splitter, 'curlCallback'));
curl_exec($ch);
// Process the last line.
$splitter->processLine($splitter->currentLine);
curl_close($ch);
error_log($splitter->totalLineCount . " lines; " .
$splitter->totalLength . " bytes.");
?>
答案 3 :(得分:5)
答案 4 :(得分:3)
memory_limit
中增加php.ini
。fopen()
和fgets()
。答案 5 :(得分:2)
将其隐藏到文件中。不要试图一次将所有数据保存在内存中。
答案 6 :(得分:0)
NB:
“基本上,如果您使用fopen打开文件,请将其关闭然后取消链接, 它工作正常。但是如果在fopen和fclose之间,你给文件句柄 要cURL做一些写入文件,然后取消链接失败。为什么 这种情况发生在我身边。我认为它可能与Bug#48676“
有关http://bugs.php.net/bug.php?id=49517
如果您使用的是旧版本的PHP,请务必小心。此页面上有一个简单的修复程序可以双重关闭文件资源:
fclose($fp);
if (is_resource($fp))
fclose($fp);