一、Selector对象
在Python中常用于提取数据的模块:
- BeautifulSoup 是非常流行的HTTP解析库,API简洁易用,但是速度较慢
- lxml 解析数据速度快,API相对复杂
Scrapy结合两者优点,实现了Selector类,它是基于lxml库构建的,并简化了API接口。在Scrapy中使用Selector对象提取页面中的数据,使用时先通过Xpath或CSS选择器选中页面中需要提取的数据,然后进行提取。
1、创建对象
Selector类的实现位于scrapy.selector模块,创建Selector对象时,可将页面的THML文档字符串传递给Selector构建器方法的text参数:
from scrapy.selector import Selector text="""Hello World
Hello Scrapy
- C++
- Java
- Python
也可以使用一个Response对象构造Selector对象,将其传递给Selector构造器方法的response参数:
from scrapy.selector import Selector from scrapy.http import HtmlResponse body="""Hello World
Hello Scrapy
- C++
- Java
- Python
2、选中数据
调用Selector对象的xpath或css方法(传入XPath或CSS选择器表达式),可以选择文档中的某个或某些部分:
selector_list=selector.xpath('//h1') #选中文档中所有的print(selector_list) #其中包含两个
对应的Selector对象 """结果显示: [
, """
xpath和css方法返回一个SelectorList对象,其中包含每个被选中部分对应的Selector对象,SelectorList支持列表接口,可使用for语句迭代访问其中的每一个Selector对象:
for sel in selector_list:
print(sel.xpath('./text()'))
"""结果显示:
[]
[]
"""
SelectorList对象也有xpath和css方法,调用它们的行为是:以接收到的参数分别调用其中每一个Selector对象的xpath或css方法,并将所有结果收集到一个新的SelectorList对象返回给用户。如下:
res=selector_list.xpath('./text()')
print(res)
"""结果显示:
[, ]
"""
3、提取数据
调用Selector或Selector_List对象的以下方法可提取选中的内容:
- extract()
- re()
- extract_first() (SelectorList专有) 针对第一个
- re_first() (SelectorList专有) 针对第一个
3.1 使用extract()方法提取选中的内容,extract_first()同理
s1=selector.xpath('.//li') print(s1) """结果显示: [, """ s2=s1[0].extract() #使用extract()方法提取选中的内容 print(s2) """结果显示:, ] C++ """ p1=selector.xpath('.//li/text()') print(p1) """结果显示: [, """ res1=p1.extract() res2=p1.extract_first() print(res1) #['C++', 'Java', 'Python'] print(res2) #C++, ]
3.2使用正则表达式提取选中内容的某部分,re()方法,re_first()同理
text="""
- Python学习手册价格:99.99
- Python基础编程价格:88.88
- Python核心知识价格:77.77
二、Response内置Selector
在实际开发中,几乎不需要手动创建Selector对象,在第一次访问一个Response对象的selector属性时,Response对象内部会以自身为参数自动创建Selector对象,并将该Selector对象缓存,以便下次使用。Scrapy中相关代码如下:
class TextResponse(Response):
def __index__(self,*args,**kwargs):
...
self._cached_selector=None
...
@property
def selector(self):
from scrapy.selector import Selector
if self._cached_selector is None:
self._cached_selector=Selector(self) #自动创建Selector对象
return self._cached_selector
...
通常,都是直接使用Response对象内置的Selector对象即可:
from scrapy.http import HtmlResponse body="""Hello World
Hello Scrapy
- C++
- Java
- Python
为了方便使用,Response对象还提供了xpath和css方法,它们在内部分别调用内置Selector对象的xpath和css方法。Scrapy源码为:
class TextResponse(Response): ... def xpath(self,query,**kwargs): return self.selector.xpath(query,**kwargs) def css(self,query): return self.selector.css(query) ...
使用这两种方法,使得代码更加简洁!
res1=response.xpath('.//h1/text()').extract() print(res1) #['Hello World', 'Hello Scrapy'] res2=response.css('li::text').extract() print(res2) #['C++', 'Java', 'Python']
三、XPath
xpath方法最常用的匹配规则: //title[@lang='eng'] 指:它代表所有名称为title,属性lang的值为eng的节点
XPath常用的基本语法
表达式 | 描述 |
/ | 选取文档的根(root) |
. | 选中当前节点 |
.. | 选中当前节点的父节点 |
ELEMENT | 选中子节点总所有ELEMENT元素节点 |
//ELEMENT | 选中后代节点中所有ELEMENT元素节点 |
* | 选中所有元素子节点 |
text() | 选中所有元素子节点 |
@ATTR | 选中名为ATTR的属性节点 |
@* | 选中所有属性节点 |
[谓语] | 谓语用来查找某个特定的节点或者包含某个特定值的节点 |
1、通过例子展示XPath的强大!
首先创建一个html文档,并构造一个HtmlResponse对象:
from scrapy.selector import Selector from scrapy.http import HtmlResponse body="""Example website """ response=HtmlResponse(url='www.example.com',body=body,encoding='utf-8')
- / 描述一个从根开始的绝对路径
res1=response.xpath('/html') print(res1) #[] res2=response.xpath('/html/head') print(res2) #[]
- E1/E2 选中E1子节点中的所有E2
#选取div子节点中的所有a
res3=response.xpath('/html/body/div/a')
print(res3)
"""结果显示:
[, , , , ]
"""
- //E 选中文档中的所有E,无论在什么位置
#选取文档中的所有a
res4=response.xpath('//a')
print(res4)
"""结果显示:
[, , , , ]
"""
- E1//E2 选中E1后代节点中的所有E2,无论在后代中的什么位置
#选取body后代中的所有img
res5=response.xpath('/html/body//img')
print(res5)
"""结果显示:
[, , , , ]
"""
- E/text() 选中E的文本子节点
#选中所有a的文本 res6=response.xpath('//a/text()') print(res6) """结果显示: [, """ print(res6.extract()) """结果显示: ['Name:Image 1', 'Name:Image 2', 'Name:Image 3', 'Name:Image 4', 'Name:Image 5'] """, , , ]
- E/* 选中E的所有元素子节点
res7=response.xpath('/html/*') #选取html中所有元素子节点 print(res7) """结果显示: [, """ res8=response.xpath('/html/body/div//*') #选取div的所有后代元素节点 print(res8) """结果显示: [] , """, , , , , , , , , , , , , ]
- */E 选中孙节点中的所有E
#选取div孙节点中的所有img
res9=response.xpath('//div/*/img')
print(res9)
"""结果显示:
[, , , , ]
"""
- E/@ATTR 选中E的ATTR属性
#选中所有immg的src属性
res10=response.xpath('//img/@src')
print(res10)
"""结果显示:
[, , , , ]
"""
- //@ATTR 选中文档中所有ATTR属性
#选取所有的href属性
res11=response.xpath('//@href')
print(res11)
"""结果显示:
[, , , , , ]
"""
- E/@* 选中E的所有属性
#选取第一个a下img的所有属性(这里只有src一个属性)
res12=response.xpath('//a[1]/img/@*')
print(res12)
"""结果显示:[]
"""
- . 选中当前节点,用来描述相对路径
res13=response.xpath('//a')[0] #获取第一个a的选择器对象 print(res13) ##假设我们想选中当前这个a后代中的所有img,下面的做法是错误的!---->会找到文档中所有的img #因为//img是绝对路径,会从文档的根开始搜索,而不是从当前的a 开始 res14=res13.xpath('//img') print(res14) """结果显示: [ , """ #需要使用.//img来描述当前节点后代中的所有img res15=res13.xpath('.//img') print(res15) #[, , , ] ]
- .. 选中当前节点的父节点,用来描述相对路径
#选取所有img的父节点
res16=response.xpath('//img/..')
print(res16)
"""结果显示:
[, , , , ]
"""
- node[谓语] 谓语用来查找某个特定的节点或者包含某个特定值的节点
res17=response.xpath('//a[3]') #选取所有a中的第3个 print(res17) #[] res18=response.xpath('//a[last()]') #使用last函数,选中最后一个 print(res18) #[] res19=response.xpath('//a[position()<=3]') #使用position函数,选中前3个 print(res19) #[, res20=response.xpath('//div[@id]') #选中所有含有id属性的div print(res20) #[, ] ] res21=response.xpath('//div[@id="images"]') #选中所有含有id属性且值为images的div print(res21) #[]
2、常用函数 position() last() string() contains()
- string(arg) 返回参数的字符串值
from scrapy.selector import Selector text='Click here to go to the Next Page' sel=Selector(text=text) print(sel) ##以下做法和sel.xapth('/html/body/a/strong/text()') 得到相同结果 res22=sel.xpath('string(/html/body/a/strong)').extract() print(res22) #['Next Page'] #如果想得到a中的整个字符串‘Click here to go to the Next Page’ #使用text()就不行,因为Click here to go to the和Next Page不在同一个元素下 #以下做法将得到两个子串 res23=sel.xpath('/html/body/a//text()').extract() print(res23) #['Click here to go to the ', 'Next Page'] #这话情况下使用string()函数 res24=sel.xpath('string(/html/body/a)').extract() print(res24) #['Click here to go to the Next Page']
- Contains(str1,str2) 判断str1中是否包含str2,返回布尔值
text="""""" sel=Selector(text=text) res25=sel.xpath('//p[contains(@class,"small")]') #选择class属性中包含“small”的p元素 print(res25) """结果显示:[hello world
hello scrapy
] """ res26=sel.xpath('//p[contains(@class,"info")]') #选择class属性中包含“info”的p元素 print(res26) """结果显示:[, """]
四、CSS选择器
css选择器比xpath简单一点,但是功能没有那么强大
css选择器
表达式 | 描述 | 例子 |
* | 选中所有元素 | * |
E | 选中E元素 | P |
E1,E2 | 选中E1和E2元素 | div,pre |
E1 E2 | 选中E1后代元素中的E2元素 | div p |
E1>E2 | 选中E1子元素中的E2元素 | div>p |
E1+E2 | 选中E1兄弟元素中的E2元素 | p+strong |
.CLASS | 选中class属性为CLASS的元素 | .info |
#ID | 选中id属性为ID的元素 | #main |
[ATTR] | 选中包含ATTR属性的元素 | [href] |
[ATTR=VALUE] | 选中包含ATTR属性且值为VALUE的元素 | [method=post] |
[ATTR~=VALUE] | 选中包含ATTR属性且值包含VALUE的元素 | [method~=clearfix] |
E:nth-child(n) E:nth-last-child(n) |
选中E元素,且该元素必须是其父元素的(倒数)第n个子元素 | a:nth-last(1) a:nth-last-child(2) |
E:first-child E:last-child |
选中E元素,且该元素必须是其父元素的(倒数)第一个子元素 | a:first-child a:last-child |
E:empty | 选中没有子元素的E元素 | div:empty |
E::text | 选中E元素的文本节点 | p::text |
根据例子,来感受css选择器的强大!
首先创建一个HTML文档并构建HtmlResponse对象:
from scrapy.selector import Selector from scrapy.http import HtmlResponse body="""Example website """ response=HtmlResponse(url='www.example.com',body=body,encoding='utf-8')
- E 选中E元素
#选择所有img
res1=response.css('img')
print(res)
"""结果显示:
[, , , , ]
"""
- E1,E2 选中E1和E2元素
#选择所有base和title
res2=response.css('base,title')
print(res2)
"""结果显示:
[, ]
"""
- E1 E2 选中E1后代元素中的E2元素
#div后代中的img
res3=response.css('div img')
print(res3)
"""结果显示:
[, , , , ]
"""
- E1>E2 选中E1子元素中的E2元素
#body子元素中的div
res4=response.css('body>div')
print(res4)
"""结果显示:
[, ]
"""
- [ATTR] 选中包含ATTR属性的元素
#选中保安style属性的元素
res5=response.css('[style]')
print(res5) #[]
- [ATTR=VALUE] 选中包含ATTR属性且值为VALUE的元素
#选择属性id值为images-1的元素
res6=response.css('[id=images-1]')
print(res6) #[]
- E:nth-child(n) 选中E元素,且该元素必须是其父元素的第n个子元素
#选择每个div的第一个a res7=response.css('div>a:nth-child(1)') print(res7) """结果显示: [, """ #选择第二个div的第一个a res8=response.css('div:nth-child(2)>a:nth-child(1)') print(res8) """结果显示: [] ] """
- E:first-child 选中E元素,该元素必须是其父元素的第一个子元素
- E:last-child 选中E元素,该元素必须是其父元素的倒数第一个子元素
#选择最后一个div的最后一个a
res9=response.css('div:first-child>a:last-child')
print(res9)
"""结果显示:
[]
"""
- E::text 选中E元素的文本节点
#选取所有a的文本 res10=response.css('a::text') print(res10) """结果显示: [, """ res11=response.css('a::text').extract() print(res11) """结果显示: ['Name:Image 1', 'Name:Image 2', 'Name:Image 3', 'Name:Image 4', 'Name:Image 5'] """, , , ]