note.nkmk.me

Python, Scrapyの使い方(Webクローリング、スクレイピング)

Date: 2018-06-25 / tags: Python, スクレイピング
このエントリーをはてなブックマークに追加

PythonのWebクローリングとスクレイピングのフレームワークであるScrapyの使い方をサンプルコードとともに説明する。

以下の内容について説明する。具体例はYahoo! Japanを対象としている。

  • クローリングとスクレイピング
  • ScrapyとBeautifulSoupの違い
  • Scrapyのインストール
  • Scrapyの基本的な使い方
  • 単独のページをスクレイピングする例
  • リンクを指定してクローリング、スクレイピングする例
  • リンクを抽出してクローリング、スクレイピングする例
  • リンクを抽出して再帰的にクローリング、スクレイピングする例

Scrapyのバージョンは1.5.0。バージョン1.1からPython3にも対応している。

レポジトリは以下。

スポンサーリンク

クローリングとスクレイピング

クローリングは「Webページのリンクをたどって巡回し、Webページの情報をダウンロードすること」で、クローリングのためのプログラムをクローラーやボット、スパイダーなどと呼ぶ。

スクレイピングは「ダウンロードしたWebページの情報(htmlファイルなど)を解析して必要な情報を抜き出すこと」。

ScrapyとBeautifulSoupの違い

BeautifulSoupはスクレイピングのためのライブラリで、ダウンロードしたhtmlファイルなどから必要な部分を抽出することができる。スクレイピング以外の処理、例えば対象をダウンロードしたりリンクを辿ったりする処理は自分で用意する必要がある。

BeautifulSoupの実際の処理の例は以下の記事を参照。

Scrapyはスクレイピングだけでなくクローリングも行う。

Webページのリンクを再帰的に辿って巡回(クローリング)し、各ページに対して所定の処理を行って必要な情報を抽出(スクレイピング)することができる。JSONやXML、CSVなどのファイル出力も簡単。

複数ページを対象とするのならScrapyのほうが便利。

Scrapyのインストール

Scrapyのインストールの公式説明ページは以下。

他のライブラリと同様にpip(環境によってはpip3)でインストールできる。

$ pip install scrapy

AnacondaやMinicondaを使っている場合は、condaでインストールできる。

$ conda install -c conda-forge scrapy

特にWIndowsでゼロから環境構築する場合はAnacondaのインストーラーを使うのが楽だと思う。

Scrapyの基本的な使い方

公式のチュートリアルで使われているサイトを例にScrapyの基本的な使い方を説明する。

以下のような流れ。

  • scrapy startprojectでプロジェクト作成
  • scrapy genspiderでスパイダーの雛形作成
  • itemsを定義しスパイダーに処理を追加
  • settings.pyに各種設定を記述
  • scrapy crawlで実行、ファイル出力

scrapy shellによるデバッグについても述べる。

scrapy startprojectでプロジェクト生成

scrapy startprojectコマンドでScrapyプロジェクトを作成する。

[project_dir]<project_name>というプロジェクトを生成するコマンドは以下の通り。[project_dir]を省略するとカレントディレクトリにプロジェクトが生成される。

$ scrapy startproject <project_name> [project_dir]

ここでは、任意のディレクトリでtutorialという名前のプロジェクトを作成する。

$ scrapy startproject tutorial

以下のような構造のディレクトリとファイルが生成される。tutorialというプロジェクトのディレクトリの中に同名(tutorial)のサブディレクトリが生成される。

tutorial
├── scrapy.cfg
└── tutorial
    ├── __init__.py
    ├── __pycache__
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        ├── __init__.py
        └── __pycache__

scrapy genspiderでスパイダーの雛形作成

以降のコマンドは生成されたプロジェクトディレクトリで実行するので、scrapy startprojectから続けて実行する場合はcdコマンドで移動する。

$ cd tutorial

scrapy genspiderコマンドでスパイダーの雛形を作成する。スパイダーはボットやクローラーとも呼ばれるクローリングのためのプログラム。

<name>という名前で<domain>を対象ドメインとするスパイダーを生成するコマンドは以下の通り。-tオプションでテンプレートを指定することができる。

$ scrapy genspider [-t template] <name> <domain>

ここでは上述の名言サイトquotes.toscrape.comを対象とするquotesという名前のスパイダーを生成する。-tは省略してデフォルトのまま。テンプレートを指定する例は後述。

