PythonでWebスクレイピング

Web上のデータを、取得して、その構造を理解し、pandasのDataFrameにします。

いくつか注意すべき事項を並べておきます。

1.) Web上のデータだからと言って、勝手に利用していいとは限りませんので、利用する前に確認しておきましょう。

2.) プログラムで連続的にサーバに負荷をかけると、先方にブロックされて接続できなくなる危険性もありますので、注意しましょう。

3.) Webサイトのデザインが変わることは、日常茶飯ですので、時間が経ったら、コードを見直す必要があるかも知れません。

4.) Webページは、データの取得用に作られていませんので、取得したデータをクレンジングする(エラーなどを取り除いて整形する作業)する必要は大いにあります。

5.) というわけで、やはりWebページごとにコードを書いてデータ取得の方法をカスタマイズする必要はあります。

やはりHTMLについて知っておく必要はあります

書籍を使ってしっかり学ぶのが良いかと思いますが、以下の様なサイトも参考になると思います。

HTMLクイックリファレンス

W3School(英語)

準備

それぞれ、Webスクレイピングのために必要なモジュールです。セットアップされていない場合は、インストールして使えるようにしておきましょう。

1.) BeautifulSoup
http://www.crummy.com/software/BeautifulSoup/bs4/doc/
OSのコマンドプロンプト(ターミナル)で、
pip install beautifulsoup4
またはAnacondaを利用している場合
conda install beautifulsoup4

2.) lxml
http://lxml.de/
pip install lxml
または
conda install lxml

3.) requests
http://docs.python-requests.org/en/latest/
pip install requests
または
conda install requests

In [28]:
from bs4 import BeautifulSoup
import requests
In [29]:
import pandas as pd
from pandas import Series,DataFrame

サンプルデータは何でも良いんですが、ここではカリフォルニア大学のページから、予算に関する報告書のページを扱ってみます。もちろん他のページも良いですが、データの利用については、注意してください。

今回取得するデータのURLです。

In [30]:
url = 'http://www.ucop.edu/operating-budget/budgets-and-reports/legislative-reports/2013-14-legislative-session.html'

requestsでデータをとってきて、BeautifulSoupで中身を解析します。

In [31]:
# データを取得
result = requests.get(url)
c = result.content

# HTMLをもとに、オブジェクトを作る
soup = BeautifulSoup(c)

Beautiful Soupを使って、HTMLを扱う準備が出来ました。

In [32]:
# 目的の部分を切り出します。
summary = soup.find("div",{'class':'list-land','id':'content'})

# tableを見つけます。
tables = summary.find_all('table')

通常、HTMLのテーブルでは、trタグで1行を表現し、tdタグでその中にいくつか列を作っていくイメージです。

ですので、trを見つけて、その中からtdを探し出します。

ここで紹介するのは、Beautiful Soupのごくごく一部の機能です。詳しく知りたい方はドキュメントを参照してください。.

In [33]:
# データを格納するためのリストです。
data = []

# テーブルから行をすべて探し出します。
rows = tables[0].find_all('tr')

# 行から、それぞれのcellを取り出して画面に表示しつつ、dataに格納します。
for tr in rows:
    cols = tr.find_all('td')
    # textを探し出します。
    for td in cols:
        text = td.find(text=True) 
        print(text)
        data.append(text)    
1
08/01/13
2013-14 (EDU 92495) Proposed Capital Outlay Projects (2013-14 only) (pdf)
2
09/01/13
2014-15  (EDU 92495) Proposed Capital Outlay Projects (pdf)
3
11/01/13
Utilization of Classroom and Teaching Laboratories (pdf)
4
11/01/13
Instruction and Research Space Summary & Analysis (pdf)
5
11/15/13
Statewide Energy Partnership Program (pdf)
6
11/30/13
2013-23 Capital Financial Plan (pdf)
7
11/30/13
Projects Savings Funded from Capital Outlay Bond Funds (pdf)
8
12/01/13
Streamlined Capital Projects Funded from Capital (pdf)
9
01/01/14
Annual General Obligation Bonds Accountability (pdf)
10
01/01/14
Small Business Utilization (pdf)
11
01/01/14
Institutional Financial Aid Programs - Preliminary report (pdf)
12
01/10/14
Summer Enrollment (pdf)
13
01/15/14
Contracting Out for Services at Newly Developed Facilities (pdf)
14
03/01/14
Performance Measures (pdf)
15
03/01/14
Entry Level Writing Requirement (pdf)
16
03/31/14
Annual Report on Student Financial Support (pdf)
17
04/01/14
Unique Statewide Pupil Identifier (pdf)
18
04/01/14
Riverside School of Medicine (pdf)
19
04/01/14
SAPEP Funds and Outcomes - N/A
20
05/15/14
Receipt and Use of Lottery Funds (pdf)
21
07/01/14
Cogeneration and Energy Consv Major Capital Projects (pdf)






 
Future Reports


24
12-
Breast Cancer Research Fund
25
12-31-15
Cigarette and Tobacco Products Surtax Research Program
26
01-01-16
Best Value Program
27
01-01-16
California Subject Matter Programs
28
04-01-16
COSMOS Program Outcomes

おなじくdataにも入っています。

