Python で動的サイトをスクレイピング(1)

Python で動的サイトをスクレイピング(1)

Python を始めてみようと思ったのはWebサイトのスクレイピングに向いている言語ということからです。早速いろいろ試してみました。

Python のインストール記事は以下です。

www.imuza.com

静的サイト

スクレイピングの基本的な流れは、データの取得 -> データの抽出 という流れになり、データの取得には静的サイトか動的サイトかにより方法が異なってきます。

まず、静的サイトのスクレイピングの基本を整理してみました。

データの取得 urllib.request

Python には urllib という標準モジュールがが付属しており、urllib.request を使って静的サイトを取得できます。

import urllib.request


url = 'https://news.yahoo.co.jp'
with urllib.request.urlopen(url) as response:
  html = response.read().decode()


  # ファイルに保存してみる
  with open('output.html', 'w', encoding='utf-8') as f:
    print(html, file=f)

Yahoo! ニュースサイトを取得し、カレントディレクトリに output.html というファイル名で保存します。

データの取得 requests

requests というHTMLライブラリがあります。

インストールは、

$ pip install requests
import requests


url = 'https://news.yahoo.co.jp'
html = requests.get(url).text


with open('output.html', 'w', encoding='utf-8') as f:
  print(html, file=f)

同じように Yahoo! ニュースサイトを保存します。

データの抽出 BeautifulSoup

取得した HTMLファイルから必要なデータを取り出すためにはデータを構造化して扱いやすくします。そのためのライブラリが BeautifulSoup です。

インストールは、

$ pip install beautifulsoup4
import requests
from bs4 import BeautifulSoup


url = 'https://news.yahoo.co.jp'
html = requests.get(url).text


soup = BeautifulSoup(html, 'html.parser')
print(soup.title)


#<title>Yahoo!ニュース</title>

サイトのタイトルがタグ付きで表示されます。

データ抽出にはパーサーが必要ですので、第2引数に指定します。html.parser は BeautifulSoup に内蔵されているパーサーです。

他に lxml というパーサーがあります。インストールは、

$ pip insatall lxml
import requests
from bs4 import BeautifulSoup
import lxml


url = 'https://news.yahoo.co.jp'
html = requests.get(url).text


soup = BeautifulSoup(html, 'lxml')
print(soup.title)


#<title>Yahoo!ニュース</title>

同じ結果が得られます。

find_all, find, フィルター

構造化されたデータから必要なものを抽出するには、find_all, find などのメソッドを使います。また、メソッドに渡すフィルターには、文字列の他、正規表現や関数も指定できるようです。詳しくはドキュメントをご覧ください。

import requests
from bs4 import BeautifulSoup
import lxml


url = 'https://news.yahoo.co.jp'
html = requests.get(url).text


soup = BeautifulSoup(html, 'lxml')
# トピックス一覧 <ul> のクラス名のフィルターを与えてみる
# class は予約語のため class_ とする
found = soup.find(class_='topicsList_main')


with open('output.html', 'w', encoding='utf-8') as f:
  print(found, file=f)

Yahoo! ニュースから主要ニュースの一覧を取り出せます。

select

CSSセレクタでも指定できます。こちらのほうが便利そうです。

import requests
from bs4 import BeautifulSoup
import lxml


url = 'https://news.yahoo.co.jp'
html = requests.get(url).text


soup = BeautifulSoup(html, 'lxml')
# トピックス一覧各項目 <li><a> で抽出してみる
found = soup.select('.topicsListItem a')


with open('output.html', 'w', encoding='utf-8') as f:
  print(found, file=f)

同じく主要ニュースのタイトルをリンク付きのリストとして取り出せます。

などなど、フィルターのかけ方や find_all, find の使い方はまだいろいろあります。詳しくはドキュメントをご覧ください。

動的サイト

ということで Python を使えば簡単にスクレイピングができるのですが、Javascript を使った動的サイトではページが読み込まれてから Javascript がデータを取りに行く場合があり、その場合は requests などの HTMLライブラリではデータが取得できません。

例えば、当サイトの「よく読まれている10記事」は、サイドバーの注目記事を使っており、固定ページに次の HTML を書いているだけです。

<div class="hatena-module hatena-module-entries-access-ranking"
  data-count="10"
  data-source="access"
  data-enable_customize_format="0"


  data-display_entry_category="0"
  data-display_entry_image="1"
  data-display_entry_image_size_width="120"
  data-display_entry_image_size_height="120"
  data-display_entry_body_length="0"
  data-display_entry_date="1"
  data-display_entry_title_length="20"
  data-restrict_entry_title_length="0"
  data-display_bookmark_count="0"


>
  <div class="hatena-module-title">
  </div>
  <div class="hatena-module-body">
  </div>
</div>

ページが読み込まれますと、Javascript が(多分)class = "hatena-module-entries-access-ranking" を探しに行き、もしあれば ajax でサーバから取得したデータを class = "hatena-module-body" に書き込みます。ですので、requests では上の HTMLソースが取得できるだけです。

なお、この注目記事の記事数(data-count)は最大で 10 が仕様のようです。

Selenium, WebDriver

「Seleniumはブラウザー自動化を可能にし、それを支えるツール群とライブラリー群プロジェクトです(ドキュメントから)」

スクレイピングに関連させて言えば、実際にブラウザを走らせて Javascriptも実行させ、その結果のデータを取得することができるようになります。

まず、Selenium のインストールは、

$ pip install selenium

走らせるブラウザの WebDriver が必要になります。ブラウザは、Chrome, Firefox, Internet Explorer, Opera, Safari がサポートされていますので、それぞれサードパーティから提供されている driver を入れることになります。

Chrome の場合

それぞれサードパーティの driver のダウンロード先は下のリンク先で紹介されています。

Chrome でやってみます。インストールは実行ファイルをダウンロードしパスを通すか、または pip でインストールすればパスを通す手間がなくなります。ただし、使用しているブラウザとバージョンを合わせる必要があります(多分)。

私の場合は、ブラウザが バージョン: 83.0.4103.97(Official Build) (64 ビット) でしたので、$ pip install chromedriver-binary==83.0.4103.97 としましたら、ChromeDriver 83.0.4103.39 がインストールされました。メジャーバージョンを合わせれば問題ないということでしょうか。現在のところよくわかりません。ブラウザのバージョンが上がった場合はどうなるのかもわかりません。おそらく driver の方もバージョンアップしないとダメなんでしょう。

で、インストールは、

$ pip install chromedriver-binary==バージョン
import lxml
from bs4 import BeautifulSoup
from selenium import webdriver
import chromedriver_binary


driver = webdriver.Chrome()


url = 'https://www.imuza.com/mostread'


driver.get(url)
html = driver.page_source
soup = BeautifulSoup(html, 'lxml')
found = soup.select('.entries-access-ranking')


with open('output.html', 'w', encoding='utf-8') as g:
  print(found, file=g)


driver.quit()

このプログラムを走らせますと、実際に Chrome が立ち上がりサイトが表示されて、その後閉じます。そして保存された HTMLファイルを開きますと確かに注目記事一覧が表示されます。しかし、これはたまたま Javascript の読み込みが間に合っただけですので、データ読み込みを遅らせるか、ページが完全に表示されるまで待たなくてはいけません。また、実際の運用にはいちいちブラザが立ち上がっては鬱陶しいですのでそれも制御する必要があります。

長くなりましたので次回です。