前書き

多くの場合、WebクロールまたはWebスパイダーと呼ばれるWebスクレイピング、または「プログラムでWebページのコレクションを調べてデータを抽出する」は、Web上のデータを操作するための強力なツールです。

Webスクレイパーを使用すると、一連の製品に関するデータをマイニングしたり、大量のテキストや定量データを再生したり、公式APIを使用せずにサイトからデータを取得したり、個人的な好奇心を満たすことができます。

このチュートリアルでは、遊び心のあるデータセットを探索しながら、スクレイピングおよびスパイダープロセスの基本について学習します。 http://brickset.com [BrickSet]を使用します。これは、レゴセットに関する情報を含むコミュニティ運営のサイトです。 このチュートリアルの終わりまでに、Bricksetの一連のページをウォークスルーし、各ページからLEGOセットに関するデータを抽出し、画面にデータを表示する、完全に機能するPython Webスクレイパーができます。

スクレーパーは簡単に拡張できるので、いじくり回して、Webからデータをスクレイピングする独自のプロジェクトの基盤として使用できます。

前提条件

このチュートリアルを完了するには、Python 3のローカル開発環境が必要です。 https://www.digitalocean.com/community/tutorial_series/how-to-install-and-set-up-a-local-programming-environment-for-python-3 [インストールおよびセットアップ方法Python 3のローカルプログラミング環境]を使用して、必要なすべてを設定します。

ステップ1-基本的なスクレーパーの作成

スクレイピングは2段階のプロセスです。

  1. Webページを体系的に見つけてダウンロードします。

  2. それらのWebページを取得し、そこから情報を抽出します。

これらの手順は両方とも、多くの言語でさまざまな方法で実装できます。

modulesまたはプログラミング言語が提供するライブラリを使用してスクレイパーをゼロから構築できますが、スクレーパーの複雑化に伴う潜在的な頭痛に対処するため。 たとえば、一度に複数のページをクロールできるように、並行性を処理する必要があります。 おそらく、スクレイピングしたデータをCSV、XML、JSONなどのさまざまな形式に変換する方法を理解する必要があります。 また、特定の設定とアクセスパターンを必要とするサイトに対処しなければならない場合があります。

これらの問題を処理する既存のライブラリの上にスクレーパーを構築すると、幸運が得られます。 このチュートリアルでは、Pythonとhttp://doc.scrapy.org/en/1.1/intro/overview.html[Scrapy]を使用してスクレーパーを構築します。

Scrapyは、最も人気があり強力なPythonスクレイピングライブラリの1つです。スクレイピングには「バッテリーを含む」アプローチが必要です。つまり、すべてのスクレイパーが必要とする多くの共通機能を処理するため、開発者は毎回車輪を作り直す必要がありません。 スクレイピングが迅速で楽しいプロセスになります!

Scrapyは、ほとんどのPythonパッケージと同様、PyPI(「+ pip +」としても知られています)上にあります。 PythonパッケージインデックスであるPyPIは、公開されているすべてのPythonソフトウェアのコミュニティ所有のリポジ​​トリです。

このチュートリアルの前提条件で説明されているようなPythonインストールがある場合は、マシンにすでに `+ pip +`がインストールされているため、次のコマンドでScrapyをインストールできます。

pip install scrapy

インストールで問題が発生した場合、または「+ pip +」を使用せずにScrapyをインストールする場合は、https://doc.scrapy.org/en/1.1/intro/install.html [公式インストールドキュメント]をご覧ください。 。

Scrapyをインストールしたら、プロジェクト用の新しいフォルダーを作成しましょう。 ターミナルでこれを実行するには、次を実行します。

mkdir brickset-scraper

次に、作成した新しいディレクトリに移動します。

cd brickset-scraper

次に、 `+ scraper.py `という名前のスクレイパー用の新しいPythonファイルを作成します。 このチュートリアルでは、すべてのコードをこのファイルに配置します。 次のように、ターミナルで ` touch +`コマンドを使用してこのファイルを作成できます。

touch

または、テキストエディタまたはグラフィカルファイルマネージャを使用してファイルを作成できます。

