Pythonライブラリ MechanicalSoup 詳細解説 🤖⚙️🥣

プログラミング

Webスクレイピングとブラウザ操作をもっと手軽に!

はじめに:MechanicalSoupとは?🤔

MechanicalSoupは、Pythonでウェブサイトとのインタラクションを自動化するためのライブラリです。特に、フォームの入力・送信やリンクのクリックといった、ブラウザの操作を模倣するタスクを得意としています。Webスクレイピングや自動テストなどで活躍します。

このライブラリは、内部的に有名な2つのPythonライブラリ、Requests(HTTPリクエスト担当)とBeautifulSoup(HTML/XMLパース担当)を利用しています。これにより、Requestsの強力なHTTP通信機能と、BeautifulSoupの柔軟なHTML解析能力を組み合わせ、より簡単にウェブサイトを操作できるようになっています。まさに、それぞれの良いとこ取りをしたライブラリと言えるでしょう!👍

MechanicalSoupの主な機能は以下の通りです。

  • ウェブページの取得
  • リンクのたどり方(クリック)
  • フォームの選択、入力、送信
  • Cookie(クッキー)の自動管理(セッション維持)
  • リダイレクトの自動追跡
注意点: MechanicalSoupはJavaScriptを実行する機能は持っていません。そのため、JavaScriptを多用する動的なウェブサイトの操作には向いていません。そのような場合は、Seleniumなどの他のライブラリを検討する必要があります。

なぜMechanicalSoupなのか? Requests/BeautifulSoupとの違い

RequestsとBeautifulSoupだけでも、ウェブページのHTMLを取得し、そこから情報を抽出することは可能です。しかし、ログインが必要なページや、フォームを送信した後のページなど、状態を維持しながら複数のステップで操作を行う場合、これらのライブラリだけではコードが複雑になりがちです。

MechanicalSoupは、`StatefulBrowser`というクラスを提供し、これがCookieや現在のURLなどの状態(ステート)を自動で管理してくれます。これにより、あたかも人間がブラウザを操作しているかのように、複数のリクエストにまたがる一連の操作をシンプルに記述できます。

簡単にまとめると、以下のようになります。

ライブラリ 主な役割 状態管理 フォーム操作 JavaScript実行
Requests HTTPリクエストの送信・受信 手動管理が必要 直接サポートなし 不可
BeautifulSoup HTML/XMLのパース(解析) 不可 不可(HTML構造の解析は可能) 不可
MechanicalSoup ウェブサイト操作の自動化 (Requests + BeautifulSoup) 自動管理 (StatefulBrowser) 容易に可能 不可
Selenium ブラウザの完全自動操作 自動管理 可能 可能 (ただし、リソース消費大)

したがって、JavaScriptが不要で、フォーム送信やログインなど、状態を維持したウェブサイト操作が必要な場合に、MechanicalSoupは非常に有効な選択肢となります。💡

インストール 💻

MechanicalSoupのインストールは非常に簡単です。pipコマンドを使用します。

pip install MechanicalSoup

これにより、MechanicalSoup本体と、依存関係にあるRequests、BeautifulSoupなどのライブラリも一緒にインストールされます。

インストールが完了したら、Pythonのインタラクティブシェルやスクリプトでバージョンを確認してみましょう。

import mechanicalsoup
print(mechanicalsoup.__version__)

バージョン番号が表示されれば、インストールは成功です!🎉 (最新バージョンは開発状況により変わります。2023年7月4日にバージョン1.3.0がリリースされています。)

仮想環境の利用推奨: プロジェクトごとにライブラリのバージョンを管理するために、Pythonの仮想環境(venvなど)を作成し、その中でMechanicalSoupをインストールすることをお勧めします。
# 仮想環境を作成 (例: envという名前で作成)
python -m venv env
# 仮想環境を有効化 (Windowsの場合: env\Scripts\activate, macOS/Linuxの場合: source env/bin/activate)
source env/bin/activate
# MechanicalSoupをインストール
pip install MechanicalSoup

基本的な使い方 🚀

MechanicalSoupの基本的な使い方を、ステップバイステップで見ていきましょう。ここでは、状態を管理してくれる`StatefulBrowser`クラスを使用します。

