毎日テキストマイニング

180日間、毎日テキストマイニングをするブログです

2018/7/30【37日目】scrapyを使いこなして、自動でテキストを集めよう

土日の間、止まっていましたスクレイピングの話を続けていきます。

Beautifulsoupを使って本文の取得には成功しましたが、どうも手間が多く便利そうじゃないですね。

調べてみましたが、Scrapyというスクレイピングフレームワークがあるそうなので、こっちも勉強していきたいと思います。 まずはいつも通り公式のチュートリアルから始めてみます。

Scrapy チュートリアル — Scrapy 1.2.2 ドキュメント

まずはpip installから。

pip install scrapy

インストールしたらバージョンの確認をしてみます。

scrapy --version
Scrapy 1.5.1 - no active project

no active projectって出てきました。何も動いてないってことですね。 scarapyはどうやらprojecというもので管理するらしいです。

プロジェクトの作成

プロジェクトはこのコマンドで作るらしいです。

scrapy startproject project_name

実行結果。

New Scrapy project 'tutorial', using template directory '/anaconda/lib/python3.6/site-packages/scrapy/templates/project', created in:
    /Users/py_sql/tutorial

You can start your first spider with:
    cd tutorial
    scrapy genspider example example.com

いろいろフォルダができました。tutorialというプロジェクト名で作成すると、tutorialフォルダーの中にもう1つtutorialフォルダーができるので、そこに移動します。それで、さらにspidersというフォルダがあるので、そこに移動します。ここが作業フォルダになるらしいです。

チュートリアルを読んでいくと、ここにサンプルとして[quotes_spider.py]を作成しろっと書いてあるので、そのままコピペします。

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)

サンプルコードを上から見ていきます。

  • nameというのが、このスパイダーの名前らしいです。nameは必ず一意にしなければならないとのこと。
  • start_requestsという関数にスクレイピングしたいurlを記述していくみたいですね。
  • paraseは何回か出ていますが、Webページの構成を解析する関数ですね。

サンプルができたら実行してみます。

scrapy crawl quotes #spiderのname

実行結果。

2018-07-30 12:26:58 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
(以下、省略)

tutorialの結果と全然違いますが、ミドルウェアをダウンロードしたみたいですね。 下の方にスクロールしていくと、ファイルらしきものがあります。

2018-07-30 12:30:28 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2018-07-30 12:30:29 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)
2018-07-30 12:30:29 [quotes] DEBUG: Saved file quotes-2.html
2018-07-30 12:30:29 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)

このあたりでスクレイピングしているようです。

一番簡単なデータの取得方法

一番簡単な取得の方法はshell上で行うようです。

scrapy shell 'http://quotes.toscrape.com'

上のコマンドをターミナルに入力すると、シェルが起動します。

実行結果。

[s]   view(response)    View response in a browser
In [1]: 

In [1]: response.css('title')
Out[1]: [<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]

ipythonのようなシェルが立ち上がりました。

In [3]: title = quote.css("span.text::text").extract_first()

In [4]: title
Out[4]: “The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”

テキストが取得できました。 この辺りの使い心地はJupyter Notebookとそんなに変わらないですね。

スクレイピングした情報をログに出す方法

今のところそんなに便利そうでもないですが、とりあえず進めていきます。

スクレイピングした情報をログの中に出すには、yieldというものを使うらしいです。 サンプルコードをそのまま貼り付けます。

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('span small::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

parse関数の中に記述されているのが、yieldですね。 次のようにログの中に出ていることが確認できました。

{'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'author': 'Albert Einstein', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}
2018-07-30 23:27:57 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”', 'author': 'J.K. Rowling', 'tags': ['abilities', 'choices']}

なるほどですね。

json形式で保存する

一番簡単なデータの保存方法がjson形式で保存することらしいです。json形式とは簡単にいうとJavaScriptにある書き方を真似た読みやすいテキストだそうですね。

例を示すとこんな感じだそうです。

 {
    “AKB”:{
        “岡田奈々”:”5位”,
        "横山由依”:”6位”
    }
}

それで、この形式で保存するのは楽らしく、コマンドだけでいけるらしいです。

scrapy crawl quotes -o quotes.json

実行結果。

[
{"text": "\u201cThe world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.\u201d", "author": "Albert Einstein", "tags": ["change", "deep-thoughts", "thinking", "world"]},
(以下、省略)

このようなjson形式のテキストデータのファイルができました。 長くなってきましたので、明日に続きます。

今日の結果

今日のAKBメンバーによる呟きは32件でした。 f:id:rimt:20180731004357p:plain

画像を見るとやたらと「から」が大きいですね。朝から、台風から、明日から、結構「から」を使う機会が多いんですね。