在学习xpath提取数据之前,得先了解下解析HTML代码的一些方法,如果读者想更加深入学习HTML代码等相关内容,需要去查看下前端HTML相关内容,本文仅介绍网络爬虫需要用到的部分内容。
本文介绍使用lxml模块解析HTML与XML,因其支持XPath解析方式,且在解析效率方面非常优秀。
Tips: 你可以通过
pip install lxml
命令安装lxml模块
etree.parse(source, parser=None, *, base_url=None)
返回加载源元素的ElementTree对象。如果没有解析器作为第二个参数提供,则使用默认解析器。 source: 可以是下列任何一种:
注意,从文件路径或URL解析通常更快,而不是从打开的文件对象或类文件对象。支持从gzip压缩源透明解压(除非在libxml2中显式禁用)。 base_url: 关键字允许为文档设置URL从类文件对象进行解析时。这是在寻找时需要的具有相对路径的外部实体(DTD, XInclude,…)。
如果要解析字符串,请使用'fromstring()'
函数。fromstring函数可以把一串xml解析为一个xml元素(返回值类型和etree.Element一样,是lxml.etree._Element类)。
>>> some_xml_data = "<root>data</root>"
>>> root = etree.fromstring(some_xml_data)
>>> etree.tostring(root)
b'<root>data</root>'
# 'studio.html'
from lxml import etree
parser = etree.HTMLPaser() # 创建HTMLPaser对象
html = etree.parse('studio.html', parser=parser) # 解析本地的HTML文件
html_txt = etree.tostring(html, encoding='utf-8')# 转换字符串类型,并进行编码
html_txt.decode('utf-8')
使用tostring()可以提取出xml中所含的全部文本。
HTML函数会自动加上html和body元素(如果原字符串没有的话),同样是返回Element类。
>>> root = etree.HTML("<p>data</p>")
>>> etree.tostring(root)
b'<html><body><p>data</p></body></html>'
注意:HTML函数的返回值依然会被当成标准XML处理。
>>> root = etree.HTML('<head/><p>Hello<br/>World</p>')
# 没有XML声明, 默认为ASCII编码。
>>> etree.tostring(root)
b'<html><head/><body><p>Hello<br/>World</p></body></html>'
>>> etree.tostring(root, method='html')
b'<html><head></head><body><p>Hello<br>World</p></body></html>'
# 换行
>>> html_txt = etree.tostring(root, method='html', encoding='uft-8')
>>> html_txt.decode('utf-8')
'<html><head></head><body><p>Hello<br>World</p></body></html>'
lxml.etree.HTML(),lxml.etree.fromstring()和lxml.etree.tostring()
三者之间的区别和联系
文档格式化方法 | 类型type | 根节点 | 编码方式 | XPath |
---|---|---|---|---|
etree.HTML() | <class 'lxml.etree._Element'> | html | (X.encode('utf-8')) | 支持 |
etree.fromstring() | <class 'lxml.etree._Element'> | 原文档根节点 | (X.encode('utf-8')) | 支持 |
etree.tostring() | <class 'bytes'> | 无 | 无 | 不支持 |
etree.HTML()和etree.fromstring()
都是属于同一种"class类"
,这个类型才会支持使用xpath。也就说etree.tostring()是"字节bytes类"
,不能使用xpath!etree.HTML()
的文档格式已经变成html类型
,所以根节点自然就是html标签
]。但是,etree.fromstring()
的根节点还是原文档中的根节点,说明这种格式化方式并不改变原文档的整体结构,这样有利于使用xpath的绝对路径方式查找信息!而etree.tostring()
是没有所谓的根节点的,因为这个方法得到的文档类型是"bytes类"
。etree.HTML()和etree.fromstring()
的括号内参数都要以"utf-8"的方式进行编码
!表格中的X是表示用read()方法之后的原文档内容
。发送网络请求后返回的响应结果转为字符串类型,如果返回的结果是HTML代码,则需要解析HTML代码。
from lxml import etree
import requests
from requests.auth import HTTPBasicAuth
url = 'http://'
auth = HTTPBasicAuth('username', 'password')
response = requests.get(url=url, auth=auth)
if response.status_code == 200:
html = etree.HTML(response.text)
html_txt = etree.tostring(html, encoding='utf-8')
print(html_txt.decode('utf-8'))
官方网站(
https://www.w3.org/TR/xpath/all/
)
XPath
是一门路径提取语言,常用于从 html/xml
文件中提取信息。它的基规则如下.
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑他们的位置 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
* | 选取所有节点 |
下面为一些路径表达式及表达式结果:
路径表达式 | 结果 |
---|---|
petstore | 选取 petstore 元素的所有子节点 |
/petstore | 选取根元素 petstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
petstore/corgi | 选取属于 petstore 的子元素的所有 corgi 元素 |
//corgi | 选取所有 corgi 子元素,而不管它们在文档中的位置。 |
petstore//corgi | 选择属于 petstore 元素的后代的所有 corgi 元素,而不管它们位于 petstore 之下的什么位置。 |
//@dog | 选取名为 dog 的所有属性。 |
谓语用来查找某个特定的节点或者包含某个指定的值的节点。谓语被嵌在方括号中。下面为一些带有谓语的路径表达式,及表达式结果。
路径表达式 | 结果 |
---|---|
/petstore/corgi[1] | 选取属于 petstore 子元素的第一个 corgi 元素。 |
/petstore/corgi[last()] | 选取属于 petstore 子元素的最后一个 corgi 元素。 |
/petstore/corgi[last()-1] | 选取属于 petstore 子元素的倒数第二个 corgi 元素。 |
/petstore/corgi[position()<3] | 选取最前面的两个属于 petstore 元素的子元素的 corgi 元素。 |
//title[@dog] | 选取所有拥有名为 dog 的属性的 title 元素。 |
//title[@dog='female'] | 选取所有 title 元素,且这些元素拥有值为 female 的 dog 属性。 |
/petstore/corgi[price>2500.00] | 选取 petstore 元素的所有 corgi 元素,且其中的 price 元素的值须大于 2500.00。 |
/petstore/corgi[price>2500.00]/title | 选取 petstore 元素中的 corgi 元素的所有 title 元素,且其中的 price 元素的值须大于 2500.00。 |
//div[contains(@class,"f1")] | 选择div属性包含"f1"的元素 |
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/petstore/* | 选取 petstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
html/node()/meta/@* | 选择html下面任意节点下的meta节点的所有属性 |
//title[@*] | 选取所有带有属性的 title 元素。 |
contains()方法
实现属性多值匹配 contains(指定属性名称, 指定属性值) 如需既获取class=class="main-hd",又要获取class="main"的节点时,如果HTML代码中包含指定的属性值,就可以匹配成功。
html = etree.HTML(html_str)
div_all = html.xpath('//div[contains(@lcass, "main")]/text()') # text()获取问题,下面介绍
and
多属性匹配 在一个节点中出现多个属性,这时就需要同时多个属性,以便更加精确地获取指定节点中的数据。
>>> from lxml import etree
>>> html_str = '''
<div class="main-hd">
<div class="main review-item" id="13045497">云朵</div>
<div class="main review-item" id="13054201">数据STUDIO</div>
</div>
'''
>>> html = etree.HTML(html_str)
>>> div_all = html.xpath('//div[@class="main review-item" and @id="13054201"]/text()')
>>> print(div_all)
['数据STUDIO']
可以使用XPath的text()
方法获取HTML代码中的文本。
>>> from lxml import etree
>>> html_str = '''
<header class="main-hd">
<a href="https://www.douban.com/people/162291901/" class="name">苍华</a>
<span class="allstar10 main-title-rating" title="很差"/>
<span content="2020-12-10" class="main-meta">2020-12-10 01:29:29</span>
</header>
'''
>>> html = etree.HTML(html_str)
>>> a_text = html.xpath('//a/text()')
>>> print(f'所有a下节点文本信息:{a_text}')
所有a下节点文本信息:['苍华']
XPath表达式中运算符:
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
+ | 加法 | 5 + 4 | 9 |
– | 减法 | 5 – 4 | 1 |
* | 乘法 | 5 * 4 | 20 |
div | 除法 | 5 div 5 | 1 |
= | 等于 | price=100.0 | 如果 price 是 100.0,则返回 true。如果 price 是 110.0,则返回 false。 |
!= | 不等于 | price!=100.0 | 如果 price 是 100.0,则返回 true。如果 price 是 99.0,则返回 false。 |
< | 小于 | price<100.0 | 如果 price 是 99.0,则返回 true。如果 price 是 100.0,则返回 false。 |
<= | 小于或等于 | price<=100.0 | 如果 price 是 100.0,则返回 true。如果 price 是 99.0,则返回 false。 |
> | 大于 | price>100.0 | 如果 price 是 110.0,则返回 true。如果 price 是 99.0,则返回 false。 |
>= | 大于或等于 | price>=100.0 | 如果 price 是 110.0,则返回 true。如果 price 是 99.0,则返回 false。 |
or | 或 | price=100.0or price=99.0 | 如果 price 是 99.0,则返回 true。如果 price 是 99.8,则返回 false。 |
and | 与 | price>99.0 and price<100.0 | 如果 price 是 99.8,则返回 true。如果 price 是 88.0,则返回 false。 |
mod | 计算除法的余数 | 6 mod 4 | 2 |
| | 计算两个节点集 | //div|//ul | 返回所有div和a节点集 |
轴可定义相对于当前节点的节点集。
轴名称 | 结果 |
---|---|
ancestor | 当前节点的所有先辈(父、祖父等)。 |
ancestor-or-self | 当前节点的所有先辈(父、祖父等)以及当前节点本身。 |
attribute | 当前节点的所有属性。 |
child | 当前节点的所有子元素。 |
descendant | 当前节点的所有后代元素(子、孙等)。 |
descendant-or-self | 当前节点的所有后代元素(子、孙等)以及当前节点本身。 |
following | 文档中当前节点的结束标签之后的所有节点。 |
following-sibling | 当前节点之后的所有兄弟节点 |
namespace | 当前节点的所有命名空间节点。 |
parent | 当前节点的父节点。 |
preceding | 文档中当前节点的开始标签之前的所有节点。 |
preceding-sibling | 当前节点之前的所有同级节点。 |
self | 当前节点。 |
例
from lxml import etree
html_str = '''
<div class="main-hd">
<li><a href="https://www.douban.com/people/162291901/" class="name">苍华</a>
</div>
'''
html = etree.HTML(html_str)
# 获取li[0]所有祖先节点
html.xpath('//li[0]/ancestor::*')
# 获取li[0]属性为class="main"的祖先节点
class_div = html.xpath('//li[0]/ancestor::*[@class="main"]')