在之前的学习中了解了如何使用爬虫向目标服务器发送请求并获取响应,而此后便是要对响应进行处理,这里的处理在爬虫中通常指的是数据解析,即将相应内容数据化以方便我们进行有效数据的提取。在此过程中,有许多解析数据的方法,本节介绍利用Xpath和lxml库来解析数据。
Xpath
Xpath(全称XML Path Language,XML路径语言),是一门在XML和HTML文档中查找信息的语言,它提供了非常简明的路径选择表达式,可用来对网页的元素及属性进行遍历查找。
语法规则:
1.选取节点
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
表达式 | 描述 | 示例 | 说明 |
nodename | 选取此节点的所有子节点 | div | 选取div下所有子节点 |
/ | 从当前节点选取直接子节点 | /div | 从根元素下选取所有div节点 |
// | 从当前节点选取所有子孙节点 | //div | 从全局节点中选取所有的div节点 |
@ | 选取属性 | //a[@class] | 选取所有拥有class属性的a节点 |
. | 选取当前节点 | .//a | 选取当前节点下的所有a节点 |
.. | 选取当前节点的父节点 | ..//a | 选取当前节点父节点下的所有a节点 |
2.谓语
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。
表达式 | 描述 | 示例 | 说明 |
[index] | 选取指定序列的节点 | /div/p[1] | 选取div下的第一个p节点 |
[last()] | 选取最后一个节点 | /div/p[last()] | 选取div下的最后一个p节点 |
[position()<?>index] | 选取满足指定序列表达式的节点 | //p[position()<3] | 选取全局节点中的前两个p节点 |
[@attr<?>value] | 选取属性attr满足表达式的节点 | //p[@class=”text”] | 选取属性class为text的p节点 |
[contains()] | 模糊匹配 | //p[contains(@class,”for”)] | 选取属性class中包含’for’字符串的p节点 |
3.通配符
通配符,顾名思义指匹配所有节点,在爬虫中通配符用的不多,主要有如下两种:
表达式 | 描述 | 示例 | 说明 |
* | 匹配任意节点 | //div/* | 选取div下的所有节点 |
@* | 匹配任意拥有属性的节点 | //div[@*] | 选取拥有属性的所有div节点 |
4.选择多个路径
在Xpath路径选择表达式中可以通过在表达式中使用“|”运算符选取若干个路径。如表达式”//div | //div/p”表示选取所有div节点及其下的p节点。
5.运算符
在Xpath表达式中可以包含运算符,其中共有14种运算符,包括’|’ , ’+’ , ’-‘ , ’*’ , ’div’ , ’=’, ’!=’ , ’<‘ , ’<=’ , ’>’ , ’>=’, ’or’ , ’and‘ , ’mod’,这些表达式含义比较明了,其通常运用于谓语中,如” //div[@class='text' and @id='description']”表示选取class属性为’text’且id为’description’的div节点。
lxml库
lxml 是一个HTML/XML解析器,主要功能是解析和提取 HTML/XML 数据。lxml和正则一样由C实现,是一款高性能的 Python HTML/XML 解析器,利用它和XPath语法可以快速定位特定元素及节点以捕获信息。在此介绍如何利用lxml获取HTML文档对象。
安装方法
使用pip进行安装:pip install lxml
基本使用
##示例1——使用lxml.etree模块获取HTML对象 from lxml import etree # 网页源代码 text = """ <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> """ # 使用etree.HTML()方法获取_Element对象,传入参数为待解析的源代码 htmlElement = etree.HTML(text) # 返回htmlElement的类型 print(type(htmlElement)) # 返回htmlElement的内容 print(etree.tostring(htmlElement, encoding='utf-8').decode('utf-8'))
由上述结果可知,etree.HTML()返回的是一个<class 'lxml.etree._Element'>的对象,因此在获取源代码内容时还需要使用tostring的方法将其进行字符编码转化,且在转化后我们可以留意到其会自动地将源代码结构补充完整。
除了直接对HTML字符串进行解析外,我们也可以对html文件进行解析。
##示例2——使用lxml.etree对html文件进行解析 from lxml import etree # xml方式解析 def parse_file_xml(): htmlElement = etree.parse("myfile.html") print(etree.tostring(htmlElement, encoding='utf-8').decode('utf-8')) # html方式解析 def parse_file_html(): parser = etree.HTMLParser(encoding='utf-8') htmlElement = etree.parse('myfile.html',parser=parser) print(etree.tostring(htmlElement, encoding='utf-8').decode('utf-8')) if __name__ =='__main__': print(' xml文件解析') parse_file_xml() print(' html文件解析') parse_file_html()
在示例2中,我们使用了两种解析html文件的方法,这主要是为了应对一些不规范HTML网页(比如有的Tag标签未匹配dui应)而采取的措施,etree解析器默认为XML方式解析,然而当解析一个不规范页面时就会产生错误,因此有时候就有必要设置特定的解析器(parser)来解析页面。
Xpath与lxml结合使用数据解析示例
在解析数据过程中,lxml用于产生源代码的_Element对象,而Xpath则是作用于_Element对象之上进行节点的检索,故解析基本思路为先通过lxml获取对象,然后利用Xpath对其进行节点检索。下面先通过几个小例子说明二者的基本使用,然后再以一个较为综合的示例来说明具体的解析流程。
<!源代码> <table class="tablelist" cellpadding="0" cellspacing="0"> <tr class="even"> <td class="l square"><a target="_blank" href="position_detail.php?id=44256&keywords=&tid=0&lid=2218">25664-技术运营工程师(深圳)</a> </td> <td>技术类</td> <td>2</td> <td>深圳</td> <td>2018-09-16</td> </tr> <tr class="odd"> <td class="l square"><a target="_blank" href="position_detail.php?id=44252&keywords=&tid=0&lid=2218">15570-3D场景设计师(深圳)</a> </td> <td>设计类</td> <td>1</td> <td>深圳</td> <td>2018-09-16</td> </tr> <tr class="even"> <td class="l square"><a target="_blank" href="position_detail.php?id=44253&keywords=&tid=0&lid=2218">18425-国际支付产品软件测试工程师</a><span class="hot"> </span></td> <td>技术类</td> <td>2</td> <td>深圳</td> <td>2018-09-16</td> </tr> </table>
##示例3——使用html.xpath选取节点(其返回的是一个列表) from lxml import etree # 设置解析器并获取_Element对象 parser = etree.HTMLParser(encoding='utf-8') html = etree.parse('example.html',parser=parser) # 获取所有tr标签 trs = html.xpath('//tr') #[<Element tr at 0x2132da8c6c8>, <Element tr at 0x2132da8c708>, <Element tr at 0x2132da8c748>] # 获取第2个tr标签 tr2 = html.xpath('//tr[2]')[0] #<Element tr at 0x2132da8c708> # 获取所有class等于even的tr标签 tags= html.xpath('//tr[@class="even"]') #[<Element tr at 0x20cd0b736c8>, <Element tr at 0x20cd0b73748>] # 获取所有a标签的href属性 hrefs = html.xpath('//a/@href') #或hrefs = [element.get('href') for element in html.xpath('//a')]
‘’’
Output:
['position_detail.php?id=44256&keywords=&tid=0&lid=2218', 'position_detail.php?id=44252&keywords=&tid=0&lid=2218', 'position_detail.php?id=44253&keywords=&tid=0&lid=2218']
’’’
在上述简单介绍后便可以开始一个相对完整的网页数据解析,还是以上述源代码为例,具体如下:
##示例4——综合演练,获取网页上所有职位信息 from lxml import etree # 获取_Element对象 parser = etree.HTMLParser(encoding='utf-8') html = etree.parse('tencent.html',parser=parser) # 获取所有tr节点,并设置存放数据的容器position trs = html.xpath('//tr') positions = [] # 依次遍历节点并存储数据 for tr in trs: href = tr.xpath('.//a/@href')[0] title = tr.xpath('.//td[1]//text()')[0] category = tr.xpath('./td[2]//text()')[0] nums = tr.xpath('./td[3]//text()')[0] address = tr.xpath('./td[4]//text()')[0] pub_time = tr.xpath('./td[5]//text()')[0] data = { 'url':href, 'title':title, 'category':category, 'nums':nums, 'address':address, 'pub_time':pub_time } positions.append(data) for position in positions: print(position)
这里要注意的是,在解析数据时我们所写的表达式中前面都有一个’.’表示选取当前节点之下的指定路径的节点,如果缺少则指定为根节点下或全局节点,由此会出现错误。由于Xpath返回的是一个列表,所以获取具体对象时需要添加[0]操作;另外,对于节点的文本字符串,可以利用函数text()来获取。