爬虫学习:解析库的使用

1. 使用 XPath

  • XPath,全称为 XML Path Language,即 XML 路径语言,是一门在 XML 文档中查找信息的语言,但也适用于 HTML 文档的搜索

1.1 XPath 概览

  • 拥有简洁明了的路径选择表达式
  • 提供超过 100 个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等
  • 官方网站:https://www.w3.org/TR/xpath/

1.2 XPath 常用规则

  • XPath 常用规则如下:

    • nodename 选取此节点的所有子节点
    • / 从当前节点选取直接子节点
    • // 从当前节点选取子孙节点
    • . 选取当前节点
    • … 选取当前节点的父节点
    • @ 选取属性
  • 一个示例,代表选择所有名称为 title,同时属性为 lang 的值为 eng 的节点:

    //title[@lang='eng']
    

1.3 实例引入

  • 一个实例:

    • 首先声明一段 HTML 文本
    • 然后调用 HTML 类进行初始化,构造一个 XPath 解析对象。注意最后一个 li 节点是没有闭合的,但 etree 模块可以自动修正
    • 使用 tostring 方法输出修正后的 HTML 代码,但是是 bytes 类型
    • 通过 decode() 方法转化为 str 类型

    经过处理后,li 节点标签被补全,并且自动添加了 body, html 节点

from lxml import etree

# 声明一段 HTML 文本
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>
     </ul>
 </div>
