爬虫 | 产品经理书单抓取

发布时间 2023-07-27 17:40:56作者: 张Zong在修行

本实验将讲解 Beautiful Soup 4 库解析 HTML 的常见用法,它的中文名字是「美丽汤」。在使用 pip 安装该库时的名字是 beautifulsoup4 ,在使用该库时包的名字是 bs4 ,要注意它们的区别。

Beautiful Soup 4 专注于解析 HTML / XML 源码并提取想要的信息,也就是我们在使用爬虫工具爬取页面源码后处理数据的工作。在学习本节实验的时候,重点关注 bs4 中的语义化编码获取标签内容以及两个方法 find_allselect 的用法,掌握以上 2 点,bs4 最核心的知识已经被你掌握。

知识点

  • Beautiful Soup 库常用属性与方法
  • Beautiful Soup 实操解析数据

Beautiful Soup 库常用属性与方法

Beautiful Soup 4 简介

Beautiful Soup 4 是 Python 的一款解析库,为了书写方便下文统一用 bs4 来描述。跟 lxml 一样,bs4 也是一个 HTML/XML 解析库,主要配合 requests 库实现爬虫程序。

bs4 库默认使用的 Python 标准库中的 HTML 解析器,即 html.parser(Python 的内置标准库),使用该方式的解析速度慢,原因是该方式在解析的时候会载入整个文档之后再解析整个 DOM 树,导致内存开销变大,性能降低,所以现在使用 bs4 的时候都是配合 lxml 库进行,本实验也围绕 bs4 与 lxml 展开。

在开始之前,你需要明确一件事情,bs4 会将 HTML 文档转换成一个复杂的树形结构,每个结点都是一个对象,这些对象可以归纳成 4 种,后文有针对这 4 种对象名称的说明。

先看一下待解析的页面,我们选择 人人都是产品经理书单页面,本实验目的是学习 bs4 的用法,故目标为抓取书籍封面图片链接与书籍名称。为避免目标网站改版,我们使用内部地址替换。

https://labfile.oss.aliyuncs.com/courses/3086/books.html

学习新库之前,需要先建立一个整体的认知,那么咱们先运行一段代码,了解一下 bs4 ,顺便说明一下 bs4 解析之后的 4 种对象。

特别说明:运行代码前需要先通过 sudo pip install beautifulsoup4 在实验楼 WebIDE 中安装该库。

将如下代码写入 /home/project/test_bs4.py 文件:

import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69'
}

def get_detail():
    r = requests.get("https://labfile.oss.aliyuncs.com/courses/3086/books.html", headers=headers)
    r.encoding = 'utf-8'
    html_doc = r.text
    soup = BeautifulSoup(html_doc, "lxml")

    title_tag = soup.title

    title_str = title_tag.string

    # Tag 标签对象
    print(type(title_tag))

    # NavigableString 对象为字符串内容
    print(type(title_str))

    # BeautifulSoup 对象为文档的全部内容
    print(type(soup))

    # Comment 对象为文档注释内容
    # print(type(comment))

if __name__ == "__main__":
    get_detail()

代码运行之后输出结果如下,该输出包含了 3 种 bs4 中的对象,最后一种由于测试网站中不包含注释代码,故没有展示,大家可以自行测试即可,补充一下 bs4 中的四种对象,TagNavigableStringBeautifulSoupComment

上文代码输出的结果就是该 4 种对象(Comment 因注释未输出)。

<class 'bs4.element.Tag'>
<class 'bs4.element.NavigableString'>
<class 'bs4.BeautifulSoup'>

本实验后续都会围绕以上 3 种对象展开,Comment 不在本实验内学习。

标签(节点)选择器(Tag)

Tag 其实对应的就是 HTML 中的一个个标签,直接调用标签的名称就可以选择到对应的标签,例如下述代码。

