[Python] PyQueryでWebスクレイピングをしてみる

Python

Webスクレイピングは、Webサイトから自動的に情報を抽出する技術です。市場調査、競合分析、価格比較など、さまざまな目的で活用されています。PythonにはWebスクレイピングを行うためのライブラリがいくつかありますが、その中でもPyQueryは、jQueryに似た直感的で分かりやすい構文が特徴のライブラリです。

この記事では、PythonとPyQueryを使ってWebスクレイピングを行う基本的な方法と、注意点について解説していきます。jQueryを使ったことがある方なら、比較的スムーズに理解できるでしょう。😊

Webスクレイピングの注意点 ⚠️
Webスクレイピングを行う際は、対象サイトの利用規約やrobots.txtを必ず確認し、著作権法などの法律を遵守する必要があります。サーバーに過度な負荷をかけないよう、アクセス間隔を空けるなどの配慮も重要です。無許可での個人情報や機密情報の収集、著作権で保護されたコンテンツの無断転載などは違法となる可能性があります。本記事の内容は技術的な解説を目的としており、特定のサイトからのスクレイピングを推奨するものではありません。倫理的かつ合法的な範囲で技術を利用してください。

PyQueryは、PythonでHTMLやXMLドキュメントを解析・操作するためのライブラリです。2008年に最初のリリースが行われ、以来、多くの開発者に利用されています。最大の特徴は、JavaScriptの有名なライブラリであるjQueryとほぼ同じような構文(CSSセレクタなど)でHTML要素を選択し、データを抽出したり、内容を変更したりできる点です。これにより、フロントエンド開発に慣れている人にとっては学習コストが低く、直感的に操作できます。

内部的にはlxmlという高速なライブラリを利用してHTML/XMLのパースを行っています。

PyQueryの主な機能:

  • jQueryライクなCSSセレクタによる要素選択
  • DOM(Document Object Model)のトラバーサル(親子・兄弟要素への移動)
  • 要素の属性やテキスト内容の取得
  • 要素の追加、削除、内容の変更
  • HTML/XMLのパース(文字列、ファイル、URLから)

PyQueryを使用するには、まずインストールが必要です。Pythonのパッケージ管理ツールであるpipを使って簡単にインストールできます。

ターミナル(コマンドプロンプト)で以下のコマンドを実行してください。

pip install pyquery

特定のバージョンをインストールしたい場合は、バージョン番号を指定します(例: pip install pyquery==2.0.0)。

また、後述するようにWebページから直接HTMLを取得する場合は、requestsライブラリも併せてインストールしておくと便利です。

pip install requests

PyQueryの基本的な使い方を見ていきましょう。

1. インポート

まず、PyQueryライブラリをPythonスクリプトにインポートします。慣例としてpqという別名をつけることが多いです。

from pyquery import PyQuery as pq

2. HTMLの読み込み

PyQueryオブジェクトを生成するには、いくつかの方法があります。

  • HTML文字列から:
    html_content = "<html><body><h1>タイトル</h1><p class='content'>これは段落です。</p></body></html>"
    doc = pq(html_content)
    print(doc)
  • URLから直接 (内部でrequestsなどが必要):

    PyQuery自体にもURLから読み込む機能がありますが、通常はrequestsライブラリと組み合わせて使います。

    import requests
    from pyquery import PyQuery as pq
    
    # Webページを取得 (例: robots.txtで許可されていることが前提)
    try:
        response = requests.get('https://example.com') # 実際のURLに置き換えてください
        response.raise_for_status() # HTTPエラーがあれば例外を発生させる
        doc = pq(response.content) # bytes形式のコンテンツを渡す
        # または response.text を渡す (エンコーディングに注意)
        # doc = pq(response.text)
        print(doc('title').text()) # ページのタイトルを取得
    except requests.exceptions.RequestException as e:
        print(f"Error fetching URL: {e}")
    注意: URLから取得する際は、対象サイトの利用規約やrobots.txtを確認し、スクレイピングが許可されているか必ず確認してください。また、頻繁なアクセスは避け、time.sleep() などで適切な待機時間を設けるようにしましょう。
  • HTMLファイルから:
    # sample.html というファイルがある場合
    # <html><body><div id="main">ファイルからのコンテンツ</div></body></html>
    try:
        doc = pq(filename='sample.html', encoding='utf-8') # 文字コードを指定
        print(doc('#main').text())
    except FileNotFoundError:
        print("Error: sample.html not found.")