$ scrapy genspider quotes quotes.toscrape.com

tutorial/tutorial/spiders/quotes.pyが生成される。内容は以下の通り。

スパイダーの名称nameや対象ドメインallowed_domains、クロールのスタート地点となるURLstart_urlsなどの変数に適当な値が設定されている。

# -*- coding: utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        pass
source: quotes.py

start_urlsのURLからクロールが始まる。start_urlsはリストになっているので、複数のURLを指定することも可能。

取得したHTMLソースがparse()メソッドの第二引数responsescrapy.http.response.html.HtmlResponseオブジェクトとして渡される。

このparse()メソッドに処理を追加していく。

genspiderは雛形を生成するだけ。自分でゼロからスクリプトを作成してspidersディレクトリにおいてもOK。

itemsを定義しスパイダーに処理を追加

items定義

サイトから収集したい情報をtutorial/tutorial/items.pyに記述する。

プロジェクトを作成した時点でitems.pyには雛形が記述されている。

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class TutorialItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass
source: items.py

収集したい項目を<name>=scrapy.Field()で追加すればOK。

class TutorialItem(scrapy.Item):
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()
source: items.py

スパイダーに処理を追加

genspiderで作成したスパイダーquotes.pyを以下のように更新する。

# -*- coding: utf-8 -*-
import scrapy
from tutorial.items import TutorialItem

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        for quote in response.css('div.quote'):
            item = TutorialItem()
            item['author'] = quote.css('small.author::text').extract_first()
            item['text'] = quote.css('span.text::text').extract_first()
            item['tags'] = quote.css('div.tags a.tag::text').extract()
            yield item
source: quotes.py

items.pyで定義したクラスTutorialItemをインポートして使えるようにし、parse()に処理を追加する。

responseのメソッドcss()にCSSセレクタを指定することでDOM要素を絞り込んで取得できる。

::textでテキスト部分を選択し、さらにextract_first()メソッドで文字列として取り出している。extract()は選択されたオブジェクトをリストとして取り出すメソッド(要素が一個でもリスト)。tagsは複数個存在するのでextract()を使う。

TutorialItemのインスタンスitemを作成し、抽出した値を格納していく。items.pyで定義したクラスは基本的に辞書(dictオブジェクト)のように扱える。

CSSセレクタではなくXPathで要素を絞り込むxpath()メソッドもある。後述のscrapy shellを使うとどのようなCSSセレクタやXPathを指定すればよいかを試行錯誤しながら確認できる。

settings.pyに各種設定を記述

settings.pyに各種設定を記述する。プロジェクト作成時点で雛形が記述されているが、ほとんどの設定項目がコメントアウトされている。

設定項目についてのドキュメントは以下。

注意しておいたほうがいい項目は以下の通り。

  • USER_AGENT
    • ユーザーエージェントによってレスポンスが異なるサイトの場合はここで適当な値を設定しておく。
    • 次に例として示すYahoo! Japanでは設定の必要あり。
  • CONCURRENT_REQUESTS
    • 同時リクエスト数。
    • デフォルトは16
    • 対象サイトへの負荷が気になる場合は小さい値にしておく。
  • DOWNLOAD_DELAY
    • リクエストごとの遅延秒数。
    • デフォルトは0
    • 対象サイトへの負荷が気になる場合は3秒程度にしておく。
  • DEPTH_LIMIT
    • リンクを辿る階層の数。例えばDEPTH_LIMIT = 1とするとスタートページからリンクを一回しかクロールしない。DEPTH_LIMIT = 2とするとスタートページからリンクされているページ、および、そのページからリンクされているページまでクロールする。
    • デフォルトは0で制限なし。
    • settings.pyの雛形にはないので指定する場合は追記する。
    • リンクを再帰的に辿るかどうかはスパイダーのスクリプトの記述で決まる。このチュートリアルの例ではスタートページを処理するのみで、リンクからクロールすることはない。後述の再帰的に辿る場合はDEPTH_LIMITを指定しておかないと延々とリンクを辿っていく可能性があるので注意。

scrapy crawlで実行、ファイル出力

scrapy crawlコマンドでクローリング、スクレイピングを実行する。

<spider_name>という名前のスパイダーを実行するコマンドは以下の通り。

$ scrapy crawl <spider_name>

<spider_name>はスパイダーのファイル名ではなくスパイダークラスのnameで定義された名前。genspiderで生成した場合はファイル名とnameは同じになるが、自分でゼロから作成した場合は注意。

