cURL请求花费的时间太长,代码有问题吗?

时间:2019-02-11 19:42:54

标签: php mysql database curl

我一直在为游戏创建会员列表,并从其得分中获取一些数据。首先,我得到一个名称列表,然后将其插入数据库,然后将其提供给cURL以从hiscores获取统计信息,然后将其更新至数据库。

问题似乎是当我发出cURL请求时,在主机显示503错误之前(我可能是由于执行时间最长),我设法总共更新了约30个名称。但是,我必须能够进行更多更新。我会说最低为100。

我已尝试优化代码,以使其运行更快并取得一些成功。似乎有30个人是我一次查询中最多可以更新的人。

代码本身有什么问题,为什么要花这么长时间?以下是代码的cURL部分,它可能不是您所见过的最漂亮的部分。我认为cURL能够一次处理更多数据,并且在没有数据库正常工作的情况下,我也有类似的解决方案。原因可能是https吗?以前不需要,但现在需要。

<?php
$ch = curl_init();
if(isset($_POST['submit'])){ //check if form was submitted
$conn = new mysqli($servername, $username, $password, $dbname);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    } 
            //get users
    $stmt = $conn->prepare("SELECT m.name, m.id, m.group_id, p.field_1, g.prefix, g.suffix FROM members m INNER JOIN pfields_content p ON m.id = p.id INNER JOIN groups g ON g.g_id = m.group_id WHERE
    m.group_id = 1
    ");
    $stmt->execute();
    $result = $stmt->get_result();

    while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {

    // add new member ID to database
    $conn = new mysqli($servername, $username, $password, $dbname);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    } 
    $stmt = $conn->prepare("INSERT IGNORE INTO `table` (`member_id`, `name`, `dname`) VALUES ('".$row['member_id']."', '".$row['name']."', '".$row['field_1']."')");
    $stmt->execute();

        // dname
        if($row['field_1'] != '' || $row['field_1'] != NULL) {

            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
            curl_setopt($ch, CURLOPT_URL, "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player=".$row['field_1']);
            curl_setopt($ch, CURLOPT_HEADER, 0);

            // grab HTML
            $data = curl_exec($ch);
            $array = array();
            $array = explode(',', $data);

            //formula
            if (!empty($array[15]) && (is_numeric($array[15]))) {
                $level = ((round($array[13]/2, 0, PHP_ROUND_HALF_DOWN)+$array[9]+$array[7])/4) + (($array[3]+$array[5])*0.325);
                $level = number_format($level, 2);
                // if valid name, update
                $conn = new mysqli($servername, $username, $password, $dbname);
                if ($conn->connect_error) {
                    die("Connection failed: " . $conn->connect_error);
                } 
                $stmt = $conn->prepare("UPDATE table SET  
                member_id = '".$row['id']."',
                name = '".$row['name']."',
                cb = '".$level."' WHERE member_id = ".$row['id']."");
                $stmt->execute();
                $conn->close();
            }}}}

3 个答案:

答案 0 :(得分:0)

好的,有几件事值得一提:

1)为什么您只能做那么多?这是最可能的罪魁祸首:

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
        curl_setopt($ch, CURLOPT_URL, "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player=".$row['field_1']);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        // grab HTML
        $data = curl_exec($ch);

您正在为每个呼叫进行外部curl呼叫,这意味着您受其他站点的支配,并且需要花费多长时间来解决该呼叫。您可以在curl调用周围添加一些回声,以查看每个调用进行了多少时间。但是,可悲的是,由于您依赖于外部过程,因此您可能无法从代码中获得更多的速度。这可能是因为https,或者仅仅是他们的系统过载。就像我在上面说过的,如果您真的想知道每个对象需要花费多长时间,请在其周围添加一些回声,例如:

        echo "About to curl runescape " . date("H:i:s");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
        curl_setopt($ch, CURLOPT_URL, "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player=".$row['field_1']);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        // grab HTML
        $data = curl_exec($ch);
        echo "Done with call to runescape " . date("H:i:s");

其余的代码似乎在速度上不是问题。但是:

2)您的连接有点混乱。您打开一个连接,并进行查询。然后一会儿开始,您打开第二个连接并进行查询。然后,如果满足正确的条件,则打开第三个连接并进行一些工作,然后将其关闭。原始的2个连接永远不会关闭,而第二个连接实际上在您的循环中被打开了多次。为什么不重用原始的$ conn而不是每次都打开一个新连接?

