【问题标题】:Improving HTML scraper efficiency with pcntl_fork()使用 pcntl_fork() 提高 HTML 抓取效率
【发布时间】:2011-02-21 03:54:10
【问题描述】:

在前两个问题的帮助下,我现在有了一个可以将产品信息输入数据库的 HTML 抓取工具。我现在要做的是通过让我的刮刀与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 个 url - 如果该数组包含 1,000 个产品 url,那么此代码会产生 1,000 个子进程吗?如果是这样,将进程数量限制为 10 个的最佳方法是什么,再以 1,000 个 url 为例,将子工作负载拆分为每个子 (10 x 100) 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[] 并将其提供给子进程。目前我尝试过的一切都会导致子进程中的循环。 IE。我的 hrefsArray 在主进程和每个后续子进程中构建。

我确信我完全错误地处理这一切,因此非常感谢您朝着正确的方向进行一般性轻推。

【问题讨论】:

    标签: php fork pcntl


    【解决方案1】:

    简介

    pcntl_fork() 不是提高性能HTML scraper 的唯一方法,虽然使用Message Queue 可能是个好主意,但建议使用Charles,但您仍然需要一种更快速有效的方法来在您的@987654330 中提取该请求@

    解决方案 1

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

    来自 PHP 文档

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

    所以不用$html-&gt;loadHtmlFile('http://xxxx');多次加载文件,你可以使用curl_multi_init同时加载多个url

    这里有一些有趣的实现

    解决方案 2

    pthreads可以在PHP中使用多线程

    例子

    // 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);
        }
    }
    

    【讨论】:

      【解决方案2】:

      我好像每天都在推荐这个,但是你看过Gearman吗?甚至还有一个有据可查的PECL class

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-31
        • 2013-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-21
        • 1970-01-01
        相关资源
        最近更新 更多