フレームワークグラブの解析:スパイダー

私はpython Grabライブラリの作成者であり、ウェブサイトパーサーを簡単に作成できます。 しばらく前にハブについて彼女に関する紹介記事を書きました 最近、私は密接に構文解析を行うことに決め、フリーランスの構文解析命令の検索を開始し、多数のページを持つサイトを構文解析するツールが必要になりました。



このようなライブラリを使用して、Pythonスレッドを使用してマルチスレッドパーサーを実装していました。 スレッド化アプローチには長所と短所があります。 プラスは、別のスレッド(スレッド)を開始し、その中で必要なことを実行することです。同じコンテキスト内で複数のネットワーク呼び出しを連続して行うことができます-どこでも切り替えて、何かを記憶し、覚える必要はありません 欠点は、スレッドが遅くなり、メモリを消費することです。



代替手段は何ですか?



ネットワークリソースを非同期で操作します。 このデータが準備されるとすぐにすべてのデータ処理ロジックが実行され、データ自体が非同期にロードされるプログラム実行フローは1つだけです。 実際には、これにより、数百のスレッドのネットワークで大きな負担をかけずに作業できます。非常に多くのスレッドを実行しようとすると、スレッドの速度が大幅に低下します。



そこで、multicurlへのインターフェイスを作成しました。これはpycurlライブラリの一部であり、ネットワークを非同期で操作できます。 Grabがpycurlを使用しているのでmulticurlを選択しましたが、これを使用してmulticurlでも動作できると考えました。 そしてそれが起こった。 Grab:Spiderに基づくパーサーのアーキテクチャは、実験の最初の日に動作したことに多少驚いていました。一般に、スクレイピーフレームワークに基づくパーサーは非常に似ており、一般的には驚くべきことでも論理的でもありません。



単純なクモの例を挙げましょう。



# coding: utf-8 from grab.spider import Spider, Task class SimpleSpider(Spider): initial_urls = ['http://ya.ru'] def task_initial(self, grab, task): grab.set_input('text', u'') grab.submit(make_request=False) yield Task('search', grab=grab) def task_search(self, grab, task): for elem in grab.xpath_list('//h2/a'): print elem.text_content() if __name__ == '__main__': bot = SimpleSpider() bot.run() print bot.render_stats()
      
      







ここで何が起こっていますか? `self.initial_urls`の各URLに対して、頭文字の名前でタスクが作成され、multicurlがドキュメントをダウンロードした後、` task_initial`というハンドラーが呼び出されます。 最も重要なことは、ハンドラー内で、要求されたドキュメントに関連付けられたGrabオブジェクトを取得することです。GrabAPIのほとんどすべての関数を使用できます。 この例では、フォームで彼の作品を使用します。 このネットワークリクエストを非同期で処理するため、フォームがすぐに送信されないように、パラメーター「make_request = False」を指定する必要があることに注意してください。



要するに、Grab:Spiderの操作は、タスクオブジェクトを使用してクエリを生成し、特別なメソッドでさらに処理することになります。 各ジョブには名前があり、このメソッドによって、要求されたネットワークドキュメントの処理方法が選択されます。



Taskオブジェクトを作成するには2つの方法があります。 簡単な方法:

 Task('foo', url='http://google.com')
      
      







ドキュメントがネットワークから完全にダウンロードされた後、「task_foo」というメソッドが呼び出されます



より複雑な方法:

 g = Grab() g.setup(....   ...) Task('foo', grab=g)
      
      







このようにして、必要に応じてリクエストパラメータを設定し、Cookie、特別なヘッダーを設定し、POSTリクエストを生成できます。



クエリはどこで作成できますか? どのハンドラーメソッドでも、yield Taskオブジェクトを作成でき、非同期ダウンロードキューに追加されます。 returnを介してTaskオブジェクトを返すこともできます。 さらに、Taskオブジェクトを生成する方法が2つあります。



1) `self.initial_urls`属性でアドレスのリストを指定でき、それらのタスクは 'initial'という名前で作成されます。



2)「task_generator」メソッドを定義し、その中に好きなだけリクエストを生成できます。 さらに、古いリクエストが実行されると、新しいリクエストが取得されます。 これにより、たとえば、ファイルファイルから100万行以上を問題なく繰り返すことができ、すべてのメモリでポイ捨てすることはできません。



最初は、抽出されたデータの処理をスクレイピーで行う予定でした。 そこで、Pipelineオブジェクトを使用して行われます。 たとえば、映画のあるページを受け取って解析し、MovieタイプのPipelineオブジェクトを返します。 そして、以前に、Movie PipelineをデータベースまたはCSVファイルに保存するように設定で書きました。 そのようなもの。 実際には、追加のラッパーを使用せずに、リクエストハンドラメソッドでデータベースまたはファイルにすぐにデータを書き込む方が簡単であることがわかりました。 もちろん、これはマシンのクラウド全体でメソッドを並列化する場合には機能しませんが、この時点までは生き残る必要がありますが、今のところはメソッドハンドラーで直接すべてを行う方が便利です。



タスクオブジェクトには追加の引数を渡すことができます。 たとえば、Google検索でリクエストを行います。 目的のURLを形成し、Taskオブジェクトを作成します:Task( 'search'、url = '...'、query = query)次に、task_searchメソッドで、task.query属性にアクセスして、探していたクエリを正確に見つけることができます



グラブ:スパイダーは自動的にネットワークエラーを修正しようとします。 ネットワークタイムアウトの場合、タスクを再度実行します。 Spiderオブジェクトを作成するときに `network_try_limit`オプションを使用して設定できる試行回数。



パーサーを非同期スタイルで書くことが本当に好きだったと言わなければなりません。 そして、ポイントは、非同期アプローチがシステムリソースの負荷を軽減するだけでなく、パーサーのソースコードが明確で理解可能な構造を獲得することでもあります。



残念ながら、Spiderモジュールの動作を完全に説明するには、多くの時間がかかります。 私は、グラブライブラリのユーザーの軍隊に、数人の人がいることを、ただ、文書不足の陰に隠れている可能性の1つについて伝えたかっただけです。



まとめ グラブを使用する場合は、スパイダーモジュールをご覧ください。 Grabが何であるかわからない場合は、おそらくスクレイプフレームワークを確認する必要があります; Grabの100倍も美しいドキュメントです。



PS私は解析結果を保存するためにmongodbを使用しています-それはただ素晴らしいです:) 64ビットシステムを置くことを忘れないでください、さもなければ、2ギガバイト以上のデータベースを作成することはできません。



PS dumpz.org/119395サイトを解析するための実際のパーサーの例



PS grablib.orgプロジェクトの公式サイト(リポジトリ、google-group、およびドキュメントへのリンクがあります)



PSグラブに基づいてカスタムパーサーを書いています。詳細はこちらgrablab.org



All Articles