Monthly Hacker's Blog

毎月のテーマに沿ったプログラミング記事を中心に書きます。

Selenium with Pythonで読者になっておきたいブログを見つける

はじめに

6月のMonthly HackはScrapingということで、はてなブログを対象にしました。

人気エントリーなどで面白い記事を探せるけれども、特定のキーワードで記事やブログを探すのは難しいです。またはてなサイト内検索を使っても、過去にバズった記事(情報が古い)が多いです*1。そこで、有益な記事を継続的に書いてくれるブログを見つけて、読者になれたらいいと考えました。

まとめると、
特定のキーワードに関連する、読者になっておきたいブログを見つけよう!」
を試みます。

本記事では、

  • Selenium Python bindingsの基本的な機能を紹介します。
  • はてなブログをScrapingしたソースコードを解説します。
  • その他の方法や改善の余地などを考察します。

推薦モデルの概要

仕組みは以下のとおりです。

  1. キーワードを用意する。
  2. はてなサイト内検索を行う。
  3. 上位100件の記事URLを取得する。
  4. ブログ毎に記事件数を集計する。
  5. 記事件数が降順ソートして表示する。

つまり、キーワード検索でヒットした記事件数をブログの評価指標としました。

入力するキーワードは複数でも大丈夫です。
実際は「機械学習」「deep learning」「chainer」をキーワードにしました。

Seleniumの紹介

PythonでScrapingというとBeautifulSoupが有名ですが、今回は使いません。なぜかというと、参考画像のような検索結果ページをめくってもURLが変わらないからです。
f:id:ron_zacapa:20160706132455p:plain

したがって、プログラム上でクリックしてページ遷移する必要があります。そこでSelenium Python bindingsです。
SeleniumはWebのUIテスト等でよく使われているモジュールです。XPathやcss selecter等を指定することでブラウザを操作できます。

インストールはpipから*2

pip install selenium

Chromeを開くためにChromeDriverをダウンロードします。
Downloads - ChromeDriver - WebDriver for Chrome

ソースコード解説

実際のソースコードにコメントをつけてました。
不明点指摘があればコメントください。

# coding: utf-8
import re
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys


# Chromeの起動
driver = webdriver.Chrome('./chromedriver')
driver.get("http://www.hatena.ne.jp/")

# 検索窓(name='q')を指定して'テスト'を入力&リターンキーを押す. 
elem = driver.find_element_by_name("q")
elem.clear()
elem.send_keys('テスト')
elem.send_keys(Keys.RETURN)

# スクレイピングする部分のXPathを予め配列で用意する.
articles_xpath = ['//*[@id="cse"]/div/div/div/div[5]/div[2]/div/div/div[1]/ \
    div[1]/table/tbody/tr/td[2]/div[5]']
for i in range(1, 10):
    articles_xpath.append('//*[@id="cse"]/div/div/div/div[5]/div[2]/div/div/ \
        div[2]/div[%d]/div[1]/table/tbody/tr/td[2]/div[5]' % i)

# キーワードを受け取って検索を行う.
def search(keyword):
    elem = driver.find_element_by_name("search")
    elem.clear()
    elem.send_keys(keyword)
    elem.send_keys(Keys.RETURN)
    # ロードされるまで待つ.
    time.sleep(3)

# 検索結果の指定されたページへ移る.
def move_page(n_page):
    elem = driver.find_element_by_xpath('//*[@id="cse"]/div/div/div/div[5]/ \
        div[2]/div/div/div[2]/div[11]/div/div[%d]' % n_page)
    elem.send_keys(Keys.RETURN)
    time.sleep(3)

# 記事URLをスクレイピングする。
def get_urls(keyword):
    search(keyword)
    urls = []
    for i in range(1, 11):
        for j in range(10):
            try:
                elem = driver.find_element_by_xpath(articles_xpath[j])
                html = elem.get_attribute('url')
                urls.append(html)
            except:
                pass
        # 10ページ目では、もうページをめくらない.
        if i == 10:
            break
        move_page(i+1)
    return urls

article_url = []
# 検索したいキーワード(複数可)
keywords = ['機械学習', 'deep learning', 'chainer']
for keyword in keywords:
    print(keyword)
    article_url.extend(get_urls(keyword))

blog = {}
for url in article_url:
    # 記事部分のURLを切り取る.
    url = re.match('http://[^/]+/', url).group()
    if url in blog:
        blog[url] += 1
    else:
        blog[url] = 1
sort_b = sorted(blog.items(), key=lambda x: x[1])
sort_b.reverse()

# 上位20件を表示
for i in range(20):
    print(sort_b[i])

機械学習界隈でのオススメブログ

上記のコードの結果、上位3件を紹介します。有名な方々ですね。
特にid:TJO氏の○○で働くデータサイエンティストのブログはダントツの記事件数でした。
tjo.hatenablog.com
aidiary.hatenablog.com
machine-learning.hatenablog.com

振り返り

今回のポイントは、HTMLは変わるけどURLが変わらない問題をどうやって解決するかでした。そこで実際にブラウザを操作できるSeleniumを使い、スクレイピングに成功しました。これはSeleniumを使えば動的に変化するサイトでもスクレイピングが可能だということです。

実際にブラウザを動かすのできちんとロードされるまで待たないとエラーが出てしまいます。Seleniumのexpected_conditionsには、望まれた状態になるまでWaitする機能があります。それを使えば、単純にWaitするコードを書かなくて済みますね。
5. Waits — Selenium Python Bindings 2 documentation


それでは報告は以上です。
次回はbot編です。お楽しみに。

*1:あくまでも個人的な感想です。

*2:環境: MacBookPro / Anaconda / Python3.5