一般的なフレームワークのJavaクローラ利用マルチスレッディング

spider.jpg

I.はじめに

NetDiscoveryは私が達成しVert.x、RxJava 2と他のフレームワークに基づいたフレームワークを開発し、共通の爬虫類です。それは豊富含まれてい特性を

II。マルチスレッドの使用

NetDiscoveryスイッチRxJava 2のスレッドを達成するための手段が、マルチスレッドシナリオの広範な使用は、依然として存在します。この記事では、共通のフレームワーク爬虫類マルチスレッド使用シナリオの一部を示します。

2.1クローラの一時停止、再開

一時停止と再開の爬虫類は、最も一般的な使用シナリオ、援助たCountDownLatchクラスの実装です。

たCountDownLatchクラスは、1つ以上のスレッドが他のスレッドの実行前に、完全な実行の動作まで待つことができます同期ツールです。

アウトクラスたCountDownLatch pauseCountDownを初期化し、そのカウント値1を設定することができます。

カウントダウンpauseCountDown回収方法は、単にカウントがゼロに到達している、()を実行します。

    /**
     * 爬虫暂停,当前正在抓取的请求会继续抓取完成,之后的请求会等到resume的调用才继续抓取
     */
    public void pause() {
        this.pauseCountDown = new CountDownLatch(1);
        this.pause = true;
        stat.compareAndSet(SPIDER_STATUS_RUNNING, SPIDER_STATUS_PAUSE);
    }

    /**
     * 爬虫重新开始
     */
    public void resume() {

        if (stat.get() == SPIDER_STATUS_PAUSE
                && this.pauseCountDown!=null) {

            this.pauseCountDown.countDown();
            this.pause = false;
            stat.compareAndSet(SPIDER_STATUS_PAUSE, SPIDER_STATUS_RUNNING);
        }
    }
复制代码

クローラからの要求メッセージ・キューを削除する場合、必要に応じて、第一pauseCountDown待つ停止する、クローラの動作を一時停止するか否かを判定する()が実行されます。()されたCountDownLatchのカウントが0になるまで中断される爬虫類の行動によってブロックされたスレッドを起こし待って、この時間はちょうど状態の爬虫類の実行を復元します。

        while (getSpiderStatus() != SPIDER_STATUS_STOPPED) {

            //暂停抓取
            if (pause && pauseCountDown!=null) {
                try {
                    this.pauseCountDown.await();
                } catch (InterruptedException e) {
                    log.error("can't pause : ", e);
                }

                initialDelay();
            }
            // 从消息队列中取出request
           final Request request = queue.poll(name);
           ......
      }
复制代码

以上2.2緯度クロール速度制御

図は、単一のクローラーの流れを反映しています。

basic_principle.png

クロール速度が速すぎ爬虫類は、他のシステムの識別になる場合には、NetDiscoveryは、基本的な抗抗クローラスピードによって達成することができます。

NetDiscovery爬虫類の制限速度を達成するために、内部支持以上緯度。プロセスのラチチュードはまた、実質的に単一のクローラに対応します。

2.2.1リクエスト

