私は、NPOのWebサイトとコンテンツの調査結果のリストをクロールして作成する単純なクローラーを作成しようと考えていました。
これを行う方法について誰か考えがありますか?クローラーをどこに向けて開始しますか?調査結果をどのように送り返し、それでもクロールを続けますか?見つけたものなどをどうやって知るのかなど。
確かに、あなたは車輪を再発明するでしょう。しかし、基本は次のとおりです。
これらを永続ストレージに配置すると、状態を失うことなくクローラーを停止および開始できます。
アルゴリズムは次のとおりです。
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
}
}
}
クローラーの複雑な部分は、膨大な数のWebサイト/リクエストに合わせてスケーリングする場合です。この状況では、次のような問題に対処する必要があります。
情報をすべて1つのデータベースに保存することは不可能です。
十分なRAM巨大なインデックスを処理するには
マルチスレッドのパフォーマンスと同時実行性
クローラートラップ(URL、カレンダー、セッションIDを変更することで作成される無限ループ)およびコンテンツの複製。
複数のコンピューターからクロールする
不正なHTMLコード
サーバーからの一定のHTTPエラー
圧縮なしのデータベースでは、約8倍のスペースが必要になります。
再クロールルーチンと優先順位。
圧縮(Deflate/gzip)で要求を使用します(あらゆる種類のクローラーに適しています)。
およびいくつかの重要なこと
Robots.txtを尊重する
また、Webサーバーを窒息させないための各リクエストのクローラー遅延。
マルチスレッドWebクローラー
大規模なWebサイトをクロールする場合は、マルチスレッドクローラーを作成する必要があります。ファイル/データベースでのクロールされた情報の接続、取得、書き込み-これらはクロールの3つのステップですが、CPUよりもシングルスレッドを使用すると、ネットワークの使用率が高くなります。
マルチスレッドWebクローラーには、linksVisited(これはハッシュマップまたはtraiとして実装する必要があります)とlinksToBeVisited(これはキューです)の2つのデータ構造が必要です。
WebクローラーはBFSを使用してWorld Wide Webを横断します。
基本的なWebクローラーのアルゴリズム:-
キューが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;
}
}
クローラーの概念は単純です。
HTTP GETを介してルートページを取得し、それを解析してURLを検索し、既に解析されていない限りそれらをキューに入れます(したがって、解析済みのページのグローバルレコードが必要です)。
Content-typeヘッダーを使用して、コンテンツのタイプを確認し、クローラーをHTMLタイプの解析のみに制限できます。
HTMLタグを削除してプレーンテキストを取得し、テキスト分析を行うことができます(タグなど、ページの内容を取得するため)。高度なものであれば、画像のalt/titleタグでそれを行うこともできます。
また、バックグラウンドで、キューのURLを使用して同じことを行うスレッドのプールを持つことができます。もちろん、スレッドの数を制限する必要があります。
NPOのサイトが比較的大きいか複雑な場合(「翌日」リンクのあるカレンダーのような「ブラックホール」を効果的に作成する動的ページがある)、 のような実際のWebクローラーを使用した方が良いでしょうヘリトリックス
サイトのページ数が合計数であれば、curl、wget、または独自のページを使用するだけで済みます。それらが大きくなり始めた場合、または実際のクローラーを使用するためにスクリプトをより複雑にし始めた場合、または少なくともそのソースを見て、何をしているのか、そしてその理由を確認してください。
いくつかの問題(さらにあります):
ウィキペディアには web crawlers に関する良い記事があり、多くのアルゴリズムと考慮事項をカバーしています。
ただし、自分でクローラーを作成することはありません。それは多くの作業であり、「単純なクローラー」だけが必要なので、本当に必要なのは 既製のクローラー だけだと思います。無料でオープンソースのクローラーがたくさんあり、あなたが必要とするすべての作業を行うことができます。
単語のリストを作成し、googleで検索された各Wordのスレッドを作成できます。
次に、各スレッドは、ページ内で見つかったリンクごとに新しいスレッドを作成します。
各スレッドは、データベースで検出した内容を書き込む必要があります。各スレッドがページの読み取りを完了すると、終了します。
そしてそこには、データベースに非常に大きなリンクのデータベースがあります。
私は社内の検索にOpen Search Serverを使用しています。これを試してみてください。 http://open-search-server.com そのオープンソース。
Wgetを使用し、再帰的なWebサックを実行します。これにより、すべてのファイルがハードドライブにダンプされ、ダウンロードしたすべてのファイルを調べて分析する別のスクリプトを記述します。
編集:または多分wgetの代わりにcurl、しかし私はcurlに精通していません、wgetのような再帰的なダウンロードを行うかどうかはわかりません。
.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"));