Лучший способ прочитать сайт? - PullRequest
1 голос
/ 05 октября 2011

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

В настоящее время я использую следующий код, и он довольно медленный (несмотря на то, что он захватывает только имена 4 человек, я ожидаю сделать около 100 одновременно):

$skills = array(
    "overall", "attack", "defense", "strength", "constitution", "ranged",
    "prayer", "magic", "cooking", "woodcutting", "fletching", "fishing",
    "firemaking", "crafting", "smithing", "mining", "herblore", "agility",
    "thieving", "slayer", "farming", "runecrafting", "hunter", "construction",
    "summoning", "dungeoneering"
);

$participants = array("Zezima", "Allar", "Foot", "Arma150", "Green098", "Skiller 703", "Quuxx");//explode("\r\n", $_POST['names']);

$skill = isset($_GET['skill']) ? array_search($skills, $_GET['skill']) : 0;

display($participants, $skills, array_search($_GET['skill'], $skills));

function getAllStats($participants) {
    $stats = array();
    for ($i = 0; $i < count($participants); $i++) {
        $stats[] = getStats($participants[$i]);
    }
    return $stats;
}

function display($participants, $skills, $stat) {
    $all = getAllStats($participants);
    for ($i = 0; $i < count($participants); $i++) {
        $rank = getSkillData($all[$i], 0, $stat);
        $level = getSkillData($all[$i], 1, $stat);
        $experience = getSkillData($all[$i], 3, $stat);
    }
}

function getStats($username) {
    $curl = curl_init("http://hiscore.runescape.com/index_lite.ws?player=" . $username);
    curl_setopt ($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt ($curl, CURLOPT_USERAGENT, sprintf("Mozilla/%d.0", rand(4, 5)));
    curl_setopt ($curl, CURLOPT_HEADER, (int) $header);
    curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt ($curl, CURLOPT_VERBOSE, 1);
    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    $output = curl_exec($curl);
    curl_close ($curl);
    if (strstr($output, "<html><head><title>")) {
        return false;
    }
    return $output;
}

function getSkillData($stats, $row, $skill) {
    $stats = explode("\n", $stats);
    $levels = explode(",", $stats[$skill]);
    return $levels[$row];
}

Когда я тестировал это, это заняло около 5 секунд, что не слишком плохо, но представьте, если бы я делал это еще 93 раза. Я понимаю, что это не будет мгновенно, но я хотел бы стрелять менее чем за 30 секунд. Я знаю, что это возможно, потому что я видел сайты, которые делают что-то подобное, и они действуют в течение 30 секунд.

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

Есть ли способ добиться выполнения чего-то подобного без значительных задержек (и, возможно, перегрузки сервера, с которого я читаю)?

P.S .: Веб-сайт, с которого я читаю, является просто текстом, у него нет HTML-кода для анализа, что должно сократить время загрузки. Вот пример того, как выглядит страница (они все одинаковые, просто разные цифры):
69,2496,1285458634 10982,99,33055154 6608,99,30955066 6978,99,40342518 12092,99,36496288 13247,99,21606979 2812,99,13977759 926,99,36988378 415,99,153324269 329,99,59553081 472,99,40595060 2703,99,28297122 281,99,36937100 1017,99,19418910 276,99,27539259 792,99,34289312 3040,99,16675156 82,99,39712827 80,99,104504543 2386,99,21236188 655,99,28714439 852,99,30069730 29,99,200000000 3366,99,15332729 2216,99,15836767 154,120,200000000 -1,-1 -1,-1 -1,-1 -1,-1 -1,-1 30086,2183 54640,1225 89164,1028 123432,1455 -1,-1 -1,-1

Мой предыдущий тест с этим методом против curl_multi_exec:

function getTime() { 
    $timer = explode(' ', microtime()); 
    $timer = $timer[1] + $timer[0]; 
    return $timer; 
}

function benchmarkFunctions() {
    $start = getTime();
    old_f();
    $end = getTime();
    echo 'function old_f() took ' . round($end - $start, 4) . ' seconds to complete<br><br>';
    $startt = getTime();
    new_f();
    $endd = getTime();
    echo 'function new_f() took ' . round($endd - $startt, 4) . ' seconds to complete';
}

function old_f() {
    $test = array("A E T", "Ts Danne", "Funkymunky11", "Fast993", "Fast99Three", "Jeba", "Quuxx");
    getAllStats($test);
}

function new_f() {
    $test = array("A E T", "Ts Danne", "Funkymunky11", "Fast993", "Fast99Three", "Jeba", "Quuxx");
    $curl_arr = array();
    $master = curl_multi_init();

    $amt = count($test);
    for ($i = 0; $i < $amt; $i++) {
        $curl_arr[$i] = curl_init('http://hiscore.runescape.com/index_lite.ws?player=' . $test[$i]);
        curl_setopt($curl_arr[$i], CURLOPT_RETURNTRANSFER, true);
        curl_multi_add_handle($master, $curl_arr[$i]);
    }

    do {
        curl_multi_exec($master, $running);
    } while ($running > 0);

    for ($i = 0; $i < $amt; $i++) {
        $results = curl_exec($curl_arr[$i]);
    }
}

Ответы [ 4 ]

2 голосов
/ 05 октября 2011

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

В связи с этим лучший способ выполнить все ваши запросы в кратчайшие сроки - это, вероятно, выполнить их все сразу. Создайте новую тему для каждой. Для размера данных, с которыми вы работаете, возможно, вполне возможно сделать буквально все сразу, но если это проблема, то, возможно, попробуйте 20 или около того сразу.

РЕДАКТИРОВАТЬ: Я только что понял, что вы используете PHP, который не имеет потоков. Ну, наверное, плохой выбор языка, для начала. Но вы можете эмулировать потоки, создавая новые процессы. Однако это может привести к краху, если PHP работает внутри процесса веб-сервера, поскольку он клонирует весь сервер. Я посмотрю, предлагает ли PHP какой-то асинхронный веб-запрос, который может дать аналогичный эффект.

РЕДАКТИРОВАТЬ 2:

Вот страница, обсуждающая, как запустить HTTP-запрос в фоновом режиме с PHP:

http://w -shadow.com / блог / 2007/10/16 / как к перспективе-а-PHP-скрипт-в-фона /

Тем не менее, это «уволить и забыть», оно не позволяет вам подобрать ответ на ваш запрос и что-то с ним сделать. Тем не менее, один из подходов, который вы могли бы использовать для этого, состоял бы в том, чтобы использовать этот метод, чтобы запускать много запросов на разные страницы на вашем собственном сервере, и чтобы каждая из этих страниц выполняла один запрос к удаленному серверу. (Или каждый рабочий запрос может обрабатывать пакет запросов, если вы не хотите запускать слишком много запросов одновременно.)

Вам все еще понадобится способ собрать все результаты и способ определить, когда вся процедура завершена, чтобы вы могли отобразить результаты. Вероятно, я бы использовал базу данных или файловую систему для координации различных процессов.

(Опять же, выбор более мощного языка для этой задачи, вероятно, был бы полезен. В области языков, подобных PHP, я знаю, что Perl очень легко справится с этой проблемой с помощью «использования потоков», и я предполагаю, что Python или Ruby а также.)

РЕДАКТИРОВАТЬ 3:

Другое решение, использующее оболочку UNIX, чтобы обойти ограничения PHP, выполняя работу в отдельных процессах. Вы можете сделать команду примерно так:

echo '$urlList' | xargs -P 10 -r -n1 wget

Возможно, вы захотите немного поиграть с опциями wget, такими как явное указание выходного файла, но это общая идея. Вместо wget вы также можете использовать curl или даже просто вызвать PHP-скрипт, предназначенный для запуска из командной строки, если вы хотите полностью контролировать работу по извлечению страниц.

Опять же, с этим решением у вас все еще есть проблема с распознаванием, когда работа выполнена, и вы можете показать результаты.

Я получил эту идею для этого подхода с этой страницы:

http://www.commandlinefu.com/commands/view/3269/parallel-file-downloading-with-wget

1 голос
/ 05 октября 2011

Вы можете повторно использовать соединения curl. Кроме того, я изменил ваш код для проверки httpCode вместо использования strstr. Должно быть быстрее.

Кроме того, вы можете настроить curl, чтобы делать это параллельно, чего я никогда не пробовал. Смотри http://www.php.net/manual/en/function.curl-multi-exec.php

Улучшенный getStats() с повторно используемой ручкой для завитков.

function getStats(&$curl,$username) {
    curl_setopt($curl, CURLOPT_URL, "http://hiscore.runescape.com/index_lite.ws?player=" . $username);
    $output = curl_exec($curl);
    if (curl_getinfo($curl, CURLINFO_HTTP_CODE)!='200') {
        return null;
    }
    return $output;
}

Использование:

$participants = array("Zezima", "Allar", "Foot", "Arma150", "Green098", "Skiller 703", "Quuxx");

$curl = curl_init();
curl_setopt ($curl, CURLOPT_CONNECTTIMEOUT, 0); //dangerous! will wait indefinitely
curl_setopt ($curl, CURLOPT_USERAGENT, sprintf("Mozilla/%d.0", rand(4, 5)));
curl_setopt ($curl, CURLOPT_HEADER, false);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($curl, CURLOPT_VERBOSE, 1);
//try:
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
    'Connection: Keep-Alive',
    'Keep-Alive: 300'
));


