什么是在PHP中抓取大量页面的最快方法?

时间:2010-05-20 14:47:57

标签: php curl screen-scraping web-scraping curl-multi

我有一个数据聚合器,它依赖于抓取多个站点,并以可供用户搜索的方式索引其信息。

我需要能够每天刮掉大量的页面,并且使用简单的卷曲请求遇到了问题,这些请求在长时间快速执行时相当慢(刮刀基本上全天候运行) )。

在简单的while循环中运行多卷曲请求相当慢。我通过在后台进程中执行单独的curl请求加快了速度,后者的工作速度更快,但迟早请求开始堆积,这最终会导致服务器崩溃。

是否有更有效的数据抓取方式?也许是命令行卷曲?

3 个答案:

答案 0 :(得分:2)

对于大量页面,您需要某种多线程方法,因为您将花费大部分时间等待网络I / O.

上次我玩PHP线程并不是一个很好的选择,但也许这已经改变了。如果您需要坚持使用PHP,那意味着您将被迫采用多进程方法:将工作负载分成N个工作单元,并运行脚本的N个实例,每个实例接收1个工作单元。

提供强大且良好的线程实现的语言是另一种选择。我对ruby和C中的线程有很好的经验,看起来Java线程也非常成熟和可靠。

谁知道 - 也许PHP线程自从我上次玩它们(大约4年前)以来有所改进,值得一看。

答案 1 :(得分:0)

根据我的经验,使用固定数量的线程运行curl_multi请求是最快的方法,您是否可以共享您正在使用的代码,以便我们建议一些改进?使用线程方法This answer有一个相当不错的curl_multi实现,这里是转载的代码:

// -- create all the individual cURL handles and set their options
$curl_handles = array();
foreach ($urls as $url) {
    $curl_handles[$url] = curl_init();
    curl_setopt($curl_handles[$url], CURLOPT_URL, $url);
    // set other curl options here
}

// -- start going through the cURL handles and running them
$curl_multi_handle = curl_multi_init();

$i = 0; // count where we are in the list so we can break up the runs into smaller blocks
$block = array(); // to accumulate the curl_handles for each group we'll run simultaneously

foreach ($curl_handles as $a_curl_handle) {
    $i++; // increment the position-counter

    // add the handle to the curl_multi_handle and to our tracking "block"
    curl_multi_add_handle($curl_multi_handle, $a_curl_handle);
    $block[] = $a_curl_handle;

    // -- check to see if we've got a "full block" to run or if we're at the end of out list of handles
    if (($i % BLOCK_SIZE == 0) or ($i == count($curl_handles))) {
        // -- run the block

        $running = NULL;
        do {
            // track the previous loop's number of handles still running so we can tell if it changes
            $running_before = $running;

            // run the block or check on the running block and get the number of sites still running in $running
            curl_multi_exec($curl_multi_handle, $running);

            // if the number of sites still running changed, print out a message with the number of sites that are still running.
            if ($running != $running_before) {
                echo("Waiting for $running sites to finish...\n");
            }
        } while ($running > 0);

        // -- once the number still running is 0, curl_multi_ is done, so check the results
        foreach ($block as $handle) {
            // HTTP response code
            $code = curl_getinfo($handle,  CURLINFO_HTTP_CODE);

            // cURL error number
            $curl_errno = curl_errno($handle);

            // cURL error message
            $curl_error = curl_error($handle);

            // output if there was an error
            if ($curl_error) {
                echo("    *** cURL error: ($curl_errno) $curl_error\n");
            }

            // remove the (used) handle from the curl_multi_handle
            curl_multi_remove_handle($curl_multi_handle, $handle);
        }

        // reset the block to empty, since we've run its curl_handles
        $block = array();
    }
}

// close the curl_multi_handle once we're done
curl_multi_close($curl_multi_handle);

诀窍是不要一次加载太多的URL,如果这样做,整个过程将挂起,直到较慢的请求完成。如果您有带宽,我建议使用8或更高的BLOCK_SIZE

答案 2 :(得分:0)

如果你想运行单个curl请求,你可以在Linux下用Linux启动后台进程,如:

proc_close ( proc_open ("php -q yourscript.php parameter1 parameter2 & 2> /dev/null 1> /dev/null", array(), $dummy ));

您可以使用参数为您的php脚本提供有关使用哪些url的信息,例如sql中的LIMIT。

您可以通过将PID保存在某个位置来跟踪正在运行的进程,以保持所需数量的进程同时运行,或者终止尚未及时完成的​​进程。