Skip to content

9. テキストデータ処理 (2)

第 9 回では、テキストデータのスクレイピングについて学びます。

9.1 スクレイピングの概要と Web サイトの仕組み

9.1.1 スクレイピングとは

スクレイピングは、Web ページから特定の情報をプログラムを用いて効率的に抽出・収集する手法の一つです。手動では煩雑な作業を自動化でき、データ分析や研究に活用することができます。

注意事項

  • 著作権や利用規約に注意し、倫理的にスクレイピングを行うようにしてください。
  • サーバに過度な負荷をかけないよう、十分に注意してください。

9.1.1 Web サイトの仕組み

スクレイピングを行う前に、まずは Web サイトがどのように動作しているのか、その概要を理解しておきましょう。

Web サイトは、HTML (HyperText Markup Language) と呼ばれるマークアップ言語で構築されています。HTML は以下のようにタグ <> で構成されています。

<!DOCTYPE html>
<html>
  <head>
    <title>タイトル</title>
  </head>
  <body>
    <h1>見出し</h1>
    <p>これは段落です。</p>
  </body>
</html>

多くのタグは、開始タグ <tag> と終了タグ </tag> がセットになっており、タグに囲まれた領域はそのタグによって指定された機能や意味を持ちます(マークアップ)。

以下に、代表的な HTML タグのリストを示します。その他にもさまざまなタグが存在しますが、少しずつ慣れていくようにしてください。

タグ 名前・読み方 説明
<!DOCTYPE html> ドキュメントタイプ宣言 この文書が HTML5 で書かれていることをブラウザに伝える役割を持ちます。
<html></html> html タグ このタグの中に Web ページのすべての要素が入ります。
<head></head> head タグ Web ページの設定情報やメタデータ(ページの裏方情報)を記述します。
<title></title> title タグ ブラウザのタブに表示される「ページタイトル」を指定します。
<body></body> body タグ Web ページに実際に表示する内容を記述します。
<h1></h1> h1 タグ 最も大きな見出し(Heading 1)を作成します。h6 まで用意されています。
<p></p> p タグ 段落(paragraph)を表します。通常の文章はこのタグで囲みます。
<div></div> div タグ ブロック要素をグループ化する際に使います。
<a></a> a (anchor) タグ リンクを作成するためのタグです。href 属性でリンク先を指定します。
例:<a href="https://google.com">Google</a>Google
<br> br (break) タグ 文中で改行したいときに使います。

9.1.2 スクレイピングに必要な Python ライブラリ

Web 上にある HTML や XML のデータ収集には Requests 、それを解析するツールにはBeautiful Soup というライブラリがよく利用されます。Requests と Beautiful Soup は、以下のコマンドでインストールすることができます。

pip install requests beaitifulsoup4

CHIKUWA Editor における Requests と Beautiful Soup

CHIKUWA Editor には Requests と Beautiful Soup がインストール済みであり、手軽にスクレイピングの練習を行うことができるようになっています。

9.2 Beautiful Soup の基本的な使い方

Beautiful Soup を用いた HTML 文書の基本的な解析方法を見ていきましょう。

9.2.1 Beautiful Soup のインポート

Beautiful Soup は以下のようにしてインポートすることができます。

from bs4 import BeautifulSoup

9.2.1 HTML 解析の準備

HTML のテキストを解析できる形に変換するために、BeautifulSoup クラスのインスタンス(オブジェクト)を作成します。第 1 引数には HTML の文字列を、第 2 引数にはパーサ(基本的に "html.parser")を指定します。以下は、soup という名前のオブジェクトを作成する例です。

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

9.2.2 テキストの抽出

タグの情報を用いることで、BeautifulSoupt のオブジェクトからタグ内の要素を抽出することができます。

ここでは、以下の html_doc、および soup という名前の BeautifulSoup オブジェクトがあるものとします。

from bs4 import BeautifulSoup

html_doc = """
<!DOCTYPE html>
<html>
<head>
    <title>サンプルページ</title>
</head>
<body>
    <h1>見出し</h1>
    <p>これは段落です。</p>
    <div class="content">
        <p>もっと詳しい内容はこちら。<a href="https://example.com">リンク</a>をクリック。</p>
    </div>
</body>
</html>
"""

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

タイトル要素の抽出

タイトルタグの要素を抽出するには、BeautifulSoup.title にアクセスします。さらに、.string または .text にアクセスすることで、タイトルタグの中の文字列を参照することができます。