3. 要素の選択 (CSSセレクタ)

PyQueryオブジェクトに対して、jQueryと同じようにCSSセレクタを使って要素を選択できます。

html_content = """
<html>
<head><title>テストページ</title></head>
<body>
  <div id="container">
    <h1 class="main-title">ようこそ</h1>
    <p>これは最初の段落です。</p>
    <ul class="item-list">
      <li class="item">アイテム 1</li>
      <li class="item special">アイテム 2 (特別)</li>
      <li class="item">アイテム 3</li>
    </ul>
    <a href="https://example.com" id="link1">リンク1</a>
  </div>
</body>
</html>
"""
doc = pq(html_content)

# タグ名で選択
print(f"h1要素: {doc('h1')}")
# クラス名で選択
print(f".item-list要素: {doc('.item-list')}")
# IDで選択
print(f"#container要素: {doc('#container')}")
# 属性で選択
print(f"href属性を持つa要素: {doc('a[href]')}")
# 複数のクラスを持つ要素
print(f".item.special要素: {doc('.item.special')}")
# 子孫要素を選択
print(f"#container内のp要素: {doc('#container p')}")

4. テキストと属性の取得

選択した要素からテキスト内容や属性値を取得します。

  • テキスト取得 (.text()): 選択した要素とその子孫要素に含まれるすべてのテキストを連結して取得します。
    title_text = doc('h1.main-title').text()
    print(f"タイトルテキスト: {title_text}") # 出力: タイトルテキスト: ようこそ
    
    list_text = doc('.item-list').text() # リスト全体のテキスト
    print(f"リスト全体のテキスト:\n{list_text}")
    # 出力:
    # リスト全体のテキスト:
    # アイテム 1
    # アイテム 2 (特別)
    # アイテム 3
  • HTML取得 (.html()): 選択した要素の内部HTMLを取得します(最初の要素のみ)。
    list_html = doc('.item-list').html()
    print(f"リストのHTML:\n{list_html}")
    # 出力:
    # リストのHTML:
    # <li class="item">アイテム 1</li>
    #       <li class="item special">アイテム 2 (特別)</li>
    #       <li class="item">アイテム 3</li>
  • 属性取得 (.attr('属性名')): 選択した要素の指定した属性の値を取得します(最初の要素のみ)。
    link_href = doc('#link1').attr('href')
    print(f"リンクのhref属性: {link_href}") # 出力: リンクのhref属性: https://example.com
    
    link_id = doc('a').attr('id') # 最初のa要素のid
    print(f"リンクのid属性: {link_id}") # 出力: リンクのid属性: link1

    属性が存在しない場合は None が返ります。

5. 複数の要素の処理

セレクタが複数の要素にマッチした場合、それらをループで処理できます。PyQueryオブジェクトはイテラブル(繰り返し可能)です。

list_items = doc('li.item')
print(f"マッチしたアイテム数: {len(list_items)}") # 出力: マッチしたアイテム数: 3

print("各アイテムのテキスト:")
for item in list_items.items(): # .items() を使うと各要素をPyQueryオブジェクトとして扱える
    print(f"- {item.text()}")
    # クラス属性を確認
    if item.hasClass('special'):
        print("  (これは特別アイテムです ✨)")

# 出力:
# 各アイテムのテキスト:
# - アイテム 1
# - アイテム 2 (特別)
#   (これは特別アイテムです ✨)
# - アイテム 3

.items() メソッドを使うと、ループ内で各要素を個別のPyQueryオブジェクトとして扱えるため、さらにメソッドをチェーン(連結)して操作できます。

DOMトラバーサル

選択した要素を基点に、親子関係や兄弟関係を辿って他の要素にアクセスできます。

