概要
- モチベーション:日々追加されるニュースについて、その日時・ヘッドライン・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. スクレイプしたデータの絞り込み:個々のニュース
-
- において
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形式の方が良い場合はこちら