哎,还是要整理一下这枯燥的基础知识,,,没办法,逃不掉喽
我们回顾一下网页爬虫的整个思路:
- 先爬取整个网页,也就是将网页的源代码给获取下来
- 爬取下来的网页再通过文本解析提取,找到我们需要的信息,可以是图片或者文字
之前我们学的 requests 库可以获取到我们想要的网页的 HTML 代码,我们待会儿要介绍的 lxml 库和 XPath 语法可以帮助我们完成信息的提取。
实际上解析文档有很多种方式,比如最通用的 正则表达式方法,但是这种方法有时候写起来会比较困难,所以人们开发出了基于 XPath 语法、 CSS 选择器语法、和 jquery语法的解析库 ,包括 lxml、beautiful-soup、pyquery。
我们这篇文章先来说一说 XPath 。
Xpath 在使用的时候需要借助一个模型叫 etree,给人的感觉总像是 JavaScript 里面的 DOM ,他们确实有很多相似之处,比如都是树结构。
在使用 XPath 解析文本之前,需要将文档先创建成一个 etree 对象。
1. 建立 etree 的两种方式
有两种建立 etree 对象的方式,一种是直接是 str 类型的导入,一种是写在文件里面了,需要把 html 文件导入。
先看看第一种:
字符串导入
text = '''
<div>
<ul>
<li class='item-0'><a href="link1.html">1 item</a></li>
<li class='item-1'><a href="link1.html">2 item</a></li>
<li class='item-inactive'><a href="link1.html">3 item</a></li>
<li class='item-1'><a href="link1.html">4 item</a></li>
<li class='item-4'><a href="link1.html">5 item</a>
</ul>
</div>
'''# 我们删去了最后一个li 标签
htmlcontent = etree.HTML(text) # 通过文本构建 etree 实例
result = etree.tostring(htmlcontent) # 返回的是 bytes 类型,需要转化为 str 类型
print(result.decode('utf-8')) # 经过处理后的 html 代码会被自动修复 添加了 <html><body></li>
看到上面的例子你应该就知道啥叫字符串的形式导入,这种方式是直接在内存中读取,我们爬下来的 response.text 就可以直接扔进去解析,
下面我给大家隆重介绍一下导入三连;
首先是 etree.HTML()
这个方法可以使用 str 创建一个 etree 实例,可以理解为一个树模型,这个方法好呀,可以自动修复 html 代码,然后添加 <html><body>
照理儿说这个,应该就可以直接解析了,找我们想要的就完了,但是我们非得想要查看一下,导入了啥,不确定一下不痛快,ok,没关系
etree.tostring()
参数是一个 etree 对象,顾名思义,to字符串,ok 返回 etree 对应的字符串,但但但,但是,还没完,返回了个二进制的 bytes 这哪行,写入文件还是可以的哈,正好帮我们修正完了,
也没关系,我们使用 .decode('utf-8') 转化一下成为人类可读的语言就行了。
最后结果:
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">1 item</a></li>
<li class="item-1"><a href="link1.html">2 item</a></li>
<li class="item-inactive"><a href="link1.html">3 item</a></li>
<li class="item-1"><a href="link1.html">4 item</a></li>
<li class="item-4"><a href="link1.html">5 item</a>
</li></ul>
</div>
</body></html>
相对于之前确实代码被补全了。
从文件导入
其实真的跟上面的方法差不多,比如我们将上面的代码写到同目录下的 text.html 文件中,可以这样导入,
# 通过读取 html 文件创建 etree 实例
html = etree.parse('./text.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))
注意这里面的 HTMLParser 它是一个 python 自带的网页解析工具,也就是说你使用它也是可以完成解析的,但是还是那句话比较麻烦,所以在他上面又封装了一层 etree 。
结果稍微有点不同:
<html><body><div>
<ul>
<li class="item-0">12345<a href="link1.html">1 item</a></li>
<li class="item-1"><a href="link2.html">2 item</a></li>
<li class="item-inactive"><a href="link3.html">3 item</a></li>
<li class="item-1"><a href="link4.html">4 item</a></li>
<li class="item-0"><a href="link5.html">5 item</a>
</li></ul>
</div></body></html>
这里面的 
 是一个 placeholder 占位符,表示回车,问题不大。
说完如何创建 etree 对象之后就开始我们今天的重头戏,XPath 语法,XPath 是一种称为路径表达式的语法,可以用一个类似于 Windows 或 Linux 文件路径的表达式,定位到 XML 或 HTML 中的任意一个或多个节点元素,获取元素的各项信息。
XPath语法中有四个关键的概念:节点、轴、路径表达式和运算符。
节点跟 DOM 中的差不多,我也不多说了,直接参考 W3S 上面的介绍吧
https://www.w3school.com.cn/xpath/xpath_nodes.asp
2. 路径表达式
XPath 使用路径表达式来选取节点,最有用的路径表达式有:
此外我们还有一些谓语使得匹配更加精准,它用来查找某个特定的节点或者包含某个指定的值的节点,它被放在了 [ ] 中。
好的赶快上手练练:
'''
<div>
<ul>
<li class='item-0'><a href="link1.html"><span>1 item</a></li>
<li class='item-1'><a href="link1.html">2 item</a></li>
<li class='item-inactive'><a href="link1.html">3 item</a></li>
<li class='item-1'><a href="link1.html">4 item</a></li>
<li class='item-0'><a href="link1.html">5 item</a>
</ul>
</div>
'''
from lxml import etree
html = etree.parse('./text.html', etree.HTMLParser())
result = html.xpath("//*")
print(result)
# [<Element html at 0x1db8445a808>, <Element body at 0x1db8445a7c8>, <Element div at 0x1db8445a8c8>, <Element ul at 0x1db8445a908>, <Element li at 0x1db8445a948>, <Element a at 0x1db8445a9c8>, <Element li at 0x1db8445aa08>, <Element a at 0x1db8445aa48>, <Element li at 0x1db8445aa88>, <Element a at 0x1db8445a988>, <Element li at 0x1db8445aac8>, <Element a at 0x1db8445ab08>, <Element li at 0x1db8445ab48>, <Element a at 0x1db8445ab88>]
result = html.xpath("//li/a")
print(result)
# [<Element a at 0x166d312f7c8>, <Element a at 0x166d312f848>, <Element a at 0x166d312f788>, <Element a at 0x166d312f908>, <Element a at 0x166d312f988>]
我们一般都会使用 // 开头来选取所有满足条件的节点,* 代表匹配所有节点,那么所有的节点都会被获取到,并返回一个列表,
匹配也可以直接指定节点的名称,比如上面例子的 li 返回所有 li 节点下的 a 节点,如果想获取所有的子孙节点可以这样写 //li//a
'''
<div>
<ul>
<li class='item-0'><a href="link1.html"><span>1 item</a></li>
<li class='item-1'><a href="link1.html">2 item</a></li>
<li class='item-inactive'><a href="link1.html">3 item</a></li>
<li class='item-1'><a href="link1.html">4 item</a></li>
<li class='item-0'><a href="link1.html">5 item</a>
</ul>
</div>
'''
from lxml import etree
html = etree.parse('./text.html', etree.HTMLParser())
result = html.xpath("//li/text()")
print(result)
# [ '\r\n\t']
result = html.xpath("//li/a/text()")
print(result)
# ['1 item', '2 item', '3 item', '4 item', '5 item']
result = html.xpath("//li/a/@href")
print(result)
# ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
在这个例子中我们使用了 text() 访问节点中的文字,但是只会访问当前节点中的文字,不会访问子孙节点的文字,如果想要访问子孙节点,可以使用 // 或者继续向下写,比如这里的 /a 如果想要指定是某个 a 标签可以 使用 【】 比如 【1】表示第一个 a 标签,如果想要获取属性值,可以使用 @ 比如这里的 href 属性。
3. 运算符
下表给出了一些常用的 Xpath 运算符
运算符主要都是用在了 [ ] 中,
'''
<div>
<ul>
<li class='item-0 sr' name='li'><a href="link1.html"><span>1 item</a></li>
<li class='item-1'><a href="link1.html">2 item</a></li>
<li class='item-inactive'><a href="link1.html">3 item</a></li>
<li class='item-1'><a href="link1.html">4 item</a></li>
<li class='item-0'><a href="link1.html">5 item</a>
</ul>
</div>
'''
from lxml import etree
html = etree.parse('./text.html', etree.HTMLParser())
result = html.xpath("//li[@class='item-0']//text()")
print(result)
# ['5 item', '\r\n ']
result = html.xpath("//li[contains(@class,'item-0')]/a/text()")
print(result)
# ['1 item', '5 item']
result = html.xpath("//li[contains(@class,'item-0')and @name='li']/a/@href")
print(result)
# ['link1.html']
注意我们稍微修改了一下 html ,我们给第一个 li 标签的 class 属性新加了一个值 sr ,然后又加了一个 name 属性。
所以第一个访问到的是最后一个 li 标签,因为它的 class 属性只有 item-0 ,因为后面使用的是 // 所以返回下面所有子孙节点的文字。
第二个访问到的是是第一个和第五个 li 标签,因为他们的 class 属性都含有 item-0,返回所有a 标签里的文字,
最后一个是返回 li 标签中class 属性包含 item-0 并且 name属性是 li 的 节点中的a 标签的 href 属性值。
4. 节点轴选择
XPath 提供了很多节点轴选择方法,包括获取子元素、兄弟元素、父元素、祖先元素等
'''
<div>
<ul>
<li class='item-0' title="li"><a href="link1.html"><span>1 item</a></li>
<li class='item-1'><a href="link2.html">2 item</a></li>
<li class='item-inactive'><a href="link3.html"> item</a></li>
<li class='item-1'><a href="link4.html">4 item</a></li>
<li class='item-0'><a href="link5.html">5 item</a>
</ul>
</div>
'''
from lxml import etree
html = etree.parse('./text.html', etree.HTMLParser())
result = html.xpath("//li[1]/ancestor::*")
print(result)
# [<Element html at 0x18f93eaf748>, <Element body at 0x18f93eaf848>, <Element div at 0x18f93eaf888>, <Element ul at 0x18f93eaf8c8>]
result = html.xpath("//li[1]/ancestor::div")
print(result)
# [<Element div at 0x29d426bf7c8>]
result = html.xpath("//li[1]/attribute::*")
print(result)
# ['item-0']
result = html.xpath("//li[1]/child::a[@href='link1.html']")
print(result)
# [<Element a at 0x147bd72a8c8>]
result = html.xpath("//li[1]/descendant::span")
print(result)
# [<Element span at 0x2238266a988>]
result = html.xpath("//li[1]/attribute::*[2]")
print(result)
# ['my_li']
result = html.xpath("//li[1]/following-sibling::*")
print(result)
# [<Element li at 0x1905588f708>, <Element li at 0x1905588f7c8>, <Element li at 0x1905588f808>, <Element li at 0x1905588f848>]
这种东西就是多写写就会了,
网友评论