In [10]:
data
Out[10]:
['1',
 '08/01/13',
 '2013-14 (EDU 92495) Proposed Capital Outlay Projects (2013-14 only) (pdf)',
 '2',
 '09/01/13',
 '2014-15\xa0 (EDU 92495) Proposed Capital Outlay Projects (pdf)',
 '3',
 '11/01/13',
 'Utilization of Classroom and Teaching Laboratories (pdf)',
 '4',
 '11/01/13',
 'Instruction and Research Space Summary & Analysis (pdf)',
 '5',
 '11/15/13',
 'Statewide Energy Partnership Program (pdf)',
 '6',
 '11/30/13',
 '2013-23 Capital Financial Plan (pdf)',
 '7',
 '11/30/13',
 'Projects Savings Funded from Capital Outlay Bond Funds (pdf)',
 '8',
 '12/01/13',
 'Streamlined Capital Projects Funded from Capital (pdf)',
 '9',
 '01/01/14',
 'Annual General Obligation Bonds Accountability (pdf)',
 '10',
 '01/01/14',
 'Small Business Utilization (pdf)',
 '11',
 '01/01/14',
 'Institutional Financial Aid Programs - Preliminary report (pdf)',
 '12',
 '01/10/14',
 'Summer Enrollment (pdf)',
 '13',
 '01/15/14',
 'Contracting Out for Services at Newly Developed Facilities (pdf)',
 '14',
 '03/01/14',
 'Performance Measures (pdf)',
 '15',
 '03/01/14',
 'Entry Level Writing Requirement (pdf)',
 '16',
 '03/31/14',
 'Annual Report on Student\xa0Financial Support (pdf)',
 '17',
 '04/01/14',
 'Unique Statewide Pupil Identifier (pdf)',
 '18',
 '04/01/14',
 'Riverside School of Medicine (pdf)',
 '19',
 '04/01/14',
 'SAPEP Funds and Outcomes - N/A',
 '20',
 '05/15/14',
 'Receipt and Use of Lottery Funds (pdf)',
 '21',
 '07/01/14',
 'Cogeneration and Energy Consv Major Capital Projects (pdf)',
 '\n',
 '\n',
 '\n',
 '\xa0',
 'Future Reports',
 '\n',
 '24',
 '12-',
 'Breast Cancer Research Fund',
 '25',
 '12-31-15',
 'Cigarette and Tobacco Products Surtax Research Program',
 '26',
 '01-01-16',
 'Best Value Program',
 '27',
 '01-01-16',
 'California Subject Matter Programs',
 '28',
 '04-01-16',
 'COSMOS Program Outcomes']

pdfがある行だけ抜き出すことにしましょう。¥xa0はエラーなので、空白に置き換えます。

In [34]:
reports = []
date = []

# 行を保持する変数を用意します。
index = 0

# pdfの文字列があるcellを見つけだします。
for item in data:
    if 'pdf' in item:
        # 1つもどって、日付を格納します。
        date.append(data[index-1])
        
        # \xa0 を取り除いておきましょう。
        reports.append(item.replace(u'\xa0', u' '))
                    
    index += 1

'\xa0 'はユニコードのエラーなのですが、Webページは必ずしも綺麗に整形されているデータではありませんので、このように予期せぬ出来事が頻発します。その都度、調べて解決していけば、きっとスキルが上がります。

あとはデータをpandasのDataFrameにするだけです。

In [35]:
# まずはそれぞれをSeriesにします。
date = Series(date)
reports = Series(reports)
In [36]:
# 連結してDataFrameにします。
legislative_df = pd.concat([date,reports],axis=1)
In [37]:
# 列名を付けて置きましょう。
legislative_df.columns = ['Date','Reports']
In [38]:
# 完成です。
legislative_df
Out[38]:
Date Reports
0 08/01/13 2013-14 (EDU 92495) Proposed Capital Outlay Pr...
1 09/01/13 2014-15 (EDU 92495) Proposed Capital Outlay P...
2 11/01/13 Utilization of Classroom and Teaching Laborato...
3 11/01/13 Instruction and Research Space Summary & Analy...
4 11/15/13 Statewide Energy Partnership Program (pdf)
5 11/30/13 2013-23 Capital Financial Plan (pdf)
6 11/30/13 Projects Savings Funded from Capital Outlay Bo...
7 12/01/13 Streamlined Capital Projects Funded from Capit...
8 01/01/14 Annual General Obligation Bonds Accountability...
9 01/01/14 Small Business Utilization (pdf)
10 01/01/14 Institutional Financial Aid Programs - Prelimi...
11 01/10/14 Summer Enrollment (pdf)
12 01/15/14 Contracting Out for Services at Newly Develope...
13 03/01/14 Performance Measures (pdf)
14 03/01/14 Entry Level Writing Requirement (pdf)
15 03/31/14 Annual Report on Student Financial Support (pdf)
16 04/01/14 Unique Statewide Pupil Identifier (pdf)
17 04/01/14 Riverside School of Medicine (pdf)
18 05/15/14 Receipt and Use of Lottery Funds (pdf)
19 07/01/14 Cogeneration and Energy Consv Major Capital Pr...

Webスクレイピングは楽しいですが、面倒です。

これを生業にしている企業もあるようです。ご興味があれば、チェックしてみてください。

https://import.io/

https://www.kimonolabs.com/