セレクタ

When you're scraping web pages, the most common task you need to perform is to extract data from the HTML source. There are several libraries available to achieve this, such as:

  • BeautifulSoup は、Pythonプログラマーの間で非常に人気のあるWebスクレイピングライブラリです。HTMLコードの構造に基づいてPythonオブジェクトを作成し、また、不適切なマークアップを適切に処理しますが、遅いという欠点があります。
  • lxml は、 ElementTree をベースにしたPythonicなAPIを備えたXML解析ライブラリ(HTMLも解析できます)です。lxmlはPython標準ライブラリの一部ではありません。

Scrapyには、データを抽出するための独自のメカニズムが付属しています。HTMLドキュメントの特定の部分を XPath 式または CSS 式で「選択」するため、これらはセレクタと呼ばれます。

XPath はXML文書内のノードを選択するための言語ですが、HTMLでも使用できます。 CSS はHTMLドキュメントにスタイルを適用するための言語です。スタイルを特定のHTML要素に関連付けるためのセレクタを定義します。

注釈

Scrapy Selectors is a thin wrapper around parsel library; the purpose of this wrapper is to provide better integration with Scrapy Response objects.

parsel is a stand-alone web scraping library which can be used without Scrapy. It uses lxml library under the hood, and implements an easy API on top of lxml API. It means Scrapy selectors are very similar in speed and parsing accuracy to lxml.

セレクタの使用

セレクタの構築

Response objects expose a Selector instance on .selector attribute:

>>> response.selector.xpath('//span/text()').get()
'good'

Querying responses using XPath and CSS is so common that responses include two more shortcuts: response.xpath() and response.css():

>>> response.xpath('//span/text()').get()
'good'
>>> response.css('span::text').get()
'good'

Scrapy selectors are instances of Selector class constructed by passing either TextResponse object or markup as an unicode string (in text argument). Usually there is no need to construct Scrapy selectors manually: response object is available in Spider callbacks, so in most cases it is more convenient to use response.css() and response.xpath() shortcuts. By using response.selector or one of these shortcuts you can also ensure the response body is parsed only once.

But if required, it is possible to use Selector directly. Constructing from text:

>>> from scrapy.selector import Selector
>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').get()
'good'

Constructing from response - HtmlResponse is one of TextResponse subclasses:

>>> 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'

Selector automatically chooses the best parsing rules (XML vs HTML) based on input type.

セレクタの使用

セレクタの使用方法を説明するために、 Scrapyシェル (これは対話式のテストを提供します)とScrapy文書サーバーにあるサンプルページを使用します。

For the sake of completeness, here's its full HTML code:

<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コード を見て、titleタグ内のテキストを選択するためのXPathを作成しましょう。

>>> response.xpath('//title/text()')
[<Selector xpath='//title/text()' data='Example website'>]

To actually extract the textual data, you must call the selector .get() or .getall() methods, as follows:

>>> response.xpath('//title/text()').getall()
['Example website']
>>> response.xpath('//title/text()').get()
'Example website'

.get() always returns a single result; if there are several matches, content of a first match is returned; if there are no matches, None is returned. .getall() returns a list with all results.

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']

If you want to extract only the first matched element, you can call the selector .get() (or its alias .extract_first() commonly used in previous Scrapy versions):

>>> response.xpath('//div[@id="images"]/a/text()').get()
'Name: My image 1 '

It returns None if no element was found:

>>> 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'

Instead of using e.g. '@src' XPath it is possible to query for attributes using .attrib property of a Selector:

>>> [img.attrib['src'] for img in response.css('img')]
['image1_thumb.jpg',
 'image2_thumb.jpg',
 'image3_thumb.jpg',
 'image4_thumb.jpg',
 'image5_thumb.jpg']

As a shortcut, .attrib is also available on SelectorList directly; it returns attributes for the first matching element:

>>> response.css('img').attrib['src']
'image1_thumb.jpg'

This is most useful when only a single result is expected, e.g. when selecting by id, or selecting unique elements on a web page:

>>> 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']

Extensions to CSS Selectors

Per W3C standards, CSS selectors do not support selecting text nodes or attribute values. But selecting these is so essential in a web scraping context that Scrapy (parsel) implements a couple of non-standard pseudo-elements:

  • to select text nodes, use ::text
  • to select attribute values, use ::attr(name) where name is the name of the attribute that you want the value of

警告

These pseudo-elements are Scrapy-/Parsel-specific. They will most probably not work with other libraries like lxml or PyQuery.