3)最后,如果您的php文件需要运行60秒以上,请在顶部添加以下内容:

set_time_limit(0);

以上内容应该有效地让脚本运行所需的时间。但是,与在浏览器上长时间运行的脚本相比,在CLI上作为cronjob进行运行要好得多,就像上面的代码一样。

答案 1 :(得分:0)

其他人似乎做得还不错,弄清楚为什么代码这么慢(您正在执行一堆cURL请求,每个请求都花时间),以及代码的其他一些问题(您的缩进很混乱)向上;对不起,我没有更深入地挖掘。)

如何解决性能问题?

答案在某种程度上取决于您的需求:您是否需要将处理后的数据发送回原始请求者,还是将其保存到数据库中?

如果您只是将其保存到数据库中:

执行数据库查询以及执行所有请求之外的所有操作,然后生成一个单独的系统进程,该进程将异步处理所有cURL请求(并将数据保存到数据库)发回“确定,我们正在处理”响应。

如果您需要将此数据发送回呼叫者:

同时执行所有cURL请求 我实际上并不认为这可以在PHP中完成(请参见下面的curl_multi)。在其他一些语言中,这很容易。最强力的方法是为每个cURL请求分离一个异步系统进程,并将PHP置于睡眠/检查循环中,直到看到所有子进程均已将其结果写入数据库为止。

当您开始使用异步工具时,您将遇到更多的麻烦,而且还不清楚您是否正在以最佳方式解决问题。就是说,如果您走这条路,我认为您需要的第一个功能是exec。例如,这将产生一个独立的异步进程,该进程将永远向虚空大喊(实际上不这样做):

exec('yes > /dev/null &')

最后,我自己的议程:对您来说,这是将您的某些执行移出PHP的绝佳机会!尽管您可能只需要使用curl_multi就能获得所需的一切,甚至还有bypassing cURL and building your own HTTP requests的某些选项,但我建议使用更适合手头任务的工具。

答案 2 :(得分:0)

我研究了您的代码,并试图以一种更好地利用数据库连接和curl请求的方式对其进行重组。由于curl请求的目标URL是通过HTTPS进行的,因此我修改了curl选项以包括证书信息以及可能需要或可能不需要的其他一些修改-我无法完全测试此代码,因此可能会出错!

  • 初始查询不需要为prepared statement,因为它不使用任何用户提供的数据,因此很安全。
  • 使用prepared statements时,只需创建一次(这样就不会循环),并且将占位符绑定到变量(如果语句创建成功)。在那个阶段,变量实际上不需要存在(至少使用mysqli时-PDO中有所不同)
  • 仅创建一个数据库连接-较差的数据库服务器试图在循环中创建新连接,因此很可能会因此遭受损失。
  • 运行该语句后,应将其丢弃,以便可以创建新的语句。
  • 如果您使用prepared statements,请先在sql中嵌入变量(在这种情况下不是用户输入),否则不要破坏数据库-使用占位符作为参数!

我希望以下内容能对您有所帮助...我能够在5秒内使用随机名称而不使用任何数据库调用来进行测试〜6个用户


