使用pcntl_fork()提高HTML scraper效率

时间:2010-05-18 23:48:38

标签: php fork pcntl

在前两个问题的帮助下,我现在有一个可用的HTML scraper,可以将产品信息提供给数据库。我现在要做的是通过缠绕我的大脑并使我的刮刀与pcntl_fork一起工作来提高效率。

如果我将php5-cli脚本分成10个单独的块,我会通过一个很大的因素来提高总运行时间,所以我知道我不是i / o或cpu绑定,而是受到我的抓取函数的线性特性的限制。

使用代码我从多个来源拼凑而成,我有这个工作测试:

<?php
libxml_use_internal_errors(true);
ini_set('max_execution_time', 0); 
ini_set('max_input_time', 0); 
set_time_limit(0);

$hrefArray = array("http://slashdot.org", "http://slashdot.org", "http://slashdot.org", "http://slashdot.org");

function doDomStuff($singleHref,$childPid) {
    $html = new DOMDocument();
    $html->loadHtmlFile($singleHref);

    $xPath = new DOMXPath($html);

    $domQuery = '//div[@id="slogan"]/h2';
    $domReturn = $xPath->query($domQuery);

    foreach($domReturn as $return) {
        $slogan = $return->nodeValue;
        echo "Child PID #" . $childPid . " says: " . $slogan . "\n";
    }
}

$pids = array();
foreach ($hrefArray as $singleHref) {
    $pid = pcntl_fork();

    if ($pid == -1) {
        die("Couldn't fork, error!");
    } elseif ($pid > 0) {
        // We are the parent
        $pids[] = $pid;
    } else {
        // We are the child
        $childPid = posix_getpid();
        doDomStuff($singleHref,$childPid);
        exit(0);
    }
}

foreach ($pids as $pid) {
    pcntl_waitpid($pid, $status);
}

// Clear the libxml buffer so it doesn't fill up
libxml_clear_errors();

提出了以下问题:

1)鉴于我的hrefArray包含4个网址 - 如果数组包含1000个产品网址,那么此代码会产生1,000个子进程吗?如果是这样,将进程数量限制为10的最佳方法是什么,并且再次将1,000个URL作为示例将子工作负载分成每个子项100个产品(10 x 100)。

2)我已经知道pcntl_fork创建了一个进程副本以及所有变量,类等。我想要做的是用一个DOMDocument查询替换我的hrefArray变量,该查询构建要刮擦的产品列表,以及然后将它们送到子进程进行处理 - 将负载分散到10个童工中。

我的大脑告诉我需要做以下事情(显然这不起作用,所以不要运行它):

<?php
libxml_use_internal_errors(true);
ini_set('max_execution_time', 0); 
ini_set('max_input_time', 0); 
set_time_limit(0);
$maxChildWorkers = 10;

$html = new DOMDocument();
$html->loadHtmlFile('http://xxxx');
$xPath = new DOMXPath($html);

$domQuery = '//div[@id=productDetail]/a';
$domReturn = $xPath->query($domQuery);

$hrefsArray[] = $domReturn->getAttribute('href');

function doDomStuff($singleHref) {
    // Do stuff here with each product
}

// To figure out: Split href array into $maxChilderWorks # of workArray1, workArray2 ... workArray10. 
$pids = array();
foreach ($workArray(1,2,3 ... 10) as $singleHref) {
    $pid = pcntl_fork();

    if ($pid == -1) {
        die("Couldn't fork, error!");
    } elseif ($pid > 0) {
        // We are the parent
        $pids[] = $pid;
    } else {
        // We are the child
        $childPid = posix_getpid();
        doDomStuff($singleHref);
        exit(0);
    }
}


foreach ($pids as $pid) {
    pcntl_waitpid($pid, $status);
}

// Clear the libxml buffer so it doesn't fill up
libxml_clear_errors();

但我无法弄清楚的是如何仅在主/父进程中构建我的hrefsArray []并将其提供给子进程。目前我尝试的所有内容都会导致子进程中出现循环。即我的hrefsArray在master中以及每个后续子进程中构建。

我确信我所说的一切都完全错了,所以非常感谢在正确方向上的一般推动。

2 个答案:

答案 0 :(得分:4)

简介

pcntl_fork()并不是提高效果的唯一方法HTML scraper虽然使用Message Queue提出Charles可能是个好主意,但您仍然需要更快捷的有效方法在workers

中提取该请求

解决方案1 ​​

使用curl_multi_init ... curl实际上更快,使用多卷曲可以进行并行处理

来自PHP DOC

  

curl_multi_init允许并行处理多个cURL句柄。

因此,您可以使用$html->loadHtmlFile('http://xxxx');同时加载多个网址,而不是使用curl_multi_init多次加载文件

以下是一些有趣的实现

解决方案2

您可以使用pthreadsPHP

中使用多线程

示例

// Number of threads you want
$threads = 10;

// Treads storage
$ts = array();

// Your list of URLS // range just for demo
$urls = range(1, 50);

// Group Urls
$urlsGroup = array_chunk($urls, floor(count($urls) / $threads));

printf("%s:PROCESS  #load\n", date("g:i:s"));

$name = range("A", "Z");
$i = 0;
foreach ( $urlsGroup as $group ) {
    $ts[] = new AsyncScraper($group, $name[$i ++]);
}

printf("%s:PROCESS  #join\n", date("g:i:s"));

// wait for all Threads to complete
foreach ( $ts as $t ) {
    $t->join();
}

printf("%s:PROCESS  #finish\n", date("g:i:s"));

输出

9:18:00:PROCESS  #load
9:18:00:START  #5592     A
9:18:00:START  #9620     B
9:18:00:START  #11684    C
9:18:00:START  #11156    D
9:18:00:START  #11216    E
9:18:00:START  #11568    F
9:18:00:START  #2920     G
9:18:00:START  #10296    H
9:18:00:START  #11696    I
9:18:00:PROCESS  #join
9:18:00:START  #6692     J
9:18:01:END  #9620       B
9:18:01:END  #11216      E
9:18:01:END  #10296      H
9:18:02:END  #2920       G
9:18:02:END  #11696      I
9:18:04:END  #5592       A
9:18:04:END  #11568      F
9:18:04:END  #6692       J
9:18:05:END  #11684      C
9:18:05:END  #11156      D
9:18:05:PROCESS  #finish

使用的课程

class AsyncScraper extends Thread {

    public function __construct(array $urls, $name) {
        $this->urls = $urls;
        $this->name = $name;
        $this->start();
    }

    public function run() {
        printf("%s:START  #%lu \t %s \n", date("g:i:s"), $this->getThreadId(), $this->name);
        if ($this->urls) {
            // Load with CURL
            // Parse with DOM
            // Do some work

            sleep(mt_rand(1, 5));
        }
        printf("%s:END  #%lu \t %s \n", date("g:i:s"), $this->getThreadId(), $this->name);
    }
}

答案 1 :(得分:2)

好像我每天都这样建议,但你看过Gearman了吗?甚至有一个记录良好的PECL class

Gearman是一个工作队列系统。您将创建连接和侦听作业的工作者,以及连接和发送作业的客户端。客户端可以等待所请求的作业完成,也可以触发并忘记。根据您的选择,工作人员甚至可以发回状态更新,以及他们的流程。

换句话说,您可以获得多个进程或线程的好处,而无需担心进程和线程。客户和工人甚至可以在不同的机器上。