Pythonを利用したスクレイピング 

# 概要
* モチベーション：日々追加されるニュースについて、その日時・ヘッドライン・urlを集めたい
* BeautifulSoupを利用する
* なお、本稿はPython 3.x系, UNIXの利用を前提とした記述となっています。

# 
## 0. パッケージ
* スクレイピングに利用：`BeautifulSoup`, `httplib2`, `time`
* データ格納に利用：`re`, `pandas`, `datetime`

```
import pandas as pd
from bs4 import BeautifulSoup
import httplib2
import re
import time
from datetime import datetime
```

## 1. Webデータのスクレイプ
* url末尾が動的に変化するページを対象とする（e.g. `http://~/page/1`; `http://~/page/2`; ... etc.）
* 以下1〜5ではでは`num = 1`として説明。任意の整数が入ることを想定している。
* ここでは、```soup = BeautifulSoup(content, 'lxml')```により、```soup```に当該ページのデータを格納するのが目標。

```
num = 1
h = httplib2.Http('.cache')
base_url = "http://~/page"
url=base_url+ str(num)
response, content = h.request(url)
content = content.decode('utf-8')
soup = BeautifulSoup(content, 'lxml')
```

## 2. スクレイプしたデータの絞り込み：個々のニュース
* 1. において```soup```に格納されたデータのうち、必要な情報を抜き出す（投稿日時・ヘッドライン・記事のurlについて、個々のニュースをリストアップする）
* ここでは、 ```'div'```タグがついたもののうち、```"id"```の値が```"primary"```であるものを抽出し、```data```に格納する（ホームページに含まれる様々な情報のうち、個々のニュースに関する情報のみを抽出）。

```
data = soup.find_all('div',  {"id": "primary"})
```

## 3. 日時の取得・タイムスタンプの整形
* 2.にて得た`data`に含まれるニュースのリストから、そのタイムスタンプを抽出し、```dates```に格納する。```data```は```find_all```にて抽出を行っているため、そこからさらに```find_all```で抽出を行う際に、```data[0]```と指定している点に注意。
* ```dates```のデータは、日時だけでなく、時刻等の情報を含んでいる。ここでは日時のみを使用するため、日時のみを抽出し、```temp```に格納する。
* さらに、```temp```をdatetime型に変換し、```list```に格納する。元データに```%d/%m/%Y```のタイプと、```%Y-%m-%d```のタイプが混在しているため、```index```を用いて場合分けを行い、datetime型に変換している。

```
dates = data[0].find_all('span', class_ = 'posted-on')

temp = []
for item in dates:
        date = item.text
        temp.append(date[1:11].split())
dlist = []
for item in temp:
    index = item[0].find("/")
    if index != -1:
        dlist.append(datetime.strptime(item[0], '%d/%m/%Y').date())
    else:
        dlist.append(datetime.strptime(item[0], '%Y-%m-%d').date())
```

## 4. ヘッドライン、urlの取得
* 2.で得た```data```に含まれるニュースのリストから、個々のニュースのヘッドライン・urlを取得し、```newdata```に格納する。
* そのそれぞれを、```tlist```(ヘッドライン... タイトルのt), ```ulist```(url)に格納する。
* ここでは、ヘッドラインについて、エスケープシークエンス(\n|\r|\t)を取り除く作業を行なっている。


```
newdata = data[0].find_all('h2', class_ ='entry-title')
tlist = []
ulist = []
for item in newdata:
    urls = re.search('href="(?P<URL>.+?)"', str(item)).group('URL')
    titles = item.get_text()
    ulist.append(urls)
    tlist.append(re.sub(r'\n|\r|\t', '', titles))
```

## 5. 取得した情報をデータフレームにまとめる
* ここでは```pandas```を利用し、目標であるヘッドライン一覧（日時・記事タイトル・url）のデータフレームを作成する。
* このデータフレームが最終的に求める結果となる。

```
list_headline = pd.DataFrame({'date':dlist,
                            'headline':tlist,
                            'url':ulist})
```

