2

前の 2 つの質問の助けを借りて、製品情報をデータベースにフィードする 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 では、子の作業負荷を子ごとに 100 製品 (10 x 100) に分割します。

2) pcntl_fork がプロセスとすべての変数、クラスなどのコピーを作成することを学びました。私がやりたいのは、hrefArray 変数を、スクレイピングする製品のリストを作成してからフィードする DOMDocument クエリに置き換えることです。処理を行うために子プロセスにオフにするため、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 はマスターと後続の各子プロセスで構築されます。

私はこれについて完全に間違っていると確信しているので、正しい方向への一般的なナッジだけを大いに感謝します.

4

2 に答える 2

4

序章

pcntl_fork()HTML scraperを使用することをお勧めしますが、Message Queueパフォーマンスを向上させる唯一の方法ではありませんが、Charlesそのリクエストをより速く効果的にプルする方法が必要です。workers

解決策 1

使用curl_multi_init... curl は実際には高速であり、マルチカールを使用すると並列処理が可能になります

PHPドキュメントから

curl_multi_init複数の cURL ハンドルを並行して処理できるようにします。

$html->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);
    }
}
于 2013-03-28T20:21:18.760 に答える
2

私が毎日これを提案しているように思えますが、Gearmanを見たことがありますか? 十分に文書化されたPECL クラスもあります。

Gearman はワーク キュー システムです。接続してジョブをリッスンするワーカーと、接続してジョブを送信するクライアントを作成します。クライアントは、要求されたジョブが完了するのを待つか、ジョブを起動して忘れることができます。オプションで、作業者はステータスの更新と、プロセスの進行状況を返信することもできます。

つまり、プロセスやスレッドを気にすることなく、複数のプロセスやスレッドの利点を得ることができます。クライアントとワーカーが別のマシンに存在することさえあります。

于 2010-06-04T03:46:24.087 に答える