PHP登录CURL代码不按预期工作

19
我正在尝试使用PHP中的curl函数登录到特定页面。请查看下面的代码。我在banggood.com上使用我的电子邮件和密码进行连接,然后想要重定向到另一个私人页面,但是它并没有按预期工作。我没有收到任何错误信息。使用下面的代码,我被重定向到此页面(https://www.banggood.com/index.php?com=account)。登录后,我想访问一个包含我的订单的私人页面。任何帮助都将不胜感激。
//The username or email address of the account.
define('EMAIL', 'aaa@gmail.com');

//The password of the account.
define('PASSWORD', 'mypassword');

//Set a user agent. This basically tells the server that we are using Chrome ;)
define('USER_AGENT', 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36');

//Where our cookie information will be stored (needed for authentication).
define('COOKIE_FILE', 'cookie.txt');

//URL of the login form.
define('LOGIN_FORM_URL', 'https://www.banggood.com/login.html');

//Login action URL. Sometimes, this is the same URL as the login form.
define('LOGIN_ACTION_URL', 'https://www.banggood.com/login.html');


//An associative array that represents the required form fields.
//You will need to change the keys / index names to match the name of the form
//fields.
$postValues = array(
    'email' => EMAIL,
    'password' => PASSWORD
);

//Initiate cURL.
$curl = curl_init();

//Set the URL that we want to send our POST request to. In this
//case, it's the action URL of the login form.
curl_setopt($curl, CURLOPT_URL, LOGIN_ACTION_URL);

//Tell cURL that we want to carry out a POST request.
curl_setopt($curl, CURLOPT_POST, true);

//Set our post fields / date (from the array above).
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($postValues));

//We don't want any HTTPS errors.
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

//Where our cookie details are saved. This is typically required
//for authentication, as the session ID is usually saved in the cookie file.
curl_setopt($curl, CURLOPT_COOKIEJAR, COOKIE_FILE);

//Sets the user agent. Some websites will attempt to block bot user agents.
//Hence the reason I gave it a Chrome user agent.
curl_setopt($curl, CURLOPT_USERAGENT, USER_AGENT);

//Tells cURL to return the output once the request has been executed.
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

//Allows us to set the referer header. In this particular case, we are
//fooling the server into thinking that we were referred by the login form.
curl_setopt($curl, CURLOPT_REFERER, LOGIN_FORM_URL);

//Do we want to follow any redirects?
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);

//Execute the login request.
curl_exec($curl);

//Check for errors!
if(curl_errno($curl)){
    throw new Exception(curl_error($curl));
}

//We should be logged in by now. Let's attempt to access a password protected page
curl_setopt($curl, CURLOPT_URL, 'https://www.banggood.com/index.php?com=account&t=ordersList');

//Use the same cookie file.
curl_setopt($curl, CURLOPT_COOKIEJAR, COOKIE_FILE);

//Use the same user agent, just in case it is used by the server for session validation.
curl_setopt($curl, CURLOPT_USERAGENT, USER_AGENT);

//We don't want any HTTPS / SSL errors.
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

//Execute the GET request and print out the result.
curl_exec($curl);

它被设置为 false... - stefanosn
Stefanos,使用他们的API是一个选项吗?https://api.banggood.com/index.php?com=document&article_id=2 - Jannes Botis
@Jannes Botis,他们的API不支持我项目所需的功能。感谢您的建议。 - stefanosn
如果我可以建议的话,就向他们提出你想要的东西。往往情况下,开发团队很乐意接受并实现用户的反馈意见。 - Jannes Botis
@JannesBotis 我问过他们,但不幸的是他们告诉我这是不可能的。 - stefanosn
你说你正在尝试获取你的订单信息?不确定你是否看到了这个链接 - https://api.banggood.com/index.php?com=document&article_id=15 - TimBrownlaw
2个回答

21

你做了几件错误的事情:

  1. 在你还没有cookie会话的情况下,你尝试登录网站,但网站要求你先有cookie会话才能发送登录请求。

  2. 与你的cookie会话相关联的CSRF令牌,这里称为at,需要从登录页面HTML中解析出来,并在登录请求中提供,而你的代码没有获取到它。

  3. 最重要的是,与你的cookie会话相关联的验证码图片需要被获取和解决,你需要将其文本附加到你的登录请求中,而你的代码完全忽略了它。

  4. 你的登录请求需要添加头部x-requested-with: XMLHttpRequest,但你的代码没有添加该头部。

  5. 你的登录请求需要在POST数据中添加com=accountt=submitLogin字段,但你的代码没有添加其中任何一个(你尝试将它们添加到URL中,但它们不应该在URL中,它们应该在POST数据中,即你的$postValues数组中,而不是URL中)。

