ネット上のサイトやHTMLファイルからちょっとしたデータを取得したいことは誰にでもあると思います。今回はPython3でちょっとしたWebスクレイピングをやっていきます。すでにどこにでも出回ってる情報ではありますが、自分の忘備録も含めて紹介しておきます。
Webスクレイピングとは?
Webスクレイピングとクローリングについて、NTT西日本掲載のICT用語集から定義を確認します。
スクレイピングは、Webやデータベース上のデータから不要な部分を削ったり、必要な部分を抽出したりして、データを汎用的な形式に整形することを表します。抽出したデータは、市場調査や価格監視などビジネスに役立てることができたり、ビッグデータとしてAIの分析データとして使うこともできます。
スクレイピング用のツールは無料もしくは有料で公開されています。しかしスクレイピングを利用する際は、著作権法や不正アクセス禁止法などに抵触したり、Webサイトの利用規約に違反したりしないよう、細心の注意を払う必要があります。
スクレイピングに似た言葉に「クローリング(Crawling)」があります。クローリングではWebサイトを巡回して情報の「収集」を行ないます。一方、スクレイピングは特定の情報を「抽出」する手法を表します。スクレイピングとクローリングを同時に行なうケースも多く、それぞれの言葉を区別せずに使用している場合もあります。
NTT西日本 ICT用語集 スクレイピング
つまり、一般的に言われるのはWebスクレイピングはデータの取得に加えて加工という部分に焦点を当ていて、クローリングは巡回という部分に焦点を当てています。robots. txt で禁止されているのは一般的に負荷が高いクローリングの方かと思ってましたが、スクレイピングという場合もクローリングの意味が含まれて区別されずに使われるケースもあるため、robots. txt で禁止されているURLに関しては、簡易なスクレイピングも気をつけた方が良さそうです。
robots.txt は、通常はホームページの完全なURLを入力して/robots.txtを追加すると表示できます。このブログは置いてませんが、置くなら、 https://tknagayoshi.com/robots.txt ということになります。
Webスクレイピングを試してみる
ちょっとしたWebスクレイピングを試す準備としてpythonの確認をしておきます。
Python3の確認と設定
現状のPython3の確認をします。pipだけ先ほどバージョンアップしました。
バージョン古いのは承知の上ですが、今回は動けば良いのでこのまま進めます。
$ which python3
/Library/Frameworks/Python.framework/Versions/3.7/bin/python3
$ which pip3
/Library/Frameworks/Python.framework/Versions/3.7/bin/pip3
$ python3 --version
Python 3.7.3
$ pip3 --version
pip 24.0 from /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pip (python 3.7)
$ pip3 list | grep -E 'requests|bs4'
bs4 0.0.2
requests 2.22.0
BeautifulSoup の bs4 と requests を使用するので、インストールされていなければ追加しておきます。注意点として、pythonとpipのインストール先が同じであることを確認します。色々やってると設定によって、pythonとpipの使用先が違っている場合があるので。とりあえず、使用するpythonの中にライブラリが入っていれば問題ありません。
$ pip3 install requests
$ pip3 install bs4
実行はVSCodeで行います。デバッグ(Run and Debug(⇧⌘D))で値を確認しながらの方が理解しやすいと思うので、デバッグ構成を設定します。.vscode/launch.json で実行するpythonのインタプリタの設定を行います。
# .vscode/launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
// "python"プロパティでデバッグで使用するPython interpreterを設定する
{
"name": "Python: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"python": "/Library/Frameworks/Python.framework/Versions/3.7/bin/python3"
}
]
}
以下のように設定したデバッグ構成を選択します。
Webスクレイピングを行う
以下、サンプルになります。もし使用する場合は、適宜定数の部分や整形する部分の処理を置き換えてください。
基本的な流れとしては、URLをrequestsの引数にして、HTMLを取得し、BeautifulSoup で BeautifulSoup オブジェクトに変換してからデータを取得します。取得する対象の部分に関しては、CSSセレクターが利用しやすいと思います。デベロッパーツールの要素タブで確認しながら行います。
import time
import re
import requests
from bs4 import BeautifulSoup
"""
定数
"""
# 書き込み先パス
PATH = "/Users/Username/Documents"
FILE = "sample.txt"
TOFILE = PATH + FILE
# 取得したいURLの一覧が載った検索一覧のページ
TARGET_URL = "https://test.jp/"
ALINKS = 'a_links'
TARGET_SELECTOR = '.css_selector'
# BeautifulSoup オブジェクトを返す
def get_beautifulsoup_obj(url):
# スクレイピング対象の URL にリクエストを送り HTML を取得する
res = requests.get(url)
# responce の HTML から BeautifulSoup オブジェクト を作成する
soup = BeautifulSoup(res.text, 'html.parser')
return soup
# 検索一覧のページから対象の url のリストを取得する
def get_urls(url):
# BeautifulSoup オブジェクトを取得する
soup = get_beautifulsoup_obj(url)
# CSS セレクターを使って対象の要素から href の情報を取得する
target_urls = [elm.get('href') for elm in soup.select(ALINKS)]
return target_urls
# 各 url から欲しい情報を取得する
def get_page_info(url):
# BeautifulSoup オブジェクトを取得する
soup = get_beautifulsoup_obj(url)
# title タグの文字列を取得する
title_text = soup.find('title').get_text()
# CSS セレクターを使って対象の要素から text の情報を取得する
info = [format_string(tr.text) for tr in soup.select(TARGET_SELECTOR)]
return '\n\n\n'.join([title_text] + info)
# 抽出したテキストを整形する
def format_string(str):
# 改行と空白を削除、特定の文字列の前に改行を挿入する
return re.sub('(<|■)','\n\\1', re.sub('\r|\n| ', '', str))
def main():
# 検索対象のURLを取得
urls = get_urls(TARGET_URL)
# URLごとに情報を取得する
for url in urls:
data = get_page_info(url)
# 取得した情報をテキストファイルに書き込む
with open(TOFILE, mode='a') as f:
f.write(data)
time.sleep(1)
if __name__ == '__main__':
main()
ローカルのHTMLファイルからデータを取得する。
ローカルのHTMLファイルからちょっとした項目を取得したい場合などあると思います。例えば、ログインが必要とするページだとリクエスト時に認証情報を送っているので、上記の単純なコードではうまくいきません。その場合、ローカルに一旦ファイルをダウンロードしてからやるのが楽そうです。(まあ、ちょっとしたデータであれば、正規表現でも十分だとは思いますが。)
ローカルのfile:///形式のHTMLからデータを取得するコードは以下のような感じになります。今回のCSSのセレクタの対象はWordPressテーマCocoonのエントリーカードのタイトルの一覧を取得する感じです。ここは適宜変更してください。
import time
from bs4 import BeautifulSoup
from urllib.parse import urlparse, unquote
"""
定数
"""
# 書き込み先ファイル設定
PATH = "/Users/Username/Documents"
FILE = "sample.txt"
TOFILE = PATH + FILE
# ローカルのHTMLファイル
TARGET_URL = "file:///Users/Username/Desktop/index.html"
# 対象のCSSセレクタ
TARGET_SELECTOR = '.entry-card-title.card-title.e-card-title'
"""
関数
"""
# file URL をファイルパスに変換する関数
def convert_file_url_to_path(file_url):
parsed_url = urlparse(file_url)
return unquote(parsed_url.path)
def main():
# 対象ファイル
file_path = convert_file_url_to_path(TARGET_URL)
html_content = None
with open(file_path, 'r', encoding='utf-8') as file:
html_content = file.read()
soup = BeautifulSoup(html_content, 'html.parser')
title_text = [elm.get_text() for elm in soup.select(TARGET_SELECTOR)]
# print('\n'.join(title_text))
# 取得した情報をテキストファイルに書き込む
with open(TOFILE, mode='a') as f:
f.write('\n'.join(title_text))
time.sleep(1)
if __name__ == '__main__':
main()
ポイントは、TARGET_URLに設定した file:/// 形式の URLです。open 関数はローカルファイルのパスを扱いますが、file:/// 形式の URL をそのままでは認識できません。ですので、この場合、file:/// 形式の URL を通常のファイルパスに変換する必要があります。
参照
NTT西日本 ICT用語集 スクレイピング
Python debugging in VS Code
Beautiful Soup