Xpath解析:最常用且最简洁高效到的一种解析方式。通用性最强,在其他编程语言中也可应用。
本文中测试使用html文档在文末,可下载。
一:xpath解析原理
1:实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中。
2:调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
二:环境安装
这里用到的比较简单:
pip install lxml
对,你没看错,就是安装lxml模块就可以了。
三:基本使用
1 :首先引入etree模块
# 引入etree模块
from lxml import etree
2 :将本地的html文章中的源码数据加载到etree对象中:
html = etree.parse('beike.html', etree.HTMLParser())
result = etree.tostring(html)
print(result)
3 :将从互联网上获取的源码数据加载到该对象中:
# 1:指定url
url = "https://dl.ke.com/ershoufang/"
# 2:UA伪装 将对应的User-Agent封装到一个字典中(浏览器标识去浏览器中用f12查看源代码获取就好了)
headers = {
"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43'
}
# 3:模拟网络请求链接
responce = requests.get(url=url, headers=headers)
# 4:获取响应数据
content = responce.text
# 5: xpath解析
html = etree.HTML(content)
result = etree.tostring(html)
print(result)
四:xpath表达式
这个对应ps4中标签定位的表达式以及正则解析中的正则表达式
我这里使用本地html文档进行测试
1 :定位标签
/ :表示的是从跟几点开始定位,表示的是一个层级
# 引入etree模块
from lxml import etree
# 加载本地html
tree = etree.parse('beike.html')
html = tree.xpath('/html/body/div)
print(html)
报错:
lxml.etree.XMLSyntaxError: AttValue: " or ' expected, line 34, column 51
出错原因:
html代码书写不规范(不怪你)
解决方案:
# 引入etree模块
from lxml import etree
# 加载本地html
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
html = tree.xpath('/html/body/div)
print(html)
输出:
[<Element div at 0x1db4cb81688>]
// :表示的是多个层级,可以表示从任意位置开始定位(一定要理解这句话的重要性,// 不能随便用)
但是呢?像上边那样一个层级一个层级的获取元素太麻烦,有没有一次获取多个层级的方式呢?那自然是有的。使用 // 代码如下所示:
# 引入etree模块
from lxml import etree
# 加载本地html
# tree = etree.parse('beike.html')
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
# 单个层级 /
# html = tree.xpath('/html/body/div')
# 多个层级 //
html = tree.xpath('/html//div')
print(len(html))
# 多个层级 //
html = tree.xpath('//div')
print(len(html))
print(html)
输出:
473
473
[<Element div at 0x1bc824a1708>, <Element div at 0x1bc824a1748>, <Element div at 0x1bc824a1788>, <Element div at 0x1bc824a1808>……(字符串太长,这里就不展示了)]
通过上边的代码,我们可以看到
html = tree.xpath('/html//div')
html = tree.xpath('//div')
这两个表达式输出的结果是一样的。
属性定位://div[@class=’song’] 通用写法:tag[@attrName=”attrValue”]
# 引入etree模块
from lxml import etree
# 加载本地html
# tree = etree.parse('beike.html')
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
# 根据属性获取标签 返回一个列表
html = tree.xpath('//img[@class="lj-lazy"]')
print(html)
输出:
[<Element img at 0x2037f891448>, <Element img at 0x2037f891488>, <Element img at 0x2037f891508>, <Element img at 0x2037f891708>, <Element img at 0x2037f891608>, <Element img at 0x2037f8917c8>……(字符串太长,这里就不展示了)]
索引定位:tree.xpath('//img[@class="lj-lazy"][3]') 请注意看,这里的索引是从1开始,而不是从0开始
# 引入etree模块
from lxml import etree
# 加载本地html
# tree = etree.parse('beike.html')
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
# 根据属性获取标签 返回一个列表
html = tree.xpath('//dd[@class="dd"]/a[2]')
print(html)
2 :获取标签文本
/text() 获取的是标签中直系的文本内容
# 引入etree模块
from lxml import etree
# 加载本地html
# tree = etree.parse('beike.html')
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
# 根据属性获取标签,获取文本 返回一个列表
html = tree.xpath('//ul[@class="channelList"]/li/text()')
print(html)
输出:
['\r\n ', '\r\n ', '\r\n ', '\r\n
', '\r\n ', '\r\n ', '\r\n ', '\r\n
', '\r\n ', '\r\n ', '\r\n ', '\r\n
', '\r\n ', '\r\n ', '\r\n ', '\r\n
', '\r\n ', '\r\n ', '\r\n ', '\r\n
', '\r\n ', '\r\n ', '\r\n ']
因为li标签下没有直系的文本,因此输出的都是转义符以及空格
//text() 获取的是标签下所有的文本内容(直系+非直系)
# 引入etree模块
from lxml import etree
# 加载本地html
# tree = etree.parse('beike.html')
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
# 根据属性获取标签,获取文本 返回一个列表
html = tree.xpath('//ul[@class="channelList"]/li//text()')
print(html)
输出:
['\r\n ', '首页', '\r\n ', '\r\n ', '二手房', '\r\n ', '\r\n
', '新房', '\r\n ', '\r\n ', '租房', '\r\n ', '\r\n ', '海外', '\r\n
', '\r\n ', '商业办公', '\r\n ', '\r\n ', '小区', '\r\n ', '\r\n
', '发布房源', '\r\n ', '\r\n ', '贝壳研究院', '\r\n ', '\r\n
', '下载APP', '\r\n ', '\r\n ', '\r\n ', '\r\n ', '\r\n
', '开放平台', '\r\n ']
这样就取到了 li标签下所有的文本内容。
但是这个样式还是不太能用。那如何获取纯文本呢?
# 引入etree模块
from lxml import etree
# 加载本地html
# tree = etree.parse('beike.html')
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
# 根据属性获取标签,获取文本 返回一个列表
html = tree.xpath('//ul[@class="channelList"]/li/a/text()')
print(html)
输出:
['首页', '二手房', '新房', '租房', '海外', '商业办公', '小区', '发布房源', '贝壳研究院', '下载APP', '开放平台']
3 :获取标签属性
/@attrName 例如:tree.xpath('//img[@class="lj-lazy"]/@data-original')
# 引入etree模块
from lxml import etree
# 加载本地html
# tree = etree.parse('beike.html')
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse('beike.html', parser=parser)
# 根据属性获取标签,获取文本 返回一个列表
html = tree.xpath('//img[@class="lj-lazy"]/@data-original')
print(html)
输出:
['https://ke-image.ljcdn.com/110000-inspection/pc1_pq2c1MCvg_1.jpg!m_fill,w_280,h_210,f_jpg?from=ke.com',
'https://ke-image.ljcdn.com/110000-inspection/pc1_7BYJwkjXc.jpg!m_fill,w_280,h_210,f_jpg?from=ke.com',
'https://ke-image.ljcdn.com/110000-inspection/26a4b23b-59a9-40a2-a25b-f8031fead4a8_1000.jpg!m_fill,w_280,h_210,f_jpg?from=ke.com']
五:实际应用
1 :解析贝壳二手房源信息
我这里不做太复杂的,解析出每套房子的标题即可:
# 引入etree模块
from lxml import etree
# 加载线上html
# 1:指定url
url = "https://dl.ke.com/ershoufang/"
# 2:UA伪装 将对应的User-Agent封装到一个字典中(浏览器标识去浏览器中用f12查看源代码获取就好了)
headers = {
"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43'
}
# 3:模拟网络请求链接
responce = requests.get(url=url, headers=headers)
# 4:获取响应数据
content = responce.text
# 5 :xpath 解析
tree = etree.HTML(content)
# 6:解析标签,获取数据
html = tree.xpath('//ul[@class="sellListContent"]//img[@class="lj-lazy"]/@title')
print(html)
输出:
['观山悦二期,小区环境好,3室户型,采光好', '房子产权证在手,随时签约,客厅朝南采光好无浪费面积', '总监推荐好房 单价11990买红星海 不挡光 必麦好房', '南北通透两室,楼层好,得房率高,看房有钥匙。', '香炉礁 近香工街地铁站 两室南北通透 生活便利', '近欧尚南向小两室拎包入住出行方便近市场人车分流', '精装修2室,全明户型,南北卧室,电梯房高楼层', '湾里西 精装修 多层中间户,出行方便', '味食小区 2室1厅 南 北', '世元国际公寓 1室0厅 南', '店长推荐 精装修 两个南卧室,看房方便一线海景', '星海广场70年产权,近地铁交通方便', '锦华中房源天花板,一楼相当于二楼。格局嘎嘎好', '业主急售 红星海岚谷小高层 位置好', '诚心出售价格便宜,楼层高视野好,南向位置便利采光好', '万达商圈 南北通透 精装修 看房方便', '万国南向2室,手续情绪,诚心出售', '小孤山东里 2室1厅 南 北', '蓝天下清水房中间楼层带电梯采光无遮挡一线海景', '大医物业 两居室 南北通透 采光视野好 房主诚心卖', '华发新城 3室1厅 南 北', '亿达康派公寓 1室0厅 西南', '水仙,三十四,两室,无坡,保持好', '主城安家,安居乐业,住大宅大展宏图', '山河嘉园 装修好 楼层好 看房方便', '瓦纺小区 2室1厅 南 北', '楼层好,视野好,有房证,房主手续清晰', '马栏 适合年轻人过渡用,压力小,近地铁,交通便利', '老小区得房率高,精装修,楼层好。保持干净。', '左岸经典 1室1厅 东']
这里需要注意一个小问题,// 定位语法不是随意使用的。
html = tree.xpath('//ul[@class="sellListContent"]//img/@title')
上边的表达式是获取了当前文档中所有img标签的title属性值
html = tree.xpath('//ul[@class="sellListContent"]//img[@class="lj-lazy"]/@title')
上边的表达式获取了当前文档中class= sellListContent的img标签的title属性值
然后,我就发现//ul[@class="sellListContent"]这个表达式不是白写了嘛……
2 :下载贝壳二手房源图片
这个功能就是老生长谈了下载图片
# 引入etree模块
from lxml import etree
def getImage(url,num):
"""
:name 爬取网络图片
:param url: 图片url
:param num: 图片名称
:return: 无返回值
"""
# 1:指定url
# url = "https://resource.guanchao.site/uploads/sowing/welcome-image3.jpg"
# 2:模拟网络请求链接
responce = requests.get(url=url)
# 3:获取响应数据,content获取二进制数据
content = responce.content
filename = './img/'+ str(num) +'.jpg'
# 4:持久化存储
with open(filename, 'wb') as fe:
fe.write(content)
print('爬取完成')
# 加载线上html
# 1:指定url
url = "https://dl.ke.com/ershoufang/"
# 2:UA伪装 将对应的User-Agent封装到一个字典中(浏览器标识去浏览器中用f12查看源代码获取就好了)
headers = {
"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43'
}
# 3:模拟网络请求链接
responce = requests.get(url=url, headers=headers)
# 4:获取响应数据
content = responce.text
# 5 :xpath 解析
tree = etree.HTML(content)
# 6:解析标签,获取数据
html = tree.xpath('//img[@class="lj-lazy"]/@data-original')
i = 0
for item in html:
getImage(item,i)
i += 1
print(html)
3 :爬取城市列表(xpath配合 按位或 | 使用 )
还是爬取贝壳网的数据,目标链接:www.ke.com/city/
import requests
# 引入etree模块
from lxml import etree
# 加载线上html
# 1:指定url
url = "https://www.ke.com/city/"
# 2:UA伪装 将对应的User-Agent封装到一个字典中(浏览器标识去浏览器中用f12查看源代码获取就好了)
headers = {
"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43'
}
# 3:模拟网络请求链接
responce = requests.get(url=url, headers=headers)
# 4:获取响应数据
content = responce.text
# 5 :xpath 解析
tree = etree.HTML(content)
cityList = []
# 6:解析标签,获取数据
# //ul[@class="city_recommend_list"]/li/a/text() 获取热门城市
# //li[@class="CLICKDATA"]/a/text() 获取正常的城市
# 多个表达式 采用 按位或 | 链接
html = tree.xpath('//ul[@class="city_recommend_list"]/li/a/text() | //li[@class="CLICKDATA"]/a/text()')
print(html)
输出:
['北京', '上海', '广州', '深圳', '成都', '合肥', '芜湖', '淮南', '马鞍山', '铜陵', '安庆', '黄山', '阜阳', '六安', '宣城', '滁州', '北京', '重庆', '福州', '厦门', '莆田', '泉州', '宁德', '龙岩', '漳州', '广州', '深圳', '珠海', '佛山', '江门', '湛江', '肇庆', '惠州', '东莞'……(数据太多,这里不展示了)]
以上大概就是xpath的基本使用,有好的建议,请在下方输入你的评论。