コンテンツにスキップ

第13回:Webスクレイピング

前回まで扱ってきたデータ(ファイル、画像)は、すべて自分の PC の中にあった。今回はインターネット上のデータを取ってくる。これを Web スクレイピングという。

気になる情報を毎回手で写すのは無理

ニュースやブログの新着記事、気になるランキング、大学の空き教室、推しの新着情報……これらを「毎日ページを開いて確認する」のは大変だ。プログラムなら、ページを取ってきて必要な部分だけを自動で抜き出せる

身近なサービスの内部でもこれが使われている。価格比較サイト、乗換案内、まとめサイト、検索エンジンのクローラ……。卒研のデータ収集や定期的な情報監視、市場調査にも効く。「Web から集めて処理」はこの先も使う。

Web スクレイピングとは

Web スクレイピングとは、インターネット上の Web ページから、欲しい情報を取り出すこと。

Web ページは主に次のファイルからできている。

  • HTML ファイル:ページの構造と文書を記述したファイル
  • CSS ファイル:ページのデザインを記述したファイル
  • 画像ファイル:ページに表示される画像

このうち、欲しい情報(文字)が入っているのは HTML ファイル。だから HTML を読み解く

HTML の構造(タグで囲む)

HTML は、文書の構造をタグで定義する。書き方は <タグ名>文章</タグ名>開くタグ閉じるタグで文章をはさむ)。

<html>
<head>
<title>タイトル</title>
</head>
<body>
<h1>見出し</h1>
<p>段落1</p>
<p>段落2</p>
</body>
</html>
  • title はページの題名、h1 は見出し、p は段落
  • 欲しい情報が「どのタグの中にあるか」を見つけるのがコツ

Web スクレイピングの流れ(3 ステップ)

  1. インターネット上から HTML ファイルを取得する
  2. HTML のタグの構造を見て、必要な情報が書かれている場所を特定する
  3. HTML から必要な情報を取り出す

1 は requests、2〜3 は BeautifulSoup という道具を使う。

ライブラリの準備(requests / beautifulsoup4)

  • requests:インターネット上のファイルを取得するライブラリ
  • beautifulsoup4:HTML ファイルの内容を解析するライブラリ

コマンドプロンプトでインストールする(前回の OpenCV と同じく pip で入れる)。

> pip install requests
> pip install beautifulsoup4

パッケージ名と読み込む名前が違う

インストールするパッケージ名は beautifulsoup4 だが、プログラムで読み込む名前は bs4from bs4 import BeautifulSoup)。

ページを取ってくる(requests.get)

requestsget メソッドに URL を渡すと、そのページの HTML を取得できる。結果の .text に、HTML の中身(文字列)が入っている。

import requests
result = requests.get('https://www.shoeisha.co.jp/book/ranking')
print(result.text)

.text の中身は、ブラウザで「ページのソースを表示」したときと同じ内容。

プレ演習13-1