ここではquotes.pynameであるquotesを指定する。

$ scrapy crawl quotes

エラーがなければスパイダーが起動しログが表示される。ログを表示させたくない場合は--nologオプションをつける。

収集した結果をファイルに保存したい場合は-oオプションをつける。-o <file_name>とすると、拡張子のフォーマットに自動的に変換され保存される。

  • .json
  • .xml
  • .csv
  • .jlまたは.jsonlJSON Lines
  • .pickle

が自動的に認識される。

以下のように-tでフォーマットを指定した上で-o stdout:とすると結果が標準出力される。

$ scrapy crawl quotes -t csv -o stdout: --nolog

チュートリアルの結果は以下の通り。

CSVの先頭部分を示す。

author,tags,text
Albert Einstein,"change,deep-thoughts,thinking,world",“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”
J.K. Rowling,"abilities,choices","“It is our choices, Harry, that show what we truly are, far more than our abilities.”"
Albert Einstein,"inspirational,life,live,miracle,miracles",“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”
Jane Austen,"aliteracy,books,classic,humor","“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”"
source: quotes.csv

scrapy shellによるデバッグ

scrapy shellコマンドでresponseに対するcss()xpath()メソッドによるスクレイピング結果を確認することができる。

URLを指定して実行する。

$ scrapy shell <url>

インタラクティブシェルが起動するので、以下のようにparse()の第二引数responseと同じオブジェクトresponseを使ってコードを記述してその返り値を確認できる。

$ scrapy shell http://quotes.toscrape.com
In [1]: response.css('div.quote small.author::text').extract_first()
Out[1]: 'Albert Einstein'

終了するときはexitまたはquitと入力する。

以下のように<user_agent>にユーザーエージェントの文字列を指定できる。

$ scrapy shell -s USER_AGENT='<user_agent>' <url>

なお、CSSセレクタについては以下のMDNのドキュメントが詳しい。

とりあえず、

  • クラスは.classnameで指定
  • IDは#idnameで指定
  • A BA[スペース]B)はA要素の中にあるすべてのB要素

ぐらいは覚えておくとよい。

Google Chromeの場合、抽出したい対象の部分を選択して、右クリック→検証でその部分のソースが開発者ツールに表示される。下側にhtml要素から順番に要素名が書いてあるのでそれを参考にscrapy shellで試してみるいいと思う。

単独のページをスクレイピングする例

以降はYahoo! JAPANを例とする。サイトの構成が変わるとサンプルコードが動かなくなる可能性があるので注意。2018年6月24日時点で動作確認をしている。

以下の記事のBeautifulSoupの例でも書いたように、Yahoo! JapanはユーザーエージェントによってHTMLソースを切り替えている。

settins.pyUSER_AGENTを設定しておかないとブラウザで見ているソースが取得できない。以下の例ではGoogle Chromeのユーザーエージェントを指定している。

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'
source: settings.py

ユーザーエージェントの文字列は以下のサイトなどで確認できる。確認したいブラウザでアクセスする。

まずは単独のページをスクレイピングする例を示す。scrapy startprojectでプロジェクトを作成。

$ scrapy startproject yahoo_japan

トップページのニュースのヘッドラインを抽出する。

scrapy genspidertopicsという名前のスパイダーを作成。

$ cd yahoo_japan
$ scrapy genspider topics yahoo.co.jp

items.pyを更新。

import scrapy

class YahooJapanItem(scrapy.Item):
    headline = scrapy.Field()
    url = scrapy.Field()
source: items.py

スパイダーのスクリプトtopics.pyを更新。上のチュートリアルの例とほぼ同じ。::textではなく::attr(属性名)で属性の値を取得できる。リンクのURLを取得したい場合は::attr(href)とする。

import scrapy
from yahoo_japan.items import YahooJapanItem

class TopicsSpider(scrapy.Spider):
    name = 'topics'
    allowed_domains = ['yahoo.co.jp']
    start_urls = ['http://yahoo.co.jp/']

    def parse(self, response):
        for topic in response.css('div#topicsfb ul.emphasis li'):
            item = YahooJapanItem()
            item['headline'] = topic.css('a::text').extract_first()
            item['url'] = topic.css('a::attr(href)').extract_first()
            yield item
source: topics.py

実行はscrapy crawlコマンド。

$ scrapy crawl topics -o results/topics.csv

