XPath相关知识点
1. XPath介绍
1.1 基本概念
XPath(XML Path Language)是一种XML的查询语言,它能在XML树状结构中寻找节点。XPath用于在XML文档中通过元素和属性进行导航。
XML是一种标记语法的文本格式,XPath可以方便地定位XML中的元素和其中的属性值。lxml是Python中的一个第三方模块,它包含了将html文本转成xml对象,和对该对象执行xpath的功能。
1.2 举例
首先,正则表达式使用时先由用户指定模板,再根据模板匹配需要的数据。
假设有如下需求:寻找人才,要求是人品好、技术好、颜值高、不要工资。
对于正则表达式,如果给的模板比较严格(不要工资这一项,几乎没有人才能匹配上),或者模板本身很难编写,那么它的使用会受到很大的限制。
对于xpath,就好比有一位资深HR告诉你,xxx区xxx街道xxx楼可以找到所需要的人才。以上述需求为例,xpath是一种可以根据地址找人的技术。
1.3 节点关系
假设有如下XML文本:
xml_content = '''
<bookstore>
<book>
<title lang='eng'>Harry Potter</title>
<author>JK.Rowling</author>
<year>2005</year>
<price>29</price>
</book>
</bookstore>
上述文档中的节点例子:
<bookstore> (文档节点)
<author>JK.Rowling</author> (元素节点)
lang='eng' (属性节点)
1.4 XPath语法
符号 | 含义 |
---|---|
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
表格中的语法在后续安装相应的工具给出示例。
2. xpath-helper工具的使用
2.1 安装xpath-helper
- 点击扩展程序,跳转到
chrome://extensions/
- 打开开发者模式,点击加载已解压的扩展程序,安装包笔者自己已经准备好
- 安装成功后如图:
2.2 使用xpath-helper
按住快捷键Ctrl
+Shift
+X
,插件安装成功后会出现如下界面:
访问html根节点,整个html页面全部被匹配。
原本写到title,就能找到“百度一下,你就知道”的数据,但是编程时需要加上text()才能拿到文本数据。
练习使用//,不考虑具体位置,把符合条件的数据都找出来。
[] 谓语,用于查找某个特定的节点或者包含某个特定值的节点,谓语被嵌在[]中。
上述写法是有一定问题的,在这个例子中没有很好的体现。如果要找属性title的数据,应该这样写a/@title
:
.
和..
的使用,类似于dos命令cd ..
表示进入上级目录,在XPath里面分别表示当前节点和当前节点的父节点。
继续练习[],在[1]
填写数字类似于列表查找某个索引的值。
对应的网页源码如下:
[last()]
查找最后一个元素的值:
[last()-1]
查找倒数第二个元素的值:
[position()<3]
查找前两个元素的值:
3. lxml模块入门
需求:给出自定义的一段HTML文本,用xpath解析出需要的数据,将其保存到一个csv文件。
示例代码:
from lxml import etree
import csv
# 自定义的HTML文本
wb_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">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数据转化为xml对象
html_element = etree.HTML(wb_data)
print(html_element)
# href所对应的数据
links = html_element.xpath('//li/a/@href')
print('a href:', links)
# a标签所对应的文本数据
items = html_element.xpath('//li/a/text()')
print('a text:', items)
# 需求把打印出来的结果保存到一个字典当中
# 例如 {'href':'link1.html','text':'first item'}
result_lst = []
for link in links:
# 每次循环创建新的字典,字典中包含两个key-value
d = {}
d['href'] = link
# links.index(link) 表示获取link在links中的索引
# 若依次打印其结果为0,1,2,3,4
d['text'] = items[links.index(link)]
# 将创建的字典保存到列表中
result_lst.append(d)
print(result_lst)
# 将上述列表中的数据写入到一个csv文件中
titles = ['href', 'text']
with open('exer.csv', 'w', encoding='utf-8', newline='') as fobj:
# 创建writer对象, 第二个参数传递表头,对应数据的key
writer_obj = csv.DictWriter(fobj, titles)
# 写入表头
writer_obj.writeheader()
# 写入内容
writer_obj.writerows(result_lst)
运行结果:
4. 案例演示
需求:爬取豆瓣top250电影的名字、评分、引言、以及详情页的地址,爬取10页,并将数据保存到csv文件中。
4.1 思路分析
- 编写代码获取要爬取网页的URL:
# 第一页:https://movie.douban.com/top250?start=0&filter=
# 第二页:https://movie.douban.com/top250?start=25&filter=
# 第三页:https://movie.douban.com/top250?start=50&filter=
# start = (page-1)*25
base_url = 'https://movie.douban.com/top250?start={}&filter='
start_page = int(input('请输入爬取网页的起始页:'))
end_page = int(input('请输入爬取网页的终止页:'))
for i in range(start_page, end_page+1):
tar_url = base_url.format((i-1)*25)
print(tar_url)
运行结果:
- 编写代码获取网页源码文本数据:
import requests
tar_url = 'https://movie.douban.com/top250?start=0&filter='
req_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 '
}
res_obj = requests.get(tar_url, headers=req_headers)
res_obj.encoding = 'utf-8'
source_text = res_obj.text
print(source_text)
运行结果:
- 在网页源码中查找所需要的数据
source_text = '''
<li>
<div class="item">
<div class="pic">
<em class="">1</em>
<a href="https://movie.douban.com/subject/1292052/">
<img width="100" alt="肖申克的救赎" src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg" class="">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
<span class="title">肖申克的救赎</span>
<span class="title"> / The Shawshank Redemption</span>
<span class="other"> / 月黑高飞(港) / 刺激1995(台)</span>
</a>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994 / 美国 / 犯罪 剧情
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>2220628人评价</span>
</div>
<p class="quote">
<span class="inq">希望让人自由。</span>
</p>
</div>
</div>
</div>
</li>
'''
# 将HTML数据转化为xml对象
html_element = etree.HTML(source_text)
# 获取电影的名字
movie_name = html_element.xpath('//div[@class="hd"]/a/span[1]/text()')[0]
print(movie_name)
# 获取电影的详情URL
movie_url = html_element.xpath('//div[@class="hd"]/a/@href')[0]
print(movie_url)
# 获取电影的评分
movie_star = html_element.xpath('//div[@class="star"]/span[2]/text()')[0]
print(movie_star)
# 获取电影的引言
movie_quote = html_element.xpath('//p[@class="quote"]/span/text()')
if movie_quote:
movie_quote = movie_quote[0]
else:
movie_quote = ''
print(movie_quote)
最后获取电影引言的时候,加上了if语句,目的是判断xpath返回的列表是否为空。如果为空,则手动将变量movie_quote
赋值为空字符串。xpath语句后面加上[0]
表示取返回列表的第一个值,上述操作都是为后续的写入数据到csv文件做准备。
运行结果:
4.2 完整代码
class Spider(object):
def __init__(self, base_url):
self.req_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, '
'like Gecko) Chrome/87.0.4280.88 Safari/537.36 '}
self.base_url = base_url
self.titles = ['name', 'url', 'star', 'quote']
# 获取网页源码文本数据
def GetSourceText(self, tar_url):
res_obj = requests.get(tar_url, headers=self.req_headers)
res_obj.encoding = 'utf-8'
source_text = res_obj.text
return source_text
# 获取每个电影的数据,每个电影以字典的形式写到列表中
def GetEveryItem(self, source_text):
html_element = etree.HTML(source_text)
# 提取每个电影的总标签内容
movieItemList = html_element.xpath('//div[@class="info"]')
movie_list = []
for eachmovie in movieItemList:
movie_dict = {}
movie_name = eachmovie.xpath('div[@class="hd"]/a/span[1]/text()')[0]
movie_url = eachmovie.xpath('div[@class="hd"]/a/@href')[0]
movie_star = eachmovie.xpath('div[@class="bd"]/div[@class="star"]/span[2]/text()')[0]
movie_quote = eachmovie.xpath('div[@class="bd"]/p[@class="quote"]/span/text()')
if movie_quote:
movie_quote = movie_quote[0]
else:
movie_quote = ''
movie_dict[self.titles[0]] = movie_name
movie_dict[self.titles[1]] = movie_url
movie_dict[self.titles[2]] = movie_star
movie_dict[self.titles[3]] = movie_quote
movie_list.append(movie_dict)
return movie_list
# 将爬取的数据写入到csv文件中
def WirteData(self, movie_list, filename):
with open(filename, 'w', encoding='utf-8', newline='') as fobj:
writer_obj = csv.DictWriter(fobj, self.titles)
writer_obj.writeheader()
writer_obj.writerows(movie_list)
# 控制数据爬取、保存的流程
def main(self):
start_page = int(input('请输入爬取网页的起始页:'))
end_page = int(input('请输入爬取网页的终止页:'))
movie_list = []
for i in range(start_page, end_page+1):
tar_url = self.base_url.format((i-1)*25)
source_text = self.GetSourceText(tar_url)
movie_list += self.GetEveryItem(source_text)
self.WirteData(movie_list, 'movie_data.csv')
if __name__ == '__main__':
douban = Spider('https://movie.douban.com/top250?start={}&filter=')
douban.main()
运行结果:
爬取前两页的电影数据,csv文件的内容如下: