web-dev-qa-db-ja.com

クローラーの書き方

私は、NPOのWebサイトとコンテンツの調査結果のリストをクロールして作成する単純なクローラーを作成しようと考えていました。

これを行う方法について誰か考えがありますか?クローラーをどこに向けて開始しますか?調査結果をどのように送り返し、それでもクロールを続けますか?見つけたものなどをどうやって知るのかなど。

61
Jason

確かに、あなたは車輪を再発明するでしょう。しかし、基本は次のとおりです。

  • 未訪問のURLのリスト-これに1つ以上の開始ページをシードします
  • 訪問したURLのリスト-サークル内を移動しない
  • 興味のないURLのルールセット-インターネット全体をインデックスに登録しない

これらを永続ストレージに配置すると、状態を失うことなくクローラーを停止および開始できます。

アルゴリズムは次のとおりです。

while(list of unvisited URLs is not empty) {
    take URL from list
    remove it from the unvisited list and add it to the visited list
    fetch content
    record whatever it is you want to about the content
    if content is HTML {
        parse out URLs from links
        foreach URL {
           if it matches your rules
              and it's not already in either the visited or unvisited list
              add it to the unvisited list
        }
    }
}
146
slim

クローラーの複雑な部分は、膨大な数のWebサイト/リクエストに合わせてスケーリングする場合です。この状況では、次のような問題に対処する必要があります。

  • 情報をすべて1つのデータベースに保存することは不可能です。

  • 十分なRAM巨大なインデックスを処理するには

  • マルチスレッドのパフォーマンスと同時実行性

  • クローラートラップ(URL、カレンダー、セッションIDを変更することで作成される無限ループ)およびコンテンツの複製。

  • 複数のコンピューターからクロールする

  • 不正なHTMLコード

  • サーバーからの一定のHTTPエラー

  • 圧縮なしのデータベースでは、約8倍のスペースが必要になります。

  • 再クロールルーチンと優先順位。

  • 圧縮(Deflate/gzip)で要求を使用します(あらゆる種類のクローラーに適しています)。

およびいくつかの重要なこと

  • Robots.txtを尊重する

  • また、Webサーバーを窒息させないための各リクエストのクローラー遅延。

29
lexmooze

マルチスレッドWebクローラー

大規模なWebサイトをクロールする場合は、マルチスレッドクローラーを作成する必要があります。ファイル/データベースでのクロールされた情報の接続、取得、書き込み-これらはクロールの3つのステップですが、CPUよりもシングルスレッドを使用すると、ネットワークの使用率が高くなります。

マルチスレッドWebクローラーには、linksVisited(これはハッシュマップまたはtraiとして実装する必要があります)とlinksToBeVisited(これはキューです)の2つのデータ構造が必要です。

WebクローラーはBFSを使用してWorld Wide Webを横断します。

基本的なWebクローラーのアルゴリズム:-

  1. 1つ以上のシードURLをlinksToBeVisitedに追加します。 linksToBeVisitedにURLを追加するメソッドは同期する必要があります。
  2. LinksToBeVisitedから要素をポップし、これをlinksVisitedに追加します。 linksToBeVisitedからURLをポップするこのpopメソッドは同期する必要があります。
  3. インターネットからページを取得します。
  4. ファイルを解析し、ページにある今まで訪れていないリンクをlinksToBeVisitedに追加します。必要に応じて、URLをフィルタリングできます。ユーザーは、スキャンするURLをフィルタリングするための一連のルールを指定できます。
  5. ページで見つかった必要な情報は、データベースまたはファイルに保存されます。
  6. キューがlinksToBeVisitedが空になるまで、手順2〜5を繰り返します。

    スレッドを同期する方法のコードスニペットを次に示します。

     public void add(String site) {
       synchronized (this) {
       if (!linksVisited.contains(site)) {
         linksToBeVisited.add(site);
         }
       }
     }
    
     public String next() {
        if (linksToBeVisited.size() == 0) {
        return null;
        }
           synchronized (this) {
            // Need to check again if size has changed
           if (linksToBeVisited.size() > 0) {
              String s = linksToBeVisited.get(0);
              linksToBeVisited.remove(0);
              linksVisited.add(s);
              return s;
           }
         return null;
         }
      }
    
8
alienCoder

クローラーの概念は単純です。

HTTP GETを介してルートページを取得し、それを解析してURLを検索し、既に解析されていない限りそれらをキューに入れます(したがって、解析済みのページのグローバルレコードが必要です)。

Content-typeヘッダーを使用して、コンテンツのタイプを確認し、クローラーをHTMLタイプの解析のみに制限できます。

HTMLタグを削除してプレーンテキストを取得し、テキスト分析を行うことができます(タグなど、ページの内容を取得するため)。高度なものであれば、画像のalt/titleタグでそれを行うこともできます。

