Xpath解析概述
我们在正则表达式一文中,抓取了猫眼的前top100
的网页数据并使用正则表达式对其中的电影名称进行了提取,但是编写正则表达式的还是比较繁琐,万一某个地方写错了可能导致匹配失败,所以正则匹配在网页信息提取的操作中稍微有点不方便
对于网页节点来说,它可以定义 id
,class
或者其他属性,而且节点之间还有层次关系,节点之间也有层次关系,在网页源码解析中可以利用 Xpath
或者 CSS
选择器来提取某个节点,然后再调用相应方法获取它的正文内容或者属性就可以提取到我们想要的任意信息了
1.Xpath简介
Xpath:XML Path Languange
【即XML路径语言】,它是一门在XML
文档中查找信息的语言,它最初是用来搜寻XML
文档的,同样也适用于 HTML
文档的搜索,XPath
的选择功能十分强大,它提供了非常简洁明了的路径选择表达式
,另外还提供了100
个内建函数用于字符串、数值、时间的匹配以及节点、序列的处理等,几乎所有我们想要定位的节点都可以通过Xpath
来选择
首先,通过一个简单的html
来了解几个案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<book>
<id>1</id>
<name>时间简史</name>
<price>25.00$</price>
<author>
<nick>斯蒂芬·霍金</nick>
<nick>简·怀尔德</nick>
</author>
</book>
</body>
</html>
在上面的html前端代码中
1.
book
,id
,name
,price
…都被称为节点
2.id
,name
,price
,author
被称为book
的子节点,book
则被称为它们的父节点
3.id
,name
,price
,author
被称为兄弟节点【同胞节点】
html中的标签节点非常多,我们无须一次了解所有的节点以及它们的作用,从下图中我们可以大概看到一个html
页面之间标签节点之间的层级关系
环境安装
pip install lxml
用法:
- 将要解析的
html
【本地保存或者爬取下来的HTML
网页】构造出etree
对象
from lxml import etree
# page_text可以是本地保存的HTML文档也可以是通过请求下来的
tree = etree.HTML(page_text) #调用HTML类进行初始化就成功构造了一个Xpath解析对象
- 接下来使用
etree对象
的xpath()
方法配合xpath
表达式来完成标签定位和内容捕获
在整个xpath
的解析中,核心就在于xpath表达式
的编写
xpath表达式编写规则
我们一般会用//
开头的Xpath
规则来选取所有符合要求的节点
/
:从当前节点选取直接子节点,表示的是从根节点开始定位,表示的是一个层级//
:放在表达式中间表示跨多个层级,放在表达式的开头表示从任意位置开始定位属性定位
:通过@
符号进行属性过滤,具体操作看后面演示索引定位
:索引从1开始,注意与数组/列表索引的去呗取文本
/text()
:获取的是标签找中直系的文本内容,返回的是一个列表
//text()
:取标签中非直系的文本内容,即该标签下的所有文本内容,返回的是一个列表
取属性
我们用
/text()
获取节点内部的文本,节点的属性则使用@属性名
来获取对应的属性值
相信大家看到上面的用法介绍肯定还是一脸蒙蔽,别慌,还是老样子,博主不喜欢在大家对xpath
的魅力还不熟悉之前就丧失了对它的兴趣,所以博主在这里不和大家长篇大论的介绍xpath
解析的用法,我们先看几个案例,通过案例的需求带着问题去找答案,再解决问题的同时就学会并了解了Xpath
的用法
2.Xpath网页解析案例1:爬取58同城上二手房信息
访问网站,查看网页源代码,如下图所示:
1.定位到房屋列表,返回的是第一页存储房屋信息的div信息元素列表
div_list = tree.xpath('//section[@class="list"]/div[@tongji_tag="fcpc_ersflist_gzcount"]')
# 第一页的所有
# [<Element div at 0x28f37f3a200>, <Element div at 0x28f37f3af80>, <Element div at 0x28f37f3a5c0>, <Element div at 0x28f37f3a600>....]
下面是top1【即div_list[0]】的房屋信息网页源码
<div class="property" data-v-7ba6f82f="" data-v-9a1aeaec="" tongji_tag="fcpc_ersflist_gzcount">
<a class="property-ex" data-action="esf_list" data-cp='{"broker_id":"0"}' data-ep='{"exposure":{"entry_source":"","from_id":"2","project_id":"220","source_type":19,"hp_type":"220","ax_type":"1000010","search_type":"filter","vpid":"3122138714004480","esf_id":0,"isauction":0,"pos":0,"trading_area_ids":0,"found":0,"prop_expire":0,"entry":0,"from":"","guid":"DDB464BE-A475-47D5-A68A-365AE425B5A9","sid":"eabd039a-6119-4f86-876a-cdf391dd9267","pagenum":"1","sku_type":"1","value_added_num":"1","label_name":"店长推荐","label_type":"","label_info":"","label_info_type":"","is_expandingxinfang":"0","request_id":"1837","position":"1","ab_test":"909_greyrelease_a","title_idx":"0","ab_test2":"","ab_test3":"909_anxuan_a","esf_list":1,"broker_id":"0"}}' data-lego='{"entity_id":"3122138714004480","tid":"-"}' data-v-9a1aeaec="" href="https://sz.58.com/ershoufang/3122138714004480x.shtml?auction=220&hpType=60&entry=102&position=0&kwtype=filter&now_time=1697263725&typecode=220&spread=filtersearch_c&epauction=&stats_key=94761423-fdca-4c5c-93f6-acb90d015950_0&from=from_esf_List_search&index=0" target="_blank">
<div class="property-image" data-v-9a1aeaec="">
<img alt="" class="lazy-img cover" data-v-9a1aeaec="" data-v-a84560ba="" src="https://pages.anjukestatic.com/usersite/touch/img/comm/nopic_list2.png"/>
<img class="property-image-vr" data-v-9a1aeaec="" src="https://pages.anjukestatic.com/usersite/site/img/prop_detail/comm_propdetail_icon_vr_m@2x.png"/>
<span class="property-image-vr" data-v-9a1aeaec="" id="main-0">
</span>
</div>
<div class="property-content" data-v-9a1aeaec="">
<div class="property-content-detail" data-v-9a1aeaec="">
<div class="property-content-title" data-v-9a1aeaec="">
<h3 class="property-content-title-name" data-v-9a1aeaec="" style="max-width:465px;" title="翠薇园降价三房,低于指导40万,满5年红本中间楼层中间位置。">
翠薇园降价三房,低于指导40万,满5年红本中间楼层中间位置。
</h3>
<img class="property-content-title-anxuan" data-v-9a1aeaec="" src="https://pages.anjukestatic.com/usersite/app/anjukeapp/esf_list_icon_dianzhangtuijian@3x.png" style="width:74px;height:22px;"/>
<span class="property-content-title-othertag-ad" data-v-9a1aeaec="">
广告
</span>
</div>
<section data-v-9a1aeaec="">
<div class="property-content-info" data-v-9a1aeaec="">
<p class="property-content-info-text property-content-info-attribute" data-v-9a1aeaec="">
<span data-v-9a1aeaec="">
3
</span>
<span data-v-9a1aeaec="">
室
</span>
<span data-v-9a1aeaec="">
2
</span>
<span data-v-9a1aeaec="">
厅
</span>
<span data-v-9a1aeaec="">
1
</span>
<span data-v-9a1aeaec="">
卫
</span>
</p>
<p class="property-content-info-text" data-v-9a1aeaec="">
80㎡
</p>
<p class="property-content-info-text" data-v-9a1aeaec="">
南北
</p>
<p class="property-content-info-text" data-v-9a1aeaec="">
中层(共7层)
</p>
<p class="property-content-info-text" data-v-9a1aeaec="">
1993年建造
</p>
</div>
<div class="property-content-info property-content-info-comm" data-v-9a1aeaec="">
<p class="property-content-info-comm-name" data-v-9a1aeaec="">
翠薇园
</p>
<p class="property-content-info-comm-address" data-v-9a1aeaec="">
<span data-v-9a1aeaec="">
南山
</span>
<span data-v-9a1aeaec="">
蛇口
</span>
<span data-v-9a1aeaec="">
荔园路43号
</span>
</p>
</div>
<div class="property-content-info" data-v-9a1aeaec="">
<span class="property-content-info-tag" data-v-9a1aeaec="">
热门小区
</span>
<span class="property-content-info-tag" data-v-9a1aeaec="">
南北通透
</span>
<span class="property-content-info-tag" data-v-9a1aeaec="">
随时可看
</span>
</div>
</section>
<div class="property-extra-wrap" data-v-9a1aeaec="">
<div class="property-extra" data-v-9a1aeaec="">
<!-- -->
</div>
<!-- -->
</div>
<!-- -->
</div>
<div class="property-price" data-v-9a1aeaec="">
<p class="property-price-total" data-v-9a1aeaec="">
<span class="property-price-total-num" data-v-9a1aeaec="">
645
</span>
<span class="property-price-total-text" data-v-9a1aeaec="">
万
</span>
</p>
<p class="property-price-average" data-v-9a1aeaec="">
80625元/㎡
</p>
</div>
</div>
</a>
</div>
对于购房租房的人来说,我们最关注的房屋信息其实是房子的价格、户型、面积、朝向、楼层、房屋的修建时间、所在小区、具体地址,下面我们就来用xpath
逐个解析并获取这些信息,这里博主以58同城上的第一个房子top1
为例,网站房子的排名随时会发生变换,也许你下次访问的时候top1
和博主的不一样,这个没关系,分析方法是一样的
- 提取房屋的详细信息
- 提取房屋的价格信息
# 属性定位
# price_list = div_list[0].xpath('./a/div[@class="property-content"]/div[@class="property-price"]/p[@class="property-price-total"]//text()')
# 索引定位
price_list = div_list[0].xpath('./a/div[2]/div[2]/p[1]//text()')
# price_list = ['576.8', ' ', '万']
# 取 price_list 中的数据和单位进行拼接
price = "".join(i if not i.strip() else i for i in price_list)
在xpath
表达式编写的时候需要注意的地方
./
:.
表示当前节点,./
表示从当前节点选取子节点
//text()
:取标签中非直系的文本内容,即该标签下的所有文本内容
属性定位
:注意上面属性定位的表达式的写法
索引定位
:注意上面索引定位的表达式的写法
注意1:属性定位和索引定位取决于你在分析网页源码的时候怎么方便怎么来,如果一个标签节点下有几十个子节点,你想取中间某一个子节点的信息,显然用属性定位来得方便
注意2:在进行标签定位的时候建议大家还是直接从网页上查看标签之间的层级关系
- 提取房屋均价信息【即多少钱一平】
从上面的网页源码中可以看出房屋的价格和房屋的均价属于同级
<p>
标签,唯一的变化就是在定位<p>
标签的时候改下索引就好了
average_price = div_list[0].xpath('./a/div[2]/div[2]/p[2]//text()')[0]
- 提取房屋的户型信息
layout_list = div_list[0].xpath('./a/div[2]//p[@class="property-content-info-text property-content-info-attribute"]//text()')
# layout_list: ['3', ' ', '室', ' ', '2', ' ', '厅', ' ', '1', ' ', '卫']
layout = "".join(i if not i.strip() else i for i in layout_list)
# layout: 3 室 2 厅 1 卫
到此,对以下信息的获取和上面的获取方式没有什么区别,大家可以感受到xpath
解析网页的方便之处,而且也可以轻松的写出xpath表达式
,下面不做过多的介绍,都是一样的套路…
- 获取房屋的面积、朝向、楼层、建造时间等信息
info_list = div_list[0].xpath('./a/div[2]//p[@class="property-content-info-text"]//text()')
# info_list: ['\n 92.41㎡\n ', '南北', '\n 中层(共33层)\n ', '\n 2018年建造\n ']
# 对其中元素做一些特殊处理(去除换行符和空格)
area = info_list[0].strip().replace(r'\n', '') # 房屋面积
direction = info_list[1].strip().replace(r'\n', '') # 房屋朝向
floor = '无' if len(info_list) <= 3 else info_list[2].strip().replace(r'\n', '') # 房屋楼层
build_time = '无' if len(info_list) <= 3 else info_list[3].strip().replace(r'\n', '') # 房屋建造时间
- 获取小区名称和详细地址
community = div_list[0].xpath('./a/div[2]//p[@class="property-content-info-comm-name"]//text()')[0] # 小区名称
address_list = div_list[0].xpath('./a/div[2]//p[@class="property-content-info-comm-address"]//text()')
address = '-'.join(address_list) # 详细地址
对于普通的上网的用户来说,通过浏览器登录58同城的网站,经过浏览器的渲染的html
页面更加的美观和直观看房屋信息,对于爬虫工程师来说,那些页面的布局和UI
的美工其实并没有太大的意义,我们在意的是关于房屋的数据信息,而且大量的数据信息可以帮助我们找到其中的规律,价格上涨和下降的时机是什么,你准备什么时候开始炒房(下一个中国首富就是你哈哈哈哈),题外话了【这是数据分析师的工作了】。。。
爬取58同城上二手房信息完整代码
import requests
from lxml import etree
def parse_single(single_node):
for div in single_node: # 局部解析
# 房屋价格
price_list = div.xpath('./a/div[2]/div[2]/p[1]//text()')
price = "".join(i if not i.strip() else i for i in price_list)
# 房屋均价
average_price = div.xpath('./a/div[2]/div[2]/p[2]//text()')[0]
# 房屋户型
layout_list = div.xpath(
'./a/div[2]//p[@class="property-content-info-text property-content-info-attribute"]//text()')
layout = "".join(i if not i.strip() else i for i in layout_list)
info_list = div.xpath('./a/div[2]//p[@class="property-content-info-text"]//text()')
area = info_list[0].strip().replace(r'\n', '') # 房屋面积
direction = info_list[1].strip().replace(r'\n', '') # 房屋朝向
floor = '无' if len(info_list) <= 3 else info_list[2].strip().replace(r'\n', '') # 房屋楼层
build_time = '无' if len(info_list) <= 3 else info_list[3].strip().replace(r'\n', '') # 房屋建造时间
community = div.xpath('./a/div[2]//p[@class="property-content-info-comm-name"]//text()')[0] # 小区名称
address_list = div.xpath('./a/div[2]//p[@class="property-content-info-comm-address"]//text()')
address = '-'.join(address_list)
yield {
'房屋价格': price,
'均价': average_price,
'房屋户型': layout,
'房屋面积': area,
'房屋朝向': direction,
'房屋楼层': floor,
'房屋建造时间': build_time,
'小区名称': community,
'房屋地址': address
}
# 需求:爬取58二手房中的房源信息
if __name__ == "__main__":
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
'Cookie': 'f=n; commontopbar_new_city_info=4%7C%E6%B7%B1%E5%9C%B3%7Csz; commontopbar_ipcity=sz%7C%E6%B7%B1%E5%9C%B3%7C0; userid360_xml=55FD71B14D2A1EF6EAE3752AAB51731A; time_create=1699858715504; aQQ_ajkguid=CF310484-92A8-4C67-933A-DDDD03E0593B; sessid=7C190EC7-6905-49E4-94A9-CFCD7A73157A; ajk-appVersion=; ctid=4; fzq_h=fd6828b258c4e0b0235c0b2cf550abe1_1697266711138_7713e38677b24b38a5a7b56b1d6db242_2006001489; id58=CroOjGUqPBcELbW/EIs1Ag==; 58tj_uuid=333a9941-91da-454d-95d1-41349581fdf0; new_session=1; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fcallback.58.com%252F; 58home=sz; als=0; f=n; xxzl_cid=fdee971d695348e3ba3e4f825ba04ec5; xxzl_deviceid=UfSlfOw/cNTmSgSbUp5rapKhSMdHBGnkYbFM164IAifJjwHp7EUH7G0WbKW9rZ+A'
}
# 爬取到页面源码数据
url = 'https://sz.58.com/ershoufang/'
page_text = requests.get(url=url, headers=headers).text
# 数据解析
tree = etree.HTML(page_text)
# 存储的就是li标签对象
div_list = tree.xpath('//section[@class="list"]/div[@tongji_tag="fcpc_ersflist_gzcount"]')
for i in parse_single(div_list):
print(i)
注:大家复制粘贴到自己机器上运行的时候记得替换一下自己机器的
User-Agent
和Cookie
哦
部分结果展示【2023/10/14 16:31分运行的结果】
{'房屋价格': '576.8 万', '均价': '71000元/㎡', '房屋户型': '3 室 2 厅 1 卫', '房屋面积': '81.24㎡', '房屋朝向': '西南', '房屋楼层': '中层(共17层)', '房屋建造时间': '2004年建造', '小区名称': '前海花园', '房屋地址': '南山-前海-桃园路288号'}
{'房屋价格': '532.2 万', '均价': '71000元/㎡', '房屋户型': '3 室 2 厅 1 卫', '房屋面积': '74.96㎡', '房屋朝向': '南北', '房屋楼层': '中层(共7层)', '房屋建造时间': '1997年建造', '小区名称': '前海花园', '房屋地址': '南山-前海-桃园路288号'}
{'房屋价格': '676.8 万', '均价': '70500元/㎡', '房屋户型': '3 室 2 厅 2 卫', '房屋面积': '96㎡', '房屋朝向': '南北', '房屋楼层': '高层(共16层)', '房屋建造时间': '2004年建造', '小区名称': '绿景新洋房(绿景新苑二期)', '房屋地址': '福田-新洲-新洲二街274号'}
{'房屋价格': '553 万', '均价': '70000元/㎡', '房屋户型': '2 室 2 厅 1 卫', '房屋面积': '79㎡', '房屋朝向': '南', '房屋楼层': '无', '房屋建造时间': '无', '小区名称': '兆鑫汇金广场(新房)', '房屋地址': '罗湖-湖贝-深南东路与建设路交汇处(万象城东行500米)'}
{'房屋价格': '677.1 万', '均价': '78300元/㎡', '房屋户型': '3 室 2 厅 1 卫', '房屋面积': '86.48㎡', '房屋朝向': '西', '房屋楼层': '中层(共9层)', '房屋建造时间': '2008年建造', '小区名称': '方鼎华庭', '房屋地址': '南山-南头-南山大道3908号'}
{'房屋价格': '448.9 万', '均价': '52200元/㎡', '房屋户型': '3 室 2 厅 2 卫', '房屋面积': '86㎡', '房屋朝向': '南', '房屋楼层': '高层(共33层)', '房屋建造时间': '2015年建造', '小区名称': '星河时代', '房屋地址': '龙岗-大运-爱南路666号'}
{'房屋价格': '606.2 万', '均价': '71495元/㎡', '房屋户型': '3 室 2 厅 2 卫', '房屋面积': '84.79㎡', '房屋朝向': '南北', '房屋楼层': '高层(共31层)', '房屋建造时间': '2012年建造', '小区名称': '中粮锦云', '房屋地址': '宝安-流塘-前进二路33号'}
.....
最后大家可以随意修改url
中的'https://sz.58.com/ershoufang/'
城市简写找到你所在省会城市的房屋价格信息,这里博主的案例以sz
【深圳】为例,将sz
改为sh
你就可以看到【上海】的二手房的房价信息啦~,赶快复制粘贴上面的代码到你的本地编辑器运行一下感受Xpath
解析网页的威力与魅力吧,也许未来某一天当你复制粘贴运行发现没有结果甚至抛出异常,【也许是58网站的前端工程师们修改了网页源代码或者加强了反扒机制】,如果只是改了网页源代码那就不用着急,分析思路是不变的
Xpath网页解析案例2:爬取4k高清壁纸并保存到本地
点击网站链接里面有图片分类,这里我们以萌宠的图片为例,当然等你学会了爬取方式,你通过修改url
爬取美女图片和风景图片都是可以的
访问网站,查看网页源代码,如下图所示:
图片信息保存在下面的这些<li>
节点中,展开li节点
可以看到图片的地址被保存到<img>
标签下的src
属性中
但是点击该链接进去一看,图片很小,是缩略图,拿来不能当壁纸用
我们想要的其实是这种大的图片
仔细对比上面两张图片的url
你会发现并不相同,大图的url在可以找到呢,能找到我们就能用requests
请求下来
当我们点击某张图片的时候我们访问的其实是这个页面
这个页面的url
其实在,li标签
下面的a
标签的href
属性中
接着访问这个页面,我们可以找到大图的url
所在的位置
爬取4k高清壁纸完整代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 需求:解析下载图片数据 http://pic.netbian.com/4kdongwu/
import requests
from lxml import etree
import os
if __name__ == "__main__":
url = 'http://pic.netbian.com/4kdongwu/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
response = requests.get(url=url, headers=headers)
page_text = response.text
# 数据解析:src的属性值 alt属性
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]/ul/li')
# 创建一个文件夹
if not os.path.exists('./picLibs'):
os.mkdir('./picLibs')
#
for li in li_list:
# 定位到单张图片的详情页
pic_url = 'https://pic.netbian.com' + li.xpath('./a/@href')[0]
# 取图片名称(可能存在中文乱码问题,后面有针对乱码的特殊处理)
img_name = li.xpath('./a/img/@alt')[0] + '.jpg'
# 对详情页发起请求
response_2 = requests.get(url=pic_url, headers=headers).text
tree_2 = etree.HTML(response_2)
# 从详情页的页面远源码中定位到大图的url
big_picture_url = 'https://pic.netbian.com' + tree_2.xpath('//div[@class="photo-pic"]/a/img/@src')[0]
# 对大图发起请求
big_img_data = requests.get(url=big_picture_url, headers=headers).content
# 通用处理中文乱码的解决方案
img_name = img_name.encode('iso-8859-1').decode('gbk')
# 请求图片进行持久化存储
img_path = 'picLibs/' + img_name
with open(img_path, 'wb') as fp:
fp.write(big_img_data)
print(img_name, '下载成功!!!')
print("所有图片下载完成! ! !")
成果展示
结语
本文给大家介绍了Xpath
在网页源码解析中的应用,这里需要多提一句的是,尽管Xpath
非常请打并且也非常常用,但是如果网站的网页源码有变动的话,文章中的实战代码可能无法正确解析网页源码,甚至直接抛出错误,但是网页解析的原理是不变的,等你真正学会并熟悉了Xpath
的使用之后,你可以直接查看网页源代码去匹配筛选你真正想要的信息,博主这篇文章的目的旨在带领大家入门解析库,了解它的作用,而不是像技术手册一样枯燥乏味的给你介绍每个函数的入参出参,每个函数的功能,最后,如果你觉得本文对你有用的话,还请大家不吝点赞哦~