欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python爬虫 之数据解析之xpath

程序员文章站 2024-01-27 14:08:58
...


一、xpath解析

1、xpath是什么

xpath是最常用且最高效的一种解析方式。通用性很高,不止在Python语言中可以使用,在其它语言中也可以使用。

xpath 全称 XML Path Language,即 XML 路径语言,它是一门 XML 文档中查找信息的语言。它最初是用来搜寻 XML 文档的,但是它同样适用于 HTML 文档的搜索。所以在做爬虫时,我们完全可以使用 xpath 来做相应的信息抽取。

2、xpath解析原理:

① 实例化一个 etree 的对象,且需要将被解析的页面源码数据加载到该对象中。
② 调用 etree 对象中的 xpath 方法,结合着xpath表达式实现标签的定位和内容的捕获。


二、环境的安装

使用xpath解析,只需要安装一个解析器:lxml。而这个解析器在bs4中也需要安装。lxml是一个解析器,也是下面的xpath要用到的库。

环境安装:

pip install lxml

Python爬虫 之数据解析之xpath


三、实例化etree对象

首先需要导入模块

from lxml import etree

etree对象同样的有两个操作

1、将本地的 html 文档中的源码数据加载到 etree 对象中:

etree.parse(filePath)
# filePath为文件的路径

2、可以从互联网上获取的源码数据加载到该对象中:

etree.HTML('page_text')
# page_text为从页面获取的源码数据

调用 xpath 方法
使用 xpath 方法需要传入参数,这个参数是一个字符串,而这个字符串就是 xpath表达式。

xpath('xpath表达式')

也就是说在数据解析过程中,最重要的步骤就是xpath表达式的书写。意味着可以根据不同形式的xpath表达式定义不同的标签,并且可以将定位标签的相关文本数据进行获取。


四、xpath表达式

符号 含义
/ 表示的是从根节点开始定位。表示的是一个层级。
// 表示的是多个层级。可以表示从任意位置开始定位。

属性定位:

tag[@attrName="attrValue"]
# 例如:
//div[@class="book-mulu"]

索引定位:

# 索引是从[1]开始的
tag[@attrName="attrValue"]/a[num]	# a为标签名,num为第几行
# 例如
//div[@class="book-mulu"]/ul/li[3]
# 也可以表达为://div[@class="book-mulu"]//li[3]

取文本:

/text()			# 获取标签中直系的文本内容
//text()		# 获取标签中非直系文本内容(所有文本内容)

取属性:

/@attrName
# 例如:img/@src

五、项目实例

实例一

需求:
爬取58同城二手房源信息(以北京市为例)。解析出所有房源的名称,并进行持久化存储。
网址:https://bj.58.com/ershoufang/

思路:

主要就是观察页面的结构,看每个房源的名字所在的标签是哪个。然后写出xpath表达式即可。
Python爬虫 之数据解析之xpath
因为所有房源名称都在li标签中,所以我们先将li标签的所有内容都爬取下来。

# xpath表达式
tree.xpath('//ul[@class="house-list-wrap"]/li')

Python爬虫 之数据解析之xpath

我们观察到li标签下第二个div标签的h2标签下有名称信息,所有写下xpath表达式。因为这个是局部解析,div上一层标签是li所有div前面要加 “ . ”

# xpath表达式
title = li.xpath('./div[2]/h2/a/text()')[0]

Python爬虫 之数据解析之xpath

代码实现:

# 需求:爬取58通常二手房源信息
import requests
from lxml import etree

if __name__ == '__main__':
    url = 'https://bj.58.com/ershoufang/'
    header = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4209.2 Safari/537.36'
    }
    # 爬取页面源码数据
    page_text = requests.get(url=url,headers=header).text

    # 页面解析
    tree =etree.HTML(page_text)
    li_list = tree.xpath('//ul[@class="house-list-wrap"]/li')
    fp = open('58.txt','w',endcoding='utf-8')
    for li in li_list:
        # 局部解析
        # 一定要加 . 这个 . 表示的就是局部定位到的标签
        title = li.xpath('./div[2]/h2/a/text()')[0]
        print(title)
        # 存入文件
        fp.write(title + '/n')