メソッド説明
.find(セレクタ)選択した要素の子孫要素から、さらにセレクタにマッチする要素を検索doc('#container').find('li')
.children(セレクタ?)選択した要素の直接の子要素を取得 (任意でセレクタでフィルタ)doc('.item-list').children()
doc('.item-list').children('.special')
.parent(セレクタ?)選択した要素の直接の親要素を取得 (任意でセレクタでフィルタ)doc('h1.main-title').parent()
.parents(セレクタ?)選択した要素の祖先要素をすべて取得 (任意でセレクタでフィルタ)doc('li.special').parents()
doc('li.special').parents('#container')
.siblings(セレクタ?)選択した要素の兄弟要素をすべて取得 (任意でセレクタでフィルタ)doc('li.special').siblings()
doc('li.special').siblings('.item')
.next(セレクタ?)選択した要素の直後の兄弟要素を取得 (任意でセレクタでフィルタ)doc('li.item').next()
.prev(セレクタ?)選択した要素の直前の兄弟要素を取得 (任意でセレクタでフィルタ)doc('li.special').prev()

フィルタリング

選択した要素の集合から、特定の条件に合うものだけを絞り込むことができます。

メソッド説明
.filter(セレクタ or 関数)選択した要素群から、セレクタにマッチするもの、または関数がTrueを返すものだけを残すdoc('li').filter('.special')
doc('li').filter(lambda i, el: pq(el).text().startswith('アイテム 1'))
.not_(セレクタ)選択した要素群から、セレクタにマッチするものを除外するdoc('li.item').not_('.special')
.eq(インデックス)選択した要素群の中から、指定したインデックス(0始まり)の要素を取得doc('li.item').eq(0) # 最初のli.item
.first()選択した要素群の最初の要素を取得 (.eq(0) と同じ)doc('li.item').first()
.last()選択した要素群の最後の要素を取得doc('li.item').last()
.hasClass(クラス名)選択した要素(最初の要素)が指定したクラスを持っているか (True/False)doc('li.special').hasClass('special') # True

要素の操作 (追加、削除、変更)

PyQueryを使ってDOMを操作することも可能です。スクレイピングでは主にデータ抽出に利用しますが、参考までに紹介します。

  • クラスの追加/削除 (.addClass(), .removeClass()):
    # h1要素に 'title-highlight' クラスを追加
    doc('h1').addClass('title-highlight')
    # li.special から 'special' クラスを削除
    doc('li.special').removeClass('special')
    print(doc) # 変更後のHTML全体が出力される
  • 要素の削除 (.remove()):
    # class="item" の要素をすべて削除
    doc('li.item').remove()
    print(doc('.item-list').html()) # リストの中身が空になるはず
  • 要素の追加 (.append(), .prepend(), .after(), .before()):
    # .item-list の末尾に新しいliを追加
    doc('.item-list').append('<li class="new-item">新しいアイテム</li>')
    # h1 の前に新しいp要素を追加
    doc('h1').before('<p class="intro">導入文</p>')
    print(doc)

これらの操作は元のPyQueryオブジェクトを変更します。

架空のニュースサイトのHTML構造から、記事の見出しとそれに対応するリンクURLを抽出する例を見てみましょう。

サンプルHTML:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>サンプルニュース</title>
</head>
<body>
    <h1>今日のニュース</h1>
    <div class="news-list">
        <article class="news-item">
            <h2><a href="/news/001">PyQuery入門が公開されました</a></h2>
            <p>Webスクレイピングがより簡単に。</p>
            <span class="date">2025-03-31</span>
        </article>
        <article class="news-item">
            <h2><a href="/news/002">Pythonの人気、さらに上昇中</a></h2>
            <p>データサイエンス分野での活用拡大。</p>
            <span class="date">2025-03-30</span>
        </article>
        <article class="news-item important">
            <h2><a href="/news/003">Webスクレイピングの倫理について</a></h2>
            <p>注意点を守って正しく利用しましょう。</p>
            <span class="date">2025-03-29</span>
        </article>
    </div>
    <div class="sidebar">
        <h3>関連リンク</h3>
        <ul>
            <li><a href="/links/1">関連サイトA</a></li>
        </ul>
    </div>
</body>
</html>

Pythonコード:

from pyquery import PyQuery as pq