まず、要求爬虫類パッケージ要求をサポートポーズ。メッセージキューのリクエストから取り出した後、要求を中断する必要があるかどうかを確認します。

        while (getSpiderStatus() != SPIDER_STATUS_STOPPED) {

            //暂停抓取
            ......

            // 从消息队列中取出request
            final Request request = queue.poll(name);

            if (request == null) {

                waitNewRequest();
            } else {

                if (request.getSleepTime() > 0) {

                    try {
                        Thread.sleep(request.getSleepTime());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                ......
            }
        }
复制代码

2.2.2をダウンロード

爬虫類のダウンロード、ダウンローダは、たぶんRxJavaのオブジェクトを作成するとき。ダウンロード速度制限は変圧器、作曲RxJavaによって達成されます。

次のコードは、DownloaderDelayTransformerを示しています。

import cn.netdiscovery.core.domain.Request;
import io.reactivex.Maybe;
import io.reactivex.MaybeSource;
import io.reactivex.MaybeTransformer;

import java.util.concurrent.TimeUnit;

/**
 * Created by tony on 2019-04-26.
 */
public class DownloaderDelayTransformer implements MaybeTransformer {

    private Request request;

    public DownloaderDelayTransformer(Request request) {
        this.request = request;
    }

    @Override
    public MaybeSource apply(Maybe upstream) {

        return request.getDownloadDelay() > 0 ? upstream.delay(request.getDownloadDelay(), TimeUnit.MILLISECONDS) : upstream;
    }
}
复制代码

ダウンローダー限りの援助コン、DownloaderDelayTransformerとして、あなたは制限速度ダウンロードを達成することができます。

UrlConnectionDownloader例に:

        Maybe.create(new MaybeOnSubscribe<InputStream>() {

                @Override
                public void subscribe(MaybeEmitter<InputStream> emitter) throws Exception {

                    emitter.onSuccess(httpUrlConnection.getInputStream());
                }
            })
             .compose(new DownloaderDelayTransformer(request))
             .map(new Function<InputStream, Response>() {

                @Override
                public Response apply(InputStream inputStream) throws Exception {

                    ......
                    return response;
                }
            });
复制代码

2.2.3ドメイン

ドメイン制限速度の実装Scrapy基準フレーム、各ドメイン名とのConcurrentHashMapに対応し、最後のアクセス時間を節約できます。各要求は、それによって速度に単一ドメインの要求を実現する、domainDelay要求性を提供することができます。

import cn.netdiscovery.core.domain.Request;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by tony on 2019-05-06.
 */
public class Throttle {

    private Map<String,Long> domains = new ConcurrentHashMap<String,Long>();

    private static class Holder {
        private static final Throttle instance = new Throttle();
    }

    private Throttle() {
    }

    public static final Throttle getInsatance() {
        return Throttle.Holder.instance;
    }

    public void wait(Request request) {

        String domain = request.getUrlParser().getHost();
        Long lastAccessed = domains.get(domain);

        if (lastAccessed!=null && lastAccessed>0) {
            long sleepSecs = request.getDomainDelay() - (System.currentTimeMillis() - lastAccessed);
            if (sleepSecs > 0) {
                try {
                    Thread.sleep(sleepSecs);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        domains.put(domain,System.currentTimeMillis());
    }
}
复制代码

要求は、メッセージキューから削除するときは、最初の要求の後に一時停止し、その後、ドメインへのアクセスを中断する必要があるものを決定するかどうかを決定します。

        while (getSpiderStatus() != SPIDER_STATUS_STOPPED) {

            //暂停抓取
            ......

            // 从消息队列中取出request
            final Request request = queue.poll(name);

            if (request == null) {

                waitNewRequest();
            } else {

                if (request.getSleepTime() > 0) {

                    try {
                        Thread.sleep(request.getSleepTime());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                Throttle.getInsatance().wait(request);
 
                ......
            }
        }
复制代码

2.2.4パイプライン

- >応答がページに格納されている - >分析ページ - >逐次実行パイプライン - >要求を完了するための要求ネットワーク要求は(リトライメカニズムを含む)を呼び出す:爬虫類プロセスフローは、実質的に求めるようになっています。

                // request正在处理
                downloader.download(request)
                        .retryWhen(new RetryWithDelay(maxRetries, retryDelayMillis, request)) // 对网络请求的重试机制
                        .map(new Function<Response, Page>() {

                            @Override
                            public Page apply(Response response) throws Exception {
                                // 将 response 存放到 page
                                ......                            
                                return page;
                            }
                        })
                        .map(new Function<Page, Page>() {

                            @Override
                            public Page apply(Page page) throws Exception {

                                if (parser != null) {

                                    parser.process(page);
                                }

                                return page;
                            }
                        })
                        .map(new Function<Page, Page>() {

                            @Override
                            public Page apply(Page page) throws Exception {

                                if (!page.getResultItems().isSkip() && Preconditions.isNotBlank(pipelines)) {

                                    pipelines.stream()
                                            .forEach(pipeline -> {
                                                pipeline.process(page.getResultItems());
                                            });
                                }

                                return page;
                            }
                        })
                        .observeOn(Schedulers.io())
                        .subscribe(new Consumer<Page>() {

                            @Override
                            public void accept(Page page) throws Exception {

                                log.info(page.getUrl());

                                if (request.getAfterRequest() != null) {

                                    request.getAfterRequest().process(page);
                                }

                                signalNewRequest();
                            }
                        }, new Consumer<Throwable>() {
                            @Override
                            public void accept(Throwable throwable) throws Exception {

                                log.error(throwable.getMessage(), throwable);
                            }
                        });
复制代码

遅延ブロックオペレータの実装の本質パイプラインRxJavaの制限速度を持ちます。

map(new Function<Page, Page>() {

        @Override
        public Page apply(Page page) throws Exception {

               if (!page.getResultItems().isSkip() && Preconditions.isNotBlank(pipelines)) {

                   pipelines.stream()
                          .forEach(pipeline -> {

                                if (pipeline.getPipelineDelay()>0) {

                                        // Pipeline Delay
                                        Observable.just("pipeline delay").delay(pipeline.getPipelineDelay(),TimeUnit.MILLISECONDS).blockingFirst();
                                 }

                                pipeline.process(page.getResultItems());
                          });
               }

                return page;
       }
})
复制代码

また、NetDiscoveryクローラーを構成するapplication.yamlまたはapplication.propertiesファイルを設定することもできます。パラメータをレート制限を設定するには、ランダムな値の使用をサポートしながら、もちろん、また、スピードのパラメータをサポートしています。

2.3ノンブロッキング爬虫類の実行

爬虫類の後に実行以前のバージョンでは、新しい要求を追加することはできません。爬虫類は、デフォルト出口プログラム後のコンシューマ・キュー要求を終えたので。

援助条件の新バージョンでは、爬虫類が実行されている場合でも、まだメッセージキューへのリクエストに追加することができます。

アクションロックは条件より正確な制御です。その条件を使用して、オブジェクト待ち(の使用に比べスレッド)との間のコラボレーションを可能通知())(通知)(ウェイトの伝統的なオブジェクトを置換するために使用される(待機)、信号()は、スレッド間でこのようにして達成しましたより安全で効率的なコラボレーション。

ReentrantLockのは、スパイダーに定義し、条件にする必要があります。

その後waitNewRequest()、signalNewRequest()メソッドを定義し、その役割は、新しい爬虫類の要求を待つために、現在のスレッドを保留中の要求の爬虫類メッセージキュースレッドの消費を起こしています。

    private ReentrantLock newRequestLock = new ReentrantLock();
    private Condition newRequestCondition = newRequestLock.newCondition();
  
    ......

    private void waitNewRequest() {
        newRequestLock.lock();

        try {
            newRequestCondition.await(sleepTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.error("waitNewRequest - interrupted, error {}", e);
        } finally {
            newRequestLock.unlock();
        }
    }

    public void signalNewRequest() {
        newRequestLock.lock();

        try {
            newRequestCondition.signalAll();
        } finally {
            newRequestLock.unlock();
        }
    }
复制代码

これは、要求メッセージ・キューから取られていない場合、それはwaitNewRequestを実行する、ことを見ることができます()。

        while (getSpiderStatus() != SPIDER_STATUS_STOPPED) {

            //暂停抓取
            if (pause && pauseCountDown!=null) {
                try {
                    this.pauseCountDown.await();
                } catch (InterruptedException e) {
                    log.error("can't pause : ", e);
                }

                initialDelay();
            }

            // 从消息队列中取出request
            final Request request = queue.poll(name);

            if (request == null) {

                waitNewRequest();
            } else {
                ......
            }
     }
复制代码

次いで、内部要求がキューにそれを押し込むことを除いてデフォルトメソッドpushToRunninSpider()を含むキュー・インターフェース、およびコールspider.signalNewRequestに()。

    /**
     * 把Request请求添加到正在运行爬虫的Queue中,无需阻塞爬虫的运行
     *
     * @param request request
     */
    default void pushToRunninSpider(Request request, Spider spider) {

        push(request);
        spider.signalNewRequest();
    }
复制代码

最後に、クローラが実行されたにもかかわらず、キューのクローラが随時に該当する要求に加えてもよいです。

        Spider spider = Spider.create(new DisruptorQueue())
                .name("tony")
                .url("http://www.163.com");

        CompletableFuture.runAsync(()->{
            spider.run();
        });

        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        spider.getQueue().pushToRunninSpider(new Request("https://www.baidu.com", "tony"),spider);

        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        spider.getQueue().pushToRunninSpider(new Request("https://www.jianshu.com", "tony"),spider);

        System.out.println("end....");
复制代码

概要

爬虫類のフレームワークのgithubの住所:github.com/fengzhizi71 ...

この記事では、特定のシナリオでマルチスレッドを使用するには、どのように一般的なフレームワークの爬虫類をまとめました。将来、NetDiscoveryは、より一般的な機能を増加します。


AndroidとJavaのテクノロジ・スタック:プッシュ毎週更新独自の技術記事、国民はあなたの共通の開発と進歩を楽しみにして、2次元コードの下の数字と懸念をスキャンするために歓迎されています。

おすすめ

転載: juejin.im/post/5ceb423e51882530e807e383