<?php

    try{

        $start=time();
        $cacert='c:/wwwroot/cacert.pem'; # <-------edit as appropriate
        $baseurl='https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws';

        if( isset( $_POST['submit'], $servername, $username, $password, $dbname ) ){

            /* should only need the one curl connection */
            $curl=curl_init();
            curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
            curl_setopt( $curl, CURLOPT_BINARYTRANSFER, true );
            curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
            curl_setopt( $curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)" );
            curl_setopt( $curl, CURLOPT_HEADER, false );
            curl_setopt( $curl, CURLINFO_HEADER_OUT, false );
            curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, true );
            curl_setopt( $curl, CURLOPT_SSL_VERIFYHOST, 2 );
            curl_setopt( $curl, CURLOPT_CAINFO, $cacert );
            curl_setopt( $curl, CURLOPT_MAXREDIRS, 10 );
            curl_setopt( $curl, CURLOPT_ENCODING, '' );


            /* only need the one db connection */
            $conn = new mysqli( $servername, $username, $password, $dbname );

            /* initial db query does not need to be a prepared statement as there are no user supplied parameters */
            $sql='select m.`name`, m.`id`, m.`group_id`, p.`field_1`, g.`prefix`, g.`suffix`
                    from members m 
                    inner join pfields_content p on m.`id` = p.`id`
                    inner join groups g on g.`g_id` = m.`group_id`
                    where m.`group_id` = 1';
            $res=$conn->query( $sql );
            if( $res ){

                /* create the prepared statement for inserts ONCE, outside the loop */
                $sql='insert ignore into `table` ( `member_id`, `name`, `dname` ) values ( ?,?,? )';
                $stmt=$conn->prepare( $sql );

                if( $stmt ){

                    /* bind the placeholders to variables - the variables do not need to exist YET in mysqli */
                    $stmt->bind_param('iss', $id, $name, $field_1 );

                    /* placeholder arrays for bits of the recordset */
                    $data=array();
                    $urls=array();

                    /* 
                        collect all the relevant player names into an array
                        and store info for use in INSERT query
                    */
                    while( $rs=$res->fetch_object() ){

                        if( !empty( $rs->field_1 ) ) {
                            $urls[ $rs->field_1 ]=(object)array( 
                                'name'  =>  $rs->name,
                                'id'    =>  $rs->id
                            );
                        }

                        $data[]=array( 
                            'name'      =>  $rs->name,
                            'id'        =>  $rs->id,    /* original code references `member_id` which does not exist in the recordset */
                            'field_1'   =>  $rs->field_1
                        );
                    }

                    /* now loop through $data to do the inserts */
                    foreach( $data as $obj ){
                        /* create/dimension the variables for the prepared statement parameters */
                        $name=$obj->name;
                        $id=$obj->id;
                        $field_1=$obj->field_1;

                        /* run the insert cmd */
                        $stmt->execute();
                    }

                    /* we should now be finished with the initial prepared statement */
                    $stmt->free_result();
                    $stmt->close();

                    /*
                        now for the curl calls... no idea how many there will be but this should be known
                        by sizeof( $urls )

                        Dependant upon the number you might opt to perform the curl calls in chunks or use
                        `curl_multi_init` ~ more complicated but perhaps could help.

                        Also need to define a new sql statement ~ which sort of does not make sense as it was
                        ~ do not need to update the `member_id`!
                    */
                    $sql='update `table` set `name`=?, `cb`=? where `member_id`=?';
                    $stmt=$conn->prepare( $sql );
                    if( $stmt ){
                        $stmt->bind_param( 'ssi', $name, $level, $id );

                        foreach( $urls as $player => $obj ){

                            $url = $baseurl . '?player=' . $player;

                            /* set the url for curl */
                            curl_setopt( $curl, CURLOPT_URL, $url );

                            /* execute the curl request... */
                            $results=curl_exec( $curl );
                            $info=(object)curl_getinfo( $curl );
                            $errors=curl_error( $curl );

                            if( $info->http_code==200 ){
                                /* curl request was successful */
                                $array=explode( ',', $results );
                                if( !empty( $array[15] ) && is_numeric( $array[15] ) ) {

                                    $level = ((round($array[13]/2, 0, PHP_ROUND_HALF_DOWN)+$array[9]+$array[7])/4) + (($array[3]+$array[5])*0.325);
                                    $level = number_format($level, 2);

                                    /* update db ~ use $obj from urls array + level defined above */
                                    $name=$obj->name;
                                    $id=$obj->id;

                                    $stmt->execute();
                                }
                            } else {
                                throw new Exception( sprintf('curl request to %s failed with status %s', $url, $info->http_code ) );
                            }
                        }// end loop

                        $stmt->free_result();
                        $stmt->close();
                        curl_close( $curl );

                        printf( 'Finished...Operation took %ss',( time() - $start ) );

                    }else{
                        throw new Exception( 'Failed to prepare sql statement for UPDATE' );
                    }
                }else{
                    throw new Exception( 'Failed to prepare sql statement for INSERT' );
                }
            }else{
                throw new Exception( 'Initial query returned no results' );
            }
        }
    }catch( Exception $e ){
        exit( $e->getMessage() );
    }
?>