'''
# 调用 HTML 类进行初始化,构造一个 XPath 解析对象
# 注意最后一个 li 节点是没有闭合的,但 etree 模块可以自动修正
html = etree.HTML(text)
# 使用 tostring 方法输出修正后的 HTML 代码,但是是 bytes 类型
result = etree.tostring(html)
# 通过 decode() 方法转化为 str 类型
print(result.decode('utf-8'))
<html><body><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>
</body></html>
  • 直接读取文本文件进行解析:
    • test.html 文件的内容和上面的一模一样
    • 输出多了也给 DOCTYPE 声明,不过对解析无影响
    • 节点后面的 &#13 代表什么?
from lxml import etree

html = etree.parse('test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div>&#13;
    <ul>&#13;
         <li class="item-0"><a href="link1.html">first item</a></li>&#13;
         <li class="item-1"><a href="link2.html">second item</a></li>&#13;
         <li class="item-inactive"><a href="link3.html">third item</a></li>&#13;
         <li class="item-1"><a href="link4.html">fourth item</a></li>&#13;
         <li class="item-0"><a href="link5.html">fifth item</a>&#13;
     </li></ul>&#13;
 </div></body></html>

1.4 所有节点

  • 一般用 // 开头的 XPath 规则来选取所有符合要求的节点,如下例子,其中 * 代表匹配所有节点,返回形式是一个列表,每个元素都是 Element 类型,其后跟了节点的名称:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)
[<Element html at 0x1ba1ab711c8>, <Element body at 0x1ba1ab43f88>, <Element div at 0x1ba1ab71388>, <Element ul at 0x1ba1ab8a688>, <Element li at 0x1ba1ab8a648>, <Element a at 0x1ba1ab8a1c8>, <Element li at 0x1ba1ab8a048>, <Element a at 0x1ba1ab8a308>, <Element li at 0x1ba1ab8a408>, <Element a at 0x1ba1ab8a508>, <Element li at 0x1ba1ab8a6c8>, <Element a at 0x1ba1ab8a2c8>, <Element li at 0x1ba1ab8a288>, <Element a at 0x1ba1ab8a348>]
  • 也可以指定节点名称,如下获取所有 li 节点:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//li')
print(result)
[<Element li at 0x1ba1ab71508>, <Element li at 0x1ba1ab8a388>, <Element li at 0x1ba1ab8a608>, <Element li at 0x1ba1ab8a488>, <Element li at 0x1ba1a755e88>]

1.5 子节点

  • 通过 / 或 // 即可查找元素的子节点或孙节点,如下选择 li 节点的所有直接 a 子节点:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)
[<Element a at 0x1ba1ab71148>, <Element a at 0x1ba1ab71408>, <Element a at 0x1ba1ab710c8>, <Element a at 0x1ba1ab71108>, <Element a at 0x1ba1aebd108>]
  • 如下使用 // 选择 ul 节点下的 a 节点:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//ul//a')
print(result)
[<Element a at 0x1ba1ab19888>, <Element a at 0x1ba1ab9dd08>, <Element a at 0x1ba1aafb648>, <Element a at 0x1ba1abbff48>, <Element a at 0x1ba1abbf988>]

1.6 父节点

  • 父节点的选择通过 . . 来实现,如下实例先选中 href 属性为 link4.html 的 a 节点,然后再获取其父节点,然后再获取其 class 属性:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)
['item-1']
  • 也可以通过 parent:: 来获取父节点:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/parent::*/@class')
print(result)
['item-1']

1.7 属性匹配

  • 如上例,使用 @ 符号进行属性过滤
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
print(result)
[<Element li at 0x1ba1ac41a08>, <Element li at 0x1ba1ac415c8>]

1.8 文本获取

  • 使用 XPath 中的 text() 方法获取节点中的文本,如下实例尝试获取前面 li 节点的文本:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
print(result)
['\r\n     ']
  • 但输出只有一个回车符和一个换行符,是因为 text() 前面是 /,/ 的含义是选取直接子节点,li 的直接子节点都是 a 节点,文本都在 a 节点的内部的,因此这里匹配到的结果就是被修正的 li 节点内部的回车符和换行符,因为自动修正的 li 节点的尾标签换行了;
  • 也就是说提取到的文本是 li 节点的尾标签和 a 节点的尾标签之间的文本
  • 要获取 li 节点内部的文本,一个是先选取 a 节点再获取文本,一种是使用 // 获取子孙节点的文本,后者获取到的内容包括前者,因为选择的是所有子孙节点的文本,而前者只是子节点 a 内部的文本
# 先获取 a 节点:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)

# 使用 // 获取子孙节点:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
print(result)
['first item', 'fifth item']
['first item', 'fifth item', '\r\n     ']

1.9 属性获取

  • 属性通过 @ 来获取:
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

1.10 属性多值匹配

  • 当一个节点的属性有多个值,就无法使用之前的属性匹配获取
  • 这种情况下使用 contains() 方法,contains() 方法的第一个参数为属性名称,第二个参数为属性值,示例如下:
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)

# 使用之前的匹配方法获得的结果为空
result = html.xpath('//li[@class="li"]/a/text()')
print(result)

# 使用 contains() 方法
result = html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)
[]
['first item']

1.11 多属性匹配

  • 根据多个属性确定一个节点,比如同时满足两个属性,或者满足两个属性中的一个,需要用到 XPath 中的运算符,如 and, or, mod, |(计算两个节点集), +, -, *, div, =, !=, <, <=, >, >= 等
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)
['first item']

1.12 按序选择

  • 我们在选择的时候,某些属性可能同时匹配了多个节点,但是只想要其中的某个节点,可以利用中括号传入所有的方法进行获取
  • 中括号不仅可以是数字,也可以是位置函数如 last(), last()-2, position() < 3 等,另外需要注意这里 1 即第一个,和代码不同
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>
     </ul>
 </div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/a/text()')
print(result)
result = html.xpath('//li[last()]/a/text()')
print(result)
result = html.xpath('//li[last()-2]/a/text()')
print(result)
result = html.xpath('//li[position()<3]/a/text()')
print(result)
['first item']
['fifth item']
['third item']
['first item', 'second item']

1.13 节点轴选择

  • XPath 具有很多节点轴选择方法,包括获取子元素,兄弟元素,父元素,祖先元素等,如下示例:
    • 第一次选择:调用 acestor 轴,其后加了两个冒号以及 * 号,以获取所有祖先节点
    • 第二次选择,调用 acestor 轴,冒号后面加了 div,代表选择祖先节点中的 div 节点
    • 第三次选择,调用 attribute 轴,可以获取所有属性值
    • 第四次选择,调用 child 轴&#x
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值