Python爬虫(六)数据解析—xpath(重点)

207 阅读9分钟

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的基本使用,有好的建议,请在下方输入你的评论。