また、バックグラウンドで、キューのURLを使用して同じことを行うスレッドのプールを持つことができます。もちろん、スレッドの数を制限する必要があります。

5
JeeBee

NPOのサイトが比較的大きいか複雑な場合(「翌日」リンクのあるカレンダーのような「ブラックホール」を効果的に作成する動的ページがある)、 のような実際のWebクローラーを使用した方が良いでしょうヘリトリックス

サイトのページ数が合計数であれば、curl、wget、または独自のページを使用するだけで済みます。それらが大きくなり始めた場合、または実際のクローラーを使用するためにスクリプトをより複雑にし始めた場合、または少なくともそのソースを見て、何をしているのか、そしてその理由を確認してください。

いくつかの問題(さらにあります):

  • ブラックホール(説明のとおり)
  • 再試行(500を取得した場合はどうなりますか?)
  • リダイレクト
  • フロー制御(サイトに負担がかかる場合があります)
  • robots.txtの実装
5
Vinko Vrsalovic

ウィキペディアには web crawlers に関する良い記事があり、多くのアルゴリズムと考慮事項をカバーしています。

ただし、自分でクローラーを作成することはありません。それは多くの作業であり、「単純なクローラー」だけが必要なので、本当に必要なのは 既製のクローラー だけだと思います。無料でオープンソースのクローラーがたくさんあり、あなたが必要とするすべての作業を行うことができます。

4
Derek Park

単語のリストを作成し、googleで検索された各Wordのスレッドを作成できます。
次に、各スレッドは、ページ内で見つかったリンクごとに新しいスレッドを作成します。
各スレッドは、データベースで検出した内容を書き込む必要があります。各スレッドがページの読み取りを完了すると、終了します。
そしてそこには、データベースに非常に大きなリンクのデータベースがあります。

2
Gero

私は社内の検索にOpen Search Serverを使用しています。これを試してみてください。 http://open-search-server.com そのオープンソース。

1
Sathishkumar

Wgetを使用し、再帰的なWebサックを実行します。これにより、すべてのファイルがハードドライブにダンプされ、ダウンロードしたすべてのファイルを調べて分析する別のスクリプトを記述します。

編集:または多分wgetの代わりにcurl、しかし私はcurlに精通していません、wgetのような再帰的なダウンロードを行うかどうかはわかりません。

0
whatsisname

.netで事後対応型の拡張機能を使用して簡単なWebクローラーを実行しました。

https://github.com/Misterhex/WebCrawler

public class Crawler
    {
    class ReceivingCrawledUri : ObservableBase<Uri>
    {
        public int _numberOfLinksLeft = 0;

        private ReplaySubject<Uri> _subject = new ReplaySubject<Uri>();
        private Uri _rootUri;
        private IEnumerable<IUriFilter> _filters;

        public ReceivingCrawledUri(Uri uri)
            : this(uri, Enumerable.Empty<IUriFilter>().ToArray())
        { }

        public ReceivingCrawledUri(Uri uri, params IUriFilter[] filters)
        {
            _filters = filters;

            CrawlAsync(uri).Start();
        }

        protected override IDisposable SubscribeCore(IObserver<Uri> observer)
        {
            return _subject.Subscribe(observer);
        }

        private async Task CrawlAsync(Uri uri)
        {
            using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromMinutes(1) })
            {
                IEnumerable<Uri> result = new List<Uri>();

                try
                {
                    string html = await client.GetStringAsync(uri);
                    result = CQ.Create(html)["a"].Select(i => i.Attributes["href"]).SafeSelect(i => new Uri(i));
                    result = Filter(result, _filters.ToArray());

                    result.ToList().ForEach(async i =>
                    {
                        Interlocked.Increment(ref _numberOfLinksLeft);
                        _subject.OnNext(i);
                        await CrawlAsync(i);
                    });
                }
                catch
                { }

                if (Interlocked.Decrement(ref _numberOfLinksLeft) == 0)
                    _subject.OnCompleted();
            }
        }

        private static List<Uri> Filter(IEnumerable<Uri> uris, params IUriFilter[] filters)
        {
            var filtered = uris.ToList();
            foreach (var filter in filters.ToList())
            {
                filtered = filter.Filter(filtered);
            }
            return filtered;
        }
    }

    public IObservable<Uri> Crawl(Uri uri)
    {
        return new ReceivingCrawledUri(uri, new ExcludeRootUriFilter(uri), new ExternalUriFilter(uri), new AlreadyVisitedUriFilter());
    }

    public IObservable<Uri> Crawl(Uri uri, params IUriFilter[] filters)
    {
        return new ReceivingCrawledUri(uri, filters);
    }
}

次のように使用できます。

Crawler crawler = new Crawler();
IObservable observable = crawler.Crawl(new Uri("http://www.codinghorror.com/"));
observable.Subscribe(onNext: Console.WriteLine, 
onCompleted: () => Console.WriteLine("Crawling completed"));
0
Misterhex