Examples:

  • title::text selects children text nodes of a descendant <title> element:

    >>> response.css('title::text').get()
    'Example website'
    
  • *::text selects all descendant text nodes of the current selector context:

    >>> 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 returns no results if foo element exists, but contains no text (i.e. text is empty):

    >>> response.css('img::text').getall()
    []
    

    This means .css('foo::text').get() could return None even if an element exists. Use default='' if you always want a string:

    >>> response.css('img::text').get()
    >>> response.css('img::text').get(default='')
    ''
    
  • a::attr(href) selects the href attribute value of descendant links:

    >>> response.css('a::attr(href)').getall()
    ['image1.html',
     'image2.html',
     'image3.html',
     'image4.html',
     'image5.html']
    

注釈

See also: Selecting element attributes.

注釈

You cannot chain these pseudo-elements. But in practice it would not make much sense: text nodes do not have attributes, and attribute values are string values already and do not have children nodes.

ネストされたセレクタ

選択メソッド( .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'

Selecting element attributes

There are several ways to get a value of an attribute. First, one can use XPath syntax:

>>> response.xpath("//a/@href").getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

XPath syntax has a few advantages: it is a standard XPath feature, and @attributes can be used in other parts of an XPath expression - e.g. it is possible to filter by attribute value.

Scrapy also provides an extension to CSS selectors (::attr(...)) which allows to get attribute values:

>>> response.css('a::attr(href)').getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

In addition to that, there is a .attrib property of Selector. You can use it if you prefer to lookup attributes in Python code, without using XPaths or CSS extensions:

>>> [a.attrib['href'] for a in response.css('a')]
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

This property is also available on SelectorList; it returns a dictionary with attributes of a first matching element. It is convenient to use when a selector is expected to give a single result (e.g. when selecting by element ID, or when selecting an unique element on a page):

>>> response.css('base').attrib
{'href': 'http://example.com/'}
>>> response.css('base').attrib['href']
'http://example.com/'

.attrib property of an empty SelectorList is empty:

>>> 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']

There's an additional helper reciprocating .get() (and its alias .extract_first()) for .re(), named .re_first(). Use it to extract just the first matching string:

>>> response.xpath('//a[contains(@href, "image")]/text()').re_first(r'Name:\s*(.*)')
'My image 1'

extract() and extract_first()

If you're a long-time Scrapy user, you're probably familiar with .extract() and .extract_first() selector methods. Many blog posts and tutorials are using them as well. These methods are still supported by Scrapy, there are no plans to deprecate them.

However, Scrapy usage docs are now written using .get() and .getall() methods. We feel that these new methods result in a more concise and readable code.

The following examples show how these methods map to each other.

  1. SelectorList.get() is the same as SelectorList.extract_first():

    >>> response.css('a::attr(href)').get()
    'image1.html'
    >>> response.css('a::attr(href)').extract_first()
    'image1.html'
    
  2. SelectorList.getall() is the same as 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']
    
  1. Selector.get() is the same as Selector.extract():

    >>> response.css('a::attr(href)')[0].get()
    'image1.html'
    >>> response.css('a::attr(href)')[0].extract()
    'image1.html'
    
  1. For consistency, there is also Selector.getall(), which returns a list:

    >>> response.css('a::attr(href)')[0].getall()
    ['image1.html']
    

So, the main difference is that output of .get() and .getall() methods is more predictable: .get() always returns a single result, .getall() always returns a list of all extracted results. With .extract() method it was not always obvious if a result is a list or not; to get a single result either .extract() or .extract_first() should be called.

Working with XPaths

Here are some tips which may help you to use XPath with Scrapy selectors effectively. If you are not much familiar with XPath yet, you may want to take a look first at this XPath tutorial.

注釈

Some of the tips are based on this post from ScrapingHub's blog.

XPathを相対的に操作する

セレクタを入れ子にして / で始まるXPathを使用する場合、そのXPathはドキュメントに対して絶対的なものであり、呼び出し元の Selector に対して相対的なものではないことに留意してください。

たとえば、 <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 .//p にドットプレフィックスがあります)。

>>> 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] は、それぞれの親の下に最初に出現するすべてのノードを選択します。