html_doc = """
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>サンプルニュース</title>
</head>
<body>
    <h1>今日のニュース</h1>
    <div class="news-list">
        <article class="news-item">
            <h2><a href="/news/001">PyQuery入門が公開されました</a></h2>
            <p>Webスクレイピングがより簡単に。</p>
            <span class="date">2025-03-31</span>
        </article>
        <article class="news-item">
            <h2><a href="/news/002">Pythonの人気、さらに上昇中</a></h2>
            <p>データサイエンス分野での活用拡大。</p>
            <span class="date">2025-03-30</span>
        </article>
        <article class="news-item important">
            <h2><a href="/news/003">Webスクレイピングの倫理について</a></h2>
            <p>注意点を守って正しく利用しましょう。</p>
            <span class="date">2025-03-29</span>
        </article>
    </div>
    <div class="sidebar">
        <h3>関連リンク</h3>
        <ul>
            <li><a href="/links/1">関連サイトA</a></li>
        </ul>
    </div>
</body>
</html>
"""

doc = pq(html_doc)

news_articles = []

# 各ニュース記事 (.news-item) をループ処理
for article in doc('.news-item').items():
    # 記事内の h2 タグの中の a タグを探す
    link_element = article.find('h2 a')

    # a タグが見つかれば、テキスト(見出し)と href 属性(URL)を取得
    if link_element:
        title = link_element.text()
        url = link_element.attr('href')
        # 相対URLを絶対URLに変換する必要がある場合 (今回は省略)
        # base_url = "http://example-news.com"
        # absolute_url = urljoin(base_url, url)
        news_articles.append({
            'title': title,
            'url': url
        })

# 結果の表示
print("抽出したニュース記事:")
for article_data in news_articles:
    print(f"- 見出し: {article_data['title']}")
    print(f"  URL: {article_data['url']}")

# 出力結果:
# 抽出したニュース記事:
# - 見出し: PyQuery入門が公開されました
#   URL: /news/001
# - 見出し: Pythonの人気、さらに上昇中
#   URL: /news/002
# - 見出し: Webスクレイピングの倫理について
#   URL: /news/003

この例では、まず.news-itemクラスを持つ記事要素をすべて選択し、.items()でループ処理しています。各記事要素の中で、.find('h2 a')を使って見出しのリンク要素(<a>タグ)を特定し、そのテキストとhref属性を取得しています。実際のスクレイピングでは、取得した相対URLをベースURLと結合して絶対URLに変換する処理が必要になることが多いです(urllib.parse.urljoinなどが使えます)。

PyQueryは強力なツールですが、Webスクレイピングを行う際には以下の点に注意し、責任ある行動を心がけましょう。

  • robots.txtの確認: 多くのサイトでは、ルートディレクトリにrobots.txtファイルを設置し、クローラー(ボット)に対してアクセスして良い範囲や禁止事項を明示しています。スクレイピングを行う前に必ずhttps://example.com/robots.txt のようなURLを確認し、その指示に従いましょう。Pythonのurllib.robotparserが解析に役立ちます。
  • 利用規約の確認: サイトの利用規約(Terms of Service, ToS)にスクレイピングに関する記述がないか確認します。明示的に禁止されている場合、スクレイピングは避けるべきです。特に、Amazonや楽天、Facebook、X (旧Twitter)、YouTubeなどは規約で自動収集ツール(ロボットなど)の使用を制限または禁止している場合があります。
  • サーバー負荷への配慮(Rate Limiting): 短時間に大量のリクエストを送ると、相手のサーバーに大きな負荷をかけ、サービス停止を引き起こす可能性があります。これは「DoS攻撃」とみなされる場合もあり、法的な問題に発展する可能性もあります。人間がブラウジングする速度を模倣し、リクエスト間に適切な待機時間(例: time.sleep(1) などで数秒待つ)を設けることが非常に重要です。深夜や早朝など、サイトのオフピーク時間帯を狙うのも良い方法です。
  • User-Agentの設定: HTTPリクエストヘッダーのUser-Agentは、どのようなクライアント(ブラウザやボット)がアクセスしているかを示す情報です。デフォルト(例: python-requests/2.x.x)のままだとボットであることが容易に識別され、アクセスをブロックされる可能性があります。一般的なブラウザのUser-Agent文字列(例: ChromeやFirefoxのもの)を設定し、自分が誰であるか(連絡先など)を明記することが推奨される場合もあります。ただし、偽装することが規約違反になる場合もあるため注意が必要です。複数のUser-Agentをローテーションさせることも有効な場合があります。
  • APIの利用を検討: スクレイピングしたいデータが、サイト提供の公式API経由で取得できる場合、APIを利用する方が安定性、合法性の両面で推奨されます。APIは通常、利用規約が明確で、サーバー負荷も考慮された設計になっています。
  • 著作権とデータ利用: スクレイピングで取得したデータ(テキスト、画像、動画など)には著作権が存在します。取得したデータを無断で複製、再配布、公開することは著作権侵害にあたる可能性があります。私的利用や、法律で認められた引用、情報解析の範囲を超えて利用する場合は、権利者の許諾が必要です。特に、収集したデータを元に新たなサービスを提供する場合は注意が必要です。
  • 個人情報の取り扱い: 個人情報を含むデータを収集・利用する場合は、個人情報保護法などの関連法規を遵守する必要があります。不当な目的での個人情報収集は絶対に避けてください。
  • 動的コンテンツへの対応限界: PyQuery(および内部で使われるrequests)は、基本的にサーバーから返された静的なHTMLを解析します。JavaScriptによって動的に生成・変更されるコンテンツ(いわゆるAjaxなど)は、そのままでは取得・解析できないことが多いです。このようなサイトをスクレイピングするには、SeleniumやPlaywrightのようなブラウザ自動操作ツールが必要になる場合があります。
  • エラーハンドリング: ネットワークエラー、HTTPエラー(404 Not Found, 403 Forbidden, 503 Service Unavailableなど)、HTML構造の変更によるパースエラーなど、スクレイピングには予期せぬエラーがつきものです。適切なtry-exceptブロックを使ってエラーを捕捉し、処理が停止しないように、あるいはエラー発生時に適切に対応できるように実装することが重要です。
