パーセル Webスクレイピング:PythonにおけるCSSとXPathセレクター
Senior Web Scraping Engineer
TL;DR:
- parselはHTMLからデータをCSSとXPathの両方を使って選択します。これは同じドキュメントに対してScrapyが使用するセレクターエンジンで、スタンドアロンのライブラリとして利用可能です。
- parselが行わないことはデータの取得です。 HTTPクライアントは持っておらず、JavaScriptも実行しません。マークアップを渡すと、クエリ可能なツリーを構築します。
- ギャップは1つのスクリプトに現れます。 JavaScriptでレンダリングされたデモページでのプレーンGETはparselに0の引用ノードを与えますが、
js_renderを通してScrapeless Universal Scraping APIで取得した同じURLはすべて10の引用ノードを与えます。 - CSSとXPath、並行して。 実行時に同じ10の著者を2つの方法で引き出しました —
sel.css("small.author::text")とsel.xpath("//small[@class='author']/text()")— レンダリングされたHTMLから。 - 2つのレイヤーは別々に保たれています。 Scrapelessは取得とレンダリングを行う; parselは選択を行います。どちらもお互いの仕事に干渉しません。
- 取得側から自由に始められます。 app.scrapeless.comでScrapeless APIキーを作成してください。
parselとは何か、そして何でないか
parselは、CSSセレクターとXPath式を使用してHTMLおよびXMLからデータを抽出するためのPythonライブラリです。これはScrapyが構築されているセレクターレイヤーで、どこでも使用できるようにパッケージ化されており、内部でlxmlをラップしているため、両方のセレクター言語が同じ高速なパースツリーに対して実行されます。CSS専用のパーサーに対してparselを使う理由はXPathです。フィールドがその位置、テキスト、またはクラスではなく兄弟との関係によって定義されている場合、XPathはそれを表現し、CSSでは表現できません。
これはセレクターレイブラリでそれ以上ではありません。parselにはHTTPクライアントはなく、セッションも持たず、JavaScriptも実行しません。文字列を渡すとクエリ可能なSelectorを構築しますが、URLを取得する方法はありません。したがって、すべての「parselウェブスクレイピング」設定は2つのレイヤーで構成されています:忠実なHTMLを返す何かと、その中から選択するparselです。このガイドは最初のレイヤーとしてScrapeless Universal Scraping APIを使用しています。なぜなら、プレーンHTTPクライアントはJavaScriptでコンテンツを構築するページ上のプレレンダーマークアップを返すからです。より広範なPythonウェブスクレイピングチュートリアルでは周辺エコシステムをカバーしています。
インストール
parselとrequestsがツールチェーン全体です。このガイドが執筆されたバージョンはparsel 1.11.0です:
bash
pip install "parsel==1.11.0" requests
キーは環境に保存し、ソースには決して保存しないでください:
bash
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
パースする価値のあるHTMLを取得
選択の品質は取得の忠実性によって制約されるため、そこから始めましょう。JavaScriptでレンダリングされたページでは、プレーンHTTPクライアントが受け取るマークアップはリーダーが見るマークアップとは異なります:コンテンツはスクリプトがDOMを構築した後にのみ到着します。このライフサイクルはHTMLスクリプティング仕様によって定義されています。あるスクリプトがparsel自体を通してその違いを数えます:
python
# fidelity.py — parselが見るもの:プレーンGET vs サーバーサイドレンダリング
import os
import requests
from parsel import Selector
URL = "https://quotes.toscrape.com/js/"
plain = requests.get(URL, timeout=60).text
print("plain GET chars:", len(plain), "| quote nodes:", len(Selector(text=plain).css("div.quote")))
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("rendered chars:", len(rendered), "| quote nodes:", len(Selector(text=rendered).css("div.quote")))
実行結果はプレーン取得で0の引用ノード、レンダリングされたものでは10の引用ノードを印刷します:
text
plain GET chars: 5806 | quote nodes: 0
rendered chars: 8940 | quote nodes: 10
レンダリング、アンロック、およびプロキシルーティングはすべてサーバー側でその1つのPOST内で発生します — Universal Scraping APIが取得レイヤーであり、レンダリングされたマークアップがparselが選択すべきものです。
CSSとXPathで抽出
実際のHTMLを手に入れたら、parselは抽出を行います。cssとxpathは両方ともSelectorListを返し、getは最初の一致を返し、getallはすべての一致を返します。::text擬似要素とtext()ノードテストの両方がテキストを引き出すので、同じフィールドをどちらの方法でも読むことができます — CSSはW3Cセレクター仕様に従い、XPathはW3C XPath仕様に従います:
python
# extract.py — レンダリングされたページを取得し、次にCSSとXPathを使って選択
import os
import requests
from parsel import Selector
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": "https://quotes.toscrape.com/js/", "js_render": True}},
timeout=120,
)
resp.raise_for_status()
sel = Selector(text=resp.json().get("data", ""))
cssノード = sel.css("div.quote")
xpath著者 = sel.xpath("//div[@class='quote']//small[@class='author']/text()").getall()
print("css引用ノード:", len(cssノード))
print("xpath著者:", len(xpath著者))
レコード = []
for quote in cssノード:
レコード.append({
"text": quote.css("span.text::text").get(),
"author": quote.xpath(".//small[@class='author']/text()").get(),
"tags": quote.css("a.tag::text").getall(),
})
print("最初の著者:", レコード[0]["author"])
print("最初のタグ:", レコード[0]["tags"])
実行結果では、すべての10件のレコードが選択され、CSSとXPathが同じ著者を返しました:
```text
css引用ノード: 10
xpath著者: 10
最初の著者: アルバート・アインシュタイン
最初のタグ: ['変化', '深い考え', '思考', '世界']
これが全体のスクレイパーです:フェッチとレンダリングのために1回のPOST、クエリのための1つのSelector。簡単なフィールドにはCSSを、位置的なフィールドにはXPathを使う — quote.xpath("...")は各ノードに対して相対的に実行される — というパターンは、ページが複雑になるにつれてスクレイパーを可読性のあるものに保ちます。
無料プランでAPIキーを取得してください:app.scrapeless.com
高度なパターン
- CSSが限界に達したらXPathを使う。 テキストによる選択(
//a[contains(text(), "Next")])、位置による選択((//tr)[2])、または軸による選択(following-sibling::td)はXPathの領域です;CSSには同等のものはありません。 - ノードに対して相対的にセレクタを連結する。
quote.css(...)とquote.xpath(".//...")の両方がそのノードにスコープを持ちます — XPathの先頭の.はそれを相対的に保ちます。これは「この引用の中の著者」と「ページ上のすべての著者」の違いです。 get(default="")を使ってNoneを避ける。sel.css("span.missing::text").get(default="")はNoneの代わりに空の文字列を返し、記録の不均一なループが奇数ページで壊れないようにします。- 属性を
::attr()または@で引き出す。sel.css("a::attr(href)")とsel.xpath("//a/@href")は両方とも属性を読み取ります;セレクタの残りの言語に応じて使用します。
トラブルシューティング
- ブラウザで見えるページからゼロノード。 コンテンツがJavaScriptでレンダリングされており、フェッチがプリレンダリングされたHTMLを返しました。最初のスクリプトが行うように既知のセレクタをカウントします;ほぼ空のツリーはフェッチ問題であり、
js_renderで修正されるもので、セレクタ問題ではありません。 get()がNoneを返す。 セレクタは何も一致しませんでした。レンダリングされたマークアップを確認し、クラスやパスを確認し、見逃した際に下流のコードがクラッシュしないようにdefaultを渡します。- XPathが要素を返すのにテキストを望んでいる。 パスに
/text()を追加するか、::textCSSセレクタの.get()を使用します;裸の要素パスはノードを返し、その文字列ではありません。 - 相対的XPathがページ全体を取得する。
//で始まるパスは絶対的であり、ノードに対して呼ばれても同様です。.でプレフィックスを付けて —.//small— 現在の要素にスコープを決定します。
結論
parselは、一般的なケースにCSSを、CSSが到達できないケースにXPathを使用する選択層としてその地位を確立しています。最初のスクリプトの0対10カウントがそれを決定付けるのは、フェッチ層がそれを可能にする層です — サーバーでレンダリングされた1回のPOSTでギャップが解消されます。これら2つを結びつけると、デモページの10の引用がきれいなレコードとして届き、どの方法で選択しても最適な方法で処理されます。
無料のScrapelessアカウントを作成してAPIキーを取得し、開発者ドキュメントではunlocker.webunlockerのパラメータが説明されています。定期的なジョブを計画する際はScrapelessの料金を確認してください。
FAQ
Q: parselは自分自身でウェブサイトをスクレイピングできますか?
いいえ。parselは、すでに持っているHTMLからデータを選択します;HTTPクライアントはなく、JavaScriptを実行しません。フェッチ層と組み合わせてください — ここではScrapeless Universal Scraping APIがサーバー側でページをレンダリングします — そしてparselは返されたマークアップからの抽出を処理します。
Q: parselとScrapyのセレクタの違いは何ですか?
実際には、parselはScrapyが使用するセレクタエンジンであり、スタンドアロンライブラリとしてリリースされています。Scrapyスパイダーでresponse.cssまたはresponse.xpathを使用したことがあるなら、それはparselです。これを直接使用することで、フレームワーク全体を採用することなく、セレクタAPIを維持できます。
Q: parselでCSSとXPathのどちらを使うべきですか?
必要に応じて両方です。CSSはクラスやタグに基づく選択には短く、XPathはテキスト、位置、または軸による選択を処理し、CSSでは表現できません。parselは同じツリーに対して両方を実行するので、フィールドごとに混ぜて使用してください。
Q: parselはJavaScriptでレンダリングされたページに対応していますか?
自力では対応していません — スクリプトを実行することはありません。初期HTMLの後にコンテンツがロードされる場合、通常のフェッチではparselに空のツリーを渡します。最初にjs_renderを使ってScrapeless APIを通じてページをフェッチし、その後、レンダリングされたHTMLから選択します。このガイドのように。
Q: parselを使用したスクレイピングは合法ですか?
セレクタライブラリは収集ルールを変更しません。公開ページのみを取得し、サイトの利用規約およびロボット排除プロトコルで標準化されたロボット指示に従い、ボリュームを制限し、適用される法律に従って個人データを処理してください。
Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



