简介
XPath(XML Path Language)是一种用于在XML文档中定位和选择节点的语言。它是一种基于树结构的查询语言,常用于解析和处理XML文档。XPath提供了一组用于导航和选择XML文档中节点的表达式。
XPath表达式由一系列路径表达式组成,用于描述节点的层次结构和关系。
XPath 于 1999 年 11 月 16 日 成为 W3C 标准。
XPath广泛应用于各种XML处理工具和库中,例如XPath可以用于解析XML文档、提取数据、遍历节点等。在Web开发中,XPath也常用于解析HTML文档,从中提取所需的数据。
安装
XML 和 HTML 有什么区别?
XML(可扩展标记语言)和 HTML(超文本标记语言)是两种不同的标记语言,用于表示和组织文档内容。它们有以下几个主要区别:
设计目的:XML的设计目的是传输和存储数据,强调数据的结构化和扩展性。它被广泛用于数据交换和配置文件等领域。HTML的设计目的是在Web上呈现和展示文档,强调文档的可视化和布局。
标签定义:XML允许用户自定义标签,因此可以根据需要创建自定义的标记结构。HTML使用预定义的标签,这些标签具有特定的语义和功能,用于描述文档的结构和内容。
标签语义:XML中的标签没有预定义的语义,其含义由应用程序或处理工具来解释。HTML中的标签具有固定的语义,例如<p>
表示段落,<h1>
表示一级标题,<table>
表示表格等。
样式和布局:HTML提供了丰富的样式和布局功能,可以通过CSS(层叠样式表)来控制文档的外观和排版。XML本身并不关注样式和布局,它更专注于数据的结构和内容。
解析方式:HTML的解析器通常比较宽松,能够容忍一些语法错误,并尝试修复错误以展示页面。XML的解析器要求文档必须符合严格的语法规则,否则会报错。
尽管XML和HTML有一些区别,但它们也有一些相似之处。例如,它们都使用尖括号包围标签,都采用树状结构表示文档的层次关系,都支持属性用于描述标签的附加信息。此外,XML和HTML可以相互转换,可以使用XSLT(可扩展样式表语言转换)将XML转换为HTML,或者使用HTML解析器解析XML文档。
常用的路径表达式
表达式
描述
/
从根节点选取。
./
从当前节点开始往下查找
…/
从当前节点的父节点查找
//
不考虑位置的查找。
@
选取属性。
实例
路径表达式
结果
bookstore/book
查找根节点bookstore下面所有直接子节点book
/bookstore/*
查找bookstore元素的所有子元素
//book
查找所有book
bookstore//book
查找bookstore下面所有的book
/bookstore/book[1]
查找bookstore里面的第一个book
/bookstore/book[last()]
查找bookstore里面的最后一个book
/bookstore/book[last()-1]
查找bookstore里面的倒数第二个book元素
/bookstore/book[position()<3]
查找bookstore里面的最前面两个book元素
//title[@lang]
查找所有的带有lang属性的title节点
//title[not(@lang)]
查找所有的不带有lang属性的title节点
//title[@lang="eng"]
查找所有的lang属性值为eng的title节点
//title[not(@lang="eng")]
排除所有的lang属性值为eng的title节点
//title[contains(@lang, "eng")]
查找所有的lang属性值中包含eng的title节点
//title[not(contains(@lang, "eng"))]
排除所有的lang属性值中包含eng的title节点
//title[@*]
查找所有带有属性的 title 元素
//a[text()="下一页"]
查找所有文本内容为下一页 的的a标签
//book[price>35.00]
查找所有book元素,且其中的price元素的值须大于 35.00
//book/title | //book/price
选取 book 元素的所有 title 和 price 元素
安装 Xpath 插件 (XPath Helper)
下载XPath插件
将 Xpath 插件拖动到谷歌浏览器扩展程序中,安装成功(或在Chrome应用商店下载,需要梯子)
启动和关闭插件
属性定位
1 2 //input [@id ="kw" ] //input [@class ="bg s_btn" ]
层级定位
1 2 //div[@id ="head" ]/div[2 ]/a[1 ] //div[@id ="head" ]/div[2 ]/a[@class ="toindex" ]
索引定位
1 2 3 4 //div[@id ="head" ]/div/div[2 ]/a[@class ="toindex" ] //div[@id ="head" ]//a[@class ="toindex" ]
逻辑运算
1 2 //input [@class ="s_ipt" and @name="wd" ] //input [@class ="s_ipt" or @name="wd" ]
模糊匹配
contains
1 2 3 4 5 6 //input [contains(@class , "s_i" )] //input [contains(text(), "爱" )] //input [not (contains(text(), "爱" ))]
starts-with
1 2 //input [starts-with (@class , "s" )]
取文本
1 2 3 4 5 6 7 //div[@id ="s-top-left" ]/a[5 ]/text() //div[@id ="s-top-left" ]//text() ret = tree.xpath('//div[@class="song"]' ) string = ret[0 ].xpath('string(.)' ) print (string.replace('\n' , '' ).replace('\t' , '' ))
取属性
1 2 //div[@id ="s-top-left" ]/a[5 ]/@href
轴可定义相对于当前节点的节点集。
轴名称
结果
ancestor
选取当前节点的所有先辈(父、祖父等)。
ancestor-or-self
选取当前节点的所有先辈(父、祖父等)以及当前节点本身。
attribute
选取当前节点的所有属性。
child
选取当前节点的所有子元素。
descendant
选取当前节点的所有后代元素(子、孙等)。
descendant-or-self
选取当前节点的所有后代元素(子、孙等)以及当前节点本身。
following
选取文档中当前节点的结束标签之后的所有节点。
following-sibling
选取当前节点之后的所有兄弟节点
namespace
选取当前节点的所有命名空间节点。
parent
选取当前节点的父节点。
preceding
选取文档中当前节点的开始标签之前的所有节点。
preceding-sibling
选取当前节点之前的所有同级节点。
self
选取当前节点。
示例:
例子
结果
child::book
选取所有属于当前节点的子元素的 book 节点。
attribute::lang
选取当前节点的 lang 属性。
child::*
选取当前节点的所有子元素。
attribute::*
选取当前节点的所有属性。
child::text()
选取当前节点的所有文本子节点。
child::node()
选取当前节点的所有子节点。
descendant::book
选取当前节点的所有 book 后代。
ancestor::book
选择当前节点的所有 book 先辈。
ancestor-or-self::book
选取当前节点的所有 book 先辈以及当前节点(如果此节点是 book 节点)
child::*/child::price
选取当前节点的所有 price 孙节点。
XPath 表达式可返回节点集、字符串、逻辑值以及数字。
运算符
描述
实例
返回值
|
计算两个节点集
//book | //cd
返回所有拥有 book 和 cd 元素的节点集
+
加法
6 + 4
10
-
减法
6 - 4
2
*
乘法
6 * 4
24
div
除法
8 div 4
2
=
等于
price=9.80
如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。
!=
不等于
price!=9.80
如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
<
小于
price<9.80
如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
<=
小于或等于
price<=9.80
如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
>
大于
price>9.80
如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
>=
大于或等于
price>=9.80
如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。
or
或
price=9.80 or price=9.70
如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
and
与
price>9.00 and price<9.90
如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
mod
计算除法的余数
5 mod 2
1
代码中使用 Xpath
引入
两种方式使用:将html文档变成一个对象,然后调用对象的方法去查找指定的节点
本地文件
网络文件1 tree = etree.HTML(网页字符串)
ret = tree.xpath(路径表达式)
:ret
是一个列表
有时使用 parse()
函数读取本地 HTML 文件时因为 HTML 文件不规范导致报错该如何解决?,参见:使用lxml解析本地html文件报错?
在 Xpath 中使用索引取值时,如果没有获取到值就会报错,如:1 icon = otr.xpath('./td[@class="td-model"]/div/a/i/text()' )[0 ]
那么该如何解决这个问题呢?
方法一1 2 3 4 5 icon = otr.xpath('./td[@class="td-model"]/div/a/i' ) if icon: icon_text = icon[0 ].text else : icon_text = "default_value"
方法二(推荐)1 2 3 4 5 icon_text = otr.findtext('./td[@class="td-model"]/div/a/i' , default="default_value" ) icon_href = otr.find('./td[@class="td-model"]/div/a' ).get('href' , default="default_value" )
常用的 lxml
方法及其作用:
ElementTree()
:创建一个 XML/HTML 文档的根元素对象。
Element()
:创建一个 XML/HTML 元素对象。
fromstring()
:将 XML/HTML 字符串解析为一个元素对象。
parse()
:解析 XML/HTML 文件,并返回一个根元素对象。
find()
:查找符合指定 XPath 表达式的第一个节点,并返回该节点对象。
findall()
:查找符合指定 XPath 表达式的所有节点,并返回节点对象列表。
findtext()
:查找符合指定 XPath 表达式的第一个节点,并返回节点的文本值。
get()
:获取指定节点的属性值。
iter()
:迭代遍历文档中的所有节点。
iterfind()
:迭代查找符合指定 XPath 表达式的节点。
attrib
:获取节点的属性字典。
text
:获取节点的文本值。
tag
:获取节点的标签名。
getparent()
:获取节点的父节点。
getchildren()
:获取节点的子节点列表。
addnext()
:在当前节点之后插入一个新节点。
addprevious()
:在当前节点之前插入一个新节点。
append()
:将一个节点添加为当前节点的子节点。
remove()
:从文档中删除当前节点。
示例1:测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from lxml import etreetree = etree.parse('xpath.html' ) odiv = tree.xpath('//div[@class="tang"]' )[0 ] ret = odiv.xpath('.//li[@class="balove"]' ) print (ret)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" /> <title > xpath测试</title > </head > <body > <div class ="song" > 火药 <b > 指南针</b > <b > 印刷术</b > 造纸术 </div > <div class ="tang" > <ul > <li class ="balove" > 停车坐爱枫林晚,霜叶红于二月花</li > <li id ="hua" > 商女不知亡国恨,隔江犹唱后庭花</li > <li class ="love" name ="yang" > 一骑红尘妃子笑,无人知是荔枝来</li > <li id ="bei" > 葡萄美酒夜光杯,欲饮琵琶马上催</li > <li > <a href ="http://www.baidu.com/" > 百度一下</a > </li > </ul > </div > <ol > <li class ="balove" > 寻寻觅觅,冷冷清清,凄凄惨惨戚戚</li > <li class ="balily" > 乍暖还寒时候,最难将息</li > <li class ="lilei" > 三杯两盏淡酒</li > <li > 怎敌他晚来风急</li > <li > 雁过也,正伤心,却是旧时相识</li > <li > 爱就一个字,我只说一次</li > <li > 爱情36计,我要立刻美丽</li > </ol > </body > </html >
示例2:爬取好段子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 import urllib.requestimport urllib.parsefrom lxml import etreeimport timeimport jsonitem_list = [] def handle_request (url, page ): headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36' } url = url.format (page) request = urllib.request.Request(url=url, headers=headers) return request def parse_content (content ): tree = etree.HTML(content) div_list = tree.xpath('//div[@class="log cate10 auth1"]' ) for odiv in div_list: title = odiv.xpath('.//h3/a/text()' )[0 ] text_lt = odiv.xpath('.//div[@class="cont"]/p/text()' ) text = '\n' .join(text_lt) item = { '标题' : title, '内容' : text, } item_list.append(item) def main (): start_page = int (input ('请输入起始页码:' )) end_page = int (input ('请输入结束页码:' )) url = 'http://www.haoduanzi.com/category-10_{}.html' for page in range (start_page, end_page + 1 ): request = handle_request(url, page) content = urllib.request.urlopen(request).read().decode() parse_content(content) time.sleep(2 ) with open ('duanzi.txt' , 'w' , encoding='utf8' ) as fp: fp.write(str (item_list)) if __name__ == '__main__' : main()
换行问题
存储的时候显示的\n
是有效的
在读取过来单独使用的时候换行符生效
示例1 2 3 4 5 6 7 8 9 10 11 lt = [{'name' : '宫本武藏\n小田纯一郎' }] fp = open ('lala.txt' , 'r' , encoding='utf8' ) string = fp.read() fp.close() lt = eval (string) print (lt[0 ]['name' ])
运行结果
下载图片
**懒加载技术:**用到时候再加载
实现方式 1 2 <img src2="图片路径" > <img src="图片路径" src2="" >
**示例:**http://sc.chinaz.com/tupian/xingganmeinvtupian.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import urllib.requestimport urllib.parsefrom lxml import etreeimport timeimport osdef handle_request (url, page ): if page == 1 : url = url.format ('' ) else : url = url.format ('_' + str (page)) headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36' } request = urllib.request.Request(url=url, headers=headers) return request def parse_content (content ): tree = etree.HTML(content) image_list = tree.xpath('//div[@id="container"]/div/div/a/img/@src2' ) for image_src in image_list: download_image(image_src) def download_image (image_src ): dirpath = 'xinggan' if not os.path.exists(dirpath): os.mkdir(dirpath) filename = os.path.basename(image_src) filepath = os.path.join(dirpath, filename) headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36' } request = urllib.request.Request(url=image_src, headers=headers) response = urllib.request.urlopen(request) with open (filepath, 'wb' ) as fp: fp.write(response.read()) def main (): url = 'http://sc.chinaz.com/tupian/xingganmeinvtupian{}.html' start_page = int (input ('请输入起始页码:' )) end_page = int (input ('请输入结束页码:' )) for page in range (start_page, end_page + 1 ): request = handle_request(url, page) content = urllib.request.urlopen(request).read().decode() parse_content(content) time.sleep(2 ) if __name__ == '__main__' : main()