以下是你需要做的:

  • 首先向登录页面发送普通的GET请求,这将给你一个会话cookie id、CSRF令牌和验证码图像的URL。
  • 存储Cookie id并确保将其提供给所有后续请求,然后解析出CSRF令牌(它在HTML中看起来像<input type="hidden" name="at" value="5aabxxx5dcac0" />),以及验证码图像的URL(对于每个Cookie会话都是不同的,因此不要硬编码它)。
  • 然后获取验证码图像,解决它,并将所有内容添加到您的登录请求的POST数据中,包括用户名、密码、验证码答案、comt,并在登录请求中添加HTTP头x-requested-with: XMLHttpRequest,将其发送到https://www.banggood.com/login.html,然后您就可以登录了!

以下是使用hhb_curl进行Web请求的示例实现(它是一个curl_包装器,负责处理cookie,将静默curl_错误转换为RuntimeExceptions等),使用DOMDocument解析出CSRF令牌,以及deathbycaptcha.com的API来破解验证码。

Ps:除非您在第6和7行提供真正的deathbycaptcha.com api用户名/密码,否则示例代码将无法工作。编辑,似乎自我写作以来他们改进了他们的验证码,现在看起来非常困难。此外,banggood帐户只是一个临时测试帐户,如果我在这里发布用户名/密码,则不会受到任何损害。

<?php

declare(strict_types = 1);
require_once ('hhb_.inc.php');
$banggood_username = 'igcpilojhkfhtdz@my10minutemail.com';
$banggood_password = 'igcpilojhkfhtdz@my10minutemail.com';
$deathbycaptcha_username = '?';
$deathbycaptcha_password = '?';

$hc = new hhb_curl ( '', true );
$html = $hc->exec ( 'https://www.banggood.com/login.html' )->getStdOut ();
$domd = @DOMDocument::loadHTML ( $html );
$xp = new DOMXPath ( $domd );
$csrf_token = $xp->query ( '//input[@name="at"]' )->item ( 0 )->getAttribute ( "value" );
$captcha_image_url = 'https://www.banggood.com/' . $domd->getElementById ( "get_login_image" )->getAttribute ( "src" );
$captcha_image = $hc->exec ( $captcha_image_url )->getStdOut ();

$captcha_answer = deathbycaptcha ( $captcha_image, $deathbycaptcha_username, $deathbycaptcha_password );

$html = $hc->setopt_array ( array (
        CURLOPT_POST => 1,
        CURLOPT_POSTFIELDS => http_build_query ( array (
                'com' => 'account',
                't' => 'submitlogin',
                'email' => $banggood_username,
                'pwd' => $banggood_password,
                'at' => $csrf_token,
                'login_image_code' => $captcha_answer 
        ) ),
        CURLOPT_HTTPHEADER => array (
                'x-requested-with: XMLHttpRequest' 
        ) 
) )->exec ()->getStdOut ();
var_dump ( // $hc->getStdErr (),
$html );

function deathbycaptcha(string $imageBinary, string $apiUsername, string $apiPassword): string {
    $hc = new hhb_curl ( '', true );
    $response = $hc->setopt_array ( array (
            CURLOPT_URL => 'http://api.dbcapi.me/api/captcha',
            CURLOPT_POST => 1,
            CURLOPT_HTTPHEADER => array (
                    'Accept: application/json' 
            ),
            CURLOPT_POSTFIELDS => array (
                    'username' => $apiUsername,
                    'password' => $apiPassword,
                    'captchafile' => 'base64:' . base64_encode ( $imageBinary )  // use base64 because CURLFile requires a file, and i cba with tmpfile() .. but it would save bandwidth.
            ),
            CURLOPT_FOLLOWLOCATION => 0 
    ) )->exec ()->getStdOut ();
    $response_code = $hc->getinfo ( CURLINFO_HTTP_CODE );
    if ($response_code !== 303) {
        // some error
        $err = "DeathByCaptcha api retuned \"$response_code\", expected 303, ";
        switch ($response_code) {
            case 403 :
                $err .= " the api username/password was rejected";
                break;
            case 400 :
                $err .= " we sent an invalid request to the api (maybe the API specs has been updated?)";
                break;
            case 500 :
                $err .= " the api had an internal server error";
                break;
            case 503 :
                $err .= " api is temorarily unreachable, try again later";
                break;
            default :
                {
                    $err .= " unknown error";
                    break;
                }
        }
        $err .= ' - ' . $response;
        throw new \RuntimeException ( $err );
    }
    $response = json_decode ( $response, true );
    if (! empty ( $response ['text'] ) && $response ['text'] !== '?') {
        return $response ['text']; // sometimes the answer might be available right away.
    }
    $id = $response ['captcha'];
    $url = 'http://api.dbcapi.me/api/captcha/' . urlencode ( $id );
    while ( true ) {
        sleep ( 10 ); // check every 10 seconds
        $response = $hc->setopt ( CURLOPT_HTTPHEADER, array (
                'Accept: application/json' 
        ) )->exec ( $url )->getStdOut ();
        $response = json_decode ( $response, true );
        if (! empty ( $response ['text'] ) && $response ['text'] !== '?') {
            return $response ['text'];
        }
    }
}

