Pythonで簡単にスクレイピングするときのメモ。
最低限必要なライブラリの準備
RequestsとBeautifulSoup4が必須です。
python3 -m pip install requests
python3 -m pip install beautifulsoup4
最も基本的な使い方
ダウンロードと解析の最も簡単な方法は次の通り。
import requests
from bs4 import BeautifulSoup
# URLを取得
url = 'https://uta.pw/sakusibbs/index.php?action=fav_ranking'
html = requests.get(url).text
# BeautifulSoup4で解析
soup = BeautifulSoup(html, 'html.parser')
# CSSセレクタで a タグの class=indextile の要素一覧を取得
a_list = soup.select("a.indextitle")
# 取得した後、繰り返し a タグの一覧を繰り返す
for a in a_list:
# リンク先(href属性)を表示
print(a.attrs['href'])
リンク先の相対URLを絶対URLに変換する
なお、リンクは相対URLとなっています。例えば、「./aaa/bbb/ccc」のようなものです。改めて別のリンク先のページを取得する時には、絶対URLが必要です。そこで、相対URLを絶対URLに変換しましょう。
そのために、urllib.parse.urljoin(基本URL, 相対URL)
を使います。
# ...抜粋 ...
for a in a_list:
# リンク先(href属性)を表示
href = a.attrs['href']
full = urllib.parse.urljoin(url, href)
print("url=", full)
取得結果をキャッシュ - 試行錯誤のために
上記が簡単だが、スクレイピングのプログラムを完成させるまでには、何度もURLからHTMLをダウンロードして解析がうまくいくかを試すことになるはずです。そこで、最初の1回だけダウンロードして、後はキャッシュしたファイルから結果を確認できると、相手先のサーバーに迷惑をかけずに済みます。そこで、以下のように簡単なモジュールを用意して、キャッシュする関数を定義しておくと便利です。
import requests, os, urllib.parse, time
from bs4 import BeautifulSoup
def get(url):
# キャッシュの確認
cache_dir = './.cache_data'
if not os.path.exists(cache_dir): os.mkdir(cache_dir)
fname = urllib.parse.quote(url)
fname = os.path.join(cache_dir, fname.replace('/', '__'))
if os.path.exists(fname):
print('### cache=', url)
with open(fname, 'rt') as fp:
return fp.read()
# データを取得したら1秒待つ
print('### download=', url)
req = requests.get(url)
with open(fname, 'wt') as fp:
fp.write(req.text)
time.sleep(1) # 迷惑をかけないために重要な待機時間
return req.text
def soup(url):
return BeautifulSoup(get(url), 'html.parser')
利用例1 - 簡単な例
このモジュールを使うには、以下のように記述する。以下は、現在時刻を返すページを取得するが、キャッシュしているため、最初に取得した時間がずっと表示されることを確認しよう。
import requests_cache as req
url = "https://api.aoikujira.com/time/get.php"
print(req.get(url))
利用例2 - パースしてリンク一覧を取得
なお、上記のモジュールにはオマケで、BeautifulSoup4のオブジェクトを返すsoupメソッドを用意したので、取得したHTMLをBeautifulSoup4ですぐにパースできます。
以下は、作詞掲示板のランキングに掲載されている作品一覧のタイトルとそのリンクを表示するプログラムです。
import requests_cache as req
import urllib.parse
# URLを取得してBeautifulSoup4オブジェクトを得る
url = 'https://uta.pw/sakusibbs/index.php?action=fav_ranking'
soup = req.soup(url)
# リンク一覧を得る
a_list = soup.select("a.indextitle")
for a in a_list:
# リンク先(href属性)を表示
href = a.attrs['href']
label = a.text.strip()
full = urllib.parse.urljoin(url, href)
print('*', label)
print('- link=', full)
ファイルのダウンロード
これまでダウンロードには、urllib.request.urlretrieve
が便利と思っていましたが、利用は非推奨とのこと。
import urllib
url = '...'
save_path = '...'
urllib.request.urlretrieve(url, save_path)
そこで、requestsのストリーミングを使うとエレガントかと思います。
import requests, shutil
def download_to_file(url, save_path):
res = requests.get(url, stream=True)
if res.status_code != 200:
raise IOError('Could not download:', url)
with open(save_path, 'wb') as fp:
res.raw.decode_content = True
shutil.copyfileobj(res.raw, fp)
download_to_file('https://...', '/path/to/file')
もちろん、小さなファイルなら、メモリに取得してファイルに直接保存する方法が面倒がないでしょう。
import requests
url = 'https://...'
save_path = '...'
# (1) 2行で書く
with open(save_path, 'wb') as fp:
fp.write(requests.get(url).content)
# (2) 1行で書く
open(save_path, 'wb').write(requests.get(url).content)
参考書籍
スクレイピングに関する本を書きました。基本から応用まで、全体的に書いています。良かったら見てください!
(追記) 長時間にわたってスクレイピングしたい時は?
- (1) なお、取得したいデータが結構な量がある場合、どのようにスクレイピングさせておくことができるでしょうか。一番簡単なのは、PCを付けっぱなしにしておくことです。
- (2) とは言え、電気代も気になりますし、何よりその間PCが使えないのは不便です。そこで、省電力なRaspberry Piなど小型のLinuxマシンを使うのも手でしょう。スクレイピング程度の仕事なら、ほとんどCPUパワーも不要なので、格安のラズパイでも十分役立ってくれます。
- (3) また、素直に月数百円で使える、VPSなどを契約するのも手です。
リモートマシンにSSH接続して、Pythonを実行する時
上記(2)のRaspberry Piや、(3)のVPSを使う場合に、SSH接続でマシンにリモート接続する場合、ターミナルを閉じると実行中のPythonも終了してしまいます。そこで、端末多重化アプリのtmux
を使います。
- (1)tmuxを起動してから、
python3 プログラム
でスクレイピングのプログラムを実行します。 - (2) そして、その後、
[プレフィックスキー(ctrl+bなど)] d
を押すとtmuxからデタッチします。(デタッチすると、プロセスは終了するのではなく実行中のまま残ります。) - (3) 作業が終わったか確認したい場合、SSHでマシンに接続した後、
tmux a
で改めて前回起動したセッションに接続できます。