当抓取网页时,做的最常见的任务就是从HTML源码中提取数据。
- BeautifulSoup时非常流行的网页分析库,它基于K+HTML到码的结构来构造一个Python对象,但是它很慢。
- lxml是一个基于ElemenTree的python化的XML的解析库。
Scrapy提取数据有自己的一套机制。被称为ie选择器,因为它们通过特定的XPath或者CSS表达式来选择HTML文件种的某个部分。XPath是一门用来在XML文件中选择节点的语言。Scrapy选择器构建于lxml库之上。
使用选择器Selectors
构造选择器
Scrapy selectors是以文字或TextResponse构造的Selector实例。其根据输入的类型自动选择最优的分析方法。
In [1]: from scrapy.selector import Selector
In [2]: from scrapy.http import HtmlResponse
以文字构造
In [3]: body = '<html><body><span>good</span></body></html>'
In [4]: Selector(text=body).xpath('//span/text()').extract()
Out[4]: [u'good']
以response为构造
In [23]: response=HtmlResponse(url='http://example.com',body=body)
In [24]: Selector(response=response).xpath('//span/text()').extract()
Out[24]: [u'good']
response对象以.selector属性提供了一个selector,可以随时使用该快捷方法:
>>> response.selector.xpath('//span/text()').extract()
[u'good']
使用选择器
示例网页
源代码
<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>
首先打开shell
scrapy shell http://doc.scrapy.org/en/latest/_static/selectors-sample1.html
当shell载入后,将获得名为response的shell变量,其响应的response,并且在response.selector属性上绑定一个selector。
In [1]: response.selector.xpath('//title/text()')
Out[1]: [<Selector xpath='//title/text()' data=u'Example website'>]
#快捷方式
In [2]: response.xpath('//title/text()')
Out[2]: [<Selector xpath='//title/text()' data=u'Example website'>]
In [4]: response.css('title::text')
Out[4]: [<Selector xpath=u'descendant-or-self::title/text()' data=u'Example website'>]
.xpath()和.css()方法返回一个类SelectorList的实例,它是一个新的选择器的列表。这个API可以用来快速提取嵌套数据。
得到根URL和一些图片链接:
In [5]: response.xpath('//base/@href').extract()
Out[5]: [u'http://example.com/']
In [6]: response.css('base::attr(href)').extract()
Out[6]: [u'http://example.com/']
In [8]: response.xpath('//a[contains(@href,"image")]/@href').extract()
Out[8]:
[u'image1.html',
u'image2.html',
u'image3.html',
u'image4.html',
u'image5.html']
In [9]: response.css('a[href*=image]::attr(href)').extract()
Out[9]:
[u'image1.html',
u'image2.html',
u'image3.html',
u'image4.html',
u'image5.html']
In [10]: response.xpath('//a[contains(@href,"image")]/img/@src').extract()
Out[10]:
[u'image1_thumb.jpg',
u'image2_thumb.jpg',
u'image3_thumb.jpg',
u'image4_thumb.jpg',
u'image5_thumb.jpg']
In [11]: response.css('a[href*=image] img::attr(src)').extract()
Out[11]:
[u'image1_thumb.jpg',
u'image2_thumb.jpg',
u'image3_thumb.jpg',
u'image4_thumb.jpg',
u'image5_thumb.jpg']
嵌套选择器
选择器方法( .xpath() or .css() )返回相同类型的选择器列表,因此你也可以对这些选择器调用选择器方法。
In [12]: link=response.xpath('//a[contains(@href,"image")]')
In [13]: link.extract()
Out[13]:
[u'<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>',
u'<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg"></a>',
u'<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg"></a>',
u'<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg"></a>',
u'<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg"></a>']
In [14]: for index,links in enumerate(link):
...: args=(index,links.xpath('@href').extract(),links.xpath('img/@src').extract())
...: print 'Link number %d points to utl %s and image %s' %args
...:
Link number 0 points to utl [u'image1.html'] and image [u'image1_thumb.jpg']
Link number 1 points to utl [u'image2.html'] and image [u'image2_thumb.jpg']
Link number 2 points to utl [u'image3.html'] and image [u'image3_thumb.jpg']
Link number 3 points to utl [u'image4.html'] and image [u'image4_thumb.jpg']
Link number 4 points to utl [u'image5.html'] and image [u'image5_thumb.jpg']
结合正则表达式使用选择器
Selector也有一个.re()方法,用来通过正则表达式来提取数据。不同于前两个属性的是.re()方法返回unicode字符串列表,所以无法构造嵌套式的.re()调用。
In [18]: response.xpath('//a[contains(@href,"image")]/text()').re(r'Name:\s*(.*)'
...: )
Out[18]:
[u'My image 1 ',
u'My image 2 ',
u'My image 3 ',
u'My image 4 ',
u'My image 5 ']
使用相对XPaths
‘//’表示直接从后的标签开始,相对路径。
In [20]: divs=response.xpath('//div')
In [23]: for a in divs.xpath('//a'):
...: print a.extract()
...:
<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>
使用EXSLT扩展
因为建立于lxml,Scrapy选择器也支持一些EXSLT扩展,可以在XPath表达式使用这些预先指定的命令空间:
正则表达式
在XPath的 starts-with() 或 contains() 无法满足需求时, test() 函数可以非常有用。
from scrapy import Selector
>>> doc = """
... <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').extract()
[u'link1.html', u'link2.html', u'link3.html', u'link4.html', u'link5.html']
>>> sel.xpath('//li[re:test(@class, "item-\d$")]//@href').extract()
[u'link1.html', u'link2.html', u'link4.html', u'link5.html']
集合操作
集合操作可以方便地用于在提取文字元素前从文档树中去除一些部分。
>>> doc = """
... <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>
... """
>>>
>>> for scope in sel.xpath('//div[@itemscope]'):
... print "current scope:", scope.xpath('@itemtype').extract()
... props = scope.xpath('''
... set:difference(./descendant::*/@itemprop,
... .//*[@itemscope]/*/@itemprop)''')
... print " properties:", props.extract()
... print
...
current scope: [u'http://schema.org/Product']
properties: [u'name', u'aggregateRating', u'offers', u'description', u'review', u'review']
current scope: [u'http://schema.org/AggregateRating']
properties: [u'ratingValue', u'reviewCount']
current scope: [u'http://schema.org/Offer']
properties: [u'price', u'availability']
current scope: [u'http://schema.org/Review']
properties: [u'name', u'author', u'datePublished', u'reviewRating', u'description']
current scope: [u'http://schema.org/Rating']
properties: [u'worstRating', u'ratingValue', u'bestRating']
current scope: [u'http://schema.org/Review']
properties: [u'name', u'author', u'datePublished', u'reviewRating', u'description']
current scope: [u'http://schema.org/Rating']
properties: [u'worstRating', u'ratingValue', u'bestRating']
>>>
内建选择器参考
class scrapy.selector.Selector(response=None, text=None, type=None)
response
是HtmlResponse或XmlResponse的一个对象,将被用来选择和提取数据。text
是在response
不可用时得一个unicode字符串或utf8编码的文字。将两个一起使用是未定义行为。type
定义了选择类型,可以是html,xml或None。
如果type是None,选择器会根据response类型自动选择最佳的类型,或者在和text一起使用时,默认为html。
如果type是None,并传递一个response,选择器类型将从response类型中推导如下:
- html for HtmlResponse type
- xml for XmlResponse type
- html for anything else;
register_namespace(prefix, uri)
注册给定的命名空间,其将在Selector中使用。不注明命名空间,你将无法从非标准命名空间选择或提取数据。remove_namespaces()
移除所有命名空间,允许使用少量的命名空间xpaths遍历文档。__nonzero__()
如果选择了任意真实文档,将返回True,否则返回False。也就是说Selector的布尔值是通过他选择的内容确定的。
SelectorList对象
xpath(query)
对列表中的每个元素调用.xpath()
方法,返回结果为另一个单一化的SelectorList。css(query)
对列表中的每个元素调用.css()
方法,返回结果为另一个单一化的SelectorList。extract(query)
对列表中的每个元素调用.extract()
方法,返回结果为单一化的unicode字符串列表。re()
对列表中的每个元素调用.re()
方法,返回结果为单一化的unicode字符串列表;__nonzero__()
列表非空则返回True,否则返回False。
在XML响应上的选择器样例
假设已经有了一个通过XmlResponse对象实例化的Selector。
sel = Selector(html_response)
从XML响应主题中选择所有的<product>
元素,返回Selector对象的列表:
sel.xpath("//product")
从 Google Base XML feed 中提取所有的价钱,这需要注册一个命名空间:
sel.register_namespace("g", "http://base.google.com/ns/1.0")
sel.xpath("//g:price").extract()
移除命名空间
在处理爬虫仙姑时,完全去掉命名空间而仅仅处理元素名字,会方便很多,可以使用remove_namespaces()
方法。
首先,使用想爬取的url来打开shell:
$ scrapy shell "https://github.com/blog.atom"
尝试选择所有link对象
>>> response.xpath("//link")
[]
因为Atom XML命名空间混淆了这些节点
去除所有命名空间,所有的节点都可以直接通过他们的名字来访问。
In [2]: response.selector.remove_namespaces()
In [3]: response.xpath('//link')
Out[3]:
[<Selector xpath='//link' data=u'<link rel="stylesheet" type="text/css" h'>,
<Selector xpath='//link' data=u'<link rel="icon" type="image/x-icon" hre'>,
<Selector xpath='//link' data=u'<link rel="canonical" href="https://blog'>,
<Selector xpath='//link' data=u'<link rel="next" href="https://blog.gith'>,
<Selector xpath='//link' data=u'<link type="application/atom+xml" rel="a'>]