出力されたCSVの先頭部分を示す。

headline,url
蒸し暑さ到来か 月曜から注意,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAWo6JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDQyAA--/RK=0/RS=_9gfqbCvAHIP84UmhBqIced1Oiw-
男性死亡 成田で逃走の男発見,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAW46JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDQzAA--/RK=0/RS=4feMOELOwKrak.x0d.8l9ckBwM4-
カネミ油症「許して」母悔恨,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAXI6JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDM3AA--/RK=0/RS=2fT.gQYqZNV7sKZQIA8jCfGb1N4-
菊池桃子氏にまたストーカー,https://rdsig.yahoo.co.jp/_ylt=A2RiWbfSKy9bUAEAXY6JBtF7/RV=2/RE=1529904466/RH=cmRzaWcueWFob28uY28uanA-/RB=VavfPUkulHthHXVoX6gs1JDQneo-/RU=aHR0cHM6Ly9uZXdzLnlhaG9vLmNvLmpwL3BpY2t1cC82Mjg3NDQwAA--/RK=0/RS=gQ1c8t0S_2wcaL1vCJF7GwnCHCU-
source: topics.csv

リンクを指定してクローリング、スクレイピングする例

コード中でリンクのURLを指定してリンク先の情報を取得する例として、トップページのニュースのヘッドラインのリンクを辿ってニュースタイトル(省略されていない見出し)を取得する。

scrapy genspidertopics_detailという名前のスパイダーを作成。プロジェクトディレクトリで以下のコマンドを実行する。

$ scrapy genspider topics_detail yahoo.co.jp

items.pyにクラスを追加。

class YahooJapanDetailItem(scrapy.Item):
    headline = scrapy.Field()
    url = scrapy.Field()
    title = scrapy.Field()
source: items.py

スパイダーのスクリプトtopics_detail.pyを更新。

import scrapy
from yahoo_japan.items import YahooJapanDetailItem

class TopicsDetailSpider(scrapy.Spider):
    name = 'topics_detail'
    allowed_domains = ['yahoo.co.jp']
    start_urls = ['http://yahoo.co.jp/']

    def parse(self, response):
        for topic in response.css('div#topicsfb ul.emphasis li'):
            item = YahooJapanDetailItem()
            item['headline'] = topic.css('a::text').extract_first()
            yield scrapy.Request(topic.css('a::attr(href)').extract_first(),
                                 callback=self.parse_detail,
                                 meta={'item': item})

    def parse_detail(self, response):
        item = response.meta['item']
        item['url'] = response.url
        item['title'] = response.css('div.topicsDetail h2 a::text').extract_first()
        yield item

リンク先を読み込んで処理するにはscrapy.Request()を使う。引数として。

  • ヘッドラインのリンクのURL(読み込みたいページのURL)
  • callback: リンク先のページに適用する関数
  • meta: responseオブジェクトに渡すオブジェクト(辞書形式で指定)
    • ここでは取得した情報を格納するオブジェクトを指定

を渡す。

metaでオブジェクトを渡すあたりは分かりにくいので注意。公式ドキュメントの説明ページは以下。

また、トップページのヘッドラインのリンクのドメインはrdsig.yahoo.co.jpnews.yahoo.co.jpにリダイレクトされる。allowed_domainswww.yahoo.co.jpとしている場合は別ドメインとみなされアクセスできない(Filtered offsite requestとなる)。yahoo.co.jpとしておけばいずれのドメインも許可されるのでOK。

実行はscrapy crawlコマンド。

$ scrapy crawl topics_detail -o results/topics.csv

出力されたCSVの先頭部分を示す。

headline,title,url
蒸し暑さ到来か 月曜から注意,月曜以降は「蒸し蒸し猛暑」到来か 梅雨明け前に暑さに注意,https://news.yahoo.co.jp/pickup/6287442
菊池桃子氏にまたストーカー,また菊池桃子さんにストーカー 元タクシー運転手を逮捕,https://news.yahoo.co.jp/pickup/6287440
松本人志 連れ去り企画を謝罪,松本人志、「水曜日のダウンタウン」芸人連れ去り企画問題を謝罪,https://news.yahoo.co.jp/pickup/6287438
セネガル戦 勝てばGL突破も,日本代表、セネガルに勝利でGL突破決定も…決勝T進出条件は?,https://news.yahoo.co.jp/pickup/6287441