实例二

需求:
爬取《红楼梦》所有章节的标题。
网址:https://www.shicimingju.com/book/hongloumeng.html

思路:

主要是对xpath表达式的书写。通过观察标签写出xpath表达式。
Python爬虫 之数据解析之xpath

# xpath表达式
tree = etree.xpath('//div[@class="card bookmark-list"]/div[4]/ul/li/a/text()')

代码实现:

import requests
from lxml import etree

if __name__ == '__main__':
    url = 'https://www.shicimingju.com/book/hongloumeng.html'
    header = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4209.2 Safari/537.36'
    }
    # 爬取页面源码数据
    page_text = requests.get(url=url,headers=header).text
    # 实例化etree
    tree = etree.HTML(page_text)
    # xpath表达式
    list1 = tree.xpath('//div[@class="card bookmark-list"]/div[4]/ul/li/a/text()')
    for li in list1:
        title = li
        print(title)

实例三

需求:
解析下载图片数据。
网址:http://pic.netbian.com/4kfengjing/

思路:
主要是对 xpath表达式的书写,和怎样处理中文乱码。
xpath表达式可以从< div class = “slist”>标签开始,也可以从更上面的标签开始,比如< idv id = “main” > 可以从这里开始。
当然两个写法的含义是一样的。

# 1
src_list = tree.xpath('///div[@class="slist"]/ul/li/a/img/@src')
# 2
src_list = tree.xpath('//div[@id="main"]/div[3]/ul/li/a/img/@src')
# 3也可以选择简写
src_list = tree.xpath('///div[@class="slist"]//li/a/img/@src')

Python爬虫 之数据解析之xpath

代码实现:

import requests
from lxml import etree
import os

if __name__ == '__main__':
    url = 'http://pic.netbian.com/4kfengjing/'
    header = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4209.2 Safari/537.36'
    }
    # 爬取页面源码数据 获取相应对象
    reponse = requests.get(url=url,headers=header)
    # 手动设置响应数据编码格式
    #reponse.encoding = 'utf-8'
    page_text = reponse.text
    # 数据解析,解析src的属性值,解析alt的属性值
    # 实例化etree
    tree = etree.HTML(page_text)
    # xpath表达式
    #src_list = tree.xpath('//div[@id="main"]/div[3]/ul/li/a/img/@src')
    #alt_list = tree.xpath('//div[@id="main"]/div[3]/ul/li/a/img/@alt')
    # 也可以这样写
    src_list = tree.xpath('//div[@class="slist"]/ul/li')

    # 创建一个文件夹
    if not os.path.exists('./tupian'):
        os.mkdir('./tupian')

    for li in src_list:
        img_src = 'http://pic.netbian.com' + li.xpath('./a/img/@src')[0]
        img_alt = li.xpath('./a/img/@alt')[0] + '.jpg'
        # 统用处理解决中文乱码的解决方法
        img_alt = img_alt.encode('iso-8859-1').decode('gbk')
        print(img_alt + " : " + img_src)
        # 图片地址转化成二进制
        img_data = requests.get(url=img_src,headers=header).content
        img_path = 'tupian/' + img_alt
        # 存储
        with open(img_path,'wb') as fp:
            fp.write(img_data)

案例四

需求:
解析出所给网址中全国城市的名称。
网址:https://www.aqistudy.cn/historydata/

思路:

首先实例化xpath对象,然后根据热门城市和全部城市的标签层级关系写出xpath表达式。解析表达式所对应的a标签,然后xpath函数返回一个列表,列表中存的就是a标签对应的城市。然后我们遍历列表即可。

Python爬虫 之数据解析之xpath