次のコードをそのまま打って実行し、翔泳社のページの title が表示されることを確認しよう。さらに、URL を別のサイト(例:大学のトップページ https://www.kanagawa-u.ac.jp/)に変えて実行し、title の中身が変わることも見てみよう。

import requests
from bs4 import BeautifulSoup
result = requests.get('https://www.shoeisha.co.jp/book/ranking')
soup = BeautifulSoup(result.text, 'html.parser')
print(soup.title)

取ってきた HTML を解析する(BeautifulSoup)

取ってきた HTML(result.text)を BeautifulSoup に渡すと、タグを扱えるオブジェクト(soup)になる。soup.title のように書くと、title タグに囲まれた部分が取り出せる。

import requests
from bs4 import BeautifulSoup
result = requests.get('取得したいURL')
soup = BeautifulSoup(result.text, 'html.parser')
print(soup.title)        # <title>…</title> タグごと
print(soup.title.text)   # タグの中の文字列だけ

.text の有無が重要

.text を付けると、タグを外して「中の文字列だけ」になる。付けないと <title>…</title>タグごとになる。この違いがあとで効いてくる。

必要な部分を取り出す(select / select_one)

ページの中から狙ったタグを取り出すメソッドが 2 つある。

メソッド 取り出すもの 戻り値
select('タグ名') 指定したタグの要素をすべて リスト(複数)
select_one('タグ名') 指定したタグの先頭の要素 要素 1 つ
  • リスト(select)は for で 1 つずつ回す
  • 要素 1 つ(select_one)は、そのまま .text で中身が取れる
  • ネスト(入れ子)もたどれる:sec.select_one('ul').select_one('li') は「sec の中の最初の ul の中の最初の li

プレ演習13-2

下のプログラムを実行する前に、画面に何が出るかを紙やメモ帳に予測して書こう。そのあと実行して、予測と合っているか確認しよう。

from bs4 import BeautifulSoup
html = '''
<section><h2>果物</h2><ul><li>りんご</li><li>みかん</li></ul></section>
<section><h2>野菜</h2><ul><li>にんじん</li></ul></section>
'''
soup = BeautifulSoup(html, 'html.parser')
for sec in soup.select('section'):
    print(sec.select_one('h2').text, ':', sec.select_one('li').text)
ポイント
  • select('section') は 2 つの section順に回す。
  • select_one('li') は各 section最初の li だけを取る。2 つ目以降の li はどうなる? ここが取り違えやすい。
  • .text を付けているので、タグを外した中の文字列が表示される。

翔泳社ランキングの HTML 構造を読む

翔泳社のランキングページ(https://www.shoeisha.co.jp/book/ranking)は、カテゴリごとに section が並ぶ構造になっている。

<section id="cate1">
  <h2>書籍ランキング</h2>          <!-- カテゴリ名 -->
  <div class="newbooks"> …(略) </div>
  <div class="column">
    <ul class="list-unstyled">
      <li> <a href="xxx"><span class="date">1位</span> タイトル1 </a></li>
      <li> <a href="xxx"><span class="date">2位</span> タイトル2 </a></li>
      …(略)
      <li> <a href="xxx"><span class="date">10位</span> タイトル10 </a></li>
    </ul>
  </div>
</section>

カテゴリ名は h2、ランキングの各行は li。これを select / select_one で取り出す。

カテゴリ名と書籍名を取り出す(メイン例)

各カテゴリ名(h2)と、その 1 位の書籍名(最初の li)を取り出してみる。

import requests
from bs4 import BeautifulSoup

result = requests.get('https://www.shoeisha.co.jp/book/ranking')
soup = BeautifulSoup(result.text, 'html.parser')

for sec in soup.select('section'):                       # section を1つずつ取り出す
    if sec.select_one('h2'):                             # h2 タグが存在するか調べる
        category = sec.select_one('h2').text             # 最初の h2 をカテゴリ名に
        title = sec.select_one('ul').select_one('li').text   # 最初の ul の最初の li を書籍名に
        print('カテゴリ:', category)
        print('書籍名:', title[3:])                       # 先頭の「1位」を除いて表示
  • if sec.select_one('h2'): … カテゴリの section には h2 があるが、先頭のナビ用 section には h2 が無い。無いものを飛ばすための番人(これが無いと None になってエラー)
  • title[3:] … 文字列の 4 文字目以降(=先頭の「1位」を除いた部分)を表示

実行すると、各カテゴリと、その 1 位の書籍名が並ぶ(中身はアクセスした時点のランキング)。

プレ演習13-3

下は「各 section のカテゴリ名(h2)と、最初の品目(li)を表示」したいが、3 か所まちがいがある。それぞれ見つけて、正しく動くように直そう。

from bs4 import BeautifulSoup
html = '''
<section><h2>果物</h2><ul><li>りんご</li><li>みかん</li></ul></section>
<section><h2>野菜</h2><ul><li>にんじん</li></ul></section>
'''
soup = BeautifulSoup(html, 'html.parser')
for sec in soup.select_one('section'):   # ①
    category = sec.select('h2').text     # ②
    item = sec.select_one('li')          # ③
    print(category, item)
ヒント
  • forsection を 1 つずつ回したい。select_one は要素を1 つしか返さない。複数を順に処理するには、どちらのメソッド?
  • select('h2') の戻り値はリスト。リストに .text は付けられる? 要素を 1 つ取るには?
  • ③ このままだと <li> タグごと表示される。中の文字列がほしいときは何を付ける?(本文「.text の有無」を見直す)

プレ演習13-4

次の HTML を使って、果物をすべて(りんご・みかん・ぶどう)1 行ずつ表示するプログラムをゼロから書こう。

html = '''
<section><h2>果物</h2><ul><li>りんご</li><li>みかん</li><li>ぶどう</li></ul></section>
'''
# ↑ この html をそのまま使ってよい
ヒント
  • すべての li を取り出すには selectselect_one だと最初の 1 つだけ)。
  • select('li') はリストを返すので、for で 1 つずつ回して .text を表示する。

もっと知りたい人へ(発展)

  • 保存済み HTML を読む:ネットにつながなくても、保存した HTML ファイルを解析できる。

    from bs4 import BeautifulSoup
    with open('ranking.html', 'r', encoding='UTF-8') as f:
        soup = BeautifulSoup(f.read(), 'html.parser')
    print(soup.title)
    
  • クラス名・id で絞り込むselect('a.btn')classbtna)、select('#cate1')idcate1)のように細かく指定できる。

  • JavaScript で作られるページrequests は「最初に届く HTML」しか取れない。ボタンを押した後に中身が出るサイトなどは別の道具(Selenium など)が必要。
  • スクレイピングのマナー:相手のサーバに負担をかけない(短時間に何度もアクセスしない)。サイトの利用規約・robots.txt・著作権を守る。取得したデータの扱いにも注意する。

