第13回:Webスクレイピング¶
前回まで扱ってきたデータ(ファイル、画像)は、すべて自分の PC の中にあった。今回はインターネット上のデータを取ってくる。これを Web スクレイピングという。
気になる情報を毎回手で写すのは無理¶
ニュースやブログの新着記事、気になるランキング、大学の空き教室、推しの新着情報……これらを「毎日ページを開いて確認する」のは大変だ。プログラムなら、ページを取ってきて必要な部分だけを自動で抜き出せる。
身近なサービスの内部でもこれが使われている。価格比較サイト、乗換案内、まとめサイト、検索エンジンのクローラ……。卒研のデータ収集や定期的な情報監視、市場調査にも効く。「Web から集めて処理」はこの先も使う。
Web スクレイピングとは¶
Web スクレイピングとは、インターネット上の Web ページから、欲しい情報を取り出すこと。
Web ページは主に次のファイルからできている。
- HTML ファイル:ページの構造と文書を記述したファイル
- CSS ファイル:ページのデザインを記述したファイル
- 画像ファイル:ページに表示される画像
このうち、欲しい情報(文字)が入っているのは HTML ファイル。だから HTML を読み解く。
HTML の構造(タグで囲む)¶
HTML は、文書の構造をタグで定義する。書き方は <タグ名>文章</タグ名>(開くタグと閉じるタグで文章をはさむ)。
titleはページの題名、h1は見出し、pは段落- 欲しい情報が「どのタグの中にあるか」を見つけるのがコツ
Web スクレイピングの流れ(3 ステップ)¶
- インターネット上から HTML ファイルを取得する
- HTML のタグの構造を見て、必要な情報が書かれている場所を特定する
- HTML から必要な情報を取り出す
1 は requests、2〜3 は BeautifulSoup という道具を使う。
ライブラリの準備(requests / beautifulsoup4)¶
- requests:インターネット上のファイルを取得するライブラリ
- beautifulsoup4:HTML ファイルの内容を解析するライブラリ
コマンドプロンプトでインストールする(前回の OpenCV と同じく pip で入れる)。
パッケージ名と読み込む名前が違う
インストールするパッケージ名は beautifulsoup4 だが、プログラムで読み込む名前は bs4(from bs4 import BeautifulSoup)。
ページを取ってくる(requests.get)¶
requests の get メソッドに URL を渡すと、そのページの HTML を取得できる。結果の .text に、HTML の中身(文字列)が入っている。
.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)
ヒント
- ①
forでsectionを 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を取り出すにはselect(select_oneだと最初の 1 つだけ)。 select('li')はリストを返すので、forで 1 つずつ回して.textを表示する。
もっと知りたい人へ(発展)¶
-
保存済み HTML を読む:ネットにつながなくても、保存した HTML ファイルを解析できる。
-
クラス名・id で絞り込む:
select('a.btn')(classがbtnのa)、select('#cate1')(idがcate1)のように細かく指定できる。 - 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の番号(enumerateやrange)を使う - 選んだカテゴリの
sectionの中で、liをselect(1 つだけのselect_oneではない)ですべて取り出して回す