1
@stefanosn 不需要,PHP5完全能够胜任此工作,但我只是碰巧用PHP7编写了这个程序,因为那更容易,并且现在我只使用PHP7(例如, 在PHP5中function f($str){if(!is_string($str)){throw new InvalidArgumentException('argument 1 must be a string, but '.gettype($str).' given!');}} - 而相应的PHP7代码是function f(string $str){} - 标量类型输入验证在php7中更简单)- 但这里有一个hhb_curl的php5版本 https://github.com/divinity76/hhb_.inc.php/blob/master/hhb_.inc.php5.php - 但它大部分时间都未得到更新。 - hanshenrik
1
@stefanosn strict_types 只支持 PHP7+,所以如果你使用的是 PHP5,请将其删除。您可以通过使用 CURLOPT_COOKIE 或 CURLOPT_COOKIEFILE 在浏览器中跳过登录阶段,并将浏览器的 cookie 复制到 PHP 中。我认为您不需要使用 deathbycaptcha,banggood 的验证码看起来非常简单,我认为 PWNtcha 可以破解它(一个免费的开源验证码破解器,请参见 http://caca.zoy.org/wiki/PWNtcha)。至于在登录后检查您的订单列表,`$html=$hc->exec(' banggood.com/index.php?com=account&t=ordersList')->getStdOut(); echo $html;` - hanshenrik
1
@stefanosn,你使用CURLOPT_COOKIE的方式不正确,但是关于CURLOPT_COOKIEFILE是正确的,你可以这样做。请查看https://curl.haxx.se/libcurl/c/CURLOPT_COOKIE.html了解如何使用CURLOPT_COOKIE。 - hanshenrik
1
@stefanosn 然后通过 JavaScript 获取项目。但是,这是错误的,页面源代码确实会更改,但您可能正在查看“View-Source:”版本,该版本不显示当前页面源代码,而是显示运行 JavaScript 之前的页面源代码。JavaScript 对页面所做的任何更改都不会显示在“View-Source:”中。要查看当前的 JavaScript 修改后的页面 HTML,请使用开发工具中的 DOM 检查器(大多数浏览器都有某个版本的 DOM 检查器,在 Chrome 和 Firefox 中,例如,您可以通过按 F12 打开它)。 (评论太长,将在下一个评论中继续) - hanshenrik
1
有没有办法从PHP触发JavaScript以更改每页或类别的结果数量? - 是的,你必须使用PHP / curl模拟JavaScript代码的XMLHttpRequests。通常,您可以使用Chrom Dev Tools找到模拟数据包,但有时您需要Fiddler Proxy来弄清楚如何在PHP中模拟它们。- 顺便说一句,如果你有时间,你可以联系我,也许我们可以一起解决它。 - hanshenrik
显示剩余18条评论

-2

CURLOPT_FOLLOWLOCATION设置为1或true,您也可能需要CURLOPT_AUTOREFERER而不是静态REFERER。

你的COOKIEJAR(cookie.txt)中是否获取了一些cookie?请记住,文件必须已经存在并且PHP需要写入权限。

如果您在本地主机上执行PHP,则网络嗅探工具可以帮助调试问题,请尝试使用Wireshark或某种等效软件。因为也许请求仍然缺少一些重要的HTTP头,比如Host。


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