文章目录
引入
有人说,我正则用的不好,处理HTML文档很累,有没有其他的方法?
有!那就是XPath,我们可以先将网络获取的String类型数据转换成 HTML/XML文档,然后用 XPath 查找 HTML/XML 节点或元素。
什么是XML
大家都知道HTML,那XML又是什么呢?
- XML 指可扩展标记语言(EXtensible Markup Language)
- XML 是一种标记语言,很类似 HTML
- XML 的设计宗旨是传输数据,而非显示数据
- XML 的标签需要我们自行定义。
- XML 被设计为具有自我描述性。
- XML 是 W3C 的推荐标准
图解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vUeRZNSF-1590634079149)(C:\Users\王利钦\Desktop\爬虫总结\XML图解1.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Pxx1cJ1-1590634079153)(C:\Users\王利钦\Desktop\爬虫总结\XML图解2.jpg)]
XML的节点关系
父、子、同胞、先辈、后代
XPath定义
XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。
XPath表达式
-
最常用的路径表达式
通配符 描述 / 从根节点选取。 // 从匹配选择的当前节点选择文档中的节点,而不考虑他们的位置。 . 选取当前节点。 … 选取当前节点的父节点。 @ 选取属性。 -
常用路径表达式以及表达式的结果
表达式 结果 /bookstore 选取根元素bookstore。注释:假如路径起始于正斜杆(/),则此路径始终代表到某元素的绝对路径! bookstore/book 选取属于bookstore的子元素的所有book元素 //book 选取所有book子元素,而不管它们在文档中的位置。 bookstore//book 选取属于bookstore元素的后代的所有book元素,而不管它们位于bookstore之下的什么位置。 //@lang 选取名为lang的所有属性 -
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中
路径表达式 结果 /bookstore/book[1] 选取属于bookstore子元素的第一个book元素。 /bookstore/book[last()] 选取属于bookstore子元素的最后一个book元素。 /bookstore/book[last()-1] 选取属于bookstore子元素的倒数第二个book元素。 /bookstore/book[postion()❤️] 选取最前面的两个属于bookstore元素的子元素的book元素。 //title[@lang] 选取所有拥有名为lang的属性的title元素。 //title[@lang=‘eng’] 选取所有title元素,且这些元素拥有值为eng的lang属性。 /bookstore/book[price>35.00] 选取bookstore元素的所有book元素,且其中的price元素的值须大于35.00。 /bookstore/book[price>35.00]/title 选取bookstore元素中的book元素的所有title元素,且其中的price元素的值须大于35.00。 -
选取未知节点
通配符 描述 * 匹配任何元素节点。 @* 匹配任何属性节点。 路径表达式 结果 /bookstore/* 选取bookstore元素的所有子元素。 //* 选取文档中的所有元素。 //title[@*] 选取所有带有属性的title元素。 -
选取若干路径,通过在路径表达式中使用“|”运算符,您可以选取若干个路径
路径表达式 结果 //book/title|//book/price 选取book元素的所有title和price元素。 //title|//price 选取文档中的所有title和price元素。 /bookstore/book/title|//price 选取属于bookstore元素的book元素的所有title元素,以及文档中所有的price元素。 -
XPath的运算符
运算符 描述 实例 返回值 | 计算两个节点集 //book|//cd 返回所有拥有book和cd元素的节点集 + 加法 6+4 10 - 减法 6-4 2 * 乘法 6*4 24 div 除法 8div4 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或者9.70,则返回true。 and 与 price>9.00 and price<9.90 如果price是9.80,则返回true。如果price是8.50,则返回false。 mod 计算除法的余数 5 mod 2 1
lxml库
定义
-
lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
-
lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath语法,来快速的定位特定元素以及节点信息。
lxml数据转换
-
源码
from lxml import etree str = '''<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> ''' #利用etree.HTML,将String字符串解析为HTML文档 html = etree.HTML(str) result = etree.tostring(html) print(result.decode('utf-8'))
-
所得数据
'''<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>
'''
lxml读取文件
-
源码
from lxml import etree # 读取外部文件 hello.html html = etree.parse('./hello.html') print(html)
-
文件数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>测试页面</title>
</head>
<body>
<ol>
<li class="haha">醉卧沙场君莫笑,古来征战几人回</li>
<li class="heihei">两岸猿声啼不住,轻舟已过万重山</li>
<li id="hehe" class="nene">一骑红尘妃子笑,无人知是荔枝来</li>
<li class="xixi">停车坐爱枫林晚,霜叶红于二月花</li>
<li class="lala">商女不知亡国恨,隔江犹唱后庭花</li>
</ol>
<div id="pp">
<div>
<a href="http://www.baidu.com">李白</a>
</div>
<ol>
<li class="huanghe">君不见黄河之水天上来,奔流到海不复回</li>
<li id="tata" class="hehe">李白乘舟将欲行,忽闻岸上踏歌声</li>
<li class="tanshui">桃花潭水深千尺,不及汪伦送我情</li>
</ol>
<div class="hh">
<a href="http://mi.com">雷军</a>
</div>
<div class="jj">
<b href="http://mi.com"><c>3</c></b>
<b href="http://mi.com"><c>5</c></b>
<b href="http://mi.com"><c>6</c></b>
<b href="http://mi.com"><c>8</c></b>
<b href="http://mi.com"><c>9</c></b>
<b href="http://mi.com"><c>3</c></b>
</div>
<ol>
<li class="dudu">are you ok</li>
<li class="meme">会飞的猪</li>
</ol>
</div>
</body>
</html>
XPath具体用法
-
被解析网页原码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>测试页面</title> </head> <body> <ol> <li class="haha">醉卧沙场君莫笑,古来征战几人回</li> <li class="heihei">两岸猿声啼不住,轻舟已过万重山</li> <li id="hehe" class="nene">一骑红尘妃子笑,无人知是荔枝来</li> <li class="xixi">停车坐爱枫林晚,霜叶红于二月花</li> <li class="lala">商女不知亡国恨,隔江犹唱后庭花</li> </ol> <div id="pp"> <div> <a href="http://www.baidu.com">李白</a> </div> <ol> <li class="huanghe">君不见黄河之水天上来,奔流到海不复回</li> <li id="tata" class="hehe">李白乘舟将欲行,忽闻岸上踏歌声</li> <li class="tanshui">桃花潭水深千尺,不及汪伦送我情</li> </ol> <div class="hh"> <a href="http://mi.com">雷军</a> </div> <div class="jj"> <b href="http://mi.com"><c>3</c></b> <b href="http://mi.com"><c>5</c></b> <b href="http://mi.com"><c>6</c></b> <b href="http://mi.com"><c>8</c></b> <b href="http://mi.com"><c>9</c></b> <b href="http://mi.com"><c>3</c></b> </div> <ol> <li class="dudu">are you ok</li> <li class="meme">会飞的猪</li> </ol> </div> </body> </html>
-
获取所有的< li >标签
from lxml import etree html = etree.parse('hello.html') li_list = html.xpath('//li') print(li_list) # 打印<li>标签的元素集合 print(len(li_list))
-
继续获取< li > 标签的所有 class属性
from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/@class') print(result)
-
继续获取< li >标签下href为 link1.html 的 < a > 标签
from lxml import etree html = etree.parse('./hello.html') result = html.xpath('//li/a[@href="link1.html"]') print(result)
-
获取< li >标签下的所有< span >标签
from lxml import etree data = ''' <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"><span class="bold">third item</span></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>''' html = etree.HTML(data) result = html.xpath('//li//span') print(result[0].text)
-
获取< li >标签下的< a >标签的所有class
# 获取 <li> 标签下的<a>标签里的所有 class from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a//@class') print(result)
-
获取最后一个< li >的< a >的href
from lxml import etree xml = etree.parse('./hello.html') result = xml.xpath('//li[last()]/a/@href') print(result)
-
获取倒数第二个元素的内容
from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()-1]/a') print(result[0].text) print(result)
-
获取class值为bold的标签名
# 获取 class 值为 bold 的标签名 from lxml import etree html = etree.parse('hello.html') result = html.xpath('//*[@class="bold"]') # tag方法可以获取标签名 print(result[0].tag) print(result[0].text)
-
条件使用
- 获取文本数据://li[@id=“hehe”]/text()
- 包含某个条件://li[contains(@class,“h”)]
- 等于某个条件://div[@id=“pp”]/ol[last()]/li/@*
- 条件并用://li[@id=“hehe”] [@class=“nene”]/text()
XPath案例
import requests
from lxml import etree
url1 = 'https://www.neihanba.com/dz/'
url = 'https://www.neihanba.com/dz/list_%d.html'
if __name__ == '__main__':
fp = open('./duanzi.csv',mode = 'a',encoding='utf-8')
for i in range(1,101):
if i == 1:
url_duanzi = url1
else:
url_duanzi = url%(i)
response = requests.get(url_duanzi)
response.encoding = 'gbk'
content = response.text
html = etree.HTML(content)
result = html.xpath('//ul[@class="piclist longList"]/li')
for li in result:
try:
title = li.xpath('.//h4/a/b/text()')[0]
content = li.xpath('.//div[@class="f18 mb20"]/text()')[0].strip().strip('\n')
info = ''.join(li.xpath('.//div[@class="ft"]/span//text()')[1:])
fp.write('%s\t%s\t%s\n'%(title,content,info))
except Exception as e:
# 异常保存,第二天,分析,单独爬取。
pass
print('第%d页内容保存成功!'%(i))
fp.close()
# !!!缺少异常捕获