LoginSignup
3
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-23

概要

  • モチベーション:日々追加されるニュースについて、その日時・ヘッドライン・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に格納する。datafind_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形式の方が良い場合はこちら
3
5
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
3
5