見出し画像

Pythonを使ったスクレイピング #Granvalley

皆様はじめまして。グランバレイ社員兼釣りビギナーのJim2@GVと申します。最近は家に帰って SUPER SUZY を読むのが楽しみな、ひよっこエンジニアです。
AIエンジニアを夢見て今年の9月からPythonを勉強し始めた私ですが、このブログでは私が日々勉強して得た知識や経験を紹介していこうと思います。
これからPythonや機械学習を勉強する方への参考になれば幸いです。

今回は業務の中でプログラムを組む機会のあったPythonでのスクレイピング方法を紹介したいと思います。

1.スクレイピングとは


プログラムの内容へ進む前に、スクレイピングって何?という方へ簡単にご説明します。
スクレイピングとはWebスクレイピングの略称で、Webページの情報を抽出・収集することを指します。
プログラミング言語と組み合わせれば、複数のWebページからデータを自動で抽出できたりもします。
とても便利そうですね。

ただし、使用時には“抽出データの利用目的が法に抵触しないか”や“対象のWebサイトがスクレイピングを許可しているか”などを確認する必要がありますので、注意してください。

※スクレイピングに対し違法性があるという認識者も存在するため。


2.コード


では、さっそく本編へ。

今回は、当社のホームページ(https://www.granvalley.co.jp/)をターゲットにスクレイピングを行っていきます。

blog_1_HP画像

ホーム画面です。

blog_1_HP画像2

AIサービスのページです。
グランバレイでは定期的に機械学習のセミナーを開催していて、これはその開催日などを一覧で表示しているページです。

今回は、このセミナーのリストをCSVファイルへ出力していきます。


作成したプログラムはこんな感じ。

◆ コード ◆

## 必要なライブラリのインポート --------------
# データ加工
import pandas as pd
import numpy as np

# スクレイピング
from bs4 import BeautifulSoup
import requests

# ログ抽出
import logging.config
logging.config.fileConfig('./logging.conf')
logger = logging.getLogger()
import traceback

# 変数セット ------------------------------
URL = 'https://www.granvalley.co.jp/ai'
csvpath = './seminarsinfo.csv'

     
# スクレイピング ---------------------------
try:
    # URLを開く
    response = requests.get(URL)
    # HTMLを取得
    soup = BeautifulSoup(response.content, 'html.parser')
    logger.info('Webサイトと接続できました')
except:
    tb = traceback.format_exc()
    logger.error(tb)
    logger.info('Webサイトとの接続が確立できませんでした')

# HTML上の任意の箇所を抽出
soup = soup.find_all('span',['date','cat','place','ttl'])
# テキストのみをリストで取得
soup_list = [x.text for x in soup]


# 成形のため配列に格納
soup_array = np.array(soup_list)
# 2次元配列に変換
shape_w = 4
shape_h = soup_array.size / shape_w
soup_array = soup_array.reshape(shape_h, shape_w)
# DFに変換
seminarsinfo = pd.DataFrame(soup_array)
# カラム名を指定
seminarsinfo = seminarsinfo.rename(columns={0:'日付',1:'カテゴリ',2:'開催地',3:'タイトル'})

# csv出力 -------------------------------
try:  
    seminarsinfo.to_csv(csvpath, index=False)
    logger.info('seminarsinfo.csvの書き込みが完了しました')

except:
    tb = traceback.format_exc()
    logger.error(tb)
    logger.info('seminarsinfo.csvの書き込みに失敗しました')

◆ 出力結果 ◆

日付,カテゴリ,開催地,タイトル
2019年10月31日(木)-1日(金),AI,東京,作ってわかる!人工知能-機械学習講座<中級編>
2019年11月13日(水),AI,東京,さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
2019年12月11日(水),AI,東京,さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
2019年12月19日(木)-20日(金),AI,東京,作ってわかる!人工知能-機械学習講座<中級編>

◆ 実行環境 ◆

・開発言語・・・・ Python 3.5.2
・ライブラリ・・・ BeautifulSoup4 4.5.1 / requests 2.13.0 / NumPy 1.14.2 /
          Pandas 0.20.3

※今回使用しているBeautifulSoup4、requests、NumPy、Pandasは外部ライブラリなので別途インストールが必要です。


今回は短いプログラムなのでログを取る必要はあまりありませんが、勉強のため取得しています。
不要であれば記述を削ってしまっても大丈夫です。
もしログを取得する場合は、実行するpyファイルと同じフォルダに、以下のファイルとlogという名前の空ファイルを置けば、ログファイル(scraping.log)が作成されます。
コード内でloggerモジュールをインポートすることもお忘れなく!


次項から、スクレイピングに関する記述を説明していきます。

3.コード説明


この項では、どのような意図でコードを書いているのかという部分を説明していこうと思っていますので、ライブラリなどについての詳細にはあまり触れません。
気になる方は、この項の最後に関連するリンクを掲載していますのでそちらをご覧ください。

それでは、まずはこの記述から。

response = requests.get(URL)
soup = BeautifulSoup(response.content, 'html.parser')

スクレイピングをするために必須の記述です。
URLを開き、WebページのHTMLやXMLをBeautifulSoup4の形式で変数に格納しています。
これによってPythonでWebページの情報が取得できるようになります。

ちなみに、soupの中身はこんな感じ。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width,initial-scale=1" name="viewport"/>
<link href="https://www.granvalley.co.jp/wp/wp-content/themes/granvalley/img/favicon.ico" rel="icon" type="image/vnd.microsoft.icon"/>
<meta content="グランバレイは、これらの課題に対して、クラウド上のデータを活用し、AI-人工知能-や機械学習、データ分析を通じて「ビジネスモデル創出」の支援や「業務の効率化/スリム化」を実現するサービスやソリューションを提供します。" name="description"/>
<meta content="AI,AI,AI,ANALYTICS,Analytics,Anaplan,Anaplan,BI,BOARD,cloud,GV,IBM,Infor,IoT,Microsoft,Oracle,Others,Pentaho,Qlik,Qlik Sense,QlikView,R,SAP,SAS,Sisense,Sisense,Tableau,TIPS,WingArc1st,Yellowfin,グランバレイ,モータースポーツアナリティクス,ロボティクス,事例,名古屋,大阪,投稿をトップに表示,提供サービス,新機能,東京,製品情報,製品評価,軽技Web,BIコンサルティング,経営分析,データ分析,ソリューション,SAP,BW,Qlik,Infor BI" name="keywords"/>
<meta content="Copyright (c) 2019 GRANVALLEY Co.,Ltd." name="copyright"/>
<title>AIサービス | グランバレイ株式会社</title>
<meta content="article" property="og:type"/>
<meta content="AIサービス | グランバレイ株式会社" property="og:title"/>
<meta content="グランバレイは、これらの課題に対して、クラウド上のデータを活用し、AI-人工知能-や機械学習、データ分析を通じて「ビジネスモデル創出」の支援や「業務の効率化/スリム化」を実現するサービスやソリューションを提供します。" property="og:description"/>
<meta content="https://www.granvalley.co.jp/wp/wp-content/themes/granvalley/img/ogp.png" property="og:image"/>
<meta content="https://www.granvalley.co.jp/ai" property="og:url"/>
<meta content="グランバレイ株式会社" property="og:site_name"/>
<meta content="96348" property="fb:admins"/>
<meta content="https://www.facebook.com/granvalley/" property="article:publisher"/>
<meta content="summary_large_image" name="twitter:card"/>
<meta content="@granvalleyjapan" name="twitter:site"/><link


# ・・・まだまだつづきます。

開いたURLのHTML(XML)をすべて取ってきます。
これでは目的の箇所がどこなのか全く分かりません。

なので、目的の箇所のみを抽出します。

その記述がこちら。

soup = soup.find_all('span',['date','cat','place','ttl'])

BeautifulSoup4のfind_allメソッドを使って、指定したタグおよびクラスのみを抽出しています。
対象のタグ、クラスは実際のHTML(XML)を確認してください。
今回は、spanタグのdate、cat、place、ttlというクラスを抽出しています。

ちなみに、この時のsoupの中身はこんな感じ。

# 見やすくするため改行していますが、実際は一行で出力されます。

[<span class="date">2019年10月31日(木)-1日(金)</span>,
<span class="cat" style="background-color:#8224e3;">AI</span>,
<span class="place" style="background-color:#1a9133;">東京</span>,
<span class="ttl">作ってわかる!人工知能-機械学習講座<中級編></span>,
<span class="date">2019年11月13日(水)</span>,
<span class="cat" style="background-color:#8224e3;">AI</span>,
<span class="place" style="background-color:#1a9133;">東京</span>,
<span class="ttl">さわってわかる!人工知能・機械学習講座<初級編><br/>じっくり1日コース</span>,
<span class="date">2019年12月11日(水)</span>,
<span class="cat" style="background-color:#8224e3;">AI</span>,
<span class="place" style="background-color:#1a9133;">東京</span>,
<span class="ttl">さわってわかる!人工知能・機械学習講座<初級編><br/>じっくり1日コース</span>,
<span class="date">2019年12月19日(木)-20日(金)</span>,
<span class="cat" style="background-color:#8224e3;">AI</span>,
<span class="place" style="background-color:#1a9133;">東京</span>,
<span class="ttl">作ってわかる!人工知能-機械学習講座<中級編></span>]

目的の部分だけを抽出できましたが、タグやら何やら余計なものがくっついています。
今回必要なのはテキスト部分のみなので、余計な部分を削り必要な部分だけを取得します。

その記述がこちら。

soup_list = [x.text for x in soup]

soupのテキスト部分のみを抽出し、リストに格納するという処理をこの一行で行っています。


ちょっと余談ですが、このコードは内包表記という書き方で記述をしています。Pythonではメジャーな記述で、コードの行数を減らすことができるのでとても便利です。
ちなみに、内包表記を使わない場合の記述はこんな感じ。

soup_list = []
for x in soup:
    soup_list = np.append(x.text)

出力結果は同じですが、内包表記のほうが見た目がすっきりしますね。


それでは、説明に戻ります。
ちなみにsoup_listの中身はこんな感じです。

['2019年10月31日(木)-1日(金)', 'AI', '東京', '作ってわかる!人工知能-機械学習講座<中級編>', '2019年11月13日(水)', 'AI', '東京', 'さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース', '2019年12月11日(水)', 'AI', '東京', 'さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース', '2019年12月19日(木)-20日(金)', 'AI', '東京', '作ってわかる!人工知能-機械学習講座<中級編>']

テキストのみを抽出することはできましたが、一行で出力されてしまいました。
このままでは見づらいので、NumPyを使って形を整えていきます。

その記述がこちら。

soup_array = np.array(soup_list)

shape_w = 4
shape_h = soup_array.size / shape_w
soup_array = soup_array.reshape(shape_h, shape_w)

まずはNumPyで処理できるようにするため、arrayメソッドを使ってsoup_listを1次元配列に変換します。
そのあと、shape_wとshape_hを指定していますが、ここでreshapeメソッドで変換する2次元配列の大きさを決めています。
shape_hについては数式によって値を求めていますが、これはHTML(XML)を取得するタイミングで変化する値だからです。
sizeメソッドで配列の要素数を取得し、これを列数で割ることで、変換する配列の行数を求めています。

この処理で、1次元配列をshape_h行4列に変換しました。
soup_arrayの中身はこんな感じ。

[['2019年10月31日(木)-1日(金)' 'AI' '東京' '作ってわかる!人工知能-機械学習講座<中級編>']
['2019年11月13日(水)' 'AI' '東京' 'さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース']
['2019年12月11日(水)' 'AI' '東京' 'さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース']
['2019年12月19日(木)-20日(金)' 'AI' '東京' '作ってわかる!人工知能-機械学習講座<中級編>']]

各回ごとに並んで、たいぶん見やすくなりました。
もっと見やすくするために、項目名を表示させたいと思います。
項目名の表示には、Pandasのデータフレームを使います。

その記述がこちら。

seminarsinfo = pd.DataFrame(soup_array)

まず、先ほど形を整えた2次元配列soup_arrayをデータフレームに変換します。
データフレームに変換することで、自動的にインデックス名とカラム名をくっつけてくれます。

                       0   1   2                                  3
0   2019年10月31日(木)-1日(金)  AI  東京            作ってわかる!人工知能-機械学習講座<中級編>
1         2019年11月13日(水)  AI  東京  さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
2         2019年12月11日(水)  AI  東京  さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
3  2019年12月19日(木)-20日(金)  AI  東京            作ってわかる!人工知能-機械学習講座<中級編>

こんな感じです。
ちなみに、行ごとにくっついているのがインデックス名、列ごとにくっついているのがカラム名です。
しかし、自動でくっついたものはただの数値なので、わかりやすいように日本語にします。

その記述がこちら。

seminarsinfo = seminarsinfo.rename(columns={0:'日付',1:'カテゴリ',2:'開催地',3:'タイトル'})

renameメソッドでカラム名を任意の文言に変換しています。

変換したものがこちら。

                      日付 カテゴリ 開催地                               タイトル
0   2019年10月31日(木)-1日(金)   AI  東京            作ってわかる!人工知能-機械学習講座<中級編>
1         2019年11月13日(水)   AI  東京  さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
2         2019年12月11日(水)   AI  東京  さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
3  2019年12月19日(木)-20日(金)   AI  東京            作ってわかる!人工知能-機械学習講座<中級編>

列名が表示されました。
あとはCSVファイルへ出力するだけですが、データフレームに変換したときにくっついてきたインデックス名が残っています。
あってもいいけどできれば削除したい・・・
そんな時はto_csvメソッドでインデックスの表示を変更することができます。

その記述がこちら。

seminarsinfo.to_csv(csvpath, index=False)

引数のindexにFalseを記述することで、CSVファイルへ出力するデータにはインデックス名が表示されなくなります。

出力されたCSVファイルがこちら。

日付,カテゴリ,開催地,タイトル
2019年10月31日(木)-1日(金),AI,東京,作ってわかる!人工知能-機械学習講座<中級編>
2019年11月13日(水),AI,東京,さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
2019年12月11日(水),AI,東京,さわってわかる!人工知能・機械学習講座<初級編>じっくり1日コース
2019年12月19日(木)-20日(金),AI,東京,作ってわかる!人工知能-機械学習講座<中級編>

無事にデータを取得することができました!

◆ 関連リンク ◆

*Python公式ドキュメント

*BeautifulSoup4

*requests

*NumPy

*Pandas


4.おわりに


皆さんが取得したいWebページの仕様によっては、このプログラムを使ってもうまくいかない場合があるかもしれませんが、スクレイピングの流れとしては大体こんな感じかと思います。
今回初めてスクレイピングに挑戦しましたが、日本語の文字化けや取得タイミングによるWebページ画面の変化などに苦戦しました・・・。
今回は文字のみ抽出しましたが、画像などの抽出もやってみたいですね。

次回は機械学習についての記事を書こうかなと思っています。
それでは、ここまで読んでくださりありがとうございました!


※グランバレイにご興味のある方はこちらをご覧ください!


この記事が気に入ったらサポートをしてみませんか?