授業内演習

翔泳社のウェブサイト(https://www.shoeisha.co.jp/book/ranking)から、実行例のとおり、すべての section タグの中にある h2 タグの中身をカテゴリ名として出力するプログラムを作成しなさい。

実行例(2025 年時点):

カテゴリ: 書籍ランキング
カテゴリ: 電子書籍ランキング
カテゴリ: コンピュータ入門書ランキング
カテゴリ: コンピュータ専門書ランキング
カテゴリ: 資格書ランキング
カテゴリ: ビジネス書ランキング
カテゴリ: 福祉書ランキング
カテゴリ: デザイン書ランキング
カテゴリ: 実用書ランキング

確認の観点:

  • select('section') で全 section を回す
  • h2 が無い section(先頭のナビ用など)があると None でエラーになる。番人(本文「メイン例」の if sec.select_one('h2'):)で飛ばす

基本課題

翔泳社のウェブサイト(https://www.shoeisha.co.jp/book/ranking)から、実行例のとおり、すべてのランキングカテゴリーを出力するプログラムを作成しなさい。

実行例(2025 年時点):

翔泳社のすべてのランキングカテゴリーを表示します:
書籍ランキング
電子書籍ランキング
コンピュータ入門書ランキング
コンピュータ専門書ランキング
資格書ランキング
ビジネス書ランキング
福祉書ランキング
デザイン書ランキング
実用書ランキング

ヒント:

  • すべてのランキングカテゴリーは、HTML の最初の section タグの中にある a タグを見ればわかる(下の参考 HTML)。
  • a タグをすべて取り出すのだから select。中の文字列は .text

参考(https://www.shoeisha.co.jp/book/ranking の HTML の一部):

<h1>ランキング</h1>
<section>
  <a class="btn btn-default" href="#cate1">書籍ランキング</a>
  <a class="btn btn-default" href="#cate2">電子書籍ランキング</a>
  <a class="btn btn-default" href="#cate3">コンピュータ入門書ランキング</a>
  …(略)
  <a class="btn btn-default" href="#cate9">実用書ランキング</a>
</section>

応用課題

翔泳社のウェブサイト(https://www.shoeisha.co.jp/book/ranking)から、カテゴリ一覧を取得して、実行例のとおり、番号付きのカテゴリ一覧を表示させ、入力した番号のカテゴリーのすべてのランキングを出力するプログラムを作成しなさい。

実行例(2025 年時点。書籍名はアクセス時点のランキングによる):

カテゴリ一覧:
1: 書籍ランキング
2: 電子書籍ランキング
3: コンピュータ入門書ランキング
…(略)
9: 実用書ランキング
番号を選択してください: 3
カテゴリ: コンピュータ入門書ランキング
書籍名: 1位これだけで基本がしっかり身につく HTML/CSS&Webデザイン1冊目の本
書籍名: 2位独学Photoshop 楽しく基本が身につくガイドブック
…(以下、そのカテゴリの全ランキングが続く)

約束ごと:

  • カテゴリ一覧は番号付きで表示すること(1: から始める)
  • 番号はキーボードから入力すること
  • 選んだカテゴリのすべての書籍を出力すること(1 位だけでなく最後まで)

ヒント:

  • 番号付き一覧は、for の番号(enumeraterange)を使う
  • 選んだカテゴリの section の中で、liselect(1 つだけの select_one ではない)ですべて取り出して回す