4

私は Web スクレイパーをマルチスレッド化することを考えていました。通常のスレッド (例:Thread Scrape = new Thread(Function);) ではなく、非常に多数のスレッドが存在する可能性があるスレッドプールのようなものです。

私のスクレーパーは、forループを使用してページをスクレイピングすることで機能します。

for (int i = (int)pagesMin.Value; i <= (int)pagesMax.Value; i++)

では、関数 (ループを含む) をスレッドプールのようなものでマルチスレッド化するにはどうすればよいでしょうか? 私は以前にスレッドプールを使用したことがなく、私が見た例は非常に混乱しているか、わかりにくいものでした。


ループを次のように変更しました。

int min = (int)pagesMin.Value;
int max = (int)pagesMax.Value;
ParallelOptions pOptions = new ParallelOptions();
pOptions.MaxDegreeOfParallelism = Properties.Settings.Default.Threads;
Parallel.For(min, max, pOptions, i =>{
    //Scraping
});

それは機能しますか、それとも何か間違っていますか?

4

5 に答える 5

5

プール スレッドを使用する際の問題は、ほとんどの時間を Web サイトからの応答を待つことに費やすことです。使用の問題Parallel.ForEachは、並列処理が制限されることです。

非同期 Web 要求を使用することで、最高のパフォーマンスが得られました。Semaphore同時リクエストの数を制限するために a を使用し、コールバック関数がスクレイピングを行いました。

メイン スレッドはSemaphore、次のようにを作成します。

Semaphore _requestsSemaphore = new Semaphore(20, 20);

20、試行錯誤によって導き出されました。制限要因は DNS 解決であり、平均して約 50 ミリ秒かかることが判明しました。少なくとも、私の環境ではそうでした。20 の同時リクエストが絶対最大数でした。15がおそらくより合理的です。

メイン スレッドは基本的に次のようにループします。

while (true)
{
    _requestsSemaphore.WaitOne();
    string urlToCrawl = DequeueUrl();  // however you do that
    var request = (HttpWebRequest)WebRequest.Create(urlToCrawl);
    // set request properties as appropriate
    // and then do an asynchronous request
    request.BeginGetResponse(ResponseCallback, request);
}

プール スレッドで呼び出されるResponseCallbackメソッドは、処理を実行し、応答を破棄してから、別の要求を行うことができるようにセマフォを解放します。

void ResponseCallback(IAsyncResult ir)
{
    try
    {
        var request = (HttpWebRequest)ir.AsyncState;
        // you'll want exception handling here
        using (var response = (HttpWebResponse)request.EndGetResponse(ir))
        {
            // process the response here.
        }
    }
    finally
    {
        // release the semaphore so that another request can be made
        _requestSemaphore.Release();
    }
}

前述したように、制限要因は DNS 解決です。DNS 解決は呼び出しスレッド (この場合はメイン スレッド) で行われることがわかります。これは本当に非同期ですか?を参照してください。詳細については。

これは実装が簡単で、非常にうまく機能します。20 を超える同時リクエストを取得することも可能ですが、私の経験では、そうするにはかなりの労力が必要です。大量の DNS キャッシングを行う必要がありましたが、それは困難でした。

Taskおそらく、C# 5.0 (.NET 4.5) の新しい非同期機能を使用して、上記を単純化できます。しかし、私はそれらについて十分に詳しく知りません。

于 2013-04-20T01:12:49.383 に答える
3

TPL、つまりParallel.ForEachを使用してPartitionerでオーバーロードを使用することをお勧めします。ワークロードを自動的に管理します。

ご参考までに。スレッド数が多いからといって高速になるわけではないことを理解しておく必要があります。Parallel.ForEachパラメータ化されていないものとユーザー定義のものを比較するために、いくつかのテストを行うことをお勧めします。

アップデート

    public void ParallelScraper(int fromInclusive, int toExclusive,
                                Action<int> scrape, int desiredThreadsCount)
    {
        int chunkSize = (toExclusive - fromInclusive +
            desiredThreadsCount - 1) / desiredThreadsCount;
        ParallelOptions pOptions = new ParallelOptions
        {
            MaxDegreeOfParallelism = desiredThreadsCount
        };

        Parallel.ForEach(Partitioner.Create(fromInclusive, toExclusive, chunkSize),
            rng =>
            {
                for (int i = rng.Item1; i < rng.Item2; i++)
                    scrape(i);
            });
    }

asyncあなたの状況では、より良いものになる可能性があります。

于 2013-04-20T00:49:51.147 に答える
2

Web スクレーパーが for ループを使用するのが好きだと思われる場合は、foreach ループに似たParallel.ForEach()を見ることができます。ただし、その中で、列挙可能なデータを反復処理します。Parallel.ForEachは複数のスレッドを使用してループ本体を呼び出します。

詳細については、並列ループを参照してください。

アップデート:

Parallel.For()はParallel.ForEach ()と非常に似ていますが、 for または foreach ループを使用するなどのコンテキストに依存します。

于 2013-04-20T00:53:32.157 に答える
0

これは、TPL Dataflow のActionBlockにとって完璧なシナリオです。同時実行を制限するように簡単に構成できます。ドキュメントの例の 1 つを次に示します。

var downloader = new ActionBlock<string>(async url =>
{
    byte [] imageData = await DownloadAsync(url);
    Process(imageData);
}, new DataflowBlockOptions { MaxDegreeOfParallelism = 5 }); 

downloader.Post("http://msdn.com/concurrency ");
downloader.Post("http://blogs.msdn.com/pfxteam");

Introduction to TPL Dataflow をダウンロードすると、ActionBlock (参照されている例を含む) について読むことができます。

于 2013-04-20T02:57:54.673 に答える
0

「 Crawler-Lib Framework 」のテスト中に、並列、TPL、またはスレッド化を試みても、必要なスループットが得られないことがわかりました。ローカル マシンで 1 秒あたり 300 ~ 500 のリクエストに固執しました。何千ものリクエストを並行して実行したい場合は、それらを非同期パターンで実行し、結果を並行して処理する必要があります。当社の Crawler-Lib エンジン (ワークフロー対応のリクエスト プロセッサ) は、ローカル マシンで 1 秒あたり約 10.000 ~ 20.000 リクエストでこれを行います。高速なスクレーパーが必要な場合は、TPL を使用しないでください。代わりに非同期パターン (Begin... End...) を使用し、すべてのリクエストを 1 つのスレッドで開始します。

リクエストの多くが 30 秒後にタイムアウトする傾向にある場合、状況はさらに悪化します。この場合、TPL ベースのソリューションは 5 という醜い悪いスループットを取得しますか? 1? 1 秒あたりのリクエスト数。非同期パターンでは、1 秒あたり少なくとも 100 ~ 300 のリクエストが得られます。Crawler-Lib Engine はこれを適切に処理し、最大限のリクエストを取得します。TCP/IP タックが 60000 のアウトバウンド接続を持つように構成されているとしましょう (すべての接続にはアウトバウンド ポートが必要なので、65535 が最大値です)。この場合、60000 接続 / 30 秒のタイムアウト = 2000 リクエスト / 秒のスループットが得られます。

于 2013-08-30T19:57:32.250 に答える