title = soup.title
print("タイトル:", title)
print("タイトル文字列:", title.string)
Output
タイトル: <title>サンプルページ</title>
タイトル文字列: サンプルページ

段落要素の抽出

段落 (p タグ) の要素を抽出するには、BeautifulSoupt オブジェクトに対して、find() メソッドを適用します。以下のように引数で "p" を指定することで、最初に登場する p タグの情報を抽出することができます。さらに、.text にアクセスすることで、タグの中身のみを抽出することができます。

paragraph = soup.find("p")
print("最初の段落:", paragraph)
print("最初の段落のテキスト:", paragraph.text)
Output
最初の段落: <p>これは段落です。</p>
最初の段落のテキスト: これは段落です。

HTML に含まれるすべての p タグ要素を抽出したい場合には、find_all() メソッドを用います。以下は、すべての段落を抽出し、for 文で段落のリストを表示する例です。

paragraphs = soup.find_all("p")
for i, para in enumerate(paragraphs):
    print(i, para.text)
Output
0 これは段落です。
1 もっと詳しい内容はこちら。リンクをクリックしてください。

以下のように、find_all() の引数にリストを入れ、複数のタグ要素をまとめて抽出することもできます。

paragraphs = soup.find_all(["p", "a"])
for i, para in enumerate(paragraphs):
    print(i, para.text)
Output
0 これは段落です。
1 もっと詳しい内容はこちら。リンクをクリックしてください。
2 リンク

リンク情報の抽出

a タグに含まれるリンク情報(href 属性)などを抽出するには、find()find_all() で要素を抽出したうえで、属性を取り出すのが一般的です。例えば、以下のようにブラケット [] を用いて抽出することができます。

link = soup.find("a")["href"] # または soup.find("a").get("href")
print("リンク:", link)
Output
リンク: https://example.com

9.3 Requests による Web スクレイピング

実際にサーバ上にある Web ページからテキストデータを取得する方法を見ていきましょう。Web ページの内容を取得するための Requests ライブラリは、以下のようにインポートします。

import requests

Web ページからデータを取得するには、requests.get() 関数を用います。引数には、Web ページの URL を指定します。以下は、本講義のテキストマイニング練習サイトから情報を取得するコードの例です。

# Web ページの URL
url = "https://kano.ac/texts/stories/chikuwa-hanpen1.html"

# Web ページからデータを取得
response = requests.get(url)

# 先頭の 500 文字を表示
print(response.text[:500])
Output
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>
      ちくわ君とはんぺん君のプログラミング大作戦 - テキストマイニング練習サイト
    </title>
    <link rel="stylesheet" href="../style.css" />
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h1 class="story-title">ちくわ君とはんぺん君のプログã

HTML らしいデータの取得をすることはできましたが、文字化けをしてしまっています。

9.3.1 日本語の文字エンコーディング

上のコードで文字化けをしてしまったのは、requests.get() が日本語を含む文字エンコーディング(文字列の変換ルール)を正しく判定できていないためです。日本語の文字エンコーディングには大きく以下の 3 つが存在します。

  • utf-8: 現代の Web における国際標準かつ主流の文字エンコーディング。
  • shift_jis: 古い Windows や 古い日本語 Web サイトで使用される文字エンコーディング。
  • euc_jp: Unix/Linux の古い日本語環境で使用される文字エンコーディング。

基本的には、国際標準である UTF-8 (utf-8) を指定し、それでうまくいかなければ shift_jiseuc_jp の順番で試すことをおすすめします。エンコーディングの種類を自動判別するためのライブラリ(chardet など)も存在しますが、本講義では取り扱いません。

文字エンコーディングの判別方法

多くの Web サイトでは、HTML のヘッダ部分に文字エンコーディングの情報が含まれています。例えば、head タグ内に以下のような meta タグが書かれていた場合、文字エンコーディングは UTF-8 であることが確定します。

<meta charset="utf-8">

文字エンコーディングを指定して、先ほどのコードを再度実行してみましょう。

import requests

# Web ページの URL
url = "https://kano.ac/texts/stories/chikuwa-hanpen1.html"

# Web ページからデータを取得
response = requests.get(url)

# 文字エンコーディングの指定
response.encoding = "utf-8"

