35
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-21

はじめに

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

まず最初にやること

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

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ページから任意のデータを抽出する〜

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

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

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

 データが保存できるようになったので次は実際のページからデータをとってくるところを行いたいと思います。。みるページは以下のサイトにします
警視庁管内不審者情報

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

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が含まれる場合データを取ることを断念)

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

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()

終わりに

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?