1. ブラウザオブジェクトの作成

まず、`StatefulBrowser`のインスタンスを作成します。これが、ウェブサイトと対話するための「ブラウザ」となります。

import mechanicalsoup

# StatefulBrowserオブジェクトを作成
browser = mechanicalsoup.StatefulBrowser(
    soup_config={'features': 'lxml'},  # HTMLパーサーとしてlxmlを指定 (推奨)
    raise_on_404=True,                # 404エラーで例外を発生させる
    user_agent='MyCoolBot/1.0 (MyCompany; +http://mycompany.com/botinfo)', # ユーザーエージェントを設定
)

`StatefulBrowser`のコンストラクタでは、いくつかのオプションを指定できます。

  • `soup_config={‘features’: ‘…’}`: BeautifulSoupが内部で使用するHTMLパーサーを指定します。`’lxml’`は高速で推奨されますが、別途`pip install lxml`が必要です。指定しない場合は、Python標準の`’html.parser’`が使われます。
  • `raise_on_404=True`: ページが見つからない場合(HTTPステータスコード404)に、`mechanicalsoup.LinkNotFoundError`例外を発生させます。デフォルトは`False`です。
  • `user_agent=’…’`: HTTPリクエストヘッダーに含まれるUser-Agent文字列を指定します。ウェブサイトによっては、特定のUser-Agentを要求したり、ボットからのアクセスを識別したりするため、適切な値を設定することが推奨されます。

2. ウェブページの取得 (`open`)

`open()`メソッドを使って、指定したURLのウェブページを取得します。

# 例として、HTTPリクエストのテスト用サイトを開く
url = "https://httpbin.org/"
response = browser.open(url)

# レスポンス情報を確認 (オプション)
print(f"Status Code: {response.status_code}") # HTTPステータスコード (200なら成功)
# print(response.headers) # レスポンスヘッダー
# print(response.text) # レスポンスボディ (HTMLなど)

`open()`メソッドは、Requestsライブラリの`Response`オブジェクトを返します。このオブジェクトには、ステータスコード、ヘッダー、コンテンツなどの情報が含まれています。

`StatefulBrowser`は、アクセスしたページの情報を内部で保持しています。現在のURLやページの内容(BeautifulSoupオブジェクト)にアクセスできます。

# 現在のURLを取得
current_url = browser.get_url() # or browser.url
print(f"Current URL: {current_url}")

# 現在のページのBeautifulSoupオブジェクトを取得
current_page_soup = browser.get_current_page() # or browser.page
print(f"Page Title: {current_page_soup.title.string}")

# 特定の要素を取得 (BeautifulSoupの機能を利用)
all_links = current_page_soup.find_all('a')
print(f"Number of links: {len(all_links)}")

`browser.page` (または `browser.get_current_page()`) で取得できるBeautifulSoupオブジェクトを使えば、自由にHTML要素を検索・抽出できます。

3. リンクのクリック (`follow_link`)

ページ内のリンクをたどる(クリックする)には、`follow_link()`メソッドを使用します。引数には、リンクを指定するための条件(CSSセレクタ、リンクテキスト、正規表現など)を渡します。

# httpbin.orgのトップページにある "Forms" というテキストのリンクをたどる
forms_link_response = browser.follow_link("forms") # 部分一致するテキストで検索
# またはCSSセレクタで指定: forms_link_response = browser.follow_link(link_css_selector='a[href="/forms/post"]')

print(f"After following link, Status Code: {forms_link_response.status_code}")
print(f"New URL: {browser.get_url()}")
print(f"New Page Title: {browser.page.title.string}")

`follow_link()`は、指定された条件に一致する最初のリンクをたどり、移動後のページの`Response`オブジェクトを返します。ブラウザの状態(URL、ページ内容など)も自動的に更新されます。

もし該当するリンクが見つからない場合は、`mechanicalsoup.LinkNotFoundError`が発生します。

4. フォームの選択と入力 (`select_form`, `__setitem__`)

ウェブサイト操作で最も重要な機能の一つがフォームの扱いです。MechanicalSoupでは、まず操作対象のフォームを選択し、次に入力フィールドに値を設定します。