リンクを抽出してクローリング、スクレイピングする例

上の例はリンクのURLをコードの中で取得しそれを指定してリンク先のページを読み込んだが、Scrapyではページ中のリンクを抽出して自動的にクロールする処理も可能。

トップページではなくYahoo!ニュースの各カテゴリページ(経済スポーツなど)のヘッドラインを抽出する例を示す。

なお、この例のようにクロールするURLが固定されている場合はstart_urlsに対象URL(各カテゴリページのURL)をリストで指定しても所望の動作が可能。以下のサンプルコードでは説明のためにリンクを自動的にクロールする処理を行っている。

items.pyにクラスを追加。

class YahooJapanNewsItem(scrapy.Item):
    url = scrapy.Field()
    headline = scrapy.Field()
    category = scrapy.Field()
source: items.py

scrapy genspidernews_crawlという名前のスパイダーを作成。プロジェクトディレクトリで以下のコマンドを実行する。-t crawlとすることでページ内リンクをクロールするスパイダーの雛形が生成される。

$ scrapy genspider -t crawl news_crawl yahoo.co.jp

生成される雛形news_crawl.pyは以下の通り。-t crawlによりデフォルトとは異なる雛形が生成される。

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class NewsCrawlSpider(CrawlSpider):
    name = 'news_crawl'
    allowed_domains = ['yahoo.co.jp']
    start_urls = ['http://yahoo.co.jp/']

    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        i = {}
        #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
        #i['name'] = response.xpath('//div[@id="name"]').extract()
        #i['description'] = response.xpath('//div[@id="description"]').extract()
        return i

これを以下のように更新する。

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from yahoo_japan.items import YahooJapanNewsItem

class NewsCrawlSpider(CrawlSpider):
    name = 'news_crawl'
    allowed_domains = ['yahoo.co.jp']
    start_urls = ['http://news.yahoo.co.jp/']

    rules = (
        Rule(
            LinkExtractor(restrict_css='ul#gnSec'),
            callback='parse_item'
        ),
    )

    def parse_item(self, response):
        category = response.css('ul#gnSec li.current a::text').extract_first()
        for topic in response.css('div#epTabTop ul.topics h1.ttl, p.ttl'):
            item = YahooJapanNewsItem()
            item['headline'] = topic.css('a::text').extract_first()
            item['url'] = topic.css('a::attr(href)').extract_first()
            item['category'] = category
            yield item

rulesRuleでクローリングのルールを指定する。

どのリンクをクロールするかのルールはRuleの第一引数LinkExtractor()で指定する。

LinkExtractor()の引数restrict_cssにCSSセレクタを指定すると選択された要素内のリンクのみがクロール対象となる。ここではYahoo!ニュース上部の各カテゴリページへのリンク部分を選択している。

LinkExtractor()にはクロールするリンクのURLを正規表現で指定する引数allowや範囲をXPathで指定する引数restrict_xpathsなどもある。いずれも複数値をリストで指定可能。

Ruleの引数callbackにリンク先を処理する関数名を指定する。

Ruleの引数followには、リンク先のページに含まれているリンクを再帰的にクロールするかをTrue, Falseで指定する。引数callbackに関数を指定した場合はデフォルトでfollow=Falseとなる(それ以上リンクを辿らない)。この例ではデフォルトでいいので引数followは指定していない。

引数callbackを指定しない場合はデフォルトでfollow=Trueとなる。次の例で示す。

なお、この例のようにrulesRuleを指定した場合はRuleに合致したリンクがcallbackの関数で処理されるのみで、スタートページ(start_urlsのURLのページ)自体は処理対象とならない。スタートページにも何らかの処理を適用したい場合は、parse_start_url()をオーバーライドする。

例えば以下のように記述する。

def parse_start_url(self, response):
    return self.parse_item(response)

この例ではparse_start_url()をオーバーライドしなくてもスタートページのヘッドラインが抽出されるが、それはスタートページに自身へのリンクが存在しているから。

また、rulesで指定したクローリングでは同一URLのページはデフォルトでスキップされるようになっている。各カテゴリページには各カテゴリページへのリンクがあるが、一度クロールしたページに再びクロールすることはない。

そのほか以下の点に注意。

  • start_urlsを雛形の値から変更している。
  • rulesはタプルなので要素のRuleが一つの場合は末尾にコンマ,が必要。
  • Ruleの引数followなどの設定を誤ると延々とリンクを辿ってしまう可能性があるので、まずはsettings.pyDEPTH_LIMIT12くらいにしておいたほうが安全。

