命令行 cURL 和 PHP cURL 之间的区别

4

我有一个类似于以下的cURL命令:

curl 'https://www.example.com' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36' \
  -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3' \
  -H 'accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7' \
  -H 'authority: www.example.com'

在我的Mac上像在终端应用程序中执行此命令,结果会得到预期的输出。

(如果您自己测试:如果此输出包含单词Sicherheitsüberprüfung,则已被地理位置屏蔽,您必须使用德国IP进行测试。)

我将确切的命令转移到了PHP cURL,如下所示:

<?php
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://www.example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');

$headers = array();
$headers[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36';
$headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3';
$headers[] = 'Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7';
$headers[] = 'Authority: www.example.com';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>

当我运行这段代码时,我收到了一条消息,说我的请求被识别为自动请求/机器人:它显示Sicherheitsüberprüfung,意思是安全检查
当然,我在命令行和PHP cURL请求中使用的是相同的IP。
为什么会这样?难道命令行cURL和PHP cURL不一样吗?
还是我的PHP脚本有问题?
更新:
我偶然发现了以下内容:我在我的Mac上使用Coda作为代码编辑器。这有一个内置的PHP渲染引擎。使用这个与我的PHP脚本,结果是预期的。这与我在命令行中得到的结果相同。
第二次更新:
我按照Jannes Botis在他的答案中建议的做法去做了。然后我在我的Coda代码编辑器应用程序中运行PHP脚本(输出了预期的结果),并在MAMP上作为localhost运行(总是被识别为自动请求)。

我发现使用 MAMP 执行的代码使用了 HTTP/2,而在 Coda 中执行的代码使用了 HTTP/1.1。为了解决这个问题,我将以下内容添加到脚本中:

curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

现在,两者输出的字符串完全相同:

GET / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Authority: www.example.com

但是,情况仍然是一样的:一个正在工作,另一个被识别为自动请求。


你尝试过使用 CURLOPT 常量设置头部信息吗?例如 $headers[CURLOPT_USERAGENT] = 'Mozilla/5.0...',然后使用 curl_setopt_array() 函数吗? - code-kobold
1
可能是重复的问题:php curl:如何完全模拟Web浏览器的GET请求? - Nico Haase
@code-kobold,AcceptAccept-LanguageAuthority 没有常量,是吗? - David
@Dmitry 谢谢。正如您所看到的,此代码包含单词 Sicherheitsüberprüfung,因为它被识别为自动请求。在命令行中执行该命令时,您会得到什么? - David
1
@SalmanA 不,我是在我的 Mac 上做的。但我在一个“真实”的服务器上测试过了。那里也是一样的。 - David
显示剩余17条评论
3个回答

4
尝试在两种情况下调试请求:
a) 终端:使用curl的详细模式(verbose mode):curl -v,并检查发送的http请求,特别是检查头部列表。
b) PHP curl:使用CURLINFO_HEADER_OUT打印http请求:
curl_setopt($ch, CURLINFO_HEADER_OUT, true);

curl_exec($ch);

$info = curl_getinfo($ch);
print_r($info['request_header']);

测试不同的标头,让它起作用的方法是在请求中添加“Pragma: no-cache”标头:

$headers[] = 'Pragma: no-cache';

另一方面,在终端使用curl时,我必须将请求头部分大写,例如User-Agent等。

尝试使用 fsockopen 创建tcp连接:

$fp = fsockopen("ssl://"."www.example.com", 443, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $headers = array();
    $headers[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36';
    $headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3';
    $headers[] = 'Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7';
    $headers[] = 'Authority: www.example.com';
    $out .= $headers;
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 1024);
    }
    fclose($fp);

测试一下是否有效。也许问题要么是php curl向http请求添加了一些信息,要么是在tcp连接级别上存在问题,某些信息被添加到那里。

参考资料


我添加了 $headers[] = 'Pragma: no-cache'; 但还是一样。你能否把输出结果发布到某个地方,例如 https://codeshare.io? - David
嗯,我不太确定。你可以试试这个链接:https://dev59.com/-F4d5IYBdhLWcg3wLf7P 另外,你也可以尝试添加“Connection: Keep-Alive”头部。 - Jannes Botis
“Connection: Keep-Alive” 没有起到作用。顺便说一下,我已经更新了我的问题并添加了一些新细节。 - David
@David,你能否尝试一下答案第二部分的TCP请求? - Jannes Botis

3

命令行curl:

它是一种工具,用于使用任何支持的协议(HTTP,FTP,IMAP,POP3,SCP,SFTP,SMTP,TFTP,TELNET,LDAP或FILE)将数据传输到服务器或从服务器传输数据。curl由Libcurl提供支持。该工具适用于自动化,因为它旨在无需用户交互即可运行。curl可以同时传输多个文件。 有关命令行curl的更多详细信息

语法:

curl [options] [URL...]

示例:

使用curl命令访问 http://site.{one, two, three}.com

PHP cURL

$ch = curl_init('http://example.com/wp-login.php');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);

if($this->getRequestType() == 'POST')
{
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, 
        array(
            'user[name]'    => 'Generic+Username',
            'user[email]'   => 'mahekpatel04@gmail.com'
        );
    );
}

$response   = curl_exec($ch);

0
问题出在PHP的cURL默认选择的密码。使用curl命令和-Ivs选项运行,可以看到它使用了哪些密码:
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH

在 PHP 中设置它们可以绕过这个神秘的检查:
curl_setopt($ch,
  CURLOPT_SSL_CIPHER_LIST,
  'ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH'
);

此外,似乎需要添加Host头和使用HTTPv2:
$headers[] = 'Host: www.11880.com';
// ...
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

问题出在PHP的cURL默认选择的密码。这听起来像是一个非常明确的陈述,但回答中没有任何支持它的证据。如果你是通过测试确定的,应该说出来。如果这只是一种有根据的猜测(当然没有错),你也应该说出来。 - miken32
@Styx 我在命令行中使用 -Ivs 执行了我的 curl 命令,并得到了以下结果:* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH 我在我的 PHP 脚本中添加了 curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH');,但它仍然无法工作。 - David
@David 是的,我使用了你的代码,只是添加了 Host 头,并切换到使用 HTTP2。还添加了密码。这是代码:https://codeshare.io/24vdL4。这是完整的答案:https://codeshare.io/5D4wqm。VPN 服务器使用的是:德国 / 法尔肯斯坦。我已更新我的答案以反映所做的更改。 - Styx
@Styx 是的,你的答案是正确的。我使用了你在 https://codeshare.io/24vdL4 中提供的全部代码,但结果仍然错误。 - David
@David 有时服务器会重定向到 DDoS 保护服务。你确定你得到的是来自服务器的回应而不是这个服务吗? - Styx
显示剩余3条评论

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