(//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> 要素をすべて取得します。

>>> xp("//li[1]")
['<li>1</li>', '<li>4</li>']

そしてこちらは、文書全体の最初の <li> 要素を取得します。

>>> xp("(//li)[1]")
['<li>1</li>']

こちらは、親が <ul> である最初の <li> 要素をすべて取得します。

>>> xp("//ul/li[1]")
['<li>1</li>', '<li>4</li>']

そしてこちらは、文書全体の最初の、親が <ul> である <li> 要素を取得します。

>>> xp("(//ul/li)[1]")
['<li>1</li>']

条件内でのテキストノードの使用

XPath文字列関数 の引数としてテキストコンテンツを使う必要がある場合は、 .//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'

すべての変数参照は、 .xpath() を呼び出すときにバインド値を持つ必要があります(そうでなければ、 ValueError: XPath error: 例外が発生します)。これは、必要な数の名前付き引数を渡すことによって行われます。

Scrapyのセレクタを強力にしているライブラリ parsel には、 XPath変数 に関する詳細と例があります。

ネームスペースを削除する

スクレイピングプロジェクトを扱うときに、ネームスペースを完全に取り除くと、要素名を扱うだけで、より単純で便利なXPathを書くことが非常に便になります。これには Selector.remove_namespaces() メソッドを使用できます。

Let's show an example that illustrates this with the Python Insider blog atom feed.

まず、抽出したいURLでシェルを開きます。

$ scrapy shell https://feeds.feedburner.com/PythonInsider

This is how the file starts:

<?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">
  ...

You can see several namespace declarations including a default "http://www.w3.org/2005/Atom" and another one using the "gd:" prefix for "http://schemas.google.com/g/2005".

シェルに入ったら、すべての <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つです。

  1. Removing namespaces requires to iterate and modify all nodes in the document, which is a reasonably expensive operation to perform by default for all documents crawled by Scrapy
  2. 非常にまれなケースですが、いくつかの要素名がネームスペースの間で衝突する場合のために、ネームスペースの使用が実際に必要とされる場合があるかもしれません。

EXSLT拡張機能を使う

Being built atop lxml, Scrapy selectors support some EXSLT extensions and come with these pre-registered namespaces to use in XPath expressions:

プレフィクス ネームスペース 使用法
re http://exslt.org/regular-expressions regular expressions
set http://exslt.org/sets set manipulation

正規表現

たとえば、 test() 関数は、XPathの starts-with()contains() だけでは不十分な場合に非常に便利です。

数字で終わる「class」属性を持つリスト項目内のリンクを選択する例:

>>> 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式で正規表現関数を使用すると、パフォーマンスが若干低下する可能性があります。

集合演算

これらは、例えばテキスト要素を抽出する前に文書ツリーの一部を除外するのに便利です。

itemscopeとそれに対応する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 要素を反復し、それぞれに対して itemprops 要素をすべて探し、別の itemscope 内にあるものを除外します。

Other XPath extensions

Scrapy selectors also provide a sorely missed XPath extension function has-class that returns True for nodes that have all of the specified HTML classes.

For the following HTML:

<p class="foo bar-baz">First</p>
<p class="foo">Second</p>
<p class="bar">Third</p>
<p>Fourth</p>

You can use it like this:

>>> 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")]')
[]

So XPath //p[has-class("foo", "bar-baz")] is roughly equivalent to CSS p.foo.bar-baz. Please note, that it is slower in most of the cases, because it's a pure-Python function that's invoked for every node in question whereas the CSS lookup is translated into XPath and thus runs more efficiently, so performance-wise its uses are limited to situations that are not easily described with CSS selectors.

Parsel also simplifies adding your own XPath extensions.

組み込みのセレクタリファレンス

Selectorオブジェクト

SelectorListオブジェクト

Examples

HTMLレスポンスに関するセレクタの例

Here are some Selector examples to illustrate several concepts. In all cases, we assume there is already a Selector instantiated with a HtmlResponse object like this:

sel = Selector(html_response)
  1. HTMLのレスポンスボディからすべての <h1> 要素を選択し、 Selector オブジェクトのリスト(つまり SelectorList オブジェクト)を返します。

    sel.xpath("//h1")
    
  2. HTMLのレスポンスボディからすべての <h1> 要素のテキストを抽出し、Unicode文字列のリストを返します。

    sel.xpath("//h1").getall()         # this includes the h1 tag
    sel.xpath("//h1/text()").getall()  # this excludes the h1 tag
    
  3. すべての <p> タグを繰り返し処理し、それらのclass属性を出力します。

    for node in sel.xpath("//p"):
        print(node.attrib['class'])
    

XMLレスポンスに関するセレクタの例

Here are some examples to illustrate concepts for Selector objects instantiated with an XmlResponse object:

sel = Selector(xml_response)
  1. XMLのレスポンスボディからすべての <product> 要素を選択し、 Selector オブジェクトのリスト(つまり SelectorList オブジェクト)を返します。

    sel.xpath("//product")
    
  2. ネームスペースを登録する必要がある Google Base XML feed フィードからすべての価格を抽出します。

    sel.register_namespace("g", "http://base.google.com/ns/1.0")
    sel.xpath("//g:price").getall()