## 6. 関数化
* 1.に記したように、ここまでは```num = 1```として説明を進めてきたが、以下では、複数のページに対して同じ作業を行う場合を考える。
* 各ページが同じ構造をしていると仮定すれば、以上の1.~4.を関数化し、ページの切り替えを変数にて司るのが有効であるといえる。
* ここでは、```num```を変数として設定し、```num```の値に即し、同じ構造のページが自動的に取得できるとする。（1.における```url=base_url+ str(num)```がこれを定義している）
* 関数化するには、```def```にて関数名（ここでは```headline```）と変数（ここでは```num```）を宣言し、関数の中身はインデントして記述する（詳細は下記「実際のコード」を参照）。
* 最後に、（5.で述べたように、この作業で最終的に求める結果である）データフレームを返り値として指定している。

```
def headline(num):
    h = httplib2.Http('.cache')
    base_url = "http://~/page"
    url=base_url+ str(num)
# 中略 #
    return list_headline

```

## 7. コードの繰り返し実施
* ここでは、`num`の値が1から5をとるとして、コードを実施するとする。
* まず、`num=1`の場合に関数を実行し、`headlines`に格納する。空のオブジェクトに対してループを使った格納が行えないためである。
* サーバーへの負担回避のため、スクリプトの実行に5秒間の待機時間を設定している(`time.sleep(5)`)。
* その上で、`num`が2から5を取る場合について、`for`を用いて繰り返し関数を適用し、得られた結果を`headlines`に追加していく（既存の`headlines`のデータフレームに対し、新たなデータフレームを積み上げていくイメージ）。
* `print(i)`はエラーチェックのために利用している。

```
headlines = headline(1)
time.sleep(5)

for i in range (2,5):
    temp = headline(i)
    headlines = pd.concat([headlines, temp]) 
    time.sleep(5)
    print (i)
```

## 8. 保存
* 7.によって得られた結果を保存する。
* ここでは、.csv, .xlsxの2つの保存方法を紹介している。

```
#headlines.to_csv(datetime.today().strftime("%Y%m%d")+'FILENAME.csv') ## 基本的には.csvの方が利用しやすくおすすめ
headlines.to_excel('/Users/USERNAME/FOLDERNAME/'+ datetime.today().strftime("%Y%m%d")+'FILENAME.xlsx') ## excel形式の方が良い場合はこちら
```


# 実際のコード
* 以上、1~8をまとめると、以下のようなコードとなる。
* ホームページアドレス(`base_url`)、保存先（上記8.参照）は架空の数値を挿入しているため、このコードを直接利用しても結果が得られない点は留意願いたい。
* また、ホームページの構造により、ページアドレスのナンバリング（本稿ではアドレスの末尾が`page1`, `page2`, ... と変化していくと想定）が異なるほか、タグの構成も千差万別である。実際の利用にあたっては、ページのソースコードをよく確認されたい。
* スクレイピングを禁止しているホームページも存在する。よく確認するようにしてほしい。

```
import pandas as pd
from bs4 import BeautifulSoup
import httplib2
import re
import time
from datetime import datetime

def headline(num):
    h = httplib2.Http('.cache')
    base_url = "http://~/page"
    url=base_url+ str(num)
    response, content = h.request(url)
    soup = BeautifulSoup(content, 'lxml')
    data = soup.find_all('div',  {"id": "primary"})
    dates = data[0].find_all('span', class_ = 'posted-on')
    temp = []
    for item in dates:
            date = item.text
            temp.append(date[1:11].split())
    dlist = []
    for item in temp:
        index = item[0].find("/")
        if index != -1:
            dlist.append(datetime.strptime(item[0], '%d/%m/%Y').date())
        else:
            dlist.append(datetime.strptime(item[0], '%Y-%m-%d').date())

    newdata = data[0].find_all('h2', class_ ='entry-title')
    tlist = []
    ulist = []
    for item in newdata:
        urls = re.search('href="(?P<URL>.+?)"', str(item)).group('URL')
        titles = item.get_text()
        ulist.append(urls)
        tlist.append(re.sub(r'\n|\r|\t', '', titles))


    list_headline = pd.DataFrame({'date':dlist,
                            'headline':tlist,
                            'url':ulist})
    return list_headline

headlines = headline(1)
time.sleep(5)

for i in range (2,5):
    temp = headline(i)
    headlines = pd.concat([headlines, temp]) 
    time.sleep(5)
    print (i)

#headlines.to_csv(datetime.today().strftime("%Y%m%d")+'FILENAME.csv')
headlines.to_excel('/Users/USERNAME/FOLDERNAME/'+ datetime.today().strftime("%Y%m%d")+'FILENAME.xlsx') ## excel形式の方が良い場合はこちら
```