3. 数据解析

数据解析

版权声明:本博客来自路飞学城Python全栈开发培训课件,仅用于学习之用,严禁用于商业用途。
欢迎访问路飞学城官网:https://www.luffycity.com/

本节重点

  • 正则表达式解析
  • bs4解析
  • xpath解析

1. 引子

回顾requests模块实现数据爬取的流程:

  • 指定url
  • 发起请求
  • 获取响应数据
  • 持久化存储

其实,在上述流程中还需要较为重要的一步,就是在持久化存储之前需要进行指定数据解析。因为大多数情况下的需求,我们都会指定去使用聚焦爬虫,也就是爬取页面中指定部分的数据值,而不是整个页面的数据。因此,本次课程中会给大家详细介绍讲解三种聚焦爬虫中的数据解析方式。至此,我们的数据爬取的流程可以修改为:

  • 指定url
  • 发起请求
  • 获取响应数据
  • 数据解析
  • 持久化存储

聚焦爬虫:爬取页面中指定的页面内容。

数据解析流程

解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储,解析流程为:

  • 进行指定标签的定位;
  • 标签或者标签对应的属性中存储的数据值进行提取(解析)。

python实现数据解析的方法有正则表达式解析、bs4解析、xpath解析等。

2. 正则表达式

2.1 正则语法

在这里插入图片描述

2.2 正则实战

需求

爬取糗事百科热图https://www.qiushibaike.com/imgrank/

解析步骤:

  • 在开发者模式下的 Network 监听组件中查看源代码,如图所示:

    在这里插入图片描述

    注意,这里不要在 Elements 选项卡中直接查看源码,因为那里的源码可能经过 JavaScript 操作而与原始请求不同,而是需要从 Network 选项卡部分查看原始请求得到的源码。

  • 分析源码数据

    查看其中一个条目的源代码,如图所示:

    在这里插入图片描述

  • 正则表达式编写

可以看到,一个图片对应一个img标签,img标签在一个class为thumb的div内,提取src的正则表达式可以写成如下格式:

ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>'
  • 提取所需数据

    下一步即可通过调用 findall() 方法提取出所有图片地址,然后再通过requests发起get请求获取二进制数据,保存即可。

实例:

import requests
import re
import os

# 需求:爬取糗事百科中糗图板块下所有的糗图图片
if __name__ == "__main__":
    # 创建一个文件夹,保存所有的图片
    if not os.path.exists('./qiutuLibs'):
        os.mkdir('./qiutuLibs')

    url = 'https://www.qiushibaike.com/imgrank/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'

    }
    # 使用通用爬虫对url对应的一整张页面进行爬取
    page_text = requests.get(url=url, headers=headers).text

    # 使用聚焦爬虫将页面中所有的糗图进行解析/提取
    ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>'
    img_src_list = re.findall(ex, page_text, re.S)
    # print(img_src_list)
    for src in img_src_list:
        # 拼接出一个完整的图片url
        src = 'https:' + src
        # 请求到了图片的二进制数据
        img_data = requests.get(url=src, headers=headers).content
        # 生成图片名称
        img_name = src.split('/')[-1]
        # 图片存储的路径
        imgPath = './qiutuLibs/' + img_name
        with open(imgPath, 'wb') as fp:
            fp.write(img_data)
        print(img_name, '下载成功!!!')

正则表达式解析的思考

从以上案例可以看到,使用正则表达式解析数据比较烦琐,万一有地方写错了,可能导致匹配失败,所以使用正则表达式提取页面信息多多少少还是有些不方便。

对于网页的节点来说,它可以定义 idclass 或其他属性。而且节点之间还有层次关系,在网页中可以通过 XPath 或 CSS 选择器来定位一个或多个节点。那么,在页面解析时,利用 XPath 或 CSS 选择器来提取某个节点,然后再调用相应方法获取它的正文内容或者属性,不就可以提取我们想要的任意信息了吗?

在 Python 中,怎样实现这个操作呢?不用担心,这种解析库已经非常多,其中比较强大的库有 lxml、Beautiful Soup、pyquery 等,有了它们,我们就不用再为正则表达式发愁,而且解析效率也会大大提高。

3 bs4解析

这一节中,我们就来介绍一个强大的解析工具 Beautiful Soup,它借助网页的结构和属性等特性来解析网页。有了它,我们不用再去写一些复杂的正则表达式,只需要简单的几条语句,就可以完成网页中某个元素的提取。

废话不多说,接下来就来感受一下 Beautiful Soup 的强大之处吧。

3.1 简介

简单来说,Beautiful Soup 就是 Python 的一个 HTML 或 XML 的解析库,可以用它来方便地从网页中提取数据。官方解释如下:

Beautiful Soup 提供一些简单的、Python 式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。

Beautiful Soup 已成为和 lxml、html6lib 一样出色的 Python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。

所以说,利用它可以省去很多烦琐的提取工作,提高了解析效率。

3.2 环境安装

pip install bs4
pip install lxml

3.3 解析器

Beautiful Soup 在解析时实际上依赖解析器,它除了支持 Python 标准库中的 HTML 解析器外,还支持一些第三方解析器(比如 lxml)。lxml 解析器有解析 HTML 和 XML 的功能,而且速度快,容错能力强,所以推荐使用它。

