note.nkmk.me

Python, Beautiful Soupでスクレイピング、Yahooのヘッドライン抽出

Date: 2017-02-15 / tags: Python, Beautiful Soup, スクレイピング

Beautiful Soupは、HTMLXMLのファイルからデータを抽出(スクレイピング)するためのPythonライブラリ。

Beautiful Soup is a Python library for pulling data out of HTML and XML files.
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation

ここではBeautiful Soupの基本的な使い方の例として、以下の内容について説明する。

  • Beautiful Soupのインストール
  • Yahooのヘッドラインを抽出する例
    • urllibでサイトにアクセス
    • Beautiful Soupで対象を抽出

Pythonにはスクレイピングに加えてクローリングも行うフレームワークScrapyもある。リンクを辿って複数ページから情報を抽出するような場合はScrapyのほうが便利。

スポンサーリンク

Beautiful Soupのインストール

pipでインストールできる(環境によってはpip3)。

$ pip install beautifulsoup4

beautifulsoupではなくbeautifulsoup4なので注意。

Yahooのヘッドラインを抽出する例

例として、Yahoo! JAPANのニュースのヘッドラインを抽出する。

  • macOS: 10.12.3
  • Python: 3.6.0

で動作確認。

import urllib.request
from bs4 import BeautifulSoup

url = 'http://www.yahoo.co.jp/'
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) '\
     'AppleWebKit/537.36 (KHTML, like Gecko) '\
     'Chrome/55.0.2883.95 Safari/537.36 '

req = urllib.request.Request(url, headers={'User-Agent': ua})
html = urllib.request.urlopen(req)
soup = BeautifulSoup(html, "html.parser")
topicsindex = soup.find('div', attrs={'class': 'topicsindex'})
topics = topicsindex.find_all('li')

for topic in topics:
    print(topic.find('a').contents[0])

以下、サンプルコードの各部分を説明する。

urllibでサイトにアクセス

標準ライブラリurllibモジュールはPython3から変更されているので注意。

urllib モジュールは、Python 3 で urllib.request, urllib.parse, urllib.error に分割されて名称変更されました。 2to3 ツールが自動的にソースコードのimportを修正します。また、 Python 3 の urllib.request.urlopen() 関数は urllib2.urlopen() を移動したもので、 urllib.urlopen() のほうは削除されています。
20.5. urllib — URL による任意のリソースへのアクセス — Python 2.7.x ドキュメント

今回はPython3で実行しているので、urllib.requestを使う。

urllib.request.Request()でユーザーエージェントを偽装

url = 'http://www.yahoo.co.jp/'
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) '\
     'AppleWebKit/537.36 (KHTML, like Gecko) '\
     'Chrome/55.0.2883.95 Safari/537.36 '

req = urllib.request.Request(url, headers={'User-Agent': ua})

Yahoo! JAPANは、ユーザーエージェント (User agent)によって表示を変えているので、urllib.request.Request()でユーザーエージェントを偽装する。

class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
このクラスは URL リクエストを抽象化したものです。

headers は辞書でなければなりません。この辞書は add_header() を辞書のキーおよび値を引数として呼び出した時と同じように扱われます。この引数は、多くの場合ブラウザーが何であるかを特定する User-Agent ヘッダーの値を “偽装” するために用いられます。これは一部の HTTP サーバーが、スクリプトからのアクセスを禁止するために一般的なブラウザーの User-Agent ヘッダーしか許可しないためです。例えば、 Mozilla Firefox は User-Agent に "Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11" のように設定し、 urllib はデフォルトで "Python-urllib/2.6" (Python 2.6の場合) と設定します。
21.6. urllib.request — URL を開くための拡張可能なライブラリ — Python 3.5.2 ドキュメント

自分が使っているブラウザのユーザーエージェントは、

現在のブラウザーで確認できる。

ユーザーエージェントはけっこう長い文字列になる。複数行に分けて書く方法は以下の関連記事参照。

urllib.request.urlopen()でurlを開く

urllib.request.Request()でユーザーエージェントを偽装したRequestオブジェクトを渡す。

html = urllib.request.urlopen(req)

特にユーザーエージェントを偽装したりする必要がなければ、http://www.yahoo.co.jp/などのようにurlをそのまま渡せばOK。

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
URL url を開きます。 url は文字列でも Request オブジェクトでもかまいません。
21.6. urllib.request — URL を開くための拡張可能なライブラリ — Python 3.5.2 ドキュメント

標準ライブラリのurllibではなくサードパーティライブラリのRequestsを使ってurlを開いてデータを取得することも可能。Requestsのほうがシンプルに書ける。以下の記事を参照。

Beautiful Soupで対象を抽出

htmlをパースする

soup = BeautifulSoup(html, "html.parser")

HTMLパーサーを指定しないとWarningが出る。

Python標準ライブラリのhtml.parser以外に、lxmlhtml5libを指定することもできる。その場合は、それぞれをpipなどでインストールしておく必要がある。

$ pip install lxml
$ pip install html5lib

Beautiful Soupのドキュメントによるとlxmlが速いのでオススメとのこと。

If you can, I recommend you install and use lxml for speed.
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation

目的の部分を抽出する

topicsindex = soup.find('div', attrs={'class': 'topicsindex'})
topics = topicsindex.find_all('li')

for topic in topics:
    # print(topic.find('a').text)
    # 北 5年前から正男氏暗殺狙う写真NEW
    # のように、"写真NEW"という余計な部分が表示されてしまう
    print(topic.find('a').contents[0])

Chromeの場合、抽出したい対象の部分を選択して、右クリック→検証でその部分のソースが開発者ツールに表示される。

今回の例の場合、抽出したいYahooのヘッドラインは、<div class="topicsindex">で囲まれて、<li><a>タグが列挙されていることが分かる。

find()は先頭の一つのタグを取得するのに対し、find_all()は指定したすべてのタグを取得しリストで返す。

タグの内容は、.string, .text, .contentsなどで取得できる。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事