まず、Scrapyを基盤とする非常に基本的なスクレーパーを作成します。 それを行うために、 `+ scrapy.Spider + `、Scrapyが提供する基本的なスパイダークラス。 このクラスには2つの必須属性があります。

  • + name +-スパイダーの名前。

  • + start_urls +-クロールを開始するURLのhttps://www.digitalocean.com/community/tutorials/understanding-lists-in-python-3[list]。 1つのURLから始めます。

テキストエディターで `+ scrapy.py +`ファイルを開き、次のコードを追加して基本的なスパイダーを作成します。

scraper.py

import scrapy


class BrickSetSpider(scrapy.Spider):
   name = "brickset_spider"
   start_urls = ['']

これを行ごとに分けましょう。

まず、https://www.digitalocean.com/community/tutorials/how-to-import-modules-in-python-3 [import] `+ scrapy +`を使用して、パッケージが提供するクラスを使用できるようにします。

次に、Scrapyが提供する `+ Spider `クラスを取得し、そのクラスから ` BrickSetSpider `という_subclass_を作成します。 サブクラスは、親クラスのより特殊な形式と考えてください。 ` Spider +`サブクラスには、URLをたどり、見つかったページからデータを抽出する方法を定義するメソッドと動作がありますが、検索する場所や検索するデータがわかりません。 サブクラス化することで、その情報を提供できます。

次に、スパイダーに「+ brickset_spider +」という名前を付けます。

最後に、スクレイパーから開始する単一のURLを提供します:http://brickset.com/sets/year-2016。 ブラウザでそのURLを開くと、検索結果ページが表示され、LEGOセットを含む多くのページの最初のページが表示されます。

それでは、スクレーパーをテストしてみましょう。 通常、Pythonファイルを実行するには、 `+ python path / to / file.py +`のようなコマンドを実行します。 ただし、Scrapyにはhttps://doc.scrapy.org/en/latest/topics/commands.html [独自のコマンドラインインターフェイス]が付属しており、スクレーパーの起動プロセスを合理化します。 次のコマンドでスクレーパーを起動します。

scrapy runspider

次のようなものが表示されます。

Output2016-09-22 23:37:45 [scrapy] INFO: Scrapy 1.1.2 started (bot: scrapybot)
2016-09-22 23:37:45 [scrapy] INFO: Overridden settings: {}
2016-09-22 23:37:45 [scrapy] INFO: Enabled extensions:
['scrapy.extensions.logstats.LogStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.corestats.CoreStats']
2016-09-22 23:37:45 [scrapy] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
...
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2016-09-22 23:37:45 [scrapy] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
...
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2016-09-22 23:37:45 [scrapy] INFO: Enabled item pipelines:
[]
2016-09-22 23:37:45 [scrapy] INFO: Spider opened
2016-09-22 23:37:45 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-09-22 23:37:45 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-09-22 23:37:47 [scrapy] DEBUG: Crawled (200) <GET http://brickset.com/sets/year-2016> (referer: None)
2016-09-22 23:37:47 [scrapy] INFO: Closing spider (finished)
2016-09-22 23:37:47 [scrapy] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 224,
'downloader/request_count': 1,
...
'scheduler/enqueued/memory': 1,
'start_time': datetime.datetime(2016, 9, 23, 6, 37, 45, 995167)}
2016-09-22 23:37:47 [scrapy] INFO: Spider closed (finished)

それは大量の出力なので、それを分解しましょう。

  • スクレーパーは、URLからのデータの読み取りを処理するために必要な追加のコンポーネントと拡張機能を初期化し、ロードしました。

  • `+ start_urls +`リストで提供したURLを使用し、Webブラウザーと同じようにHTMLを取得しました。

  • そのHTMLを `+ parse `メソッドに渡しましたが、デフォルトでは何もしません。 独自の ` parse +`メソッドを作成したことがないため、スパイダーは何もせずに終了します。

次に、ページからデータを取得します。

ステップ2-ページからデータを抽出する

ページをプルダウンする非常に基本的なプログラムを作成しましたが、まだスクレイピングやスパイダーは行いません。 抽出するデータを与えましょう。

http://brickset.com/sets/year-2016 [スクレイピングするページ]を見ると、次のような構造になっていることがわかります。

  • すべてのページにヘッダーがあります。

  • 一致の数、検索対象、サイトのパンくずなど、トップレベルの検索データがいくつかあります。

  • 次に、セット自体があり、テーブルまたは順序付きリストのように表示されます。 各セットには同様の形式があります。

