xsuchy09/rolling-curl

Rolling-Curl:PHP的一个非阻塞、非DOS多curl库

3.1.6 2017-04-27 11:59 UTC

This package is auto-updated.

Last update: 2024-08-29 04:09:10 UTC


README

一个cURL库,在保持固定数量的同时连接的情况下获取大量的资源

作者

  • Jeff Minard (jrm.cc)
  • Josh Fraser (joshfraser.com)
  • Alexander Makarov (rmcreative.ru)
  • Petr Suchy (xsuchy09 - www.wamos.cz)

概述

RollingCurl是curl_multi()的一个更有效率的实现。

curl_multi是PHP中并行处理多个HTTP请求的一个好方法,但存在一些问题

  1. curl_multi的文档非常晦涩,因此容易实现错误或效果不佳
  2. 大多数curl_multi示例将所有请求排队,然后一次性执行

第二点是重要的,原因有两个

  1. 如果你必须等待每个请求完成,你的程序将被运行时间最长的请求“阻塞”。
  2. 更重要的是,当你同时运行大量cURL请求时,本质上你是在运行一个DOS攻击。如果你需要获取数百甚至数千个URL,你很可能会被自动DOS系统阻止。最糟糕的情况是你不是互联网的好公民。

RollingCurl通过保持最大数量的同时请求和当现有请求完成时将新请求“滚动”到队列中来处理这两个问题。当请求完成时,其他请求仍在运行时,RollingCurl可以运行匿名函数来处理获取的结果。(你也可以选择跳过函数,而是等待所有请求完成后处理,如果你更喜欢这样做的话。)

安装(通过composer)

获取composer并在composer.json的要求部分添加以下内容

{
    "require": {
        "xsuchy09/rolling-curl": "*"
    }
}

然后

composer install

用法

基本示例

$rollingCurl = new \RollingCurl\RollingCurl();
$rollingCurl
    ->get('http://yahoo.com')
    ->get('http://google.com')
    ->get('http://hotmail.com')
    ->get('http://msn.com')
    ->get('http://reddit.com')
    ->setCallback(function(\RollingCurl\Request $request, \RollingCurl\RollingCurl $rollingCurl) {
        // parsing html with regex is evil (http://bit.ly/3x9sQX), but this is just a demo
        if (preg_match("#<title>(.*)</title>#i", $request->getResponseText(), $out)) {
            $title = $out[1];
        }
        else {
            $title = '[No Title Tag Found]';
        }
        echo "Fetch complete for (" . $request->getUrl() . ") $title " . PHP_EOL;
    })
    ->setSimultaneousLimit(3)
    ->execute();

获取非常大量的页面

让我们抓取google上“curl”的前500个链接和标题

$rollingCurl = new \RollingCurl\RollingCurl();
for ($i = 0; $i <= 500; $i+=10) {
    // https://www.google.com/search?q=curl&start=10
    $rollingCurl->get('https://www.google.com/search?q=curl&start=' . $i);
}

$results = array();

$start = microtime(true);
echo "Fetching..." . PHP_EOL;
$rollingCurl
    ->setCallback(function(\RollingCurl\Request $request, \RollingCurl\RollingCurl $rollingCurl) use (&$results) {
        if (preg_match_all('#<h3 class="r"><a href="([^"]+)">(.*)</a></h3>#iU', $request->getResponseText(), $out)) {
            foreach ($out[1] as $idx => $url) {
                parse_str(parse_url($url, PHP_URL_QUERY), $params);
                $results[$params['q']] = strip_tags($out[2][$idx]);
            }
        }

        // Clear list of completed requests and prune pending request queue to avoid memory growth
        $rollingCurl->clearCompleted();
        $rollingCurl->prunePendingRequestQueue();

        echo "Fetch complete for (" . $request->getUrl() . ")" . PHP_EOL;
    })
    ->setSimultaneousLimit(10)
    ->execute();
;
echo "...done in " . (microtime(true) - $start) . PHP_EOL;

echo "All results: " . PHP_EOL;
print_r($results);

设置自定义curl选项

对于每个请求

$rollingCurl = new \RollingCurl\RollingCurl();
$rollingCurl
    // setOptions will overwrite all the default options.
    // addOptions is probably a better choice
    ->setOptions(array(
        CURLOPT_HEADER => true,
        CURLOPT_NOBODY => true
    ))
    ->get('http://yahoo.com')
    ->get('http://google.com')
    ->get('http://hotmail.com')
    ->get('http://msn.com')
    ->get('http://reddit.com')
    ->setCallback(function(\RollingCurl\Request $request, \RollingCurl\RollingCurl $rollingCurl) {
        echo "Fetch complete for (" . $request->getUrl() . ")" . PHP_EOL;
    })
    ->setSimultaneousLimit(3)
    ->execute();

对于单个请求

$rollingCurl = new \RollingCurl\RollingCurl();

$sites = array(
    'http://yahoo.com' => array(
        CURLOPT_TIMEOUT => 15
    ),
    'http://google.com' => array(
        CURLOPT_TIMEOUT => 5
    ),
    'http://hotmail.com' => array(
        CURLOPT_TIMEOUT => 10
    ),
    'http://msn.com' => array(
        CURLOPT_TIMEOUT => 10
    ),
    'http://reddit.com' => array(
        CURLOPT_TIMEOUT => 25
    ),
);

foreach ($sites as $url => $options) {
    $request = new \RollingCurl\Request($url);
    $rollingCurl->add(
        $request->addOptions($options)
    );
}

$rollingCurl->execute();

更改

3.1.5:

  • 添加了Request::getActualExecutionTime。

3.1.4:

  • 超时 - 默认超时设置为5秒的CURLOPT_CONNECTTIMEOUT和20秒的CURLOPT_TIMEOUT。

3.1.3:

  • Request - 现在保护了私有变量和函数

3.1.2:

  • 每个请求都有开始,结束(带微秒的DateTime) - 因此我们可以知道每个请求的执行时间 - @see Request::getExecutionTime和Request::getExecutionTimeMicroseconds
  • 添加了RollingCurl::wasIdleCallbackCalled - 如果请求处理得太快,则可能不会调用idleCallback - 你可以检查idleCallback是否被调用。
  • 一些注释和方法参数定义。

更多示例可以在examples/目录中找到。

待办事项

  • PHPUnit测试
  • 确保PSR规范兼容性
  • 修复待办事项
  • 改进setter的验证

请随意分支并发出拉取请求以帮助处理以上内容。 :D

类似项目