如果使用 lxml,那么在初始化 Beautiful Soup 时,可以把第二个参数改为 lxml 即可:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)

在后面,Beautiful Soup 的用法实例也统一用这个解析器来演示。

3.4 解析原理

数据解析原理:

  • 数据解析的原理:

    • 标签定位
    • 提取标签、标签属性中存储的数据值
  • bs4数据解析的原理:

    • 实例化一个 Beautifulsoup对象,并且将页面源码数据加载到该对象中
    • 通过调用 Beautifulsoup对象中相关的属性或者方法进行标签定位和数据提取

3.5 基本语法

基本语法:

(1)根据标签名查找
soup.a 只能找到第一个符合要求的标签
(2)获取属性
soup.a.attrs 获取a所有的属性和属性值,返回一个字典
soup.a.attrs[‘href’] 获取href属性
soup.a[‘href’] 也可简写为这种形式
(3)获取内容
soup.a.string 只可以获取该标签下面直系的文本内容
soup.a.text
soup.a.get_text()
【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容
(4)find:找到第一个符合要求的标签
soup.find(‘a’) 找到第一个符合要求的
soup.find(‘a’, title=“xxx”)
soup.find(‘a’, alt=“xxx”)
soup.find(‘a’, class_=“xxx”)
soup.find(‘a’, id=“xxx”)
(5)find_all:找到所有符合要求的标签
soup.find_all(‘a’)
soup.find_all([‘a’,‘b’]) 找到所有的a和b标签
soup.find_all(‘a’, limit=2) 限制前两个
(6)根据选择器选择指定的内容
select:soup.select(’#feng’)
常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器
层级选择器:
soup.select(’.tang > ul > li > a’):>表示的是一个层级
soup.select(’.tang > ul a’):空格表示的多个层级
【注意】select选择器返回永远是列表,需要通过下标提取指定的对象

Beautifulsoup对象的实例化

  • 将本地的htm文档中的数据加载到该对象中

    f = open(' /test.html','r', encoding="utf-8')
    soup =Beautifulsoup(f,'lxml')
    
  • 将互联网上获取的页面源码加载到该对象中

    page_text = response.text
    soup = Beatifulsoup(page_text,'lxml')
    

3.6 bs4实战

需求: 爬取三国演义小说所有的章节标题和章节内容http://www.shicimingju.com/book/sanguoyanyi.html

实例:

import requests
from bs4 import BeautifulSoup

if __name__ == '__main__':
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
    }
    url = 'https://www.shicimingju.com/book/sanguoyanyi.html'
    response = requests.get(url=url, headers=headers)
    # 处理编码格式不一致导致的乱码
    response.encoding = 'utf-8'
    page_text = response.text
    soup = BeautifulSoup(page_text, 'lxml')
    li_list = soup.select('.book-mulu > ul > li')
    fp = open('./sanguo.txt', 'w', encoding='utf-8')
    for li in li_list:
        title = li.a.text
        detail_href = "{}{}".format("https://www.shicimingju.com", li.a['href'])
        detail_response = requests.get(detail_href, headers=headers)
        # 处理编码格式不一致导致的乱码
        detail_response.encoding = 'utf-8'
        detail_page_text = detail_response.text
        detail_page_text.encode('utf-8').decode('latin-1')
        detail_soup = BeautifulSoup(detail_page_text, 'lxml')
        detail_tag = detail_soup.find('div', class_='chapter_content')
        content = detail_tag.text

        fp.write("{}{}{}".format(title, content, '\n'))
        print('{}爬取完成!'.format(title))
    fp.close()
    print('全部爬取完成!!!')

补充知识点: 爬取中文乱码的处理方法

经历过学或者写爬虫的小伙伴可能都会有这样的问题,爬取的网页源码或文本内容直接乱码。

一般处理有两种:

  • 对响应体设置编码格式,如:response.encoding = ‘utf-8’
  • 对响应的内容编码再解码,如:detail_page_text.encode(‘utf-8’).decode(‘latin-1’)

具体设置成哪种编码格式或者编码,需要根据抓包工具来分析,根据原网页的情况设置。

大部分网页都是utf-8编码的没错,但是也有一部分网页是gbk编码的,所以只需把utf-8改成gbk即可。其他少部分的也有另外的编码格式。

除了一个个试,还有什么办法呢?

看下图:在网页中查看源代码。

在这里插入图片描述

在源代码的开头部分都会有个charset,图中是倒数第二行。

只要去查看一下源码是什么格式编写的,把utf-8改成相应编码就可以啦。

4. xpath解析

XPath,全称 XML Path Language,即 XML 路径语言,它是一门在 XML 文档中查找信息的语言。它最初是用来搜寻 XML 文档的,但是它同样适用于 HTML 文档的搜索。

4.1 简介

XPath 的选择功能十分强大,它提供了非常简洁明了的路径选择表达式。另外,它还提供了超过 100 个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。几乎所有我们想要定位的节点,都可以用 XPath 来选择。

XPath 于 1999 年 11 月 16 日成为 W3C 标准,它被设计为供 XSLT、XPointer 以及其他 XML 解析软件使用,更多的文档可以访问其官方网站:https://www.w3.org/TR/xpath/。

xpath解析是我们在爬虫中最常用也是最通用的一种数据解析方式,由于其高效且简介的解析方式受到了广大程序员的喜爱。在后期学习scrapy框架期间,也会再次使用到xpath解析。

4.2 环境安装

pip install lxml

4.3 解析原理

  • 使用通用爬虫爬取网页数据
  • 实例化etree对象,且将页面数据加载到该对象中
  • 使用xpath函数结合xpath表达式进行标签定位和指定数据提取

4.5 基本语法

属性定位:
找到class属性值为song的div标签
//div[@class=“song”]
层级&索引定位:
#找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a
//div[@class=“tang”]/ul/li[2]/a
逻辑运算:
#找到href属性值为空且class属性值为du的a标签
//a[@href="" and @class=“du”]
模糊匹配:
//div[contains(@class, “ng”)]
//div[starts-with(@class, “ta”)]
取文本:

​ /表示获取某个标签下的文本内容

​ //表示获取某个标签下的文本内容和所有子标签下的文本内容

​ //div[@class=“song”]/p[1]/text()
​ //div[@class=“tang”]//text()
取属性:
​ //div[@class=“tang”]//li[2]/a/@href

etree对象实例化:

  • 本地文件实例化

    tree = etree.parse(文件名)
    tree.xpath("xpath表达式")
    
  • 网络数据实例化

    tree = etree.HTML(网页内容字符串)
    tree.xpath("xpath表达式")
    

4.6 xpath实战

4.6.1 xpath爬取58二手房信息

需求: 爬取58二手房中的房源信息(如果爬取数据为空,则可能IP被禁,可以手动访问网址进行验证后再测试)

实例:

import requests
from lxml import etree

if __name__ == '__main__':
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    }
    url = 'https://bj.58.com/ershoufang/'
    page_text = requests.get(url=url, headers=headers).text
    tree = etree.HTML(page_text)
    tag_list = tree.xpath('//section[@class="list"]/div[@class="property"]')
    fp = open('58.txt', 'w', encoding='utf-8')
    for tag in tag_list:
        title = tag.xpath('./a/div[2]/div/div/h3/text()')[0]
        fp.write(title+'\n')
    fp.close()
    print('爬取结束!!!')

