BeautifulSoup+Requestsの基本
概要
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
find
とfind_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()