スクレーパーを作成するときは、HTMLファイルのソースを見て、構造に慣れることをお勧めします。 そのため、読みやすくするためにいくつかのものを削除しました。

brickset.com/sets/year-2016<body>
 <section class="setlist">

     <a href="https://images.brickset.com/sets/large/10251-1.jpg?201510121127"
     class="highslide plain mainimg" onclick="return hs.expand(this)"><img
     src="https://images.brickset.com/sets/small/10251-1.jpg?201510121127" title="10251-1:
     Brick Bank" onError="this.src='/assets/images/spacer.png'" /></a>
     <div class="highslide-caption">
       <h1>Brick Bank</h1><div class='tags floatleft'><a href='/sets/10251-1/Brick-
       Bank'>10251-1</a> <a href='/sets/theme-Creator-Expert'>Creator Expert</a> <a
       class='subtheme' href='/sets/theme-Creator-Expert/subtheme-Modular-
       Buildings'>Modular Buildings</a> <a class='year' href='/sets/theme-Creator-
       Expert/year-2016'>2016</a> </div><div class='floatright'>&copy;2016 LEGO
       Group</div>
         <div class="pn">
           <a href="#" onclick="return hs.previous(this)" title="Previous (left arrow
           key)">&#171; Previous</a>
           <a href="#" onclick="return hs.next(this)" title="Next (right arrow key)">Next
           &#187;</a>
         </div>
     </div>

...


 </section>
</body>

このページのスクレイピングは、2段階のプロセスです。

  1. まず、必要なデータがあるページの部分を探して、各LEGOセットを取得します。

  2. 次に、各セットについて、HTMLタグからデータを引き出して、必要なデータを取得します。

`+ scrapy `は、ユーザーが提供する_selectors_に基づいてデータを取得します。 セレクターは、ページ上の1つ以上の要素を検索するために使用できるパターンであるため、要素内のデータを操作できます。 ` scrapy +`はCSSセレクターまたはhttps://en.wikipedia.org/wiki/XPath[XPath]セレクターのいずれかをサポートします。

CSSは簡単なオプションであり、ページ上のすべてのセットを見つけるのに最適なので、ここではCSSセレクターを使用します。 ページのHTMLを見ると、各セットがクラス `+ set `で指定されていることがわかります。 クラスを探しているので、CSSセレクターには ` .set `を使用します。 次のように、セレクターを ` response`オブジェクトに渡すだけです。

scraper.py

class BrickSetSpider(scrapy.Spider):
   name = "brickset_spider"
   start_urls = ['http://brickset.com/sets/year-2016']

   def parse(self, response):

このコードは、ページ上のすべてのセットを取得し、それらをループしてデータを抽出します。 次に、それらのセットからデータを抽出して、表示できるようにします。

解析しているページのhttps://brickset.com/sets/year-2016[source]をもう一度見ると、各セットの名前が各セットの `+ h1 +`タグ内に保存されていることがわかります。

brickset.com/sets/year-2016<h1>Brick Bank</h1><div class='tags floatleft'><a href='/sets/10251-1/Brick-Bank'>10251-1</a>

ループしている `+ brickset `オブジェクトには独自の ` css +`メソッドがあるため、セレクターを渡して子要素を見つけることができます。 セットの名前を見つけて表示するには、次のようにコードを変更します。

scraper.py

class BrickSetSpider(scrapy.Spider):
   name = "brickset_spider"
   start_urls = ['http://brickset.com/sets/year-2016']

   def parse(self, response):
       SET_SELECTOR = '.set'
       for brickset in response.css(SET_SELECTOR):

このコードでは次の2つのことが行われています。

  • 名前のセレクタに `+ :: text`を追加します。 これは、タグ自体ではなく、 `+ a +`タグのテキスト_inside_を取得するCSS _pseudo-selector_です。

  • セレクタに一致する最初の要素が必要なため、 `+ brickset.css(NAME_SELECTOR)`によって返されるオブジェクトに対して ` extract_first()+`を呼び出します。 これにより、要素のリストではなくhttps://www.digitalocean.com/community/tutorial_series/working-with-strings-in-python-3[string]が得られます。

