geek開発日誌

超一流のプログラマーを目指してます!作成したポートフォリオを記録していく開発日誌です。

Scrapyでニュースを取得

こんにちは。nakatatsuです。

今回からフレームワークScrapyを使用して複数のニュースサイトから情報を取得するスクレイピングツールを作成します。まずはYahooニュースから情報を取得します。

仕様

完成形の仕様です。途中で変更があるかもしれません。

・複数のニュースサイトから情報を取得

・記事のタイトルと要約を取得

準備

Scrapyを使用するための準備を以下に示します。

Scrapyを使うと、どんなWebサイトでも使える共通処理をフレームワークに任せて、ユーザーは個々のWebサイトごとに異なる処理だけを書けばよくなります。以下のような機能を持っています。

●Webページからのリンクの抽出

robots.txtの取得と拒否されているページのクロール防止

XMLサイトマップの取得とリンクの抽出

ドメインごと/IPアドレスごとのクロール時間間隔の調整

●複数のクロール先の並行処理

●重複するURLのクロール防止

●エラー時の回数制限付きのリトライ

クローラーのデーモン化とジョブの管理


・Scrapyのインストール

(scraping) $ pip install scrapy

Scrapyはlxmlに依存しているので、libxml2とlibxsltのインストールも必要です。libxml2とlibxsltのインストールはこのブログの過去記事「サイトから画像を取得して保存」を参照してください。

nakatatsu-com.hatenablog.com



・プロジェクトの作成

(scraping) $ scrapy startproject news

newsというプロジェクトを作成しました。


ディレクトリの移動

(scraping) $ cd news

コマンドを実行する際は、基本的にこのディレクトリで実行します。


・設定

DOWNLOAD_DELAY = 3

settings.pyに記載されている「DOWNLOAD_DELAY」の項目をコメントアウトし、ページのダウンロード間隔を平均3秒空けます。

フォルダ構成

news
├── news
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-36.pyc
│   │   ├── items.cpython-36.pyc
│   │   └── settings.cpython-36.pyc
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-36.pyc
│       │   └── yahoo.cpython-36.pyc
│       └── yahoo.py
└── scrapy.cfg

フォルダ構成はこのようになっております。

ソースコード

Itemを作成します。ItemはSpiderが抜き出したデータを格納しておくためのオブジェクトです。

item.py

import scrapy


class Headline(scrapy.Item):
    '''
    ニュースのヘッドラインを格納するためのItem
    '''

    title = scrapy.Field()
    summary = scrapy.Field()


Spiderを作成します。「scrape genspider」コマンドであらかじめ定義されているテンプレートからSpiderを作成できます。「scrape genspider」コマンドの第1引数にSpiderの名前、第2引数にドメイン名を指定して実行します。

(scraping) $ scrapy genspider yahoo news.yahoo.co.jp

spidersディレクトリ内にyahoo.pyというファイルが生成されます。これをベースに書き換えていきます。

yahoo.py

import scrapy
from news.items import Headline


class YahooSpider(scrapy.Spider):
    name = 'yahoo' # Spiderの名前
    allowed_domains = ['news.yahoo.co.jp'] # クロール対象とするドメインのリスト
    start_urls = ['http://news.yahoo.co.jp/'] # クロールを開始するURLのリスト

    def parse(self, response):
        '''
        「トップ」>「主要」のトピックス一覧から個々のトピックスへのリンクを抜き出してたどる
        '''

        for url in response.css('.topicsListItem > a::attr("href")').extract():
            yield scrapy.Request(response.urljoin(url), self.parse_topics)

    def parse_topics(self, response):
        '''
        トピックスページからタイトルと要約を抜き出す
        '''

        item = Headline()
        item['title'] = response.css('.tpcNews_title::text').extract_first()
        item['summary'] = response.css(
            '.tpcNews_summary').xpath('string()').extract_first()
        yield item


実行の流れは以下のようになっています。


f:id:nakatatsu_com:20190616030933p:plain


ScrapyEngine:他のコンポーネントを制御する実行エンジン

Scheduler:Requestをキューに溜める

Downloader:Requestが指すURLのページを実際にダウンロードする

Spider:ダウンロードしたResponseを受け取り、ページからItemや次にたどるリンクを表すRequestを抜き出す

FeedExporter:Spiderが抜き出したItemをファイルなどに保存する

ItemPipeline:Spiderが抜き出したItemに関する処理を行う

DownloaderMiddleware:Downloaderの処理を拡張する

SpiderMiddleware:Spiderへの入力となるResponseやSpiderからの出力となるItemやRequestに対しての処理を拡張する


Spiderを実行すると、最初にstart_urls属性に含まれるURLを指すRequestオブジェクトがScrapyのSchedulerに渡され、Webページの取得を待つキューに追加されます。

キューに追加されたRequestオブジェクトは順にDownloaderに渡されます。DownloaderはRequestオブジェクトに指定されたURLのページを取得し、Responseオブジェクトを作成します。Downloaderの処理が完了すると、ScrapyEngineがSpiderのコールバック関数を呼び出します。デフォルトのコールバック関数はSpiderのparse()メソッドです。コールバック関数には引数としてResponseオブジェクトが渡されるので、ここからリンクやデータを抽出します。

コールバック関数ではyield文で複数のオブジェクトを返せます。リンクを抽出して次のページをクロールしたい場合は、Requestオブジェクトをyieldします。データを抽出したい場合は、Itemオブジェクトをyieldします。Requestオブジェクトをyieldした場合、再びSchedulerのキューに追加されます。Itemオブジェクトをyieldした場合、FeedExporterに送られ、ファイルなどに保存されます。

結果

結果です。長いので省略してます。

{'summary': '\u3000'
         '【ワシントン=海谷道隆、ニューヨーク=村山誠】シャナハン米国防長官代行は14日、中東ホルムズ海峡近くのオマーン沖で起きた日本などのタンカー2隻への攻撃について、「情報の機密をさらに解除し、より多くの情報を共有したい」と述べた。イランの攻撃への関与を裏付ける情報を関係国などに提供し、米国の主張に対する国際社会の理解を広げる狙いだ。',
 'title': '「イラン関与」裏付け、米が機密開示を検討'}
・・・

トピックスページからタイトルと要約を取得することができました。

まとめ

・Yahooニュースサイトから情報を取得

・記事のタイトルと要約を取得

参考文献

参考文献です。