diff --git a/.gitignore b/.gitignore
index ff6e2ea65..7392ed31e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ htmlcov/
.pytest_cache/
.coverage.*
.cache/
+.pytest_cache/
# Windows
Thumbs.db
diff --git a/docs/intro/overview.rst b/docs/intro/overview.rst
index 6f1c2c43f..9d7c94d39 100644
--- a/docs/intro/overview.rst
+++ b/docs/intro/overview.rst
@@ -34,11 +34,11 @@ http://quotes.toscrape.com, following the pagination::
def parse(self, response):
for quote in response.css('div.quote'):
yield {
- 'text': quote.css('span.text::text').extract_first(),
- 'author': quote.xpath('span/small/text()').extract_first(),
+ 'text': quote.css('span.text::text').get(),
+ 'author': quote.xpath('span/small/text()').get(),
}
- next_page = response.css('li.next a::attr("href")').extract_first()
+ next_page = response.css('li.next a::attr("href")').get()
if next_page is not None:
yield response.follow(next_page, self.parse)
diff --git a/docs/intro/tutorial.rst b/docs/intro/tutorial.rst
index fa6dc274d..ad17ef096 100644
--- a/docs/intro/tutorial.rst
+++ b/docs/intro/tutorial.rst
@@ -254,7 +254,7 @@ data.
To extract the text from the title above, you can do::
- >>> response.css('title::text').extract()
+ >>> response.css('title::text').getall()
['Quotes to Scrape']
There are two things to note here: one is that we've added ``::text`` to the
@@ -262,32 +262,33 @@ CSS query, to mean we want to select only the text elements directly inside
``
`` element. If we don't specify ``::text``, we'd get the full title
element, including its tags::
- >>> response.css('title').extract()
+ >>> response.css('title').getall()
['Quotes to Scrape']
-The other thing is that the result of calling ``.extract()`` is a list, because
-we're dealing with an instance of :class:`~scrapy.selector.SelectorList`. When
-you know you just want the first result, as in this case, you can do::
+The other thing is that the result of calling ``.getall()`` is a list: it is
+possible that a selector returns more than one result, so we extract them all.
+When you know you just want the first result, as in this case, you can do::
- >>> response.css('title::text').extract_first()
+ >>> response.css('title::text').get()
'Quotes to Scrape'
As an alternative, you could've written::
- >>> response.css('title::text')[0].extract()
+ >>> response.css('title::text')[0].get()
'Quotes to Scrape'
-However, using ``.extract_first()`` avoids an ``IndexError`` and returns
-``None`` when it doesn't find any element matching the selection.
+However, using ``.get()`` directly on a :class:`~scrapy.selector.SelectorList`
+instance avoids an ``IndexError`` and returns ``None`` when it doesn't
+find any element matching the selection.
There's a lesson here: for most scraping code, you want it to be resilient to
errors due to things not being found on a page, so that even if some parts fail
to be scraped, you can at least get **some** data.
-Besides the :meth:`~scrapy.selector.Selector.extract` and
-:meth:`~scrapy.selector.SelectorList.extract_first` methods, you can also use
-the :meth:`~scrapy.selector.Selector.re` method to extract using `regular
-expressions`::
+Besides the :meth:`~scrapy.selector.SelectorList.getall` and
+:meth:`~scrapy.selector.SelectorList.get` methods, you can also use
+the :meth:`~scrapy.selector.SelectorList.re` method to extract using `regular
+expressions`_::
>>> response.css('title::text').re(r'Quotes.*')
['Quotes to Scrape']
@@ -298,7 +299,8 @@ expressions`::
In order to find the proper CSS selectors to use, you might find useful opening
the response page from the shell in your web browser using ``view(response)``.
-You can use your browser developer tools (see section about :ref:`topics-developer-tools`).
+You can use your browser developer tools to inspect the HTML and come up
+with a selector (see section about :ref:`topics-developer-tools`).
`Selector Gadget`_ is also a nice tool to quickly find CSS selector for
visually selected elements, which works in many browsers.
@@ -314,7 +316,7 @@ Besides `CSS`_, Scrapy selectors also support using `XPath`_ expressions::
>>> response.xpath('//title')
[]
- >>> response.xpath('//title/text()').extract_first()
+ >>> response.xpath('//title/text()').get()
'Quotes to Scrape'
XPath expressions are very powerful, and are the foundation of Scrapy
@@ -383,17 +385,17 @@ variable, so that we can run our CSS selectors directly on a particular quote::
Now, let's extract ``title``, ``author`` and the ``tags`` from that quote
using the ``quote`` object we just created::
- >>> title = quote.css("span.text::text").extract_first()
+ >>> title = quote.css("span.text::text").get()
>>> title
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'
- >>> author = quote.css("small.author::text").extract_first()
+ >>> author = quote.css("small.author::text").get()
>>> author
'Albert Einstein'
-Given that the tags are a list of strings, we can use the ``.extract()`` method
+Given that the tags are a list of strings, we can use the ``.getall()`` method
to get all of them::
- >>> tags = quote.css("div.tags a.tag::text").extract()
+ >>> tags = quote.css("div.tags a.tag::text").getall()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']
@@ -401,9 +403,9 @@ Having figured out how to extract each bit, we can now iterate over all the
quotes elements and put them together into a Python dictionary::
>>> for quote in response.css("div.quote"):
- ... text = quote.css("span.text::text").extract_first()
- ... author = quote.css("small.author::text").extract_first()
- ... tags = quote.css("div.tags a.tag::text").extract()
+ ... text = quote.css("span.text::text").get()
+ ... author = quote.css("small.author::text").get()
+ ... tags = quote.css("div.tags a.tag::text").getall()
... print(dict(text=text, author=author, tags=tags))
{'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'}
{'tags': ['abilities', 'choices'], 'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”'}
@@ -434,9 +436,9 @@ in the callback, as you can see below::
def parse(self, response):
for quote in response.css('div.quote'):
yield {
- 'text': quote.css('span.text::text').extract_first(),
- 'author': quote.css('small.author::text').extract_first(),
- 'tags': quote.css('div.tags a.tag::text').extract(),
+ 'text': quote.css('span.text::text').get(),
+ 'author': quote.css('small.author::text').get(),
+ 'tags': quote.css('div.tags a.tag::text').getall(),
}
If you run this spider, it will output the extracted data with the log::
@@ -508,16 +510,22 @@ markup:
We can try extracting it in the shell::
- >>> response.css('li.next a').extract_first()
+ >>> response.css('li.next a').get()
'Next →'
This gets the anchor element, but we want the attribute ``href``. For that,
Scrapy supports a CSS extension that let's you select the attribute contents,
like this::
- >>> response.css('li.next a::attr(href)').extract_first()
+ >>> response.css('li.next a::attr(href)').get()
'/page/2/'
+There is also an ``attrib`` property available
+(see :ref:`selecting-attributes` for more)::
+
+ >>> response.css('li.next a').attrib['href']
+ '/page/2'
+
Let's see now our spider modified to recursively follow the link to the next
page, extracting data from it::
@@ -533,12 +541,12 @@ page, extracting data from it::
def parse(self, response):
for quote in response.css('div.quote'):
yield {
- 'text': quote.css('span.text::text').extract_first(),
- 'author': quote.css('small.author::text').extract_first(),
- 'tags': quote.css('div.tags a.tag::text').extract(),
+ 'text': quote.css('span.text::text').get(),
+ 'author': quote.css('small.author::text').get(),
+ 'tags': quote.css('div.tags a.tag::text').getall(),
}
- next_page = response.css('li.next a::attr(href)').extract_first()
+ next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse)
@@ -584,12 +592,12 @@ As a shortcut for creating Request objects you can use
def parse(self, response):
for quote in response.css('div.quote'):
yield {
- 'text': quote.css('span.text::text').extract_first(),
- 'author': quote.css('span small::text').extract_first(),
- 'tags': quote.css('div.tags a.tag::text').extract(),
+ 'text': quote.css('span.text::text').get(),
+ 'author': quote.css('span small::text').get(),
+ 'tags': quote.css('div.tags a.tag::text').getall(),
}
- next_page = response.css('li.next a::attr(href)').extract_first()
+ next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
yield response.follow(next_page, callback=self.parse)
@@ -641,7 +649,7 @@ this time for scraping author information::
def parse_author(self, response):
def extract_with_css(query):
- return response.css(query).extract_first().strip()
+ return response.css(query).get(default='').strip()
yield {
'name': extract_with_css('h3.author-title::text'),
@@ -710,11 +718,11 @@ with a specific tag, building the URL based on the argument::
def parse(self, response):
for quote in response.css('div.quote'):
yield {
- 'text': quote.css('span.text::text').extract_first(),
- 'author': quote.css('small.author::text').extract_first(),
+ 'text': quote.css('span.text::text').get(),
+ 'author': quote.css('small.author::text').get(),
}
- next_page = response.css('li.next a::attr(href)').extract_first()
+ next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
yield response.follow(next_page, self.parse)
@@ -738,4 +746,3 @@ modeling the scraped data. If you prefer to play with an example project, check
the :ref:`intro-examples` section.
.. _JSON: https://en.wikipedia.org/wiki/JSON
-.. _dirbot: https://github.com/scrapy/dirbot
diff --git a/docs/topics/commands.rst b/docs/topics/commands.rst
index 3088017cb..ef9c45196 100644
--- a/docs/topics/commands.rst
+++ b/docs/topics/commands.rst
@@ -458,9 +458,9 @@ Usage example::
>>> STATUS DEPTH LEVEL 1 <<<
# Scraped Items ------------------------------------------------------------
- [{'name': u'Example item',
- 'category': u'Furniture',
- 'length': u'12 cm'}]
+ [{'name': 'Example item',
+ 'category': 'Furniture',
+ 'length': '12 cm'}]
# Requests -----------------------------------------------------------------
[]
diff --git a/docs/topics/items.rst b/docs/topics/items.rst
index 4423bbda2..ae44aecd3 100644
--- a/docs/topics/items.rst
+++ b/docs/topics/items.rst
@@ -86,7 +86,7 @@ Creating items
::
>>> product = Product(name='Desktop PC', price=1000)
- >>> print product
+ >>> print(product)
Product(name='Desktop PC', price=1000)
Getting field values
@@ -161,11 +161,11 @@ Other common tasks
Copying items::
>>> product2 = Product(product)
- >>> print product2
+ >>> print(product2)
Product(name='Desktop PC', price=1000)
>>> product3 = product2.copy()
- >>> print product3
+ >>> print(product3)
Product(name='Desktop PC', price=1000)
Creating dicts from items::
diff --git a/docs/topics/jobs.rst b/docs/topics/jobs.rst
index 06c7fff3d..8e1574376 100644
--- a/docs/topics/jobs.rst
+++ b/docs/topics/jobs.rst
@@ -84,7 +84,7 @@ So, for example, this won't work::
return scrapy.Request('http://www.example.com', callback=lambda r: self.other_callback(r, somearg))
def other_callback(self, response, somearg):
- print "the argument passed is:", somearg
+ print("the argument passed is: %s" % somearg)
But this will::
@@ -94,7 +94,7 @@ But this will::
def other_callback(self, response):
somearg = response.meta['somearg']
- print "the argument passed is:", somearg
+ print("the argument passed is: %s" % somearg)
If you wish to log the requests that couldn't be serialized, you can set the
:setting:`SCHEDULER_DEBUG` setting to ``True`` in the project's settings page.
diff --git a/docs/topics/loaders.rst b/docs/topics/loaders.rst
index a895b535c..f3b6aa4a1 100644
--- a/docs/topics/loaders.rst
+++ b/docs/topics/loaders.rst
@@ -678,10 +678,10 @@ Here is a list of all built-in processors:
>>> from scrapy.loader.processors import Join
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
- u'one two three'
+ 'one two three'
>>> proc = Join(' ')
>>> proc(['one', 'two', 'three'])
- u'one two three'
+ 'one two three'
.. class:: Compose(\*functions, \**default_loader_context)
@@ -744,9 +744,9 @@ Here is a list of all built-in processors:
... return None if x == 'world' else x
...
>>> from scrapy.loader.processors import MapCompose
- >>> proc = MapCompose(filter_world, unicode.upper)
- >>> proc([u'hello', u'world', u'this', u'is', u'scrapy'])
- [u'HELLO, u'THIS', u'IS', u'SCRAPY']
+ >>> proc = MapCompose(filter_world, str.upper)
+ >>> proc(['hello', 'world', 'this', 'is', 'scrapy'])
+ ['HELLO, 'THIS', 'IS', 'SCRAPY']
As with the Compose processor, functions can receive Loader contexts, and
constructor keyword arguments are used as default context values. See
@@ -772,7 +772,7 @@ Here is a list of all built-in processors:
>>> import json
>>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
>>> proc_single_json_str('{"foo": "bar"}')
- u'bar'
+ 'bar'
>>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
>>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
- [u'bar']
+ ['bar']
diff --git a/docs/topics/selectors.rst b/docs/topics/selectors.rst
index 8ac40c3cc..9dced7473 100644
--- a/docs/topics/selectors.rst
+++ b/docs/topics/selectors.rst
@@ -6,7 +6,7 @@ Selectors
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:
+achieve this, such as:
* `BeautifulSoup`_ is a very popular web scraping library among Python
programmers which constructs a Python object based on the structure of the
@@ -25,16 +25,14 @@ either by `XPath`_ or `CSS`_ expressions.
used with HTML. `CSS`_ is a language for applying styles to HTML documents. It
defines selectors to associate those styles with specific HTML elements.
-Scrapy selectors are built over the `lxml`_ library, which means they're very
-similar in speed and parsing accuracy.
+.. note::
+ Scrapy Selectors is a thin wrapper around `parsel`_ library; the purpose of
+ this wrapper is to provide better integration with Scrapy Response objects.
-This page explains how selectors work and describes their API which is very
-small and simple, unlike the `lxml`_ API which is much bigger because the
-`lxml`_ library can be used for many other tasks, besides selecting markup
-documents.
-
-For a complete reference of the selectors API see
-:ref:`Selector reference `
+ `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.
.. _BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/
.. _lxml: http://lxml.de/
@@ -42,7 +40,7 @@ For a complete reference of the selectors API see
.. _cssselect: https://pypi.python.org/pypi/cssselect/
.. _XPath: https://www.w3.org/TR/xpath
.. _CSS: https://www.w3.org/TR/selectors
-
+.. _parsel: https://parsel.readthedocs.io/
Using selectors
===============
@@ -52,32 +50,48 @@ Constructing selectors
.. highlight:: python
+Response objects expose a :class:`~scrapy.selector.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 :class:`~scrapy.selector.Selector` class
-constructed by passing **text** or :class:`~scrapy.http.TextResponse`
-object. It automatically chooses the best parsing rules (XML vs HTML) based on
-input type::
+constructed by passing either :class:`~scrapy.http.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 = 'good'
+ >>> Selector(text=body).xpath('//span/text()').get()
+ 'good'
+
+Constructing from response - :class:`~scrapy.http.HtmlResponse` is one of
+:class:`~scrapy.http.TextResponse` subclasses::
>>> from scrapy.selector import Selector
>>> from scrapy.http import HtmlResponse
-
-Constructing from text::
-
- >>> body = 'good'
- >>> Selector(text=body).xpath('//span/text()').extract()
- [u'good']
-
-Constructing from response::
-
>>> response = HtmlResponse(url='http://example.com', body=body)
- >>> Selector(response=response).xpath('//span/text()').extract()
- [u'good']
-
-For convenience, response objects expose a selector on `.selector` attribute,
-it's totally OK to use this shortcut when possible::
-
- >>> response.selector.xpath('//span/text()').extract()
- [u'good']
+ >>> Selector(response=response).xpath('//span/text()').get()
+ 'good'
+``Selector`` automatically chooses the best parsing rules
+(XML vs HTML) based on input type.
Using selectors
---------------
@@ -90,7 +104,7 @@ documentation server:
.. _topics-selectors-htmlcode:
-Here's its HTML code:
+For the sake of completeness, here's its full HTML code:
.. literalinclude:: ../_static/selectors-sample1.html
:language: html
@@ -111,90 +125,191 @@ Since we're dealing with HTML, the selector will automatically use an HTML parse
So, by looking at the :ref:`HTML code ` of that
page, let's construct an XPath for selecting the text inside the title tag::
- >>> response.selector.xpath('//title/text()')
- []
-
-Querying responses using XPath and CSS is so common that responses include two
-convenience shortcuts: ``response.xpath()`` and ``response.css()``::
-
>>> response.xpath('//title/text()')
- []
- >>> response.css('title::text')
- []
+ []
+
+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.
+
+Notice that CSS selectors can select text or attribute nodes using CSS3
+pseudo-elements::
+
+ >>> response.css('title::text').get()
+ 'Example website'
As you can see, ``.xpath()`` and ``.css()`` methods return a
:class:`~scrapy.selector.SelectorList` instance, which is a list of new
selectors. This API can be used for quickly selecting nested data::
- >>> response.css('img').xpath('@src').extract()
- [u'image1_thumb.jpg',
- u'image2_thumb.jpg',
- u'image3_thumb.jpg',
- u'image4_thumb.jpg',
- u'image5_thumb.jpg']
+ >>> response.css('img').xpath('@src').getall()
+ ['image1_thumb.jpg',
+ 'image2_thumb.jpg',
+ 'image3_thumb.jpg',
+ 'image4_thumb.jpg',
+ 'image5_thumb.jpg']
-To actually extract the textual data, you must call the selector ``.extract()``
-method, as follows::
+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('//title/text()').extract()
- [u'Example website']
+ >>> response.xpath('//div[@id="images"]/a/text()').get()
+ 'Name: My image 1 '
-If you want to extract only first matched element, you can call the selector ``.extract_first()``
+It returns ``None`` if no element was found::
- >>> response.xpath('//div[@id="images"]/a/text()').extract_first()
- u'Name: My image 1 '
-
-It returns ``None`` if no element was found:
-
- >>> response.xpath('//div[@id="not-exists"]/text()').extract_first() is None
+ >>> response.xpath('//div[@id="not-exists"]/text()').get() is None
True
-A default return value can be provided as an argument, to be used instead of ``None``:
+A default return value can be provided as an argument, to be used instead
+of ``None``:
- >>> response.xpath('//div[@id="not-exists"]/text()').extract_first(default='not-found')
+ >>> response.xpath('//div[@id="not-exists"]/text()').get(default='not-found')
'not-found'
-Notice that CSS selectors can select text or attribute nodes using CSS3
-pseudo-elements::
+Instead of using e.g. ``'@src'`` XPath it is possible to query for attributes
+using ``.attrib`` property of a :class:`~scrapy.selector.Selector`::
- >>> response.css('title::text').extract()
- [u'Example website']
+ >>> [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/'
Now we're going to get the base URL and some image links::
- >>> response.xpath('//base/@href').extract()
- [u'http://example.com/']
+ >>> response.xpath('//base/@href').get()
+ 'http://example.com/'
- >>> response.css('base::attr(href)').extract()
- [u'http://example.com/']
+ >>> response.css('base::attr(href)').get()
+ 'http://example.com/'
- >>> response.xpath('//a[contains(@href, "image")]/@href').extract()
- [u'image1.html',
- u'image2.html',
- u'image3.html',
- u'image4.html',
- u'image5.html']
+ >>> response.css('base').attrib['href']
+ 'http://example.com/'
- >>> response.css('a[href*=image]::attr(href)').extract()
- [u'image1.html',
- u'image2.html',
- u'image3.html',
- u'image4.html',
- u'image5.html']
+ >>> response.xpath('//a[contains(@href, "image")]/@href').getall()
+ ['image1.html',
+ 'image2.html',
+ 'image3.html',
+ 'image4.html',
+ 'image5.html']
- >>> response.xpath('//a[contains(@href, "image")]/img/@src').extract()
- [u'image1_thumb.jpg',
- u'image2_thumb.jpg',
- u'image3_thumb.jpg',
- u'image4_thumb.jpg',
- u'image5_thumb.jpg']
+ >>> response.css('a[href*=image]::attr(href)').getall()
+ ['image1.html',
+ 'image2.html',
+ 'image3.html',
+ 'image4.html',
+ 'image5.html']
- >>> response.css('a[href*=image] img::attr(src)').extract()
- [u'image1_thumb.jpg',
- u'image2_thumb.jpg',
- u'image3_thumb.jpg',
- u'image4_thumb.jpg',
- u'image5_thumb.jpg']
+ >>> 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']
+
+.. _topics-selectors-css-extensions:
+
+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
+
+.. warning::
+ These pseudo-elements are Scrapy-/Parsel-specific.
+ They will most probably not work with other libraries like
+ `lxml`_ or `PyQuery`_.
+
+.. _PyQuery: https://pypi.python.org/pypi/pyquery
+
+Examples:
+
+* ``title::text`` selects children text nodes of a descendant ```` 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']
+
+.. note::
+ See also: :ref:`selecting-attributes`.
+
+.. note::
+ 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.
+
+.. _CSS Selectors: https://www.w3.org/TR/css3-selectors/#selectors
.. _topics-selectors-nesting-selectors:
@@ -206,22 +321,65 @@ of the same type, so you can call the selection methods for those selectors
too. Here's an example::
>>> links = response.xpath('//a[contains(@href, "image")]')
- >>> links.extract()
- [u'Name: My image 1 ',
- u'Name: My image 2 ',
- u'Name: My image 3 ',
- u'Name: My image 4 ',
- u'Name: My image 5 ']
+ >>> links.getall()
+ ['Name: My image 1 ',
+ 'Name: My image 2 ',
+ 'Name: My image 3 ',
+ 'Name: My image 4 ',
+ 'Name: My image 5 ']
>>> for index, link in enumerate(links):
- ... args = (index, link.xpath('@href').extract(), link.xpath('img/@src').extract())
- ... print 'Link number %d points to url %s and image %s' % args
+ ... 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 [u'image1.html'] and image [u'image1_thumb.jpg']
- Link number 1 points to url [u'image2.html'] and image [u'image2_thumb.jpg']
- Link number 2 points to url [u'image3.html'] and image [u'image3_thumb.jpg']
- Link number 3 points to url [u'image4.html'] and image [u'image4_thumb.jpg']
- Link number 4 points to url [u'image5.html'] and image [u'image5_thumb.jpg']
+ 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-attributes:
+
+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
+ {}
Using selectors with regular expressions
----------------------------------------
@@ -235,17 +393,83 @@ Here's an example used to extract image names from the :ref:`HTML code
` above::
>>> response.xpath('//a[contains(@href, "image")]/text()').re(r'Name:\s*(.*)')
- [u'My image 1',
- u'My image 2',
- u'My image 3',
- u'My image 4',
- u'My image 5']
+ ['My image 1',
+ 'My image 2',
+ 'My image 3',
+ 'My image 4',
+ 'My image 5']
-There's an additional helper reciprocating ``.extract_first()`` for ``.re()``,
-named ``.re_first()``. Use it to extract just the first matching string::
+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*(.*)')
- u'My image 1'
+ 'My image 1'
+
+.. _old-extraction-api:
+
+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']
+
+2. ``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'
+
+4. 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.
+
+
+.. _topics-selectors-xpaths:
+
+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`_.
+
+.. note::
+ Some of the tips are based on `this post from ScrapingHub's blog`_.
+
+.. _`XPath tutorial`: http://www.zvon.org/comp/r/tut-XPath_1.html
+.. _`this post from ScrapingHub's blog`: https://blog.scrapinghub.com/2014/07/17/xpath-tips-from-the-web-scraping-trenches/
+
.. _topics-selectors-relative-xpaths:
@@ -266,23 +490,131 @@ it actually extracts all ``