def get_detail():
    r = requests.get("https://labfile.oss.aliyuncs.com/courses/3086/books.html", headers=headers)
    r.encoding = 'utf-8'
    html_doc = r.text
    soup = BeautifulSoup(html_doc, "lxml")
    # 获取 title 标签
    title_tag = soup.title
    print(title_tag)
    # 获取 head 标签
    head_tag = soup.head
    print(head_tag)
    # 获取 tag 标签
    a_tag = soup.a
    print(a_tag)
    # 获取 p 标签
    p_tag = soup.p
    print(p_tag)

通过这种方式获取到的是网页文档中第一个符合的标签,在编写解析代码的时候,如果确定该标签只会出现一次,使用该方法可以快速获取到数据。

对于 Tag 对象有 2 个很重要的属性是 attrsstring,即获取标签属性与标签的文本内容。

attrs 属性获取标签属性

a_tag = soup.a
print(a_tag.attrs)

输出结果如下,返回的是字典格式数据。

{'href': '/', 'class': ['u-flex', 'u-relative']}

该数据可以与下图做比对,你将清楚了解到数据对应关系。

获取某一具体属性值

在爬虫编写过程中经常会获取超链接的 href 属性或者图片的 src 属性,所以需要用到下述知识点。

a_tag = soup.a
print(a_tag.attrs)
# 通过 attrs 再获取
print(a_tag.attrs["href"])
# 直接获取 a 标签的 class 属性
print(a_tag["class"])

获取标签文本

爬虫编写中也离不开获取网页标签中的文本内容,例如获取超链接标签 a 中的文本,在 bs4 中获取到的文本也是一种特殊的对象,称作 NavigableString ,该对象的获取非常简单,只需要调用 Tag 对象的 string 属性即可。

title_tag = soup.title
print(title_tag.string)

初识 bs4 编写爬虫,先重点掌握这两个属性值 attrsstring 可以解决大部分数据获取场景。

上文提及的知识点都是获取单个网页标签或标签内容,在爬虫编写过程中,更多需要批量获取,这时用到的是 bs4 的方法选择器。

方法选择器

通过标签选择器选择网页元素整体速度快,但是并不灵活,所以在很多时候需要借助方法选择器(也称做文档树搜索方法)对网页元素进行查找。

首先要介绍的是最常用的两个方法,find_allfind 方法。

find_all 的语法格式如下:

find_all(name, attrs, recursive, text, **kwargs)

其中各参数说明如下:

  • name,可以是标签名称,正则表达式,列表,自定义函数等内容;
  • attrs,配合前面的标签增加属性过滤,可传递多属性;
  • recursive,调用 Tag 的 find_all 方法时,bs4 会检索当前 Tag 的所有子孙节点,如果只想搜索 Tag 的直接子节点,可以使用参数 recursive = False
  • text,通过文本检索,通过 text 参数可以检索文档中的字符串内容,与 name 参数一样, text 参数接受标签名 , 正则表达式 , 列表等内容;
  • keyword,关键字参数,理解成按自定义属性检索即可。

以上是对 find_all 方法的参数说明,方法选择器包含很多内容,例如以下方法,它们的使用都与 find_all 基本一致,可以自行学习掌握。

  • find_all 与 find
  • find_parents 与 find_parent
  • find_next_siblings 与 find_next_sibling
  • find_previous_siblings 与 find_previous_sibling
  • find_all_next 与 find_next
  • find_all_previous 与 find_previous

接下来通过代码实际查看一下 find_all 方法的应用。find 方法语法格式与 find_all 一致,只是返回的是单一标签,不再赘述。

将如下代码替换 /home/project/test_bs4.py 文件中的同名函数:

def get_detail():
    r = requests.get("https://labfile.oss.aliyuncs.com/courses/3086/books.html", headers=headers)
    r.encoding = 'utf-8'
    html_doc = r.text
    soup = BeautifulSoup(html_doc, "lxml")

    print(type(soup))

    # 标签查找
    h2_tags = soup.find_all('h2')
    # print(h2_tags)
    print(type(h2_tags))

    # 属性加标签过滤
    div_tags = soup.find_all('div', attrs={'class': 'jl-book-item'})
    # print(div_tags)
    print(type(div_tags))

    # 标签列表
    tag_list = soup.find_all(["h2", "h3"])
    # print(tag_list)
    print(type(tag_list))

