クローラーのお話～その2～


これは[日本情報クリエイト Engineer's Advent Calendar 2017](https://qiita.com/advent-calendar/2017/njc)の8日目の記事です。

# はじめに

2回目の投稿となります。
またまたクローラーのお話です。

ただお気づきでしょうか、タグ。
そうです、rubyじゃありません。
Pythonですよ、Python。
なぜかって？
「クローラー作ってます」って言うと皆さん、「Python？」っていうからです。
「Rubyです。」っていうと「あぁ・・・」って。
「あぁ・・」ってなんだよって思いつつ周りから何かとPythonを推されるので作ってみます。

とまぁ、関係ない話はさておき本題行きます。

# お品書き

* Dockerで環境の準備
* クローラー作成の準備
* 実際に作る
* 動かしてみる

です。
なぜにDocker？ってとこなんですが、
筆者の環境(windows10)でPython入れて環境作ってたらクローラーに使うScrapyがどうしても入らなくて
仕方なくDockerで環境構築しました。
初めてDockerを触ったので、その手順も一緒に書いていきます。

# Dockerで環境の準備

Docker本体のインストールは簡単なので割愛

python環境を作ります。
officialのコンテナがあるようなのでそれを使います。
と、その前にホスト機のディレクトリをマウントしたいので設定を行います。

* 画面右下のタスクトレイにてDockerアイコンを右クリック→「Settings」

![img1.png](https://qiita-image-store.s3.amazonaws.com/0/212112/8abc0a70-76b0-efde-4b88-09c6ea0635c6.png)

* 「shared drives」→「shared」にチェック→「Apply」をクリック

![img2.PNG](https://qiita-image-store.s3.amazonaws.com/0/212112/6f16d1f9-c658-0f7b-ccca-c5760e5e77c4.png)

これでいったんOKです。
こうすることによってホスト機のディレクトリをマウントすることができます。
ローカルでいじった内容がそのままコンテナ上に反映されれるのでいちいちコピーしたり、
コンテナ上でソースいじったりする必要がなくなりますね。

では、コンテナを起動していきます。
今回はPython3.6を使います。
コマンドプロンプト上で以下のようなコマンドを実行します。
<pre>
$ docker run -v [マウントしたいローカルのディレクトリパス]:[コンテナ上のマウント先のパス] -it --name hogehoge python:3.6 /bin/bash
</pre>

* --name hogehoge コンテナ名を付けて起動
* python:3.6 python3.6のコンテナ使って起動。「:3.6」と指定しなければ最新が入るのかな？
* /bin/bash bashで入る！(適当)
* -it コンテナ起動してそのまま操作できる！

これで起動します。
起動したらそのまま操作画面に入っているはずなので、バージョンなどを確認してみます。
<pre>
\# python --version
Python 3.6.3

\# pip --version
pip 9.0.1 from /usr/local/lib/python3.6/site-packages (python 3.6)
</pre>
こんな感じになっていれば大丈夫です。

# クローラー作成の準備

今回は「scrapy」を使います。
読み方は「スクラピー」らしいです。

インストールするのですがpipでインストールすればいいので簡単です。
<pre>
\# pip install scrapy
</pre>

これで、クローラーを作る準備が整いました。

# 実際に作る

実際に作ります。
まずはscrapyのプロジェクトと呼ばれるものを作成します。
コンテナ起動時にディレクトリをマウントしているのでそこへ移動します。
<pre>
\# cd [マウントしたディレクトリのパス]
</pre>
移動したら、以下のコマンドでプロジェクトを作成します。
<pre>
\# scrapy startproject [プロジェクト名]
</pre>
プロジェクトディレクトリが作成されます。
ホスト機の方にもできているはずなので確認してください。

次に「spider」を作成します。
ここからの作業はマウントされたディレクトリ内での作業なので
ホスト機、コンテナ内どちらで作業しても問題ありません。
プロジェクトディレクトリ内に「spiders」というディレクトリがあるので移動します。
「spiders」ディレクトリ内に任意の名前でファイルを作成します。
今回は「crawl_test.py」という名前で作成したことにします。
では、実際にプログラムを書いていきます。
まずはお決まりでscrapyをよんであげます。

```python:crawl_test.py
import scrapy
```

クラスを生成

```python:crawl_test.py
class Crawltest(scrapy.Spider):
```

spiderの名前を設定。
実際に動かすときに使います。

```python:crawl_test.py
name = 'crawl'
```

クローリング開始URLを指定

```python:crawl_test.py
start_urls = ['https://www.n-create.co.jp/pr/column/']
```
parseメソッドを呼び出す。

```python:crawl_test.py
def parse(self,response):
```

スクレイピングする部分
for文を使ってコラムの数だけ処理を繰り返してます。
情報自体はcssをつかって取得。

```python:crawl_test.py
for column in response.css('div.layer-content'):
    yield {
        'title': column.css('.element_1::text').extract(),
        'title_selector': column.css('.element_1::text'),
        'url':column.css('.element_3::attr(href)').extract()
    }
```

クローリングする部分
1ページだけ取得しても物足りないので
次のページへにリンクを取得して全ページ分取ります。
なお、次のページへのリンクがなくなった時点で処理を終了します。

```python:crawl_test.py
next_page = response.css('.next::attr(href)').extract_first()
if next_page is not None:
    yield scrapy.Request(next_page)
```

そして、各処理をこねこねして完成したものがこちらになります。

```python:crawl_test.py
import scrapy

class Crawltest(scrapy.Spider):
    # 呼び出す際の名前
    name = 'crawl'
    # クローリング開始URL
    start_urls = ['https://www.n-create.co.jp/pr/column/']

    def parse(self,response):
        # スクレイピング部
        for column in response.css('div.layer-content'):
            yield {
                'title': column.css('.element_1::text').extract(),
                'title_selector': column.css('.element_1::text'),
                'url':column.css('.element_3::attr(href)').extract()
            }
        # クローリング部
        next_page = response.css('.next::attr(href)').extract_first()
        if next_page is not None:
            yield scrapy.Request(next_page)
```
できました。

# 動かしてみる

以下のコマンドで動かせます。
<pre>
\# scrapy crawl [spiderの名前]
</pre>

これで終わりです。
初めてのPython、初めてのDockerだったので、
間違ってたり説明が拙いところがあるかもしれませんが、
コメントでご指摘いただければと思います。

もうすぐクリスマスなので、
Twitterの怨念のこもった声とか拾い集めると面白いかもしれないですね。