Python、BeautifulSoup+requestsの基本

Python - サードパーティ製ライブラリ
2018年11月19日2:44に更新(約10時間前)
2018年11月7日21:55に作成(約11日前)

旧ブログ移行記事です。

概要

RequestsはHTTPライブラリで、BeautifulSoupはHTMLのパースを行います。この2つを使うと、スクレイピングやクローラーといったものが作成できます。

Seleniumでのブラウザの自動化と違い、プログラム上から直接HTTPリクエストを行う為、高速です。

インストール

pip install requests
pip install beautifulsoup4

使い方

基本的な使い方として、例えば、このブログのタイトルを抽出したい場合は以下のようになります。 find_all(タグ名)とすることで、要素の一覧が取得できます。

from bs4 import BeautifulSoup
import requests

res = requests.get("https://narito.ninja/")
soup = BeautifulSoup(res.text)
h2s = soup.find_all("h2")  # 全ての<h2>...</h2>を取得
for h2 in h2s:
    print(h2.text)  # .textでテキストを取得

結果

Django、よく使うフィルタ
Python、Seleniumの基本
Pythonで、進捗バーを自作する
Djangoで、404ページを作る
Djangoでシンプルなアクセスカウンターもどき
Django、templateからよくincludeするhtml
Django、ブログに使えそうなModel
Django、管理画面へのリンク
Python、マルチスレッドを使い簡易チャット
ホームページ作成に役立つサイト(非デザイナー向け)

find_allは全て取得ですが、findによる見つかったものを1つ取得するメソッドもあります。 引数は柔軟で、例えばidによる検索では、id=id名のようなキーワード引数で指定します。

header = soup.find(id="header")

classによる検索はclass_=クラス名とします。classが予約語なので、アンダースコアが必要なんですね。

h2s = soup.find_all(class_="panel-title")

h2要素の中のp要素の中のspan要素、というアクセスには単純に.タグ名でアクセスできます。具体的には、findを引数なしで呼び出す際はこちらを使えるでしょう

target = soup.h2.p.span.text

<script>といったタグももちろん指定できます。以下のコードは、ページで読み込んでいるjsをすべて抜き出すコードです。

from bs4 import BeautifulSoup
import requests

res = requests.get("https://narito.ninja/")
soup = BeautifulSoup(res.text)
all_script_tag = soup.find_all('script')
for script in all_script_tag:
    print(script.get('src'))
https://www.googletagmanager.com/gtag/js?id=UA-72333380-3
None
//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js
None
https://code.jquery.com/jquery-3.3.1.min.js
https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js
https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js
None

要素の各属性には、data['src']のようにアクセスできます。その属性がないかもしれない場合は、.get('src')ですね。

次に、href属性にcssが含まれているものを抽出します。このような、ある属性にある文字列が含まれた要素を取得する、というケースは多いでしょう。

import re
from bs4 import BeautifulSoup
import requests

res = requests.get("https://narito.ninja/")
soup = BeautifulSoup(res.text)
all_css = soup.find_all(href=re.compile("css"))
for css in all_css:
    print(css)
    print(css["href"])

結果

<link crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" rel="stylesheet"/>
https://use.fontawesome.com/releases/v5.3.1/css/all.css
<link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" rel="stylesheet"/>
https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css
<link href="https://unpkg.com/jpn.css@latest/dist/bootstrap/jpn.min.css" rel="stylesheet"/>
https://unpkg.com/jpn.css@latest/dist/bootstrap/jpn.min.css

このre.compileという書き方は他でも使えます。classに"col"の文字が含まれている物を抽出等も当然できます。

data-urlのような、html5からの属性を元に絞り込みたい場合ですが、hrefやclassと違いキーワード引数として指定ができません。そのため、少し冗長ですが、以下のように指定する必要があります。

twitter = soup.find(attrs={"data-text": "Python、BeautifulSoup+requestsの基本"})
print(twitter)
print(twitter["data-url"])

結果

<a class="twitter-share-button" data-text="Python、BeautifulSoup+requestsの基本" data-url="http://narito.ninja/detail/70" href="https://twitter.com/share">Tweet</a>
http://narito.ninja/detail/70

findfind_all以外によく使うものとして、CSSセレクタでの検索があります。 CSSセレクタでの指定をするにはselectメソッドを使います。1つだけ欲しいならば、select_oneメソッドです。

h2s = soup.select("h2")  # 複数
h2s = soup.select_one("h2")  # 一つ

よく使われるCSSセレクタをほとんどサポートしています。

soup.select("body div")  # body以下のdiv全て
soup.select("body > div")  # body直下のdiv
soup.select("div.row")  # <div class="row" だけ
soup.select('div[class="spam ham"]')  # <div class="spam ham>"だけ。class="spam"やclass="ham"だけの要素は取得されず
soup.select('.red, .blue')  # class="red"、又はclass="blue"のもの。カンマで区切る

.text.stringの違いも見ておきます。

from bs4 import BeautifulSoup


html = """
<td>some text</td>
<td></td>
<td><p>more text</p></td>
<td>even <p>more text</p></td>
"""
soup = BeautifulSoup(html, 'html.parser')
tds = soup.find_all('td')

print('-----以下string-----')
for td in tds:
    print(td.string)

print('-----以下text-----')
for td in tds:
    print(td.text)

結果。基本的にtextを使うほうが混乱は少ないです。

-----以下string-----
some text
None
more text
None
-----以下text-----
some text

more text
even more text

Requestsについて、もう少し見ておきましょう。 .textではなく.contentを使うと、バイナリデータのレスポンス本文にアクセスができます。

res = requests.get("https://narito.ninja/")
res.content

.json()を使うことで、jsonデータをデコードできます。WebAPI利用の際にはお世話になるでしょう。

res.json()

Web上のリソースをダウンロードする時なんかには、サーバーからの生のソケットレスポンスの全てを取得する.rawも使えます。

res = requests.get(url, stream=True)
with open(file_name, 'wb') as file:
    shutil.copyfileobj(res.raw, file)

HTTPヘッダーを変更することも簡単です。以下はユーザエージェントの設定です。

headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
response = requests.get(url, headers=headers)

ログインしていないと見せないページがある場合、つまりセッションが絡む場合はsessionメソッドを利用することになります。

import requests

session = requests.session()
res = session.get('https://narito.ninja')

raise_for_status()は、200番台以外のレスポンス時に例外を送出してくれます。

import requests

session = requests.session()
res = session.get('http://httpbin.org/status/404')
res.raise_for_status()

参考リンク

公式ドキュメント
Python、Seleniumの基本

記事にコメントする