セレクター¶
あなたがWebページをスクレイピングする場合、実行する必要がある最も一般的なタスクは、HTMLソースからデータを抽出することです。 これを実現するために利用可能ないくつかのライブラリがあります。:
BeautifulSoup は、Pythonプログラマーの間で非常に人気のあるWebスクレイピングライブラリであり、HTMLコードの構造に基づいてPythonオブジェクトを構築し、悪いマークアップも合理的に処理しますが、1つの欠点があります。遅いんです。
lxml は、 ElementTree に基づいたPython APIを備えたXMLパースライブラリ(HTMLもパースします)です。(lxmlはPython標準ライブラリの一部ではありません。)
Scrapyには、データを抽出するための独自のメカニズムが備わっています。 これらは、 XPath または CSS 式で指定されたHTMLドキュメントの特定の部分を「選択(select)」するため、セレクター(selector)と呼ばれます。
XPath は、XMLドキュメントでノードを選択するための言語であり、HTMLでも使用できます。 CSS は、HTMLドキュメントにスタイルを適用するための言語です。 これらのスタイルを特定のHTML要素に関連付けるセレクターを定義します。
注釈
Scrapyセレクターは、 parsel ライブラリの薄いラッパーです。 このラッパーの目的は、Scrapy Responseオブジェクトとの統合を改善することです。
parsel は、Scrapyなしで使用できるスタンドアロンのWebスクレイピングライブラリです。 内部で lxml ライブラリを使用し、lxml APIの上に簡単なAPIを実装します。 これは、Scrapyセレクターの速度と解析精度が、lxmlに非常に似ていることを意味します。
セレクターの使用¶
セレクターの構築¶
Responseオブジェクトは .selector 属性で Selector インスタンスを公開します。:
>>> response.selector.xpath('//span/text()').get()
'good'
XPathとCSSを使用したレスポンスのクエリは非常によく使われるので、レスポンスにはさらに2つのショートカットが含まれます。 response.xpath() と response.css() です。:
>>> response.xpath('//span/text()').get()
'good'
>>> response.css('span::text').get()
'good'
Scrapyセレクターは、 TextResponse オブジェクトまたはUnicode文字列でマークアップを渡す(text 引数で)ことで構築された Selector クラスのインスタンスです。通常、Scrapyセレクターを手動で作成する必要はありません。 response オブジェクトはSpiderコールバックで使用できるため、ほとんどの場合、 response.css() ショートカットと response.xpath() ショートカットを使用する方が便利です。 response.selector またはこれらのショートカットのいずれかを使用することで、レスポンス・ボディが1回だけパースされることを確認することもできます。
ただし、必要に応じて、セレクターを直接使用することができます。 テキストから構築する場合は以下です。:
>>> from scrapy.selector import Selector
>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').get()
'good'
レスポンスから構築する場合、 HtmlResponse は TextResponse のサブクラスの1つです:
>>> from scrapy.selector import Selector
>>> from scrapy.http import HtmlResponse
>>> response = HtmlResponse(url='http://example.com', body=body)
>>> Selector(response=response).xpath('//span/text()').get()
'good'
セレクターは、入力タイプに基づいて最適なパース・ルール(XMLかHTML)を自動的に選択します。
セレクターの使用¶
セレクターの使用方法を説明するために、私たちは、「Scrapyシェル」(対話的なテストを提供します)とScrapyドキュメントサーバーにあるサンプルページを使用します。:
完全を期すために、完全なHTMLコードを次に示します。:
<html>
<head>
<base href='http://example.com/' />
<title>Example website</title>
</head>
<body>
<div id='images'>
<a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
<a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
<a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
<a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
<a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
</div>
</body>
</html>
まず、シェルを開きましょう。:
scrapy shell https://docs.scrapy.org/en/latest/_static/selectors-sample1.html
次に、シェルがロードされると、レスポンスが response シェル変数として使用可能になり、 response.selector 属性にそのセレクターが当てはめられます。
HTMLを扱っているため、セレクターは自動的にHTMLパーサーを使用します。
それでは、そのページの HTMLコード を見て、タイトルタグ内のテキストを選択するためのXPathを作成しましょう。:
>>> response.xpath('//title/text()')
[<Selector xpath='//title/text()' data='Example website'>]
テキストデータを実際に抽出するには、次のようにセレクタ .get() または .getall() メソッドを呼び出す必要があります。:
>>> response.xpath('//title/text()').getall()
['Example website']
>>> response.xpath('//title/text()').get()
'Example website'
.get() は常に単一の結果を返します。 複数の一致がある場合、最初の一致のコンテンツが返されます。 一致するものがない場合はNoneが返されます。 .getall() はすべての結果を含むリストを返します。
CSSセレクターは、CSS3疑似要素を使用してテキストまたは属性ノードを選択できることに注意してください。:
>>> response.css('title::text').get()
'Example website'
あなたがご覧のとおり、 .xpath() と .css() メソッドは SelectorList のインスタンスを返します。これは新しいセレクターのリストです。 このAPIは、ネストされたデータをすばやく選択するために使用できます。:
>>> response.css('img').xpath('@src').getall()
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
あなたが最初に一致した要素のみを抽出したい場合は、セレクタ .get() (または以前のScrapyバージョンで一般的に使用されていたエイリアス .extract_first() )を呼び出すことができます。:
>>> response.xpath('//div[@id="images"]/a/text()').get()
'Name: My image 1 '
要素が見つからなかった場合は None を返します。:
>>> response.xpath('//div[@id="not-exists"]/text()').get() is None
True
None の代わりに使用されるデフォルトの戻り値を引数として提供できます。:
>>> response.xpath('//div[@id="not-exists"]/text()').get(default='not-found')
'not-found'
例えば '@src' のようなXPathを使用する代わりに、 Selector の .attrib プロパティを使用して属性を問い合わせることができます。:
>>> [img.attrib['src'] for img in response.css('img')]
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
ショートカットとして、 .attrib はSelectorListでも直接利用できます。 最初に一致する要素の属性を返します。:
>>> response.css('img').attrib['src']
'image1_thumb.jpg'
これは、単一の結果のみが予想される場合に最も役立ちます。例えばIDで選択する場合、またはWebページ上の一意の要素を選択する場合。:
>>> response.css('base').attrib['href']
'http://example.com/'
今や、私たちは、ベースURLといくつかの画像リンクを取得します。:
>>> response.xpath('//base/@href').get()
'http://example.com/'
>>> response.css('base::attr(href)').get()
'http://example.com/'
>>> response.css('base').attrib['href']
'http://example.com/'
>>> response.xpath('//a[contains(@href, "image")]/@href').getall()
['image1.html',
'image2.html',
'image3.html',
'image4.html',
'image5.html']
>>> response.css('a[href*=image]::attr(href)').getall()
['image1.html',
'image2.html',
'image3.html',
'image4.html',
'image5.html']
>>> response.xpath('//a[contains(@href, "image")]/img/@src').getall()
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
>>> response.css('a[href*=image] img::attr(src)').getall()
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
CSSセレクターの拡張機能¶
W3C標準では、 CSS selectors はテキストノードまたは属性値の選択をサポートしていません。 しかし、これらを選択することは、Webスクレイピングコンテキストでは非常に重要であるため、Scrapy(parsel)はいくつかの 非標準の擬似要素 を実装しています。:
テキストノードを選択するには
::textを使用します属性値を選択するには
::attr(name)を使用します。 name は、あなたが値を取得したい属性の名前です
例:
title::textは<title>の子孫のテキストノードを選択します。:>>> response.css('title::text').get() 'Example website'
*::textは、現在のセレクターコンテキストの全ての子孫テキストノードを選択します。:>>> response.css('#images *::text').getall() ['\n ', 'Name: My image 1 ', '\n ', 'Name: My image 2 ', '\n ', 'Name: My image 3 ', '\n ', 'Name: My image 4 ', '\n ', 'Name: My image 5 ', '\n ']
foo::textは、foo要素が存在するが、テキストを含まない場合(つまり、テキストが空の場合)の場合、結果を返しません。>>> response.css('img::text').getall() []
つまり、
.css('foo::text').get()は、要素が存在する場合でもNoneを返す可能性があることを意味します。 常に文字列が必要な場合はdefault=''引数を使用してください。:>>> response.css('img::text').get() >>> response.css('img::text').get(default='') ''
a::attr(href)は、リンクにぶら下がってる href 属性値を選択します。:>>> response.css('a::attr(href)').getall() ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
注釈
要素属性の選択 も参照下さい。
注釈
あなたは、これらの擬似要素をチェインさせることはできません。 ただし、実際にはあまり意味がありません。テキストノードには属性がなく、属性値は既に文字列値であり、子ノードはありません。
セレクターを入れ子にする¶
選択メソッド(.xpath() または .css())は同じタイプのセレクターのリストを返すため、これらのセレクターの選択メソッドも呼び出すことができます。 以下に例を示します。:
>>> links = response.xpath('//a[contains(@href, "image")]')
>>> links.getall()
['<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>',
'<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg"></a>',
'<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg"></a>',
'<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg"></a>',
'<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg"></a>']
>>> for index, link in enumerate(links):
... args = (index, link.xpath('@href').get(), link.xpath('img/@src').get())
... print('Link number %d points to url %r and image %r' % args)
Link number 0 points to url 'image1.html' and image 'image1_thumb.jpg'
Link number 1 points to url 'image2.html' and image 'image2_thumb.jpg'
Link number 2 points to url 'image3.html' and image 'image3_thumb.jpg'
Link number 3 points to url 'image4.html' and image 'image4_thumb.jpg'
Link number 4 points to url 'image5.html' and image 'image5_thumb.jpg'
要素属性の選択¶
属性の値を取得する方法はいくつかあります。 まず、XPath構文を使用できます。:
>>> response.xpath("//a/@href").getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
XPath構文にはいくつかの利点があります。これは標準のXPath機能であり、 @attributes はXPath式の他の部分で使用できます。 属性値でフィルタリングすることが可能です。
Scrapyは、属性値を取得できるCSSセレクター(::attr(...))の拡張機能も提供します。:
>>> response.css('a::attr(href)').getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
それに加えて、Selectorの .attrib プロパティがあります。 XPathまたはCSS拡張機能を使用せずに、Pythonコードで属性を検索する場合に使用できます。:
>>> [a.attrib['href'] for a in response.css('a')]
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
このプロパティはSelectorListでも使用できます。 最初に一致した要素の属性を持つ辞書を返します。 セレクターが単一の結果を返すと予想される場合(たとえば、要素IDで選択する場合、またはページ上の一意の要素を選択する場合)に使用すると便利です。:
>>> response.css('base').attrib
{'href': 'http://example.com/'}
>>> response.css('base').attrib['href']
'http://example.com/'
空のSelectorListの .attrib プロパティは空です。:
>>> response.css('foo').attrib
{}
セレクターで正規表現を使う¶
Selector には、正規表現を使用してデータを抽出する .re() メソッドもあります。 ただし、 .xpath() または .css() メソッドを使用するのとは異なり、 .re() はUnicode文字列のリストを返します。 したがって、ネストした .re() 呼び出しを構築することはできません。
上記の HTMLコード から画像名を抽出する例を次に示します。:
>>> response.xpath('//a[contains(@href, "image")]/text()').re(r'Name:\s*(.*)')
['My image 1',
'My image 2',
'My image 3',
'My image 4',
'My image 5']
.re() のために .get() (およびそのエイリアス .extract_first())に対応する追加のヘルパーがあり、名前は .re_first() です。 これを使用して、最初に一致する文字列のみを抽出します。:
>>> response.xpath('//a[contains(@href, "image")]/text()').re_first(r'Name:\s*(.*)')
'My image 1'
extract() と extract_first()¶
あなたが長年のScrapyユーザーなら、おそらく .extract() と .extract_first() セレクターメソッドに慣れているでしょう。 多くのブログ投稿とチュートリアルも同様にそれらを使用しています。 これらのメソッドはまだScrapyでサポートされており、それらを 非推奨にする計画はありません 。
けれども、Scrapyの使用法の文書は .get() と .getall() メソッドを使用して記述されるようになりました。 私たちは、これらの新しいメソッドは、より簡潔で読みやすいコードになると思います。
次の例は、これらのメソッドが互いにどのようにマッピングされるかを示しています。
SelectorList.get()はSelectorList.extract_first()と同じです:>>> response.css('a::attr(href)').get() 'image1.html' >>> response.css('a::attr(href)').extract_first() 'image1.html'
SelectorList.getall()はSelectorList.extract()と同じです:>>> response.css('a::attr(href)').getall() ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html'] >>> response.css('a::attr(href)').extract() ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
Selector.get()はSelector.extract()と同じです:>>> response.css('a::attr(href)')[0].get() 'image1.html' >>> response.css('a::attr(href)')[0].extract() 'image1.html'
一貫性のために、リストを返す
Selector.getall()もあります:>>> response.css('a::attr(href)')[0].getall() ['image1.html']
したがって、主な違いは、.get() と .getall() メソッドの出力はより予測可能なことです。 .get()``は常に単一の結果、 ``.getall() は常に抽出されたすべての結果のリストを返します。 .extract() メソッドでは、結果がリストであるかどうかは必ずしも明らかではありませんでした。 単一の結果を得るには、 .extract() または .extract_first() を呼び出す必要があります。
XPathsで作業する¶
ScrapyセレクターでXPathを効果的に使用するのに役立つヒントをいくつか紹介します。 XPathにまだ慣れていない場合は、まず、 XPath tutorial をご覧ください。
注釈
いくつかのヒントは this post from ScrapingHub's blog に基づいています。
相対XPathで作業する¶
セレクターをネストし、/ で始まるXPathを使用する場合、そのXPathはドキュメントの絶対パスであり、呼び出し元のセレクターに対して相対的ではないことに注意してください。
たとえば、 <div> 要素内のすべての <p> 要素を抽出するとします。最初に、すべての <div> 要素を取得します:
>>> divs = response.xpath('//div')
最初は、以下のアプローチを使用したくなるかもしれませんが、実際には <div> 要素内の要素だけでなく、ドキュメント内すべての <p> 要素を抽出するため、間違っています:
>>> for p in divs.xpath('//p'): # this is wrong - gets all <p> from the whole document
... print(p.get())
以下が適切な方法です(XPathの先頭に . が付いていることに注意してください):
>>> for p in divs.xpath('.//p'): # extracts all <p> inside
... print(p.get())
もう一つの一般的なやり方は、すべての子 <p> を直接抽出することです:
>>> for p in divs.xpath('p'):
... print(p.get())
相対XPathの詳細については、XPath仕様の Location Paths 節を参照してください。
クラスによってクエリーする場合、CSSの使用を検討してください¶
要素には複数のCSSクラスを含めることができるため、クラスごとに要素を選択するXPathの方法はかなり冗長です:
*[contains(concat(' ', normalize-space(@class), ' '), ' someclass ')]
あなたが @class='someclass' を使用すると、他のクラスを持つ要素が欠落する可能性があります。それを補うために、単に contains(@class, 'someclass') を使用すると、文字列 someclass を共有する別のクラス名がある場合、より多くの要素が必要になる可能性があります。
この場合、Scrapyセレクターを使用するとセレクターをチェインできるため、ほとんどの場合、CSSを使用してクラスごとに選択し、それから必要に応じてXPathに切り替えることができます。:
>>> from scrapy import Selector
>>> sel = Selector(text='<div class="hero shout"><time datetime="2014-07-23 19:00">Special date</time></div>')
>>> sel.css('.shout').xpath('./time/@datetime').getall()
['2014-07-23 19:00']
これは、上記の詳細なXPathトリックを使用するよりもクリーンです。 後に続くXPath式で . を使用することを忘れないでください。
//node[1] と (//node)[1] の違いに注意してください¶
//node[1] は、それぞれの親(parents)の下で最初に発生するすべてのノードを選択します。
(//node)[1] ドキュメント内のすべてのノードを選択し、その最初のノードのみを取得します。
例:
>>> from scrapy import Selector
>>> sel = Selector(text="""
....: <ul class="list">
....: <li>1</li>
....: <li>2</li>
....: <li>3</li>
....: </ul>
....: <ul class="list">
....: <li>4</li>
....: <li>5</li>
....: <li>6</li>
....: </ul>""")
>>> xp = lambda x: sel.xpath(x).getall()
これは、<li> 要素の親(parent)である全ての要素の子としてある、<li> 要素達の最初のを取得します:
>>> xp("//li[1]")
['<li>1</li>', '<li>4</li>']
ドキュメント全体の <li> 要素の最初のものを返します。:
>>> xp("(//li)[1]")
['<li>1</li>']
これは `` <ul> `` の子に `` <li> `` があるパターン全てが対象となり、それぞれでの最初の``<li>`` 要素を取得します。:
>>> xp("//ul/li[1]")
['<li>1</li>', '<li>4</li>']
ドキュメント全体の、 <ul> の子に <li> があるパターン全てが対象となり、その中で、一番最初の <li> 要素を取得します:
>>> xp("(//ul/li)[1]")
['<li>1</li>']
条件によるテキストノードの使用¶
あなたがテキスト内容をXPath文字列関数(XPath string function)の引数として使用する必要がある場合、 .//text() の使用を避け、代わりに . のみを使用してください。
これは、.//text() 式が ノードセット -- テキスト要素のコレクション -- を生成するためです。 そして、ノードセットが文字列に変換されるとき、つまり、 contains() または starts-with() のような文字列関数への引数として渡されるとき、最初の要素のテキストのみが渡されます。
例:
>>> from scrapy import Selector
>>> sel = Selector(text='<a href="#">Click here to go to the <strong>Next Page</strong></a>')
ノードセット から文字列への変換:
>>> sel.xpath('//a//text()').getall() # take a peek at the node-set
['Click here to go to the ', 'Next Page']
>>> sel.xpath("string(//a[1]//text())").getall() # convert it to string
['Click here to go to the ']
ただし、文字列に変換された ノード は、それ自体のテキストとそのすべての子孫のテキストを一緒にします。
>>> sel.xpath("//a[1]").getall() # select the first node
['<a href="#">Click here to go to the <strong>Next Page</strong></a>']
>>> sel.xpath("string(//a[1])").getall() # convert it to string
['Click here to go to the Next Page']
したがって、 .//text() ノードセットを使用しても、この場合は何も選択されません:
>>> sel.xpath("//a[contains(.//text(), 'Next Page')]").getall()
[]
しかし、ノードを意味するために . を使用すると、動作します:
>>> sel.xpath("//a[contains(., 'Next Page')]").getall()
['<a href="#">Click here to go to the <strong>Next Page</strong></a>']
XPath式の変数¶
XPathでは、 $somevariable 構文を使用して、XPath式の変数を参照できます。 これは、SQLの世界での、クエリの引数を ? のようなプレースホルダーに置き換え、クエリで渡された値で置換されるパラメータークエリまたはプリペアードステートメントに似ているところがあります。
"id" 属性値に基づいて、ハードコーディングせずに要素をマッチする例を次に示します(ハードコーディングする例は前述しました):
>>> # `$val` used in the expression, a `val` argument needs to be passed
>>> response.xpath('//div[@id=$val]/a/text()', val='images').get()
'Name: My image 1 '
別の例として、5つの子 <a> を含む <div> タグの "id" 属性を見つけます(ここでは整数として値 5 を渡します):
>>> response.xpath('//div[count(a)=$cnt]/@id', cnt=5).get()
'images'
All variable references must have a binding value when calling .xpath()
(otherwise you'll get a ValueError: XPath error: exception).
This is done by passing as many named arguments as necessary.
Scapyセレクターを駆動するライブラリである parsel には、XPath変数(XPath variables)の詳細と例があります。
名前空間(namespace)の削除¶
スクレイピングプロジェクトを処理する場合、名前空間を完全に削除し、要素名を操作して、より単純で便利なXPathを作成すると非常に便利です。それには Selector.remove_namespaces() メソッドを使用できます。
Python Insider blog atom フィードでこれを説明する例を示しましょう。
まず、あなたがスクレイプしたいURLでシェルを開きます:
$ scrapy shell https://feeds.feedburner.com/PythonInsider
これがファイルの開始方法です:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet ...
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/"
xmlns:blogger="http://schemas.google.com/blogger/2008"
xmlns:georss="http://www.georss.org/georss"
xmlns:gd="http://schemas.google.com/g/2005"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
...
デフォルトの http://www.w3.org/2005/Atom と、 http://schemas.google.com/g/2005 の gd: プレフィックスを使用する別の宣言を含む、いくつかの名前空間宣言を確認できます。
シェルに入ったら、すべての <link> オブジェクトを選択して、機能しないことを確認できます(Atom XML名前空間がこれらのノードを難読化しているため):
>>> response.xpath("//link")
[]
しかし、一度、 Selector.remove_namespaces() メソッドを呼び出すと、すべてのノードに名前で直接アクセスできます:
>>> response.selector.remove_namespaces()
>>> response.xpath("//link")
[<Selector xpath='//link' data='<link rel="alternate" type="text/html" h'>,
<Selector xpath='//link' data='<link rel="next" type="application/atom+'>,
...
あなたは、名前空間削除手順が手動になっていて、デフォルトで常に呼び出されるとは限らないのを疑問に思うかもしれません。これは2つの理由によるものです:
名前空間を削除するには、ドキュメント内のすべてのノードを反復して変更する必要があります。これは、Scrapyによってクロールされたすべてのドキュメントに対してデフォルトで実行するのにかなりコストのかかる操作です
いくつかの要素名が名前空間間で衝突する場合、実際には名前空間を使用する必要がある場合があります。 ただし、これらのケースは非常にまれです。
EXSLT拡張機能の使用¶
lxml の上に構築されるScrapyセレクターは、いくつかの EXSLT 拡張をサポートし、XPath式で使用できる、事前登録されたこれらの名前空間が付属します:
プレフィックス |
名前空間 |
使い方 |
|---|---|---|
re |
http://exslt.org/regular-expressions |
|
set |
http://exslt.org/sets |
正規表現¶
たとえば、 test() 関数は、XPathの starts-with() または contains() が十分でない場合に非常に便利です。
数字で終わるクラス属性を持つリスト項目内のリンクを選択する例:
>>> from scrapy import Selector
>>> doc = u"""
... <div>
... <ul>
... <li class="item-0"><a href="link1.html">first item</a></li>
... <li class="item-1"><a href="link2.html">second item</a></li>
... <li class="item-inactive"><a href="link3.html">third item</a></li>
... <li class="item-1"><a href="link4.html">fourth item</a></li>
... <li class="item-0"><a href="link5.html">fifth item</a></li>
... </ul>
... </div>
... """
>>> sel = Selector(text=doc, type="html")
>>> sel.xpath('//li//@href').getall()
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
>>> sel.xpath('//li[re:test(@class, "item-\d$")]//@href').getall()
['link1.html', 'link2.html', 'link4.html', 'link5.html']
>>>
警告
Cライブラリ libxslt はEXSLT正規表現をネイティブにサポートしていないため、 lxml の実装はPythonの re モジュールへのフックを使用します。 したがって、XPath式で正規表現関数を使用すると、パフォーマンスが若干低下する可能性があります。
組(set)の操作¶
これらは、たとえばテキスト要素を抽出する前にドキュメントツリーの一部を除外するのに便利です。
アイテムスコープのグループと対応するitempropを使用してmicrodata(http://schema.org/Productから取得したサンプルコンテンツ)を抽出する例:
>>> doc = u"""
... <div itemscope itemtype="http://schema.org/Product">
... <span itemprop="name">Kenmore White 17" Microwave</span>
... <img src="kenmore-microwave-17in.jpg" alt='Kenmore 17" Microwave' />
... <div itemprop="aggregateRating"
... itemscope itemtype="http://schema.org/AggregateRating">
... Rated <span itemprop="ratingValue">3.5</span>/5
... based on <span itemprop="reviewCount">11</span> customer reviews
... </div>
...
... <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
... <span itemprop="price">$55.00</span>
... <link itemprop="availability" href="http://schema.org/InStock" />In stock
... </div>
...
... Product description:
... <span itemprop="description">0.7 cubic feet countertop microwave.
... Has six preset cooking categories and convenience features like
... Add-A-Minute and Child Lock.</span>
...
... Customer reviews:
...
... <div itemprop="review" itemscope itemtype="http://schema.org/Review">
... <span itemprop="name">Not a happy camper</span> -
... by <span itemprop="author">Ellie</span>,
... <meta itemprop="datePublished" content="2011-04-01">April 1, 2011
... <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
... <meta itemprop="worstRating" content = "1">
... <span itemprop="ratingValue">1</span>/
... <span itemprop="bestRating">5</span>stars
... </div>
... <span itemprop="description">The lamp burned out and now I have to replace
... it. </span>
... </div>
...
... <div itemprop="review" itemscope itemtype="http://schema.org/Review">
... <span itemprop="name">Value purchase</span> -
... by <span itemprop="author">Lucas</span>,
... <meta itemprop="datePublished" content="2011-03-25">March 25, 2011
... <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
... <meta itemprop="worstRating" content = "1"/>
... <span itemprop="ratingValue">4</span>/
... <span itemprop="bestRating">5</span>stars
... </div>
... <span itemprop="description">Great microwave for the price. It is small and
... fits in my apartment.</span>
... </div>
... ...
... </div>
... """
>>> sel = Selector(text=doc, type="html")
>>> for scope in sel.xpath('//div[@itemscope]'):
... print("current scope:", scope.xpath('@itemtype').getall())
... props = scope.xpath('''
... set:difference(./descendant::*/@itemprop,
... .//*[@itemscope]/*/@itemprop)''')
... print(" properties: %s" % (props.getall()))
... print("")
current scope: ['http://schema.org/Product']
properties: ['name', 'aggregateRating', 'offers', 'description', 'review', 'review']
current scope: ['http://schema.org/AggregateRating']
properties: ['ratingValue', 'reviewCount']
current scope: ['http://schema.org/Offer']
properties: ['price', 'availability']
current scope: ['http://schema.org/Review']
properties: ['name', 'author', 'datePublished', 'reviewRating', 'description']
current scope: ['http://schema.org/Rating']
properties: ['worstRating', 'ratingValue', 'bestRating']
current scope: ['http://schema.org/Review']
properties: ['name', 'author', 'datePublished', 'reviewRating', 'description']
current scope: ['http://schema.org/Rating']
properties: ['worstRating', 'ratingValue', 'bestRating']
>>>
ここでは、まず itemscope 要素を反復処理し、各要素について、すべての itemscope 要素を探し、別の itemscope 内にある要素を除外します。
その他のXPath拡張機能¶
Scrapyセレクターは、指定されたすべてのHTMLクラスを持つノードに対して True を返す、非常に間違ったXPath拡張関数 has-class も提供します。
次のHTMLの場合:
<p class="foo bar-baz">First</p>
<p class="foo">Second</p>
<p class="bar">Third</p>
<p>Fourth</p>
あなたは以下のように使用できます:
>>> response.xpath('//p[has-class("foo")]')
[<Selector xpath='//p[has-class("foo")]' data='<p class="foo bar-baz">First</p>'>,
<Selector xpath='//p[has-class("foo")]' data='<p class="foo">Second</p>'>]
>>> response.xpath('//p[has-class("foo", "bar-baz")]')
[<Selector xpath='//p[has-class("foo", "bar-baz")]' data='<p class="foo bar-baz">First</p>'>]
>>> response.xpath('//p[has-class("foo", "bar")]')
[]
XPath //p[has-class("foo", "bar-baz")] は、CSS p.foo.bar-baz とほぼ同等です。 CSS探索はXPathに変換されてより効率的に実行されるのに対し、これは問題のすべてのノードに対して呼び出される純粋なPython関数であるため、ほとんどの場合は、CSSセレクターではありえないぐらい遅いことに注意してください。
また、Parselは、独自のXPath拡張機能の追加も簡単にします。
例¶
HTMLレスポンスのSelectorの例¶
ここで、いくつかの概念を説明するため Selector の例を示します。すべての場合において、このような HtmlResponse オブジェクトでインスタンス化された Selector が既に存在すると仮定します:
sel = Selector(html_response)
HTMLレスポンス・ボディのすべての
<h1>要素を選択し、Selectorオブジェクトのリスト(つまり、SelectorListオブジェクト)を返します:sel.xpath("//h1")
HTMLレスポンス・ボディのすべての
<h1>要素のテキストを抽出し、Unicode文字列のリストを返します:sel.xpath("//h1").getall() # this includes the h1 tag sel.xpath("//h1/text()").getall() # this excludes the h1 tag
すべての
<p>タグを反復処理し、それらのクラス属性を出力します:for node in sel.xpath("//p"): print(node.attrib['class'])
XMLレスポンスでのSelector例¶
XmlResponse オブジェクトでインスタンス化された Selector オブジェクトの概念を説明するための例をいくつか示します:
sel = Selector(xml_response)
XMLレスポンス・ボディのすべての
<product>要素を選択し、Selectorオブジェクトのリスト(つまり、SelectorListオブジェクト)を返します:sel.xpath("//product")
名前空間の登録が必要な Google Base XML feed からすべての価格を抽出します:
sel.register_namespace("g", "http://base.google.com/ns/1.0") sel.xpath("//g:price").getall()