法的リスク: 不適切なスクレイピングは、利用規約違反だけでなく、著作権法違反、不正アクセス禁止法違反、偽計業務妨害罪などに問われるリスクがあります。技術的な可能性だけでなく、常に法的・倫理的な側面を考慮してください。

PythonにはPyQuery以外にも有名なHTML/XML解析ライブラリがあります。

  • Beautiful Soup: Pythonで最も広く使われているHTML/XMLパーサーの一つです。非常に柔軟で、不完全なHTMLもある程度寛容に解釈してくれます。セレクタはCSSセレクタも使えますが、独自の検索メソッド(find(), find_all()など)も強力です。PyQueryほどjQueryライクではありませんが、初心者にも比較的扱いやすいです。
  • lxml: C言語で書かれた高速なXML/HTML処理ライブラリです。PyQueryやBeautiful Soupも内部パーサーとしてlxmlを利用できます。XPathやCSSセレクタによる要素選択が可能で、非常に高機能かつ高速ですが、他のライブラリに比べて直接使うと少し記述が冗長になる場合があります。
  • Scrapy: スクレイピング専用のフレームワークです。単なるHTML解析だけでなく、リクエストのスケジューリング、データパイプライン(抽出データの処理・保存)、ミドルウェア(User-Agent変更、プロキシ設定など)といった、大規模なスクレイピングに必要な機能が統合されています。学習コストはやや高いですが、本格的なクローラー開発に適しています。

PyQueryの立ち位置: jQueryの構文に慣れている開発者にとって、学習コストが低く、直感的にHTMLを操作・データ抽出できる点が最大のメリットです。比較的小〜中規模のスクレイピングタスクや、HTML構造が比較的安定しているサイトに対して手軽に適用できます。

PyQueryは、jQueryライクな構文でPythonから手軽にWebスクレイピングやHTML操作を行える便利なライブラリです。CSSセレクタを使った要素の選択やDOM操作が直感的に行えるため、Web開発の経験がある方には特におすすめです。👍

ただし、Webスクレイピングは技術的な側面だけでなく、対象サイトへの配慮や法的な側面も非常に重要です。利用規約やrobots.txtを遵守し、サーバーに負荷をかけないよう注意しながら、責任ある形で活用するようにしましょう。

この記事が、PyQueryを使ったWebスクレイピングの第一歩となれば幸いです。Happy Scraping! 🎉

参考情報

コメント

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