スクレイピング初心者が３時間でスクレイピングをやってみた

# はじめに
　スクレイピングしてみたいなぁと思いつつ<font color=#ff0000>クローラー／Webスクレイピング Advent Calendar 2017</font>に参加しました。ただ仕事や忘年会の準備をしていると気づけば当日のお昼...
ということでスクレイピングをやったことないですが、3時間でなんとかしていきたいと思います...笑

# まず最初にやること
　何はともあれやってみるしかないんで```スクレイピング 初心者```でググってみるところからスタート
とにかくコードが載っているページのやつを試しまくります
使用した環境が以下の環境（docker）です

```groovy:Dockerfile
FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

CMD [ "python", "./train2.py" ]

# docker build -t tmp-train .
# docker run -v `pwd`:/usr/src/app tmp-train
```

# Lv1
 ひとまず以下のページからコピペしまくる
[【初心者のためのPython入門】Webスクレイピング〜Webページから任意のデータを抽出する〜](http://su-gi-rx.com/2017/07/19/python_5/)

```python:train1.py
# python3
# bs-select.py
from bs4 import BeautifulSoup
import urllib.request as req

url = "http://su-gi-rx.com/2017/07/16/python_4/"

# urlopen()でデータを取得
res = req.urlopen(url)

# BeautifulSoup()で解析
soup = BeautifulSoup(res, 'html.parser')

# 任意のデータを抽出
title1 = soup.find("h1").string
print("title = ", title1)

p_list = soup.find_all("p")
print("text = ", p_list)
```

なんかHTML取れた！
意外と早くできそう（15分経過）！

# Lv2
　ページは取れたので次にDBへ入れてみたいと思います。DBの準備も簡単に終わらせるためにsqliteで行います。
(ここでDBの情報を残すためにdockerをマウントさせました。```docker run -v `pwd`:/usr/src/app tmp-train```)
というわけで`sqlite python`でググると以下のページを発見

[Pythonでsqlite](https://qiita.com/mas9612/items/a881e9f14d20ee1c0703)

ひとまずコピペで動いたのでそれをさっきのコードに連結
dockerfileの変更と型変換関連で時間をとってしまい30分近く使ってしまった...

```python:train2.py
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup
import urllib.request as req
import sqlite3

url = "http://su-gi-rx.com/2017/07/16/python_4/"
dbname = 'database.db'
conn = sqlite3.connect(dbname)
c = conn.cursor()
table_name = 'test'

# train1の部分
def get_html():
    # urlopen()でデータを取得
    res = req.urlopen(url)

    # BeautifulSoup()で解析
    soup = BeautifulSoup(res, 'html.parser')

    # 任意のデータを抽出
    title1 = soup.find("h1").string
    p_list = soup.find_all("p")

    return [(str(title1), str(p_list))]

# train2の部分
def create_table(tname):
    # executeメソッドでSQL文を実行する
    create_table = '''create table if NOT EXISTS {} (title varchar(64), p_list varchar(32))'''.format(tname)
    c.execute(create_table)

def insert_data(tname, data):
    # 一度に複数のSQL文を実行したいときは，タプルのリストを作成した上で
    # executemanyメソッドを実行する
    insert_sql = 'insert into {0} (title, p_list) values (?,?)'.format(tname)
    c.executemany(insert_sql, data)
    conn.commit()


if __name__ == '__main__':
    create_table(table_name)
    test = get_html()
    insert_data(table_name, test)

    select_sql = 'select * from {0}'.format(table_name)
    for row in c.execute(select_sql):
        print(row)

    conn.close()
```

# Lv3
　データが保存できるようになったので次は実際のページからデータをとってくるところを行いたいと思います。。みるページは以下のサイトにします
[警視庁管内不審者情報](http://www.keishicho.metro.tokyo.jp/kurashi/higai/kodomo/fushin/index.html)

正規表現などで手こずってしまって結構時間がかかりました...（１時間くらい）

```python:train3.py
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup
import urllib.request as req
import sqlite3
import re

url = "http://www.keishicho.metro.tokyo.jp/kurashi/higai/kodomo/fushin/20_fushin.html"
dbname = 'database.db'
conn = sqlite3.connect(dbname)
c = conn.cursor()
table_name = 'test'

# train1の部分
def get_html():
    # urlopen()でデータを取得
    res = req.urlopen(url)

    # BeautifulSoup()で解析
    soup = BeautifulSoup(res, 'html.parser')

    # 任意のデータを抽出
    main = soup.find("div", id="main").find("div", class_="main_inner").find("div", class_="t-box2")
    news_list = main.find("table").find_all("tr")

    res = []
    for news in news_list:
        res += get_res(news)

    return res

# 記事から情報を抜き出すところ
def get_res(news):
    place = news.find('th').string
    main = news.find('td').find('p').text
    main_list = re.match('(.*警察署)([0-9]{1,2})月([0-9]{1,2})日（(.)曜）、(.*)ころ、(.*)不審者の特徴：(.*)', main).groups()
    police = main_list[0]
    date = main_list[1] + main_list[2] + main_list[3]
    time = main_list[4]
    body = main_list[5]
    feature = main_list[6]

    return [(place, police, date, time, body, feature)]


def drop_table(tname):
    # executeメソッドでSQL文を実行する
    create_table = '''drop table if EXISTS {} '''.format(tname)
    c.execute(create_table)

# train2の部分
def create_table(tname):
    # executeメソッドでSQL文を実行する(型はひとまずcharで入れてます...)
    create_table = '''
create table if NOT EXISTS {} (
    place varchar(64),
    police varchar(32),
    date varchar(64),
    time varchar(64),
    body varchar(64),
    feature varchar(64))'''.format(tname)
    c.execute(create_table)

def insert_data(tname, data):
    # 一度に複数のSQL文を実行したいときは，タプルのリストを作成した上で
    # executemanyメソッドを実行する
    insert_sql = 'insert into {0} (place, police, date, time, body, feature) values (?,?,?,?,?,?)'.format(tname)
    c.executemany(insert_sql, data)
    conn.commit()

def select_all(tname):
    select_sql = 'select * from {0}'.format(table_name)
    res = ""
    for row in c.execute(select_sql):
        res += str(row)
    return res


if __name__ == '__main__':
    drop_table(table_name)
    create_table(table_name)
    test = get_html()
    insert_data(table_name, test)
    print(select_all(table_name))


    conn.close()
```

# Lv4
12月文の情報が取れたということで月をどんどん変えてとっていきたいと思います。具体的には以下のURLの`**`を変えて情報をとってきます
`http://www.keishicho.metro.tokyo.jp/kurashi/higai/kodomo/fushin/**_fushin.html`

数ページみるといろいろ問題が発生

- 小文字と大文字の違い（`不審者の特徴`の後の:）
- bodyの中でhtmlを使っている場合があった...（例外処理で回避、htmlが含まれる場合データを取ることを断念）

かなりコードが汚くなってきたのでこのコードは読み飛ばしちゃっていいです

```python:train3.py
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup
import urllib.request as req
import sqlite3
import re

url = "http://www.keishicho.metro.tokyo.jp/kurashi/higai/kodomo/fushin/"
dbname = 'database.db'
conn = sqlite3.connect(dbname)
c = conn.cursor()
table_name = 'test'

# train1の部分
def get_html(url):
    try:
        # urlopen()でデータを取得
        res = req.urlopen(url)
    except:
        # ページがない時など
        return False

    # BeautifulSoup()で解析
    soup = BeautifulSoup(res, 'html.parser')

    # 任意のデータを抽出
    main = soup.find("div", id="main")
    news_list = main.find("div", class_="main_inner").find("div", class_="t-box2").find("table").find_all("tr")

    res = []
    for news in news_list:
        tmp = get_res(news)
        if not tmp:
            continue
        res += tmp

    return res

# 記事から情報を抜き出すところ
def get_res(news):
    place = news.find('th').string
    main = news.find('td').find('p').text
    try:
        main_list = re.match('(.*警察署)([0-9]{1,2})月([0-9]{1,2})日（(.)曜）、(.*)ころ、(.*)不審者の特徴[：|:](.*)', main).groups()
    except:
        # 上記の表現で書かれていない場合
        return False
    police = main_list[0]
    date = main_list[1] + main_list[2] + main_list[3]
    time = main_list[4]
    body = main_list[5]
    feature = main_list[6]

    return [(place, police, date, time, body, feature)]


def drop_table(tname):
    # executeメソッドでSQL文を実行する
    create_table = '''drop table if EXISTS {} '''.format(tname)
    c.execute(create_table)

# train2の部分
def create_table(tname):
    # executeメソッドでSQL文を実行する(型はひとまずcharで入れてます...)
    create_table = '''
create table if NOT EXISTS {} (
    place varchar(64),
    police varchar(32),
    date varchar(64),
    time varchar(64),
    body varchar(64),
    feature varchar(64))'''.format(tname)
    c.execute(create_table)

def insert_data(tname, data):
    # 一度に複数のSQL文を実行したいときは，タプルのリストを作成した上で
    # executemanyメソッドを実行する
    insert_sql = 'insert into {0} (place, police, date, time, body, feature) values (?,?,?,?,?,?)'.format(tname)
    c.executemany(insert_sql, data)
    conn.commit()

def select_all(tname):
    select_sql = 'select * from {0}'.format(table_name)
    res = ""
    for row in c.execute(select_sql):
        res += str(row)
    return res


if __name__ == '__main__':
    drop_table(table_name)
    create_table(table_name)
    for i in range(1,30):
        _url = url + str(i) + '_fushin.html'
        test = get_html(_url)
        if not test:
            continue
        insert_data(table_name, test)
    print(select_all(table_name))


    conn.close()
```

# 終わりに
　途中から測るのがめんどくさくなったので３時間以内でどこまで行ったのか微妙です...笑。途中で感じたのはスクレイピング自体は割とすぐできるけどそれ使って何やるのかを考えるのが難しい気がしました。（欲しい情報にはだいたいAPIが実装されていたりしますし）時間があれば本文、特徴を形態素分析したりしてみたかったですね。最後に得られた不審者情報（数ヶ月分）の簡単なサマリーを載せておきます。
平日の方が不審者が多く報告されているみたいですね

| 属性      |       値 |
|:-----------------|:------------------|
|レコード数             |         10/23~12/19 51(データ少ない...)|
|場所別順位           | 足立区11件 > 葛飾区6件 > 大田区・豊島区5件 |
|曜日別順位             | 月14 > 火11 > 木8 > 金6 > 水5 > 土4 > 日3 |