我有一个数据聚合器,它依赖于抓取多个站点,并以用户可搜索的方式索引它们的信息。
我需要能够每天抓取大量页面,并且我在使用简单的 curl 请求时遇到了问题,这些请求在长时间快速执行时相当慢(抓取器基本上 24/7 运行)。
在简单的 while 循环中运行多卷曲请求相当慢。我通过在后台进程中执行单独的 curl 请求来加速它,它运行得更快,但迟早较慢的请求开始堆积,最终导致服务器崩溃。
是否有更有效的数据抓取方式?也许命令行卷曲?
我有一个数据聚合器,它依赖于抓取多个站点,并以用户可搜索的方式索引它们的信息。
我需要能够每天抓取大量页面,并且我在使用简单的 curl 请求时遇到了问题,这些请求在长时间快速执行时相当慢(抓取器基本上 24/7 运行)。
在简单的 while 循环中运行多卷曲请求相当慢。我通过在后台进程中执行单独的 curl 请求来加速它,它运行得更快,但迟早较慢的请求开始堆积,最终导致服务器崩溃。
是否有更有效的数据抓取方式?也许命令行卷曲?
对于大量页面,您将需要某种多线程方法,因为您将花费大部分时间等待网络 I/O。
上次我玩 PHP 线程时并不是一个很好的选择,但也许这已经改变了。如果您需要坚持使用 PHP,这意味着您将被迫采用多进程方法:将您的工作负载分成 N 个工作单元,并运行 N 个脚本实例,每个实例接收 1 个工作单元。
提供健壮和良好线程实现的语言是另一种选择。我在 ruby 和 C 中的线程方面有很好的经验,看起来 Java 线程也非常成熟和可靠。
谁知道——也许 PHP 线程自我上次使用它们(约 4 年前)以来有所改进,值得一看。
如果你想运行单个 curl 请求,你可以在 PHP 中启动 linux 下的后台进程,例如:
proc_close ( proc_open ("php -q yourscript.php parameter1 parameter2 & 2> /dev/null 1> /dev/null", array(), $dummy ));
您可以使用参数为您的 php 脚本提供一些有关要使用的 url 的信息,例如 sql 中的 LIMIT。
您可以通过将它们的 PID 保存在某处来跟踪正在运行的进程,以保持所需数量的进程同时运行或杀死未及时完成的进程。
根据我的经验,使用固定数量的线程运行 curl_multi 请求是最快的方法,您能否分享您正在使用的代码以便我们提出一些改进建议?这个答案有一个相当不错的 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,如果这样做,整个过程将挂起,直到较慢的请求完成。BLOCK_SIZE
如果您有带宽,我建议使用8 或更大的 a。