PHP cURL,读取远程文件并将内容写入本地文件

19

我想连接到一个远程文件,并将远程文件的输出写入到本地文件中,这是我的函数:

function get_remote_file_to_cache()
{

    $the_site="http://facebook.com";

    $curl = curl_init();
    $fp = fopen("cache/temp_file.txt", "w");
    curl_setopt ($curl, CURLOPT_URL, $the_site);
    curl_setopt($curl, CURLOPT_FILE, $fp);

    curl_setopt($curl,  CURLOPT_RETURNTRANSFER, TRUE);

    curl_exec ($curl);

    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    if($httpCode == 404) {
        touch('cache/404_err.txt');
    }else
    {
        touch('cache/'.rand(0, 99999).'--all_good.txt');
    }

    curl_close ($curl);
}

它在“cache”目录中创建了两个文件,但问题是它没有将数据写入“temp_file.txt”,为什么呢?


1
我认为您不能在同一操作中设置CURLOPT_FILECURLOPT_RETURNTRANSFER - Andre
6个回答

28

实际上,使用fwrite是部分正确的。 为了避免处理大文件时出现内存溢出问题(超过PHP的最大内存限制),您需要设置一个回调函数来写入文件。

注意:我建议创建一个专门处理文件下载和文件句柄等的类,而不是使用全局变量。但是,为了演示目的,下面展示了如何启动和运行。

因此,请按照以下步骤进行操作:

# setup a global file pointer
$GlobalFileHandle = null;

function saveRemoteFile($url, $filename) {
  global $GlobalFileHandle;

  set_time_limit(0);

  # Open the file for writing...
  $GlobalFileHandle = fopen($filename, 'w+');

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_FILE, $GlobalFileHandle);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_USERAGENT, "MY+USER+AGENT"); //Make this valid if possible
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); # optional
  curl_setopt($ch, CURLOPT_TIMEOUT, -1); # optional: -1 = unlimited, 3600 = 1 hour
  curl_setopt($ch, CURLOPT_VERBOSE, false); # Set to true to see all the innards

  # Only if you need to bypass SSL certificate validation
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

  # Assign a callback function to the CURL Write-Function
  curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'curlWriteFile');

  # Exceute the download - note we DO NOT put the result into a variable!
  curl_exec($ch);

  # Close CURL
  curl_close($ch);

  # Close the file pointer
  fclose($GlobalFileHandle);
}

function curlWriteFile($cp, $data) {
  global $GlobalFileHandle;
  $len = fwrite($GlobalFileHandle, $data);
  return $len;
}

你也可以创建一个进度回调函数来显示下载的进度/速度,但这又是另一个例子,因为在输出到CLI时可能会很复杂。

基本上,这将采取每个下载的数据块,并立即将其转储到文件中,而不是首先将整个文件下载到内存中。

这是一种更安全的做法!当然,你必须确保URL是正确的(将空格转换为%20等),并且本地文件是可写的。

谢谢, 詹姆斯。


7
在现代 PHP 中,可以使用以下更加简洁的代码: "curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($cp, $data) use ($fp) { return fwrite($fp, $data); });"(其中"$GlobalFileHandle" 变成了 "$fp")。这段代码对我来说似乎有效,但我想确认其行为是否相同。 - IBBoard
当您指定了 CURLOPT_FILE,就不需要回调函数了。我刚试过了,它直接将内容写入文件,而不是先将整个内容读入内存。 - Crouching Kitten
尝试指向此文件。您将看到已达到硬内存限制。 http://ipv4.download.thinkbroadband.com/1GB.zip“致命错误:已用尽134217728字节的允许内存大小(尝试分配65015808字节)”请注意,不同的环境将具有每个进程不同的默认内存限制。这也在Windows / Linux之间有所不同。 - doublehelix

18

让我们尝试向 http://facebook.com 发送 GET 请求:

$ curl -v http://facebook.com
* 正在重建 URL 到:http://facebook.com/
* 主机名没有被找到在 DNS 缓存中
*   尝试 69.171.230.5...
* 已连接到 facebook.com (69.171.230.5) 端口 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: facebook.com
> Accept: */*
>
< HTTP/1.1 302 Found
< Location: https://facebook.com/
< Vary: Accept-Encoding
< Content-Type: text/html
< Date: Thu, 03 Sep 2015 16:26:34 GMT
< Connection: keep-alive
< Content-Length: 0
<
* 与主机的连接已经关闭

发生了什么事?Facebook似乎将我们从 http://facebook.com 重定向到安全的 https://facebook.com/。请注意响应正文长度:

Content-Length: 0

这意味着零字节将被写入 xxxx--all_good.txt。 这就是为什么文件保持为空的原因。

您的解决方案绝对正确:

$fp = fopen('file.txt', 'w');
curl_setopt($handle, CURLOPT_FILE, $fp);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);

你只需要将 URL 更改为 https://facebook.com/

关于其他回答:

  • @JonGauthier:不,使用 curl_exec() 后无需使用 fwrite()
  • @doublehelix:不,对于这样一个简单的操作,即将内容复制到文件中,不需要使用 CURLOPT_WRITEFUNCTION
  • @ScottSaunders:如果文件不存在,touch() 将创建空文件。我认为这是 OP 的意图。

说真的,三个答案全部都是无效的吗?


你是对的,就是这么简单。只要记得提前创建“file.txt”文件,并设置其权限(例如777)。 - Kar.ma
1
不要将权限设置为777,给所有人赋予所有权限是一种安全风险。尽量不要像Andre所说的那样在CURLOPT_FILE中使用CURLOPT_RETURNTRANSFER。我得到了302返回代码,并尝试仅使用CURLOPT_FILE和CURLOPT_FOLLOWLOCATION,现在没有空文件,我已经成功将数据写入文件。 - Rajazk
我必须删除 curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); 才能使其正常工作。 - Steve

11
你需要使用fwrite来显式地向文件写入内容,将之前创建的文件句柄作为参数传递给它。
if ( $httpCode == 404 ) {
    ...
} else {
    $contents = curl_exec($curl);
    fwrite($fp, $contents);
}

curl_close($curl);
fclose($fp);

4
处理大文件时可能会遇到内存限制。请查看doublehelix的回复,这样更安全。 - Steve Horvath
@JonGauthier 这并没有解决当你有内存限制,想要避免将整个文件加载到内存中,只想将其转储到本地文件的问题。 - Mehdi Karamosly
1
请前往以下页面为唯一正确答案投票:https://dev59.com/bWsz5IYBdhLWcg3wVGTJ#62617724在代码中,OP发布的cURL代码是正确的,除了RETURNTRANSFER选项在FILE选项之后。在这种情况下,cURL会忽略FILE选项,并将下载的文件作为响应返回。这就是为什么所有其他关于使用fwrite的答案似乎都是有效解决方案,因为它们从FILE选项的失败开始,并处理响应中的文件(这也是它们必须处理内存错误的原因)。 - Jacob

5

在您的问题中,您提到了

    curl_setopt($curl, CURLOPT_FILE, $fp);

    curl_setopt($curl,  CURLOPT_RETURNTRANSFER, TRUE);

不过从 PHP 的 curl_setopt 文档注释中可以看到...

It appears that setting CURLOPT_FILE before setting CURLOPT_RETURNTRANSFER doesn't work, presumably because CURLOPT_FILE depends on CURLOPT_RETURNTRANSFER being set.

So do this:

<?php
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FILE, $fp);
?>

not this:

<?php
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
?>

引起该问题的原因是未设置CURLOPT_RETURNTRANSFER而导致无法使用CURLOPT_FILE

参考文献:https://www.php.net/manual/en/function.curl-setopt.php#99082


请注意,即使使用curl_setopt_array时,您也必须在数组中将CURLOPT_RETURNTRANSFER列在CURLOPT_FILE之前。 - Jacob

3
为了避免内存泄漏问题:
我也曾面临这个问题。很蠢,但解决方案是在设置CURLOPT_FILE之前设置CURLOPT_RETURNTRANSFER!
看起来CURLOPT_FILE取决于CURLOPT_RETURNTRANSFER。
$curl = curl_init();
$fp = fopen("cache/temp_file.txt", "w+");
curl_setopt($curl,  CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_FILE, $fp);
curl_setopt($curl, CURLOPT_URL, $url);
curl_exec ($curl);
curl_close($curl);
fclose($fp);

1
你根本不需要使用CURLOPT_RETURNTRANSFER。CURLOPT_RETURNTRANSFER设置了返回值为单个字符串;而CURLOPT_FILE改变了这种行为,它并不会将返回存储为单个字符串,而是在进行过程中将其输出到文件中。这就是为什么在CURLOPT_RETURNTRANSFER之后使用CURLOPT_FILE仍然有效...但实际上你根本不需要使用CURLOPT_RETURNTRANSFER。 - ATJ

2

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