cURL запрос занимает слишком много времени, что-то не так с кодом? - PullRequest
0 голосов
/ 11 февраля 2019

Я составляю список участников для игры и получаю некоторые данные из его истории.Сначала я получаю список имен, затем вставляю их в свою базу данных, затем я передаю их в cURL, чтобы получить статистику из его истории, и после этого я обновляю их в своей базе данных.

Кажется, проблема вКогда я делаю запрос cURL, мне удается обновить всего около 30 имен, прежде чем мой хост отобразит ошибку 503 (вероятно, из-за максимального времени выполнения).Тем не менее, я должен быть в состоянии обновить больше, чем это.Я бы сказал, что 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 голосов
/ 11 февраля 2019

Другие люди, кажется, делают нормальную работу, выясняя, почему код такой медленный (вы делаете кучу запросов cURL, и каждый занимает время), и некоторые другие проблемы с кодом (ваш отступ испорченвверх, я не копал намного глубже, извините).

Как вы можете решить проблему с производительностью?

Ответ здесь зависит от ваших потребностей: вам нужно отправитьобработанные данные обратно в исходный запросчик или просто сохранить их в базе данных?

Если вы просто сохраняете их в базе данных:

Выполните поиск в БД и все, что вам нужно сделать кроме запросов cURL, затем создайте отдельный системный процесс, который будет выполнять все запросы cURL (и сохранять данные в БД) асинхронно, пока вы отправляете ответ «ОК, мы работаем над этим».

Если вам необходимо отправить эти данные обратно вызывающей стороне:

Выполните все запросы cURL одновременно . На самом деле я не думаю, что это можно сделать в PHP (см. Curl_multi ниже).На некоторых других языках это легко.Наиболее грубым методом было бы разделение асинхронного системного процесса для каждого запроса cURL и помещение PHP в цикл ожидания / проверки, пока он не увидит, что все дочерние процессы записали свои результаты в БД.

По мере того, как вы начнете работать с асинхронными программами, вы столкнетесь с множеством других проблем, и совсем не ясно, что вы подходите к проблеме наилучшим образом.Тем не менее, если вы пойдете по этому пути, я думаю, что первая функция, которая вам понадобится, это exec .Например, это приведет к появлению независимого асинхронного процесса, который будет вечно кричать в пустоту (на самом деле не делайте этого):

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

И, наконец, моя собственная программа: Это прекрасная возможность для васперенести часть вашего исполнения из PHP!Хотя вы, вероятно, можете получить все, что вам нужно, просто с помощью curl_multi , и даже есть несколько вариантов для обхода cURL и создания собственных HTTP-запросов , я предлагаю использовать инструменты, лучше подходящие длязадача под рукой.

0 голосов
/ 12 февраля 2019

Я проработал ваш код и попытался реструктурировать его таким образом, чтобы он лучше использовал соединения с базой данных и запросы скручивания.Поскольку целевой URL для запросов curl закончился через HTTPS, я изменил параметры curl для включения информации о сертификате и некоторых других модификаций, которые могут или не могут потребоваться - у меня нет возможности полностью протестировать этот код, поэтому возможны ошибки!

  • Первоначальный запрос не обязательно должен быть prepared statement, поскольку он не использует данные, предоставленные пользователем, поэтому безопасен.
  • при использовании prepared statements создайте их только один раз (поэтому нецикл) и привязать заполнители к переменным, если инструкция была создана ОК.На этом этапе переменная не обязательно должна существовать (при использовании, по крайней мере, mysqli - отличается в PDO)
  • создать только одно соединение с базой данных - плохой сервер базы данных пытался создать новые соединения в цикле, поэтомувероятно, пострадали в результате.
  • после выполнения оператора его следует утилизировать, чтобы можно было создать новый оператор.
  • Если вы используете prepared statements, не ставьте под угрозу базу данныхк тому времени встраивая переменные (в данном случае это не ввод пользователя) в sql - используйте заполнители для параметров!

Надеюсь, что поможет следующее: хотя я смог провести некоторое тестирование с использованием случайногоимена и без использования вызовов базы данных ~ 6 пользователей за 5 секунд


<?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() );
    }
?>
0 голосов
/ 11 февраля 2019

Хорошо, я увидел несколько вещей, о которых стоит упомянуть:

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, чтобы увидеть, сколько времени занимает каждый вызов.Но, к сожалению, вы, вероятно, не сможете получить больше скорости от своего кода, так как вы зависите от внешнего процесса.Это может быть из-за 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, а не как долго выполняемый скрипт через браузер.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...