# /forms/post ページにはフォームがあるはず
# フォームを選択 (CSSセレクタで指定)
# このページの唯一のフォームを選択する場合は browser.select_form() でも可
form = browser.select_form('form[action="/post"]')

# 選択したフォームの情報を表示 (オプション)
# form.form.prettify() # フォームのHTML構造を表示
# form.print_summary() # フォーム要素の概要を表示

# フォームの入力フィールドに値を設定 (辞書のようにキーで指定)
# input要素のname属性をキーにする
form["custname"] = "侍 太郎"
form["custtel"] = "+81-90-1234-5678"
form["custemail"] = "samurai.taro@example.com"
form["size"] = "medium" # radioボタンやselect要素も同様
form["topping"] = ["bacon", "cheese"] # checkbox (複数選択可) はリストで
form["delivery"] = "14:00"
form["comments"] = "美味しいピザをお願いします!🍕"

# 値が設定されたか確認 (オプション)
print(form["custname"])
print(form["topping"])

`select_form()`メソッドは、CSSセレクタを使ってページ内の特定の`

`要素を選択します。引数を省略した場合は、ページ内の最初のフォームが選択されます。選択されたフォームは`Form`オブジェクトとして返され、`browser`オブジェクトの内部状態にも保持されます。

フォームの各入力フィールド(``, `

  • テキスト入力 (`type=”text”`, `type=”email”`, `type=”tel”`, `textarea`など): 文字列を代入します。
  • チェックボックス (`type=”checkbox”`):
    • 単一のチェックボックス: `True` (チェックする) または `False` (チェックしない) を代入します。
    • 同じ`name`属性を持つ複数のチェックボックス: チェックしたい項目の`value`属性値のリストを代入します。
  • ラジオボタン (`type=”radio”`): 選択したい項目の`value`属性値を代入します。
  • セレクトボックス (`

`form.print_summary()`を実行すると、選択中のフォームに含まれる要素とその現在の値の一覧が表示され、デバッグに役立ちます。

5. フォームの送信 (`submit_selected`)

入力値を設定したら、`submit_selected()`メソッドでフォームを送信します。

# 選択されているフォームを送信
submit_response = browser.submit_selected()

# 送信後のレスポンスと状態を確認
print(f"After Submit, Status Code: {submit_response.status_code}")
print(f"New URL: {browser.get_url()}") # /post になっているはず
# print(submit_response.text) # 送信結果 (JSON形式でフォームデータが返ってくる)

# 送信結果のJSONをパースして確認
import json
submitted_data = json.loads(submit_response.text)
print("--- Submitted Form Data ---")
print(json.dumps(submitted_data.get("form", {}), indent=2, ensure_ascii=False))
print("---------------------------")

`submit_selected()`は、現在選択されているフォームを、その`action`属性で指定されたURLと`method`属性(GETまたはPOST)に従って送信します。送信後のページの`Response`オブジェクトが返され、ブラウザの状態も更新されます。

フォーム内に複数の送信ボタン(``や`

`launch_browser()`でデバッグ: フォーム入力後やページ遷移後に、現在のページの状態を実際のブラウザで確認したい場合があります。`browser.launch_browser()`を実行すると、現在の`browser.page`の内容が一時的なHTMLファイルとして保存され、デフォルトのウェブブラウザで開かれます。これは、フォームの入力内容が正しいか、意図したページにいるかなどを視覚的に確認するのに非常に便利です。
# フォーム入力後に状態を確認
form["custname"] = "テストユーザー"
# browser.launch_browser() # ここで実行すると、入力された状態のフォームがブラウザで開く

# フォーム送信後に状態を確認
submit_response = browser.submit_selected()
# browser.launch_browser() # 送信後の結果ページがブラウザで開く

高度な機能 ✨

MechanicalSoupは基本的な操作以外にも、より複雑なウェブサイト操作のための機能を提供しています。

Cookieの管理 🍪

`StatefulBrowser`は、セッション中のCookieを自動で管理します。これにより、ログイン状態などを維持したままサイト内を移動できます。

現在のCookie(`CookieJar`オブジェクト)を取得したり、外部で作成・保存したCookieを設定したりすることも可能です。

# 現在のCookieJarを取得
cookie_jar = browser.get_cookiejar()
print("--- Current Cookies ---")
for cookie in cookie_jar:
    print(f"Name: {cookie.name}, Value: {cookie.value}, Domain: {cookie.domain}")
print("-----------------------")

# Cookieを手動で追加・設定 (例)
# import http.cookiejar
# new_cookie = http.cookiejar.Cookie(...) # 詳細な設定が可能
# browser.set_cookiejar(new_cookie_jar) # 既存のCookieJarを置き換える
# browser.get_cookiejar().set_cookie(new_cookie) # 特定のCookieを追加

これにより、例えば以前のセッションで保存したCookieを読み込んでログイン状態を復元する、といった応用が可能になります。

ユーザーエージェントの変更

ユーザーエージェントは`StatefulBrowser`作成時に指定できますが、途中で変更することも可能です。

print(f"Original User Agent: {browser.user_agent}")
browser.set_user_agent('AnotherCoolBot/2.0')
print(f"New User Agent: {browser.user_agent}")

リダイレクトの追跡

MechanicalSoup(内部のRequests)は、デフォルトでHTTPリダイレクト(ステータスコード3xx)を自動的に追跡します。リダイレクトが発生した場合、`open()`や`submit_selected()`は最終的に到達したページのレスポンスを返します。

リダイレクトの履歴は`response.history`属性(`Response`オブジェクトのリスト)で確認できます。

# リダイレクトを発生させる可能性のあるURLを開く
redirect_url = "https://httpbin.org/redirect/3" # 3回リダイレクトする例
response = browser.open(redirect_url)

print(f"Final URL: {browser.get_url()}") # リダイレクト後のURL
print(f"Status Code: {response.status_code}") # 最終的なステータスコード

print("--- Redirect History ---")
for resp in response.history:
    print(f"Status: {resp.status_code}, URL: {resp.url}")
print("------------------------")

リダイレクトを追跡したくない場合は、`open()`や`submit_selected()`の呼び出し時に`allow_redirects=False`を引数として渡します。

# リダイレクトを追跡しない場合
# response_no_redirect = browser.open(redirect_url, allow_redirects=False)
# print(f"Status Code (No Redirect): {response_no_redirect.status_code}") # 302 などになる
# print(f"URL (No Redirect): {browser.get_url()}") # リダイレクト前のURLのまま

BeautifulSoupオブジェクトへの直接アクセス

`browser.page`属性を通じて、いつでも現在のページのBeautifulSoupオブジェクトにアクセスできます。これにより、BeautifulSoupが提供する全てのHTML解析・操作機能を活用できます。

# 例: ページ内の全ての画像URLを取得
soup = browser.page
images = soup.find_all('img')
for img in images:
    src = img.get('src')
    if src:
        # 相対URLを絶対URLに変換
        absolute_src = browser.absolute_url(src)
        print(f"Image Source: {absolute_src}")

`browser.absolute_url()`メソッドは、相対URLを現在のページのURLに基づいて絶対URLに変換してくれる便利な機能です。

エラーハンドリング

ネットワークエラー、ページが見つからない場合(`raise_on_404=True`設定時)、リンクが見つからない場合など、様々な状況で例外が発生する可能性があります。`try…except`ブロックを使って適切にエラーハンドリングを行うことが重要です。

try:
    # 存在しない可能性のあるリンクをたどる
    browser.follow_link("non_existent_link_text")
except mechanicalsoup.LinkNotFoundError:
    print("指定されたリンクが見つかりませんでした。")
except requests.exceptions.RequestException as e:
    print(f"ネットワークエラーが発生しました: {e}")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")

try:
    # 存在しない可能性のあるページを開く (raise_on_404=Trueの場合)
    browser.open("https://httpbin.org/this-page-does-not-exist")
except mechanicalsoup.LinkNotFoundError as e: # RequestsのHTTPErrorをラップしている場合がある
     print(f"ページが見つかりませんでした (404 Error): {e}")
except requests.exceptions.RequestException as e:
    print(f"ネットワークエラーが発生しました: {e}")

コメント

タイトルとURLをコピーしました