pyquery Web スクレイピング:Python における jQuery スタイルの HTML パース
Senior Web Scraping Engineer
TL;DR:
- pyqueryはPythonでjQueryのAPIを提供します。
$("div.quote").find("small.author").text()を知っているなら、既にpyqueryを知っています。 - pyqueryの機能には取得が含まれていません。 それは
lxmlを利用して解析と選択を行いますが、HTTPクライアントは持たず、JavaScriptを実行しません。 - このギャップは一つのスクリプトに現れます。 JavaScriptレンダリングされたデモページに対するプレーンGETはpyqueryに0の引用ノードを返しますが、
js_renderを使ったScrapeless Universal Scraping APIで同じURLを取得すると全ての10ノードが得られます。 - このガイドでは実際に抽出を行います。 ライブ実行では
.items(),.find(),.text()を使用してレンダリングされたHTMLから著者やタグと共に全ての10の引用を取得しました。 - 二つのレイヤーは別々に保たれます。 Scrapelessが取得とレンダリングを行い、pyqueryが選択を行います。いずれもお互いの仕事には干渉しません。
- 取得側は無料で始められます。 app.scrapeless.comでScrapeless APIキーを作成してください。
pyqueryとは何か、そしてそれが何ではないのか
pyqueryはPythonライブラリで、lxmlの上にjQueryスタイルのAPIを提供します。マークアップをPyQueryオブジェクト(通常はdと名付けられます)でラップし、フロントエンド開発者が既に使っているのと同じコールを使って選択し、トラバースします:d("selector")、.find()、.eq()、.text()、.attr()、.items()。ブラウザから来た人にとっては、「DOMをクエリする方法を知っている」から「これをPythonで抽出できる」までの最短の道で、lxmlの上にあるため、選択が速いです。
これは解析と選択のライブラリに過ぎません。pyqueryにはHTTPクライアントはなく、セッションを保持せず、JavaScriptを実行しません。文字列を与えればクエリ可能なドキュメントを構築しますが、URLを取得するように頼むと、技術的には一つ引き出せますが、プレーンなリクエストを使用し、レンダリングは行わず、スクリプトによって構築されたページには不適切なツールです。したがって、すべての「pyqueryウェブスクレイピング」セットアップは二つのレイヤーです:忠実にレンダリングされたHTMLを返す何かと、その選択を行うpyqueryです。このガイドでは最初のレイヤーにScrapeless Universal Scraping APIを使用します。より広範なPythonウェブスクレイピングチュートリアルが周辺のエコシステムをカバーしています。
インストール
pyqueryとrequestsが全体のツールチェーンです。このガイドが書かれたバージョンはpyquery 2.0.1です:
bash
pip install "pyquery==2.0.1" requests
キーは環境に保持し、ソースの中には決して記載しないでください:
bash
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
解析に値するHTMLを取得する
選択の質は取得の忠実度に制約されるため、まずそこから始めます。JavaScriptレンダリングされたページでは、プレーンなHTTPクライアントが受け取るマークアップは、リーダーが見るマークアップとは異なります — コンテンツはスクリプトがDOMを構築した後にのみ到着します。このライフサイクルはHTMLスクリプティング仕様で定義されています。一つのスクリプトがpyquery自体を通じてこの違いをカウントします:
python
# fidelity.py — pyqueryが見るもの:プレーンなGET vs サーバーサイドレンダリング
import os
import requests
from pyquery import PyQuery as pq
URL = "https://quotes.toscrape.com/js/"
plain = requests.get(URL, timeout=60).text
print("プレーンGET文字数:", len(plain), "| 引用ノード:", pq(plain, parser="html")("div.quote").length)
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": URL, "js_render": True}},
timeout=120,
)
resp.raise_for_status()
rendered = resp.json().get("data", "")
print("レンダリングされた文字数:", len(rendered), "| 引用ノード:", pq(rendered, parser="html")("div.quote").length)
実行結果はプレーン取得で0の引用ノード、レンダリングされたもので10の引用ノードを表示します:
text
プレーンGET文字数: 5806 | 引用ノード: 0
レンダリングされた文字数: 8940 | 引用ノード: 10
レンダリング、アンロック、プロキシルーティングはすべてサーバーサイドでその一つのPOST内で行われます — Universal Scraping APIが取得レイヤーであり、レンダリングされたマークアップはpyqueryが選択する対象です。
jQuery APIで抽出する
実際のHTMLを手に入れたら、pyqueryはjQueryと同じ方法で抽出を行います。.items()は選択をPyQueryオブジェクトのイテレータに変え、.find()は各選択肢にサブセレクタのスコープを設定し、.text()はテキストを読み取ります — セレクタはW3Cセレクタ仕様に従い、同じシンタックスがjQueryで使用されています:
python
# extract.py — レンダリングされたページを取得し、jQuery APIで選択
import os
import requests
from pyquery import PyQuery as pq
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
ヘッダー={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": "https://quotes.toscrape.com/js/", "js_render": True}},
タイムアウト=120,
)
resp.raise_for_status()
d = pq(resp.json().get("data", ""), parser="html")
レコード = []
for quote in d("div.quote").items():
レコード.append({
"text": quote.find("span.text").text(),
"author": quote.find("small.author").text(),
"tags": [pq(tag).text() for tag in quote.find("a.tag").items()],
})
print("レコード数:", len(レコード))
print("最初の著者:", レコード[0]["author"])
print("最初のタグ:", レコード[0]["tags"])
ライブ実行はすべての10レコード、タグを含めて選択しました:
```text
レコード数: 10
最初の著者: アルベルト・アインシュタイン
最初のタグ: ['変化', '深い思考', '思考', '世界']
これが全体のスクレイパーです: 一つのPOSTリクエストで取得とレンダリングを行い、一つのPyQueryオブジェクトでクエリを実行します。.items()イテレータは覚えておくべきpyqueryの慣用句です — 選択をレコードごとのオブジェクトに変換し、.find()を用いることができるものです。これはjQueryの.each()の直接的な対応物です。
無料プランのAPIキーを取得する: app.scrapeless.com
高度なパターン
- 常に
.items()を使って反復する。PyQuery選択を直接ループすると、raw lxml要素が得られ、find()が機能しなくなります。.items()は各要素にフルAPIのラップされたオブジェクトを提供します。 .attr()で属性を読み取る。quote.find("a::attr(href)")はpyqueryの構文ではありません;quote.find("a").attr("href")を使いましょう、jQueryと全く同じです。- 本物のページには
parser="html"を渡す。 これはlxmlの寛容なHTMLパーサーを選択し、実際のサイトが送信する不正マークアップを処理します; デフォルトは厳格すぎる場合があります。 - チェインし、再クエリしない。
d("div.quote").eq(0).find("small.author")は各ステップを前のものにスコープし、より速く、あなたが既に知っているjQueryの読み方に近いです。
トラブルシューティング
- ブラウザで見ることができるページからノードがゼロ。 コンテンツはJavaScriptでレンダリングされ、取得したのはプリレンダリングされたHTMLです。最初のスクリプトがそのように行うように、既知のセレクターをカウントしてください; ほぼ空のドキュメントは取得の問題で、
js_renderで修正できる問題です。 .find()がAttributeErrorを発生させる。 あなたは選択を直接反復しているため、bare lxml要素を取得してしまいました。ループをfor x in sel.items():に切り替えてください。.text()がすべて連結されたものを返す。 pyqueryは子孫テキストを結合します。より具体的なセレクターでスコープを狭めるか、.eq(0).text()で単一ノードを読み取ってください。- エンコーディングが正しくないように見える。 レスポンステキストをpyquery優先で
parser="html"とともに渡してください; lxmlは文書の宣言されたエンコーディングを、パーサーがHTMLであるときに読み取ります。
結論
pyqueryは選択レイヤーとしてjQueryに話しかける地位を得ています: 同じ.find(), .text(), および.items()呼び出しをブラウザから、速いlxmlツリーの上で実行します。それがそのすべてが可能になるかどうかを決定するレイヤーは取得です — 最初のスクリプトの0対10カウントがそれを決定し、1つのサーバーレンダリングされたPOSTがギャップを埋めます。それを結びつけて、デモページの10の引用がすでに考えている方法でクリーンなレコードとして到着します。
無料のScrapelessアカウントを作成するためにAPIキーを取得し、開発者ドキュメントがunlocker.webunlockerパラメーターをカバーします。繰り返しジョブを計画する際には、Scrapelessの料金をチェックしてください。
FAQ
Q: pyqueryは自分でウェブサイトをスクレイプできますか?
あまりできません。pyqueryはURLを取得できますが、プレーンリクエストで行い、JavaScriptのレンダリングは行わないため、最新のページでは空のマークアップから選択します。これをパーサーと捉え、フェッチレイヤーとペアにします — ここではScrapeless Universal Scraping APIがサーバー側でページをレンダリングします — そしてpyqueryは返されたHTMLからの選択を処理します。
Q: pyqueryはjQueryと同じですか?
PythonにおけるjQueryのAPIを反映しています — d("selector"), .find(), .text(), .attr(), .items() — しかし、ブラウザではなくlxml上でサーバーサイドで実行されるため、静的なマークアップから選択し、スクリプトを実行したりイベントを処理することはありません。選択構文は伝達されます; ランタイムはそうではありません。
Q: pyqueryまたはBeautifulSoup?
好みの問題です。pyqueryはjQueryのように読み取れ、フロントエンド作業から来た場合には自然なフィットです。BeautifulSoupはよりPython的なAPIを持っています。両者は同じHTMLを解析し、JavaScriptでレンダリングされたページには別のフェッチレイヤーが必要です。
Q: pyqueryはJavaScriptでレンダリングされたページを処理しますか?
そのままでは — スクリプトを実行することはありません。コンテンツが初期HTMLの後にロードされる場合、通常のfetchではpyqueryに空のドキュメントが渡されます。最初にjs_renderを使用してScrapeless API経由でページを取得し、その後このガイドのようにレンダリングされたHTMLから選択します。
Q: pyqueryを使ったスクレイピングは合法ですか?
選択ライブラリは収集ルールを変更しません。公開ページのみを取得し、サイトの利用規約およびロボット排除プロトコルによって標準化されたロボットの指示を尊重し、ボリュームを制限し、適用される法律に従って個人データを取り扱ってください。
Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