# 热门城市
hot_city_list = tree.xpath('//div[@class="bottom"]/ul/li')

Python爬虫 之数据解析之xpath

# 全部城市
all_city_list = tree.xpath('//div[@class="bottom"]/ul/div[2]/li')

我们无法只通过一共 xpath表达式,将两个层级标签都表示数量,但是我们可以将两个层级标签写在一起,只需要用按位或 “ | ” 进行分割。这个意味着将第一个 xpath表达式或者第二个 xpath表达式,作用到 xpath函数当中。这样可以解析第一个表达式所对应的a标签定位到,也可以将第二个表达式所对应的a标签定位到。

# 用按位或进行分割 “ | ”
a_city_list = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')

xpath返回一个列表,这个列表里面存的是热门城市加全部城市a标签所对应的一个列表。

然后遍历列表即可。

代码实现:
第一种写法:分别解析热门城市和所有城市,然后把这些城市的名字存入列表中。

import requests
from lxml import etree
import os

if __name__ == '__main__':
    url = 'https://www.aqistudy.cn/historydata/'
    header = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4209.2 Safari/537.36'
    }
    # 爬取页面源码数据 获取相应对象
    page_text = requests.get(url=url,headers=header).text

    # 数据解析
    # 实例化对象
    tree = etree.HTML(page_text)
    all_city = [] #所有的城市
    # 热门城市
    hot_city_list = tree.xpath('//div[@class="bottom"]/ul/li')
    for li in hot_city_list:
        hot_city_name = li.xpath('./a/text()')[0]
        all_city.append(hot_city_name)
        #print(hot_city_name)
    # 全部城市
    all_city_list = tree.xpath('//div[@class="bottom"]/ul/div[2]/li')
    for li in all_city_list:
        all_city_name = li.xpath('./a/text()')[0]
        all_city.append(all_city_name)
        #print(all_city_name)
    print(all_city," 一共有:",len(all_city),"个城市")

第二种写法:用按位或将两个层级关系连接。

import requests
from lxml import etree

if __name__ == '__main__':
    url = 'https://www.aqistudy.cn/historydata/'
    header = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4209.2 Safari/537.36'
    }
    # 爬取页面源码数据 获取相应对象
    page_text = requests.get(url=url,headers=header).text

    # 数据解析
    # 实例化对象
    tree = etree.HTML(page_text)
    all_city = [] #所有的城市
    # 解析到热门城市和所有城市对应的a标签
    # 热门城市对应a标签层级关系://div[@class="bottom"]/ul/li/a
    # 所有城市对应a标签层级关系://div[@class="bottom"]/ul/div[2]/li/a
    # 用按位或进行分割 “ | ”
    a_city_list = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
    for a in a_city_list:
        city_name = a.xpath('./text()')[0]
        all_city.append(city_name)
    print(all_city," 一共有:",len(all_city),"个城市")

案例五

需求:

获取大学生求职简历模板的封面和名称,并且打印下来。
网址:http://sc.chinaz.com/jianli/daxuesheng.html

思路:

首先实例化xpath对象,然后根据图片和名称的标签层级关系写出xpath表达式。解析表达式所对应的a标签,然后xpath函数返回一个列表,列表中存的就是a标签对应的图片和名字。然后我们遍历列表即可。
Python爬虫 之数据解析之xpath
当然我们发现每个模板的名字(中文信息)打印出来是乱码,这就需要我们更改编码格式。由于统用处理解决中文乱码的解决方法会报错,无法正常使用,所以我们使用手动设置响应数据编码格式。

reponse = requests.get(url=url,headers=header)
# 手动设置响应数据编码格式
reponse.encoding = 'utf-8'
page_text = reponse.text

我们如何打印所有页的数据呢?
我们观察发现,模板只有13页
而网址也很相似:

第一页:http://sc.chinaz.com/jianli/daxuesheng.html
第二页:http://sc.chinaz.com/jianli/daxuesheng_2.html
第三页:http://sc.chinaz.com/jianli/daxuesheng_3.html
第四页:http://sc.chinaz.com/jianli/daxuesheng_4.html
...
第十三页:http://sc.chinaz.com/jianli/daxuesheng_13.html

我们观察发现除了第一页外其它12页的url只有后面的数字不同,我们可以用字符串拼接的方法将url拼接出来,而那个不同的数字用for循环即可,然后将循环变量强制转换成str型的,最后拼接字符串。而第一页的特殊处理,独立输出,其他页(2到13页)的循环输出。

代码实现:

import requests
from lxml import etree

if __name__ == '__main__':
    header = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4209.2 Safari/537.36'
    }
    
    # 第一页
    # 第一页的url和其它页的有所不同
    url1 ='http://sc.chinaz.com/jianli/daxuesheng.html'
    # 爬取页面源码数据 获取相应对象
    reponse = requests.get(url=url1, headers=header)
    # 手动设置响应数据编码格式
    reponse.encoding = 'utf-8'
    page_text = reponse.text
    # 数据解析
    # 实例化对象
    tree = etree.HTML(page_text)
    src_list = tree.xpath('//div[@id="main"]/div/div')
    for a in src_list:
        img_src = a.xpath('./a/img/@src')[0]
        img_alt = a.xpath('./a/img/@alt')[0]
        # 统用处理解决中文乱码的解决方法
        # 会报错:UnicodeDecodeError: 'gbk' codec can't decode byte 0xbf in position 32: incomplete multibyte sequence
        # img_alt = img_alt.encode('iso-8859-1').decode('gbk')
        print(img_alt, ' : ', img_src)

    # 其他页(2到13页)
    url = 'http://sc.chinaz.com/jianli/daxuesheng'
    for num in range(2,13+1):
        num = str(num)
        new_url = url + '_' + num + '.html'
        # 爬取页面源码数据 获取相应对象
        reponse = requests.get(url=new_url, headers=header)
        # 手动设置响应数据编码格式
        reponse.encoding = 'utf-8'
        page_text = reponse.text
        # 数据解析
        # 实例化对象
        tree = etree.HTML(page_text)
        src_list = tree.xpath('//div[@id="main"]/div/div')
        for a in src_list:
            img_src = a.xpath('./a/img/@src')[0]
            img_alt = a.xpath('./a/img/@alt')[0]
            # 统用处理解决中文乱码的解决方法
            # 会报错:UnicodeDecodeError: 'gbk' codec can't decode byte 0xbf in position 32: incomplete multibyte sequence
            #img_alt = img_alt.encode('iso-8859-1').decode('gbk')
            print(img_alt,' : ',img_src)

六、补充知识

爬取的中文数据出现乱码

解决方法一:

手动设置响应数据编码格式。

reponse = requests.get(url=url,headers=header)
# 手动设置响应数据编码格式
reponse.encoding = 'utf-8'
page_text = reponse.text

解决方法二:

找到发生乱码对应的数据,对该数据进行:encode(‘iso-8859-1’).decode(‘gbk’) 操作。

# 统用处理解决中文乱码的解决方法
img_alt = img_alt.encode('iso-8859-1').decode('gbk')

案例三和案例五就是对这两种处理中文乱码的方法的应用。
案例三:是方法二。统用处理解决中文乱码的解决方法。
案例五:是方法一。手动设置响应数据编码格式。


按位或分割标签

当两个页面数据的层级标签类似时,我们可以使用按位或“ | ”,进行标签分割。这样可以将多个 xpath表达式解析。之间是或的关系。
这个意味着将第一个 xpath表达式或者第二个 xpath表达式,作用到 xpath函数当中。这样可以解析第一个表达式所对应的a标签定位到,也可以将第二个表达式所对应的a标签定位到。

样例:

# 用按位或进行分割 “ | ”
a_city_list = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')

注:详细内容请看案例四