代码运行之后,会发现返回值的数据类型依旧为 Tag 类型的聚合:

<class 'bs4.BeautifulSoup'>
<class 'bs4.element.ResultSet'>
<class 'bs4.element.ResultSet'>
<class 'bs4.element.ResultSet'>

使用 findfind_all 等方法对网页进行解析是 bs4 中常用的知识,该内容需要牢牢掌握。

CSS 选择器

该选择器与 find_all 方法非常相似,通过一个 select 方法进行网页标签选择,学习该内容依旧需要对前端基础中的 HTML+CSS 有所了解。

通过标签名进行查找

该方式起始就是在使用 CSS 中的选择器,代码如下:

title_tag = soup.select("title")
print(title_tag)

需要特别注意的是 select 方法返回的数据类型是列表。接下来的知识内容将配合代码学习,在时间允许的情况下,尽量临摹几遍代码,做到牢牢掌握该内容,知识点不在进行文字特别说明,具体查找方式如下。

通过标签 class 属性(类名)进行查找

div_tag = soup.select(".jl-book-item")
print(div_tag)

通过 id 查找

div_tag = soup.select("#app")
print(div_tag)

通过任意属性查找

div_tags = soup.select("div[class='jl-book-item']")
print(div_tags)

直接子标签查找

# 注意 a 标签前的 >
a_tags = soup.select("div[class='jl-book-item'] > a")
print(a_tags)

后代标签查找(很多地方叫做组合查找)

# 注意 a 标签前的 空格
a_tags = soup.select("div[class='jl-book-item'] a")
print(a_tags)

获取内容

select 方法获取到网页标签都是列表对象,通过遍历形式输出,然后用 get_text 方法来获取标签的内容。

h3_tags = soup.select("div[class='jl-book-item'] h3")
for h3 in h3_tags:
    print(h3.get_text())

Beautiful Soup 实操解析数据

本实验进行到这里已经对 bs4 库有了初步的认知,这些在爬虫初期编写阶段已经足够使用,接下来完成本实验对应的案例。

解析目标区域 div 标签

使用 bs4 需要先捕获目标数据所在的 div 标签,再对该标签进行拆解,目标区域 HTML 代码如下:

通过 bs4 的 select 方法进行解析,代码与运行结果如下(只展示 get_detail 函数部分代码,其余部分参照上文即可):

将如下代码替换 /home/project/test_bs4.py 文件中的同名函数:

def get_detail():
    r = requests.get("https://labfile.oss.aliyuncs.com/courses/3086/books.html", headers=headers)
    r.encoding = 'utf-8'
    html_doc = r.text
    soup = BeautifulSoup(html_doc, "lxml")

    div_tags = soup.select("div[class='jl-book-item']")
    print(div_tags)

代码运行之后获取到页面所有包含书籍的 DIV 标签。

解析 div 标签内部的超链接与标题

寻找到目标数据,在之后通过循环即可获取到 div 区域内的子标签,我们通过上文讲解到的三种不同方式获取网页标签。

div_tags = soup.select("div[class='jl-book-item']")
for div in div_tags:
    # 获取购买链接
    bug_link = div.a.attrs["href"]
    # 获取书籍封面
    img = div.find("img").attrs["src"]
    # 获取书籍名称
    name = div.select("h3")[0].get_text()

数据存储部分依旧留个你来独立完成。

实验总结

本实验重点部分由两个方法构成,通过 2 个核心的点去掌握一款完整的库,是编程学习过程中经常使用的技巧。在爬虫后面的学习之路上,你将接触到大量的第三方库,有请求类,有解析类,有存储类,如何快速的去掌握一款新库,就可以应用本实验的学习方式,从核心点扩展到整个面。