【问题标题】:Downloading pages in parallel using PHP使用 PHP 并行下载页面
【发布时间】:2012-02-10 19:07:50
【问题描述】:

我必须废弃一个需要获取多个 URL 的网站,然后一个一个地处理它们。目前的流程有点像这样。

我获取一个基本 URL 并从该页面获取所有辅助 URL,然后对于每个辅助 URL,我获取该 URL,处理找到的页面,下载一些照片(这需要很长时间)并将这些数据存储到数据库,然后获取下一个 URL 并重复该过程。

在这个过程中,我认为我在每次迭代开始时获取辅助 URL 是在浪费一些时间。所以我试图在处理第一次迭代时并行获取下一个 URL。

我认为的解决方案是,从主进程调用 PHP 脚本,比如下载器,它将下载所有 URL(带有 curl_multiwget)并将它们存储在某个数据库中。

我的问题是

  • 如何异步调用此类下载器,我不希望我的主脚本等到下载器完成。
  • 存储下载数据的任何位置,例如共享内存。当然,除了数据库。
  • 在存储和检索数据时有可能损坏数据,如何避免这种情况?
  • 另外,如果有人有更好的计划,请指导我。

【问题讨论】:

  • PHP 并不是真正设计用于启动多个进程。为什么不看看像 python 这样的语言来完成这个?
  • @afuzzyllama 只是一个子模块,整个项目都是用PHP编写的
  • nodejs 非常适合。
  • 这可能不是正确的问题,但我可以异步调用任何 PHP 函数吗?可以使用事件或系统调用吗?
  • @Uday 是的,如果您使用 exec() 调用另一个程序并将输出重定向到一个文件(或 /dev/null),它将在后台启动,您的脚本将继续执行而不等待它完成.

标签: php performance parallel-processing web-scraping


【解决方案1】:

当我听说有人使用 curl_multi_exec 时,通常结果他们只是加载它,比如 100 个 url,然后等待全部完成,然后处理它们,然后从接下来的 100 个 url 重新开始......怪我,我也这样做了,但后来我发现可以在某些事情仍在进行中时删除/添加 curl_multi 的句柄,而且它确实节省了大量时间,特别是如果你重用已经打开的连接。我写了一个小库来处理带有回调的请求队列;我当然不会在这里发布完整版本(“小”仍然是相当多的代码),但这里是主要内容的简化版本,可以让您大致了解:

public function launch() {
    $channels = $freeChannels = array_fill(0, $this->maxConnections, NULL);
    $activeJobs = array();
    $running = 0;
    do {
        // pick jobs for free channels:
        while ( !(empty($freeChannels) || empty($this->jobQueue)) ) {
            // take free channel, (re)init curl handle and let
            // queued object set options
            $chId = key($freeChannels);
            if (empty($channels[$chId])) {
                $channels[$chId] = curl_init();
            }
            $job = array_pop($this->jobQueue);
            $job->init($channels[$chId]);
            curl_multi_add_handle($this->master, $channels[$chId]);
            $activeJobs[$chId] = $job;
            unset($freeChannels[$chId]);
        }
        $pending = count($activeJobs);

        // launch them:
        if ($pending > 0) {
            while(($mrc = curl_multi_exec($this->master, $running)) == CURLM_CALL_MULTI_PERFORM);
                // poke it while it wants
            curl_multi_select($this->master);
                // wait for some activity, don't eat CPU
            while ($running < $pending && ($info = curl_multi_info_read($this->master))) {
                // some connection(s) finished, locate that job and run response handler:
                $pending--;
                $chId = array_search($info['handle'], $channels);
                $content = curl_multi_getcontent($channels[$chId]);
                curl_multi_remove_handle($this->master, $channels[$chId]);
                $freeChannels[$chId] = NULL;
                    // free up this channel
                if ( !array_key_exists($chId, $activeJobs) ) {
                    // impossible, but...
                    continue;
                }
                $activeJobs[$chId]->onComplete($content);
                unset($activeJobs[$chId]);
            }
        }
    } while ( ($running > 0 && $mrc == CURLM_OK) || !empty($this->jobQueue) );
}

在我的版本中,$jobs 实际上是单独的类,而不是控制器或模型的实例。他们只处理设置 cURL 选项、解析响应并调用给定的回调 onComplete。 使用这种结构,新请求将在池中的某些内容完成后立即开始。

当然,如果不仅检索需要时间而且处理也需要时间,它并不能真正为您节省......而且它不是真正的并行处理。但我仍然希望它有所帮助。 :)

附:为我做了一个把戏。 :) 现在使用 50 个连接池在 3-4 分钟内完成一次 8 小时的工作。无法形容那种感觉。 :) 我真的没想到它会按计划工作,因为使用 PHP 它很少能完全按预期工作......就像“好的,希望它至少在一个小时内完成......哇......等等...... . 已经?!8-O"

【讨论】:

  • 感谢分享,另外,你搜索github也可以找到其他类似的库。
  • 我该如何使用它?
【解决方案2】:

你可以使用 curl_multi:http://www.somacon.com/p537.php

您可能还想考虑做这个客户端并使用 Javascript。


另一种解决方案是编写一个 Hunter/gatherer,您向其中提交 URL 数组,然后它执行并行工作并在完成后返回一个 JSON 数组。

换一种说法:如果您有 100 个 URL,您可以将该数组(也可能是 JSON)发布到 mysite.tld/huntergatherer - 它会以您想要的任何语言执行任何操作并返回 JSON。

【讨论】:

  • 是的,我已经在使用 curl_multi,javascript 听起来不错,我可以了解更多详细信息吗?
【解决方案3】:

除了 curl 多解决方案之外,另一个解决方案只是拥有一批 gearman workers。如果你走这条路,我发现supervisord 是启动大量守护进程的好方法。

【讨论】:

  • 谢谢!以前从未听说过此扩展程序。
【解决方案4】:

除了 CURL multi 之外你应该看看的东西:

  • 非阻塞流(例如:PHP-MIO
  • ZeroMQ 催生了许多异步请求的工作人员

虽然 node.js、ruby EventMachine 或类似工具非常适合做这些事情,但我提到的事情在 PHP 中也相当容易。

【讨论】:

  • 非阻塞流确实是一本好书,对不起,但我只能选择一个答案。谢谢
【解决方案5】:

尝试从 PHP、python-pycurl 脚本执行。比 PHP curl 更简单、更快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-25
    • 2017-01-22
    • 1970-01-01
    相关资源
    最近更新 更多