# 先頭の 500 文字を表示
print(response.text[:500])
Output
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>
      ちくわ君とはんぺん君のプログラミング大作戦 - テキストマイニング練習サイト
    </title>
    <link rel="stylesheet" href="../style.css" />
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h1 class="story-title">ちくわ君とはんぺん君のプログラミング大作戦</h1>
        <div class="story-meta">
          ジャンル: コメディ・プログラミング | 文字数: 3000文字

今度は文字化けを起こさず、正しく Web サイトの情報を表示することができました。このようにして取得した HTML と、Beautiful Soup を組み合わせることで、Web サイトから直接テキストデータの抽出や分析を行うことができるようになります。

9.4 Web スクレイピングのテクニック

さまざまな構造を持つ Web サイトから必要な情報を収集するためのチップスや、スクレイピングのテクニックを幾つかチェックしておきましょう。ただし、スクレイピングをしても問題ないサイトであるかどうかは、必ず事前にチェックするようにしてください。

9.4.1 スクレイピングをすることができるサイト

テキストマイニングの練習用にスクレイピングが(暗黙的に)許可されているサイトには、例えば以下のようなものがあります。

  • テキストマイニング練習サイト: 本講義のために用意した練習サイトです。
  • 青空文庫: 著作権切れの文学作品を公開するサイトです。明言はされていませんが、常識的な範囲でスクレイピングをしても問題ないと解釈されています。
  • Wikipedia: フリーのインターネット百科事典です。API 利用が推奨されますが、常識的な範囲で HTML によるスクレイピングをしても問題ないとされています。

9.4.2 Web ページの構造確認

スクレイピング対象のサイトが決まったら、HTML データを取得して、Beautiful Soup で必要な情報を抽出します。しかし、Web サイトは制作者によって構造がさまざまであるため、どのようにしてデータを抽出すればよいか、サイトを見ただけではわかりません。

そこで便利であるのが、ブラウザの開発者ツールです。開発者ツールを使うと、Web サイトの HTML 構造の確認や、要素の特定などを行うことができます。

開発者ツールの開き方

Edge や Chrome ブラウザを使用している場合は、[F12] キーまたは [Ctrl] + [Shift] + [I] キーを押すことで、開発者ツールを開くことができます。

開発者ツールを開いたら、ウィンドウ上部にある Elements タブ(または 要素 タブ)をクリックしてみましょう。以下のように、Web ページの HTML を確認することができます。

開発者ツール1

効率よく目的の要素の HTML を探すには、開発者ツール左上のアイコン(選択・検査アイコン)をクリックしてください。この状態で Web ページにカーソルを持っていくと、下の画像のようにカーソルのある要素がハイライトされ、それに対応する HTML をすぐに把握することができます。

開発者ツール2

9.4.3 classid によるフィルタリング

HTML を見ると、単純なタグだけではなく、<p class="content"><div id="title"> のように、classid が使われていることに気がつくと思います。これらは、HTML の要素に名前を付けて識別をしやすくするための属性であり、スクレイピングや Web 開発においてとても重要な役割を持ちます。

class は複数の要素に設定することができる属性であり、指定の class を持つ要素をスクレイピングしたい場合は以下のようにします。

content = soup.find("p", class_="content")

id は 1 つの要素に固有の名前をつけるための属性であり、指定の id を持つ要素をスクレイピングしたい場合は以下のようにします。

title = soup.find("div", id="title")

find() と select()

本サイトでは Beautiful Soup の find() メソッドと find_all() メソッドを紹介しました。単純な構造の HTML からスクレイピングを行う場合にはこれらのメソッドで十分ですが、構造が複雑である場合は select() メソッドを使うと効率よく要素の抽出を行うことができます。CSS セレクタについても学ぶ必要があるため本講義では取り扱いませんが、興味のある人は調べてみてください。

9.4.4 空白・改行の削除

HTML からテキストをそのまま抽出すると、空白や改行が多く含まれることがあります。テキスト前後の空白や改行を自動的に除去したい場合は、.text の代わりに get_text() メソッドを使うと便利です。以下のように strip=True を指定することで、空白・改行が除去されたクリーンな文字列を取得することができます。

div = soup.find("div")
print(div.get_text(strip=True))

演習

演習 9-1

kano.ac/texts のテキストを対象にスクレイピングを行い、すべての p タグを抽出してテキストを結合してください。ただし、テキストの前後にある不要な空白や改行は除去してください。

演習 9-2

kano.ac/texts のテキストを対象にスクレイピング(p タグの抽出)を行い、形態素解析をしたうえで WordCloud を作成してください。