xpath编写技巧:

在这里插入图片描述

4.6.2 xpath爬取图片

需求: 解析下载图片数据 http://pic.netbian.com/4kmeinv/

实例:

import requests
from lxml import etree
import os

if __name__ == "__main__":
    url = 'http://pic.netbian.com/4kmeinv/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    }
    response = requests.get(url=url, headers=headers)
    # 手动设定响应数据的编码格式
    # response.encoding = 'utf-8'
    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:
        img_src = 'http://pic.netbian.com' + li.xpath('./a/img/@src')[0]
        img_name = li.xpath('./a/img/@alt')[0] + '.jpg'
        # 通用处理中文乱码的解决方案
        img_name = img_name.encode('iso-8859-1').decode('gbk')

        # print(img_name,img_src)
        # 请求图片进行持久化存储
        img_data = requests.get(url=img_src, headers=headers).content
        img_path = 'picLibs/' + img_name
        with open(img_path, 'wb') as fp:
            fp.write(img_data)
            print(img_name, '下载成功!!!')
4.6.3 xpath爬取城市名称

需求: 解析出所有城市名称https://www.aqistudy.cn/historydata/

实例:

import requests
from lxml import etree


if __name__ == "__main__":
    # headers = {
    #     'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    # }
    # url = 'https://www.aqistudy.cn/historydata/'
    # page_text = requests.get(url=url,headers=headers).text
    #
    # tree = etree.HTML(page_text)
    # host_li_list = tree.xpath('//div[@class="bottom"]/ul/li')
    # all_city_names = []
    # #解析到了热门城市的城市名称
    # for li in host_li_list:
    #     hot_city_name = li.xpath('./a/text()')[0]
    #     all_city_names.append(hot_city_name)
    #
    # #解析的是全部城市的名称
    # city_names_list = tree.xpath('//div[@class="bottom"]/ul/div[2]/li')
    # for li in city_names_list:
    #     city_name = li.xpath('./a/text()')[0]
    #     all_city_names.append(city_name)
    #
    # print(all_city_names,len(all_city_names))

    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    }
    url = 'https://www.aqistudy.cn/historydata/'
    page_text = requests.get(url=url, headers=headers).text

    tree = etree.HTML(page_text)
    # 解析到热门城市和所有城市对应的a标签
    # //div[@class="bottom"]/ul/li/          热门城市a标签的层级关系
    # //div[@class="bottom"]/ul/div[2]/li/a  全部城市a标签的层级关系
    a_list = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
    all_city_names = []
    for a in a_list:
        city_name = a.xpath('./text()')[0]
        all_city_names.append(city_name)
    print(all_city_names, len(all_city_names))

小技巧:

xpath中可以利用或来同时解析多个标签

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值