Pythonによるクローリング・スクレイピング入門 – WordPressのページの内容を取得する

 こんにちは。データサイエンスチーム tmtkです。
 この記事では、クローリング・スクレイピングの入門を行います。題材として、WordPressで作られている当ブログ:DATAHOTEL Tech Blog | NHN テコラス株式会社の記事の情報を取得します。

はじめに

 ウェブページから(自動的に)情報を収集することをクローリングスクレイピングといいます。たとえば、Googleが検索エンジンに載せるためにGooglebotにいろいろなページの情報を収集させているのはクローリングの一例です。クローリングとスクレイピングはあまり区別されないことが多いようですが、クローリングはリンクを辿ってウェブページを移動していくことを指し、スクレイピングはウェブの情報を抽出することを厳密には指すようです。
 クローリング・スクレイピングはデータ分析・機械学習をするうえで、分析データの収集や教師データの作成などのために使われます。そのため、データサイエンティストには必須の技術です。
 この記事では、Python3とそのライブラリを使って当ブログの記事をスクレイピングしてみることで、スクレイピングの方法の実際を紹介します。

スクレイピングが禁止されていないか確認

 この記事では当ブログ https://techblog.nhn-techorus.com をスクレイピングします。スクレイピングをする前に、スクレイピングしようとしているウェブサイトがクローリングなどを拒否していないか確認します。

robots.txt

 ウェブサイトのルートに robots.txt を配置することで、検索エンジンのクローラにアクセスされたくないサイトを指定することができます。当ブログの場合は、 https://techblog.nhn-techorus.com/robots.txt です。これを確認すると、その内容は

User-agent: *
Disallow: /techblog/adm/wp-admin/
Allow: /techblog/adm/wp-admin/admin-ajax.php
Sitemap: https://techblog.nhn-techorus.com/sitemap.xml

となっており、普通のページのクローリングを拒絶する記述は見つからないことが確認できます。したがって、スクレイピングをしても大丈夫そうだと推測できます。
 その他必要に応じて、robots metaタグや著作権、ウェブサービスの利用規約などについても確認します。

ページ構造の観察

 スクレイピングに取り掛かる前に、まずは普通のブラウザで当ブログにアクセスし、ページの構造を観察してみます。次のことがわかります。

  • 記事の一覧が https://techblog.nhn-techorus.com/page/1, https://techblog.nhn-techorus.com/page/2, …, https://datehotel.io/page/14 で表示される

 また、https://techblog.nhn-techorus.com/page/1 のソースを読むと、次のことがわかります。
ソースを読むと、次のことがわかります

  • <article> タグに囲われた部分で記事へのリンクが定義されている
  • <article> タグの中の <h2> タグの中の <a> タグで記事へのリンクが定義され、タイトルが表示されている
    • CSSセレクターの記法でいうと、 article>h2>a
  • <article> タグの中の <ul> タグの中の <li class="entry_date"> タグの中の <a>で記事の執筆日が書かれている
    • CSSセレクターの記法でいうと、article>ul>li[class="entry_date"]>a

 次に、記事の詳細ページを見てみます。すると、次がわかります。
記事の詳細ページを見てみると、次のことがわかります

  • たとえば記事IDが 185 のページは、 https://techblog.nhn-techorus.com/archives/185 に置かれている
  • 本文は <div class="entry_content"> タグ以下に書かれている
    • CSSセレクターの記法でいうと、div[class="entry_content"]

準備

 Ubuntu 16.04のターミナル上で実行することを前提とします。
 必要なプログラムをインストールします。

sudo apt install ipython3 python3-lxml libxml2-dev libxslt-dev python-dev python3-pip
sudo pip3 install cssselect html2text

lxmlはHTMLのパースをするために、html2textはHTMLの内容を地の文に変換するために使います。

スクレイピングの実行

 いよいよ実際にスクレイピングします。
 Python3のインタプリタを起動します。ここでは、標準の対話環境より便利なIPythonを使います。

ipython3

 必要なライブラリを import し、変数を定義します。

import requests, lxml.html, time, html2text
from lxml.cssselect import CSSSelector
base_url = "https://techblog.nhn-techorus.com/"
num_of_pages = 14

 記事の一覧を取得します。記事のIDをキー、日付とタイトルのタプルを値とする辞書 articles を作成します。

def get_attrs(article):
    """ CSSSelector で抽出された article タグを受け取り、記事のID、日付、タイトルのタプルを返す """
    id = CSSSelector("h2>a")(article)[0].get("href").split("/")[-1]
    date = CSSSelector("ul>li[class=\"entry_date\"]>a")(article)[0].text
    title = CSSSelector("h2>a")(article)[0].text
    return (id, date, title)
articles = {}
for i in range(1, num_of_pages + 1):
    r = requests.get(requests.compat.urljoin(base_url, "page/{}".format(i))) # HTTPSリクエストを送信
    parsed_html = lxml.html.fromstring(r.content) # レスポンスのHTMLを lxml.html でパース
    for article in CSSSelector("article")(parsed_html): # articleタグを抽出
      articles[get_attrs(article)[0]] = get_attrs(article)[1:] # articles変数に情報を追加

articles にページの情報が格納されます。

In [46]: articles
Out[46]:
{'1047': ('2015年12月18日', 'gulp+babelでのフロントエンド開発について ? 3. bootstrapのビルド'),
 '7555': ('2017年12月25日', 'k平均法による減色処理に混合ガウスモデルを適用してもうまくいかない'),
 '2620': ('2016年03月31日', 'IT業界研究に役立つハンズオンセミナー\u3000レポート'),
 '185': ('2015年06月24日', 'AWSが簡単&すぐに使える「ECO Pack」のご紹介'),
...

 記事の本文も取得します。ここでは、辞書 articles を上書きし、値のタプルに記事の本文を追加することにします。
 現時点で記事が130前後あり、記事を1つ取得するごとに1秒間の sleep() を入れているため、実行に5分程度かかります。

h = html2text.HTML2Text()
h.ignore_links = True # ここではリンクを無視することにする
def get_content(id):
    """ 記事IDを受け取って、記事の本文を返す """
    r = requests.get(requests.compat.urljoin(base_url, "archives/{}".format(id)))
    parsed_html = lxml.html.fromstring(r.content)
    content = CSSSelector("div[class=\"entry_content\"]")(parsed_html)[0]
    return h.handle(lxml.html.tostring(content).decode())
for id in articles.keys():
    articles[id] = articles[id] + (get_content(id), )
    time.sleep(1) # サーバの負荷軽減のため、記事ごとに1秒間の間隔をあける

 これで、辞書 articles に記事IDごとに日時、タイトル、本文が保存されました。このデータを使ってデータ分析などを行うことができます。
これで、辞書 articles に記事IDごとに日時、タイトル、本文が保存されました。このデータを使ってデータ分析などを行うことができます。

まとめ

 この記事では、当ブログの記事をスクレイピングすることで、クローリング・スクレイピングのやり方の実際を示しました。

参考文献

AWS移行支援キャンペーン

あなたにおすすめの記事