header('Content-type:text/plain');
foreach($participants as &$user) {
    $stats =  getStats($curl, $user);
    if($stats!==null) {
        echo $stats."\r\n";
    }
}

curl_close($curl);
1 голос
/ 05 октября 2011

Поскольку вы делаете несколько запросов к одному и тому же хосту, вы можете повторно использовать дескриптор curl, и если сайт поддерживает запросы поддержки активности, это может значительно ускорить ваш процесс по многим запросам.

Вы можете изменить свою функцию следующим образом:

function getStats($username) {
    static $curl = null;

    if ($curl == null) {
        $curl = curl_init();
    }

    curl_setopt($curl, CURLOPT_URL, "http://hiscore.runescape.com/index_lite.ws?player=" . $username);
    curl_setopt ($curl, CURLOPT_HTTPHEADER, array('Connection: Keep-Alive'));

    //...

    // remove curl_close($curl)
}

Это позволит вам не закрывать и не устанавливать сокет для каждого запроса пользователя.Он будет использовать одно и то же соединение для всех запросов.

0 голосов
/ 05 октября 2011

curl - это очень хороший способ прочитать содержимое веб-сайта - я полагаю, что ваша проблема из-за времени, необходимого для загрузки ONE страницы. Если вы можете получить все 100 страниц параллельно, то, вероятно, все это будет обработано менее чем за 10 секунд.

Чтобы избежать работы с потоками, блокировками, семафорами и всеми сложными элементами в потоках, прочитайте эту статью и найдите способ сделать ваше приложение параллельным практически бесплатно.

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