2

スパイダーページに同時に Web スパイダーを書きました。スパイダーが見つけたリンクごとに、プロセスを最初からやり直す新しい子をフォークしたいと考えています。

ターゲット サーバーに負荷をかけたくないので、すべてのオブジェクトがアクセスできる静的配列を作成しました。各子は自分の PID を配列に追加できます。親または子のいずれかが配列をチェックして、$maxChildren が満たされているかどうかを確認する必要があります。満たされている場合は、いずれかの子が終了するまで辛抱強く待ちます。

ご覧のとおり、$maxChildren を 3 に設定しています。いつでも 3 つの同時プロセスが見られると予想しています。しかし、そうではありません。Linux の top コマンドは、常に 12 から 30 のプロセスを表示します。並行プログラミングでは、同時処理数をどのように調整できますか? 私のロジックは現在、Apache が最大の子を処理する方法に着想を得ていますが、それがどのように機能するかは正確にはわかりません。

回答の1つで指摘されているように、静的変数にグローバルにアクセスすると、競合状態の問題が発生します。これに対処するために、$children 配列はプロセスの一意の $PID をキーとその値の両方として取得し、それによって一意の値を作成します。私の考えでは、オブジェクトは 1 つの $children[$pid] 値しか扱えないので、ロックは必要ありません。これは真実ではありませんか?ある時点で 2 つのプロセスが同じ値を設定解除または追加しようとする可能性はありますか?

private static $children = array();

private $maxChildren = 3;

public function concurrentSpider($url) {

        // STEP 1:
        // Download the $url
        $pageData = http_get($url, $ref = '');

        if (!$this->checkIfSaved($url)) {
            $this->save_link_to_db($url, $pageData);
        }

        // STEP 2:
        // extract all hyperlinks from this url's page data
        $linksOnThisPage = $this->harvest_links($url, $pageData);

        // STEP 3:
        // Check the links array from STEP 2 to see if this page has
        // already been saved or is excluded because of any other
        // logic from the excluded_link() function
        $filteredLinks = $this->filterLinks($linksOnThisPage);

        shuffle($filteredLinks);

        // STEP 4: loop through each of the links and
        // repeat the process
        foreach ($filteredLinks as $filteredLink) {

            $pid = pcntl_fork();
            switch ($pid) {
                case -1:
                    print "Could not fork!\n";
                    exit(1);
                case 0:
                    if ($this->checkIfSaved($filteredLink)) {
                        exit();
                    }
                    //$pid = getmypid();
                    print "In child with PID: " . getmypid() . " processing $filteredLink \n";


                    $var[$pid]->concurrentSpider($filteredLink);
                    sleep(2);

                    exit(1);
                default:
                    // Add an element to the children array
                    self::$children[$pid] = $pid;
                    // If the maximum number of children has been
                    // achieved, wait until one or more return
                    // before continuing.

                    while (count(self::$children) >= $this->maxChildren) {
                        //print count(self::$children) . " children \n";
                        $pid = pcntl_waitpid(-1, $status);
                        unset(self::$children[$pid]);
                    }
            }
        }
    }

これはPHPで書かれています。pcntl_waitpid-1 の引数を持つ関数は、親に関係なく子が完了するのを待機することを知っています ( http://php.net/manual/en/function.pcntl-waitpid.php )。

ロジックの何が問題なの$maxChildrenですか?プロセスのみが同時に実行されるように修正するにはどうすればよいですか? 提案があれば、一般的なロジックを改善することにもオープンです。

4

4 に答える 4

4

最初に注意すること: これが本当に複数のスレッド間で共有されているグローバルである場合、複数のスレッドが一度に追加され、競合状態に陥っている可能性があります。一度に 1 つのプロセスだけがグローバル配列にアクセスするようにするには、ある種の同時実行制御が必要です。

また、新しいスパイダーがフォークされるたびに、各プロセスがその PID とグローバル配列の完全な内容を (コンソールまたはファイルに) 書き出すという単純なデバッグのトリックを試してください。これは、仮定 (ある時点で明らかに間違っている) を確認し、何が間違っているのかを理解するのに役立ちます。

編集:(コメントに応じて)

私は PHP 開発者ではありませんが、OS レベルのプロセスをカウントする OS ツールを使用しているという事実に基づいて推測する必要がある場合は、フォークが複数のプロセスを生成していると思いますが、static配列は現在のプロセス内でグローバル。システム全体の共有メモリを実装するのはもっと複雑です!

何かを数えたいだけで、共有リソースのインスタンスが制御不能にならないようにする場合は、セマフォを調べて、PHP で複数のインスタンス間で共有できる名前付きセマフォ オブジェクトを作成する方法を見つけられるかどうかを確認してください。あなたのクモの。

于 2013-06-05T23:02:15.073 に答える
1

実際のプログラミング言語を使用してください ;)

ステップ1は、データベースにある可能性があるのに、なぜダウンロードするのか、ちょっと悪いです。それをifの中に入れて、ミューテックスを配置できるかどうかを確認してください。たぶん、SQLで何かを模倣することがあります。

収穫_リンクが css セレクターをサポートする適切な html プロセッサーを使用することを願っています (私は .NET 用のフィズラーが好きです)。リンクを取得するだけなら正規表現で問題ないと思いますが、めちゃくちゃになる可能性があります。

ステップ 4 を見ましたが、それが悪いとは思いませんが、個人的には別の方法で行います。URL、ページ、フラグをデータベースに挿入するには、ステップ1のようなものがあります。次に、別のプロセスまたは同じプロセスがデータベースに未処理のページを要求し、エラーが発生した場合はフラグを何らかの値に設定し、成功した場合は別の値を設定します。これは、プロセスの終了に失敗した場合 (シャットダウン、クラッシュ、電源オフなど) に簡単に検出できるため、中断した場所を見つけるためにすべてのページをスキャンする必要がありません。データベースに次のリンクを要求し、完了しなかったものをやり直すだけです

于 2013-06-05T23:24:35.407 に答える
0

スパイダーが実用的な目的である場合は、「curl multithread」をグーグルで検索することをお勧めします

PHP による cURL マルチスレッド

于 2013-06-22T12:35:09.883 に答える
0

PHP はマルチスレッドをサポートしていないため、mutex やその他の同期方法をサポートしていません。他の人が回答で言ったように、これは競合状態につながります。

C または bash でラッパーを作成する必要があります。そうすれば、PHP スクリプトはターゲットをラッパーに送信でき、ラッパーはスケジューリングを処理します。

もう 1 つの方法は、マルチスレッドをサポートする Python または Ruby でスパイダーを書き直すことです。これにより、プロセス間通信の必要がなくなります。

編集: よく考えてみると、Python または Ruby でラッパーを記述し、既存の PHP コードをブラック ボックスとして再利用するのが最善の方法です。これは、上記のソリューションの妥協案です。

于 2013-06-06T04:24:12.120 に答える