parsel的使用
介绍
parsel这个库可以解析HTML和XML,并支持使用XPath和CSS选择器对内容进行提取和修改,同时还融合了正则表达式的提取功能。parsel灵活强大,同时也是Python最流行的爬虫框架的底层支持。
parsel的API和Scrapy选择器的API极其相似,因为Scrapy的选择器就是基于parsel做的二次封装。
准备工作
可以使用pip3安装:
pip install parsel
初始化
声明html变量:
html = ''' <div class="wrap"> <div id="container"> <ul class="list"> <li class="item-0">first item</li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li> <li class="item-1 active"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div> </div> '''
使用parsel库里的Selector这个类声明一个Selector对象:
from parsel import Selector selector = Selector(text=html)
创建Selector对象后,传入text参数,内容就是刚才声明的HTML字符串,然后把创建的对象赋值为selector变量。
有了Selector对象后,可以使用css和xpath对象方法分别传入CSS选择器和XPath进行内容提取,例如提取class包含item-0的节点:
items = selector.css('.item-0') print(len(items),type(items)) items2 = selector.xpath('//li[contains(@class,"item-0")]') print(len(items2),type(items2),items2)
先用css方法进行节点提取,然后输出了提取结果的长度和内容。xpath方法写法一样。
运行结果:
3 <class 'parsel.selector.SelectorList'> [<Selector query="descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' item-0 ')]" data='<li class="item-0">first item</li>'>, <Selector query="descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' item-0 ')]" data='<li class="item-0 active"><a href="li...'>, <Selector query="descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' item-0 ')]" data='<li class="item-0"><a href="link5.htm...'>] 3 <class 'parsel.selector.SelectorList'> [<Selector query='//li[contains(@class,"item-0")]' data='<li class="item-0">first item</li>'>, <Selector query='//li[contains(@class,"item-0")]' data='<li class="item-0 active"><a href="li...'>, <Selector query='//li[contains(@class,"item-0")]' data='<li class="item-0"><a href="link5.htm...'>]
两个结果都是SelectorList对象,这其实是一个可迭代对象。用len方法获取了结果的长度,都是3。另外,提取结果代表的解读也是一一样的,都是第1、3、5个li节点,每个节点还是以Selector对象的形式返回,其中每个Selector对象的data属性里面包含对应提取节点的HTML代码。
第一次用cs方法提取的节点,结果中的Selector对象输出的是xpath属性而不是css属性。这是因为在css方法的背后,传入的CSS选择器首先是被转成了XPath,真正用于节点提取的是XPath。其中CSS选择器准换为XPath的过程是由底层的cssselect这个库实现的,例如 .item-0 这个CSS选择器转换为XPath的结果就是 descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' item-0')],因此输出的Selector对象就有了XPath属性。
提取文本
提取的结果是一个可迭代对象SelectorList,那么想要获取提取到的所有li节点的文本内容,就要对结果进行遍历:
from parsel import Selector selector = Selector(text=html) items = selector.css('.item-0') for item in items: text = item.xpath('.//text()').get() print(text)
遍历items变量,并赋值为item,于是这里的item变成了一个Selector对象,此时又可以调用其css或xpath方法进行内容提取。这里用.//text()这个XPath写法提取了当前节点的所有内容,此时如果不在调用其他方法,那么返回结果依然为Selector构成的可迭代对象SelectorList。SelectorList中有一个get()方法,可以将SelectorList包含的Selector对象中的内容提取出来。
运行结果:
first item
third item
fifth item
get方法的作用是从SelectorList里面提取第一个Selector对象,然后输出其中的结果。
实例:
result = selector.xpath('//li[contains(@class,"item-0")]//text()').get() print(result)
运行结果:
first item
使用//li[contains(@class,"item-0")]//text()选取了所有class包含item-0的li节点的文本内容。准确来说返回结果SelectorList应该对应三个li对象,而这里get方法仅仅返回第一个li对象的文本内容,因为他其实只会提取第一个Selector对象的结果。
若想提取所有Selector对应内容,可以使用getall方法:
result = selector.xpath('//li[contains(@class,"item-0")]//text()').getall() print(result)
运行结果:
['first item', 'third item', 'fifth item']
得到的是列表类型的结果,其中的每一项和Selector对象时一一对应的。
如果要提取SelectorList里面对应的结果,可以使用get或getall方法,前者会获取第一个Selector对象里面的内容,后者会依次获取每个Selector对象对应的结果。
把xpath方法改写成css方法:
result = selector.css('.item-0 *::text').getall() print(result)
用*提取所有子节点(包括纯文本节点),提取文本需要再加上::text,最终的运行结果和上面是一样。
提取属性
直接在XPath或者CSS选择器中表示出即可。
例如提取第三个li节点内部的a节点的href属性:
from parsel import Selector selector = Selector(text=html) items = selector.css('.item-0') result = selector.css('.item-0.active a::attr(href)').get() print(result) result = selector.xpath('//li[contains(@class,"item-0") and contains(@class,"active")]/a/@href').get() print(result)
分别用css和xpath方法实现。以同时包含item-0和active两个class为依据,来选取第三个li节点,然后进一步选取了里面的a节点。对于CSS选择器,选取属性需要添加::attr(),并传入对应的属性名称才可以选取;对于XPath,直接用/@再加属性名称即可选取。最后统一用get方法提取结果。
运行结果:
link3.html
link3.html
两种方法都正确提取了href属性。
正则提取
除了常用的css和xpath方法,Selector对象还提供了正则表达式提取方法:
from parsel import Selector selector = Selector(text=html) result = selector.css('.item-0').re('link.*') print(result)
用css方法提取所有class半酣item-0的节点,然后使用re方法传入link.*,用来匹配包含link的所有结果。
运行结果:
['link3.html"><span class="bold">third item</span></a></li>', 'link5.html">fifth item</a></li>']
re方法遍历了所有提取到的Selector对象,然后根据re方法传入的正则表达式,查找出符合规则的节点源码并以列表形式返回。
如果在调用css方法时,已经提取了进一步的结果,例如提取了节点文本值,那么re方法就只针对节点文本值进行提取:
from parsel import Selector selector = Selector(text=html) # result = selector.css('.item-0').re('link.*') result = selector.css('.item-0 *::text').re('.*item') print(result)
运行结果:
['first item', 'third item', 'fifth item']
也可以使用re_first方法来提取第一个符合规则的结果:
from parsel import Selector selector = Selector(text=html) # result = selector.css('.item-0').re('link.*') # result = selector.css('.item-0 *::text').re('.*item') result = selector.css('.item-0').re_first('<span class="bold">(.*?)</span>') print(result)
调用re_first方法,提取的是被span标签包含的文本值,提取结果是用小括号括起来表示一个提取分组,最后输出的结果就是小括号包围的部分
运行结果:
third item
总结
parsel官方文档:
https://parsel.readthedocs.io/