ファイルを保存し、スクレーパーを再度実行します。

scrapy runspider

今回は、出力にセットの名前が表示されます:

Output...
[scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

[scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

[scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

[scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

...

画像、ピース、ミニチュアフィギュア、またはセットに付属する_minifigs_の新しいセレクターを追加して、これを拡張していきましょう。

特定のセットのHTMLをもう一度見てみましょう。

brickset.com/sets/year-2016<article class="set">
 <a class="highslide plain mainimg" href="http://images.brickset.com/sets/images/10251-1.jpg?201510121127" onclick="return hs.expand(this)">
   ></a>
 ...
 <div class="meta">
   <h1><a href="/sets/10251-1/Brick-Bank"><span>10251:</span> Brick Bank</a> </h1>
   ...
   <div class="col">
     <dl>




       ...
     </dl>
   </div>
   ...
 </div>
</article>

このコードを調べると、いくつかのことがわかります。

  • セットの画像は、セットの先頭にある「+ a 」タグ内の「 img 」タグの「 src +」属性に保存されます。 各セットの名前を取得したときと同じように、別のCSSセレクターを使用してこの値を取得できます。

  • ピースの数を取得するのは少し難しいです。 テキスト「+ Pieces 」を含む「 dt 」タグがあり、その後に実際のピース数を含む「 dd +」タグがあります。 これを取得するには、CSSセレクターを使用して表現するには複雑すぎるため、XMLを走査するためのクエリ言語であるhttps://en.wikipedia.org/wiki/XPath[XPath]を使用します。

  • セット内のミニフィグの数を取得することは、ピースの数を取得することに似ています。 テキスト「+ Minifigs 」を含む「 dt 」タグがあり、その直後に数字が付いた「 dd +」タグが続きます。

したがって、スクレーパーを変更して、この新しい情報を取得しましょう。

scraper.py

class BrickSetSpider(scrapy.Spider):
   name = 'brick_spider'
   start_urls = ['http://brickset.com/sets/year-2016']

   def parse(self, response):
       SET_SELECTOR = '.set'
       for brickset in response.css(SET_SELECTOR):

           NAME_SELECTOR = 'h1 ::text'



           yield {
               'name': brickset.css(NAME_SELECTOR).extract_first(),



           }

変更を保存して、スクレーパーを再度実行します。

scrapy runspider

これで、プログラムの出力に新しいデータが表示されます。

Output2016-09-22 23:52:37 [scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

2016-09-22 23:52:37 [scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

2016-09-22 23:52:37 [scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

2016-09-22 23:52:37 [scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

2016-09-22 23:52:37 [scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

2016-09-22 23:52:37 [scrapy] DEBUG: Scraped from <200 http://brickset.com/sets/year-2016>

次に、このスクレーパーをリンクをたどるクモに変えましょう。

ステップ3-複数のページのクロール

その最初のページからデータを正常に抽出しましたが、残りの結果を確認するためにそれを過ぎて進んでいません。 スパイダーの目的は、他のページへのリンクを検出してトラバースし、それらのページからデータを取得することです。

各ページの上部と下部には、結果の次のページにリンクする少し右のカラット( +> +)があります。 そのためのHTMLは次のとおりです。

brickset.com/sets/year-2016<ul class="pagelength">

 ...




 <li class="last">
   <a href="http://brickset.com/sets/year-2016/page-32">&#187;</a>
 </li>
</ul>

ご覧のとおり、 `+ next `のクラスを持つ ` li `タグがあり、そのタグ内には、次のページへのリンクがある ` a +`タグがあります。 私たちがしなければならないのは、スクレーパーにそのリンクが存在する場合はそれに従うように指示することです。

コードを次のように変更します。

scraper.py

class BrickSetSpider(scrapy.Spider):
   name = 'brick_spider'
   start_urls = ['http://brickset.com/sets/year-2016']

   def parse(self, response):
       SET_SELECTOR = '.set'
       for brickset in response.css(SET_SELECTOR):

           NAME_SELECTOR = 'h1 ::text'
           PIECES_SELECTOR = './/dl[dt/text() = "Pieces"]/dd/a/text()'
           MINIFIGS_SELECTOR = './/dl[dt/text() = "Minifigs"]/dd[2]/a/text()'
           IMAGE_SELECTOR = 'img ::attr(src)'
           yield {
               'name': brickset.css(NAME_SELECTOR).extract_first(),
               'pieces': brickset.xpath(PIECES_SELECTOR).extract_first(),
               'minifigs': brickset.xpath(MINIFIGS_SELECTOR).extract_first(),
               'image': brickset.css(IMAGE_SELECTOR).extract_first(),
           }

まず、「次のページ」リンクのセレクターを定義し、最初の一致を抽出して、存在するかどうかを確認します。 `+ scrapy.Request `は「このページをクロールします」と返される値であり、「 callback = self.parse +」は「このページからHTMLを取得したら、このメソッドに渡します」と言います。解析してデータを抽出し、次のページを見つけることができます。」

つまり、次のページに移動したら、次のページへのリンクを探し、そのページで次のページへのリンクを探します。次のページへのリンク。 これは、Webスクレイピングの重要な部分です。リンクを見つけてフォローします。 この例では、非常に直線的です。最後のページに到達するまで、あるページには次のページへのリンクがありますが、タグへのリンク、その他の検索結果、またはその他の任意のURLをたどることができます。

ここで、コードを保存してスパイダーを再度実行すると、セットの最初のページを反復処理するだけで停止しないことがわかります。 23ページで779試合すべてを続けています! 物事の壮大なスキームでは、それは巨大なデータの塊ではありませんが、スクレイプする新しいページを自動的に見つけるプロセスを知っています。

Python固有の強調表示を使用した、このチュートリアルの完成したコードは次のとおりです。

scraper.py

import scrapy


class BrickSetSpider(scrapy.Spider):
   name = 'brick_spider'
   start_urls = ['http://brickset.com/sets/year-2016']

   def parse(self, response):
       SET_SELECTOR = '.set'
       for brickset in response.css(SET_SELECTOR):

           NAME_SELECTOR = 'h1 ::text'
           PIECES_SELECTOR = './/dl[dt/text() = "Pieces"]/dd/a/text()'
           MINIFIGS_SELECTOR = './/dl[dt/text() = "Minifigs"]/dd[2]/a/text()'
           IMAGE_SELECTOR = 'img ::attr(src)'
           yield {
               'name': brickset.css(NAME_SELECTOR).extract_first(),
               'pieces': brickset.xpath(PIECES_SELECTOR).extract_first(),
               'minifigs': brickset.xpath(MINIFIGS_SELECTOR).extract_first(),
               'image': brickset.css(IMAGE_SELECTOR).extract_first(),
           }

       NEXT_PAGE_SELECTOR = '.next a ::attr(href)'
       next_page = response.css(NEXT_PAGE_SELECTOR).extract_first()
       if next_page:
           yield scrapy.Request(
               response.urljoin(next_page),
               callback=self.parse
           )

結論

このチュートリアルでは、30行未満のコードでWebページからデータを抽出する完全に機能するスパイダーを構築しました。 これは素晴らしいスタートですが、このクモでできることはたくさんあります。 作成したコードを拡張する方法をいくつか紹介します。 データのスクレイピングの練習を行います。

  1. `+ http:// brickset.com / sets / year-2016 `の ` 2016 +`の部分から推測したように、現在は2016年の結果のみを解析しています-他の結果をどのようにクロールしますか年?

  2. ほとんどのセットには小売価格が含まれています。 そのセルからデータをどのように抽出しますか? どのようにして生の数字を取得しますか? ヒント:ピースとミニフィグの数と同じように、「+ dt +」でデータを見つけます。

  3. ほとんどの結果には、セットまたはそのコンテキストに関するセマンティックデータを指定するタグがあります。 単一のセットに複数のタグがある場合、これらをどのようにクロールしますか?

これで、考えて実験することができます。 Scrapyの詳細については、https://scrapy.org/doc/ [Scrapyの公式ドキュメント]をご覧ください。 Webからのデータの操作の詳細については、https://www.digitalocean.com/community/tutorials/how-to-scrape-web-pages-with-beautiful-soup-and-python-3のチュートリアルを参照してください。 [「美しいスープとPython 3でWebページをスクレイピングする方法」]。