実行はscrapy crawlコマンド。

$ scrapy crawl news_crawl -o results/topics.csv

結果は以下の通り。

出力されたCSVの先頭部分を示す。

category,headline,url
主要,蒸し暑さ到来か 月曜から注意,https://news.yahoo.co.jp/pickup/6287442
主要,男性死亡 成田で逃走の男発見,https://news.yahoo.co.jp/pickup/6287443
主要,カネミ油症「許して」母悔恨,https://news.yahoo.co.jp/pickup/6287437
主要,菊池桃子氏にまたストーカー,https://news.yahoo.co.jp/pickup/6287440

リンクを抽出して再帰的にクローリング、スクレイピングする例

さらに再帰的にリンクをクロールする例を示す。

Yahoo!ニュースの各カテゴリページのヘッドラインのリンク先から情報を取得する。

items.pyにクラスを追加。

class YahooJapanNewsDetailItem(scrapy.Item):
    url = scrapy.Field()
    title = scrapy.Field()
    category = scrapy.Field()
source: items.py

scrapy genspidernews_crawl_detailという名前のスパイダーを作成。

$ scrapy genspider -t crawl news_crawl_detail yahoo.co.jp

生成されたnews_crawl_detail.pyを以下のように更新する。

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from yahoo_japan.items import YahooJapanNewsDetailItem

class NewsCrawlDetailSpider(CrawlSpider):
    name = 'news_crawl_detail'
    allowed_domains = ['yahoo.co.jp']
    start_urls = ['http://news.yahoo.co.jp/']

    rules = (
        Rule(
            LinkExtractor(restrict_css='ul#gnSec'),
        ),
        Rule(
            LinkExtractor(restrict_css='div#epTabTop ul.topics h1.ttl, p.ttl'),
            callback='parse_item'
        )
    )

    def parse_item(self, response):
        item = YahooJapanNewsDetailItem()
        item['url'] = response.url
        item['title'] = response.css('div.topicsDetail h2 a::text').extract_first()
        item['category'] = response.css('ul#gnSec li.current a::text').extract_first()
        return item

rulesに2つのRuleを指定する。

1つ目は上の例と同じく上部の各カテゴリページへのリンク部分。これに対しては引数callbackを指定していないため、リンク先のカテゴリページに対しては関数で処理しない(情報を抽出しない)。引数callbackを指定していないと引数followがデフォルトでTrueとなるため、カテゴリページ内のリンクも再帰的にクローリング対象となる。

2つ目のRuleの第一引数に各カテゴリページのヘッドライン(個別ニュースページへのリンク)を対象とするCSSセレクタを指定し、引数callbackにリンク先(個別ニュースページ)を処理する関数を指定している。引数followcallbackを指定した場合のデフォルトFalseなので、それ以上はクロールしない。

実行はscrapy crawlコマンド。

$ scrapy crawl news_crawl_detail -o results/topics.csv

出力されたCSVの先頭部分を示す。

category,title,url
国内,月曜以降は「蒸し蒸し猛暑」到来か 梅雨明け前に暑さに注意,https://news.yahoo.co.jp/pickup/6287442
地域,青森女性殺人 犯人どこに、動機は… 未解決のまま、過ぎていく時間 住民、不安な日々,https://news.yahoo.co.jp/pickup/6287446
国際,サウジアラビアで女性の運転解禁,https://news.yahoo.co.jp/pickup/6287444
地域,毒入り油で手料理 母悔恨「知らなかった」「ごめんね許して」 家族全員発症 カネミ油症50年の証言,https://news.yahoo.co.jp/pickup/6287437

この例の場合、

のいずれかのリンクを辿る。

settings.pyDEPTH_LIMIT = 1とすると下のルートでしかクローリングされないので、主要カテゴリのヘッドラインからリンクされている個別ニュースページの情報しか抽出されない。

また、上述のようにrulesで指定したクローリングでは同一URLのページはデフォルトでスキップされるようになっている。この例ではヘッドラインのリンク先(個別ニュースページ)へのアクセスもrulesで指定しているので、「主要」カテゴリと各カテゴリに同じニュースへのリンクがある場合、どちらか片方はスキップされる。

このため、上の例(「主要」カテゴリと各カテゴリが重複してリストアップされる)とは結果の項目数が異なる。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事