【3.0】爬虫模块之BeautifulSoup

【BS4模块】

【一】简介

【1】介绍与安装

  • 简单来说,Beautiful Soup是python的一个库

  • 最主要的功能是从网页抓取数据。

  • 官方解释如下:

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

  • 它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式

  • Beautiful Soup会帮你节省数小时甚至数天的工作时间

  • 你可能在寻找 Beautiful Soup3 的文档,Beautiful Soup 3 目前已经停止开发,官网推荐在现在的项目中使用Beautiful Soup 4。

官方文档: https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/

(1)安装

pip install bs4 

(2)引入

from bs4 import BeautifulSoup

【2】HTML解析器

  • 内置的HTML解析器
soup=BeautifulSoup(res.text,'html.parser') 
  • Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器

  • 如果我们不安装它,则 Python 会使用 Python默认的解析器

  • lxml 解析器更加强大,速度更快,推荐安装。

    pip3 install lxml
    
  • 另一个可供选择的解析器是纯Python实现的 html5lib

  • html5lib的解析方式与浏览器相同

  • 可以选择下列方法来安装html5lib:

    pip3 install html5lib
    

【3】解析器对比

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, "html.parser") Python的内置标准库 执行速度适中 文档容错能力强 Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, "lxml") 速度快 文档容错能力强 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml") 速度快 唯一支持XML的解析器 需要安装C语言库
html5lib BeautifulSoup(markup, "html5lib") 最好的容错性 以浏览器的方式解析文档 生成HTML5格式的文档 速度慢 不依赖外部扩展

【4】简单使用:

  • 从一个soup对象开始,以下两种方式生成一个soup对象

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(open("index.html"))    ##传入文件
    soup = BeautifulSoup("<html>data</html>")   ##文本
    

    构造soup对象时,可以传入解析器参数,如果不传入的话,会以最好的方式去解析

  • 下面的一段HTML代码将作为例子被多次用到.这是 爱丽丝梦游仙境的 的一段内容(以后内容中简称为 爱丽丝 的文档):

    html_doc = """
    <html><head><title>The Dormouse's story</title></head>
    <body>
    <p class="title"><b>The Dormouse's story</b></p>
    
    <p class="story">Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
    and they lived at the bottom of a well.</p>
    
    <p class="story">...</p>
    """
    
  • 使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html_doc, 'html.parser')
    
  • 从文档中找到所有标签的链接:

    for link in soup.find_all('a'):
        print(link.get('href'))
    
  • 从文档中获取所有文字内容:

    print(soup.get_text())
    

【二】四种对象

  • Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,并将每个节点都表示为Python对象。

  • 在Beautiful Soup中,有四种主要的对象类型:

    • BeautifulSoup

    • Tag

    • NavigableString

    • Comment。

【1】BeautifulSoup对象

代表整个解析后的HTML文档,是最顶层的对象。它包含了整个文档的全部内容,并提供了操作HTML文档的方法和属性。

【2】tag对象

  • 表示HTML中的标签,如<p>、<a>等。
  • Tag对象包含了标签的名称和对应的属性,并可以通过Tag对象来获取标签内的内容或进行进一步的操作。
  • 可以通过传递HTML文档给BeautifulSoup类初始化的方式创建Tag对象。
from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

(1)查找tag对象

soup = BeautifulSoup(html_doc, 'html.parser')
print(soup.head, type(soup.head))  # 输出head标签及其类型
print(soup.title, type(soup.title))  # 输出title标签及其类型
print(soup.a, type(soup.a))  # 输出第一个a标签及其类型
print(soup.p.b)  # 输出第一个p标签下的第一个b标签

(2)查找tag对象的标签名和属性

print(soup.a.name)  # a
print(soup.p.b.name)  # b
print(soup.a["href"])
print(soup.a.attrs)

(3)返回类型

  • HTML 4定义了一系列可以包含多个值的属性.

  • 在HTML5中移除了一些,却增加更多.

  • 最常见的多值的属性是 class (一个tag可以有多个CSS的class).

  • 还有一些属性 rel , rev , accept-charset , headers , accesskey .

  • 在Beautiful Soup中多值属性的返回类型是list

print(soup.a["class"])  # 返回列表

(4)修改tag的属性

  • tag的属性可以被添加,删除或修改(tag的属性操作方法与字典一样)
soup.a["class"] = ["sister c1"]
del soup.a["id"]
print(soup)

(5)获取标签对象的文本内容

# p下的文本只有一个时,取到,否则为None
print(soup.p.string)  
# 拿到一个生成器对象, 取到p下所有的文本内容
print(soup.p.strings)  
for i in soup.p.strings:
    print(i)
  • 如果tag包含了多个子节点,tag就无法确定
    • .string 方法应该调用哪个子节点的内容,
    • .string 的输出结果是 None
  • 如果只有一个子节点那么就输出该子节点的文本,
    • 比如下面的这种结构
    • soup.p.string 返回为None
    • 但soup.p.strings就可以找到所有文本
p2 = soup.find_all("p")[1]
print(p2.string)
print(p2.strings)
for i in p2.strings:
    print(i)

【2】NavigableString对象

  • 表示标签内的文本内容,或者说是非标签字符串。
  • 当tag只包含单一的字符串时,可以使用tag.stringtag.text来获取该字符串。
# 获取p标签的文本内容
print(soup.p.string)  

# 获取p标签下所有的文本内容
print(soup.p.strings)
for i in soup.p.strings:
    print(i)

【3】Comment对象

  • 代表HTML文档中的注释内容。
  • 当解析器遇到HTML文档中的注释时,Beautiful Soup会将注释封装成Comment对象。
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup,"html.parser")
comment = soup.b.string
print(comment)  # 输出注释内容
print(type(comment))  # 输出Comment对象的类型
  • 结果为:

    Hey, buddy. Want to buy a used parser?
    <class 'bs4.element.Comment'>
    
  • 我们可以看到这时候

    • .string返回的对象不再是bs4.element.NavigableString
    • 而是Comment

【三】遍历文档树(导航文档树)

  • 遍历文档树,也被称为导航文档树,是指在一个文档对象模型(DOM)中按照特定的方法和规则来遍历和浏览其中的节点。

  • DOM是一种处理XML或HTML文档的标准编程接口,它将文档解析成由节点和对象组成的树状结构。

  • 在遍历文档树的过程中,可以通过访问当前节点及其相关属性、子节点、父节点、兄弟节点等信息,来对文档进行操作和分析。

  • 下面是一种常见的文档树遍历算法:

    • 选择起始节点:

      • 首先需要确定遍历的起始节点,可以是整个文档的根节点,也可以是某个指定的节点。
    • 访问当前节点:

      • 从起始节点开始,首先访问当前节点,可以获取当前节点的标签名、属性、文本内容等信息。
    • 处理当前节点:

      • 根据需要,对当前节点进行一些处理操作,比如判断节点类型、执行特定的任务等。
    • 遍历子节点:

      • 如果当前节点有子节点,将从第一个子节点开始递归遍历,重复步骤2和步骤3。
    • 遍历兄弟节点:

      • 如果当前节点没有子节点或者子节点已经遍历完毕,将继续遍历当前节点的下一个兄弟节点,重复步骤2和步骤3。
    • 返回父节点:

      • 当遍历到某个节点的兄弟节点都被遍历完毕后,返回到该节点的父节点,并继续遍历父节点的下一个兄弟节点。
    • 结束条件:

      • 当整个文档树的节点都被遍历完毕,或者满足某个结束条件时,结束遍历过程。
from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

soup = BeautifulSoup(html_doc, 'html.parser')

【1】用法

首先导入BeautifulSoup库,并将HTML文档传入BeautifulSoup对象的构造函数中,指定解析器(这里使用lxml)。

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')

【2】获取标签的名称

使用tag.name属性可以获取当前标签的名称。例如,访问第一个<p>标签的名称:

print(soup.p.name)

【3】获取标签的属性

使用tag.attrs属性可以获取当前标签的属性字典。例如,访问第一个<p>标签的属性字典:

print(soup.p.attrs)

【4】获取标签的内容

  • 使用tag.string属性可以获取当前标签内的文本内容。如果标签内只有一个字符串,可以直接使用该属性获取内容。

    • 例如,访问第一个<p>标签内的内容:
    print(soup.p.string)
    

    若有多个字符串,则返回None。

  • 使用tag.strings方法可以获取当前标签内所有子节点的文本内容,返回一个生成器对象。

    • 例如,访问第一个<p>标签内所有文本内容:
    print(list(soup.p.strings))
    
  • 使用tag.text属性可以获取当前标签内所有子节点的文本内容,并将其连接在一起。

    • 例如,访问第一个<p>标签内所有文本内容:
    print(soup.p.text)
    
  • 使用tag.stripped_strings方法可以获取当前标签内所有子节点的文本内容,并去掉多余的空白字符。

    • 该方法返回一个生成器对象。
    • 例如,遍历输出所有标签内的文本内容:
    for line in soup.stripped_strings:
        print(line)
    

【5】嵌套选择

  • 嵌套选择可以通过访问父子节点的方式来获取特定标签的文本内容。
  • 在给定的示例中,我们使用text属性来访问特定标签的文本内容。
print(soup.head.title.text)  # 输出:The Dormouse's story
print(soup.body.a.text)  # 输出:Elsie

【6】子节点、子孙节点

  • 在BeautifulSoup中,可以通过.contents.children属性来获取标签的子节点。
  • .contents属性返回一个包含所有子节点的列表,而.children属性返回一个迭代器,可以逐个访问子节点。
# p下所有子节点
print(soup.p.contents)

# 得到一个迭代器,包含p下所有子节点
print(soup.p.children)

# 使用enumerate对迭代器进行遍历
for i, child in enumerate(soup.p.children, 1):
    print(i, child)

# 获取子孙节点,p下所有的标签都会被选择出来
print(soup.p.descendants)

# 使用enumerate对子孙节点进行遍历
for i, child in enumerate(soup.p.descendants, 1):
    print(i, child)

# 针对第二个p标签的子孙节点进行遍历
for i, child in enumerate(soup.find_all("p")[1].descendants, 1):
    print(i, child)

【7】父节点、祖先节点

  • 使用.parent属性可以获取标签的父节点
  • .parents属性则可以获取标签的所有祖先节点,从父亲的父亲开始一直到最顶层的祖先节点。
# 获取a标签的父节点
print(soup.a.parent)

# 获取a标签的父节点的文本内容
print(soup.a.parent.text)

# 找到a标签所有的祖先节点,父亲的父亲,父亲的父亲的父亲...
print(soup.a.parents)

【8】兄弟节点

  • BeautifulSoup提供了相关方法来获取标签的兄弟节点。
  • .next_sibling属性返回下一个兄弟节点,
  • .previous_sibling属性返回上一个兄弟节点。
  • 此外,.next_siblings属性返回一个生成器对象,可以逐个访问后面的兄弟节点。
print(soup.a.next_sibling)  # 输出:<class 'bs4.element.NavigableString'>

print(soup.a.next_sibling.next_sibling) #下一个兄弟

print(soup.a.previous_sibling.previous_sibling) #上一个兄弟

print(list(soup.a.next_siblings)) #下面的兄弟们=>生成器对象

print(soup.a.previous_siblings)  
# 输出:生成器对象,包含上面的兄弟节点
# 上面的兄弟们=>生成器对象

【四】搜索文档树

  • recursive 是否从当前位置递归往下查询,如果不递归,只会查询当前soup文档的子元素

  • string 这里是通过tag的内容来搜索,并且返回的是类容,而不是tag类型的元素

  • **kwargs 自动拆包接受属性值,所以才会有soup.find_all('a',id='title') ,id='title'为**kwargs自动拆包掺入

  • BeautifulSoup定义了很多搜索方法,这里着重介绍2个: find() 和 find_all() .其它方法的参数和用法类似

【1】find_all

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

(1)name参数

soup = BeautifulSoup(html_doc, 'lxml')
  • name 五种过滤器: 字符串、正则表达式、列表、方法、True

[1]字符串

  • 传入标签名
  • 例如 soup.find_all(name='a') 将返回所有的 <a> 标签。
print(soup.find_all(name='a'))

[2]正则表达式

  • 可以使用正则表达式来匹配标签名。
  • 例如 soup.find_all(name=re.compile('^b')) 将返回以 'b' 开头的标签,包括 <body><b> 标签。
print(soup.find_all(name=re.compile('^b')))  # 找出b开头的标签,结果有body和b标签

[3]列表

  • 如果传入一个列表参数,Beautiful Soup会返回与列表中任何元素匹配的内容。
  • 例如 soup.find_all(name=['a', 'b']) 将返回文档中所有的 <a> 标签和 <b> 标签。
print(soup.find_all(name=['a', 'b']))

[4]方法

  • 如果没有合适的过滤器,可以定义一个方法来匹配元素。
  • 这个方法只接受一个元素参数,如果方法返回 True 表示当前元素匹配并被找到,否则返回 False
def has_class_but_no_id(tag):
    return tag.has_attr('class') and tag.has_attr('id')

print(soup.find_all(name=has_class_but_no_id))

[5]True

  • 通过find_all(True)可以匹配所有的tag,不会返回字符串节点。
  • 在代码中,会使用循环打印出每个匹配到的tag的名称(tag.name)。
def has_class_but_no_id(tag):
    return tag.has_attr('class') and tag.has_attr('id')
print(soup.find_all(True)) 
for tag in soup.find_all(True):    
    print(tag.name)
print(soup.find_all(has_class_but_no_id))

(2)keyword 参数

print(soup.find_all(href="http://example.com/tillie"))
print(soup.find_all(href=re.compile("^http://")))
print(soup.find_all(id=True)) # 拥有id属性的tag
print(soup.find_all(href=re.compile("http://"), id='link1') # 多个属性
print(soup.find_all("a", class_="sister")) # 注意,class是Python的关键字,所以class属性用class_
print(soup.find_all("a",attrs={"href": re.compile("^http://"), "id": re.compile("^link[12]")}))      
# 通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag:
data_soup.find_all(attrs={"data-foo": "value"})    

(3)text参数

  • text 参数用于根据内容搜索标签。可以接受字符串、列表或正则表达式。

    • 字符串:

      • 返回包含指定内容的标签。
      • 例如 soup.find_all(text="Elsie") 返回所有包含文本 "Elsie" 的标签。
    • 列表:

      • 返回包含列表中任一元素内容的标签。
      • 例如 soup.find_all(text=["Tillie", "Elsie", "Lacie"]) 返回所有包含文本 "Tillie"、"Elsie" 或 "Lacie" 的标签。
    • 正则表达式:

      • 使用正则表达式来匹配内容。
      • 例如 soup.find_all(text=re.compile("Dormouse")) 返回所有包含文本中包含 "Dormouse" 的标签。
import re

print(soup.find_all(text="Elsie"))
# ['Elsie']

print(soup.find_all(text=["Tillie", "Elsie", "Lacie"]))
# ['Elsie', 'Lacie', 'Tillie']

# 只要包含Dormouse就可以
print(soup.find_all(text=re.compile("Dormouse")))
# ["The Dormouse's story", "The Dormouse's story"]

(4)limit参数

  • find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.

  • 如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量

  • 效果与SQL中的limit关键字类似

  • 当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果.

  • 例如 soup.find_all("a", limit=2) 返回前两个 <a> 标签。

print(soup.find_all("a",limit=2))

(5)recursive 参数

  • recursive 参数用于控制是否递归往下查询。
  • 默认情况下,Beautiful Soup会检索当前tag的所有子孙节点。
  • 如果想要仅搜索tag的直接子节点,可以设置 recursive=False
  • 例如 soup.find_all("div", recursive=False) 只会查找当前soup文档的直接子元素中的 <div> 标签。
print(soup.find_all("div", recursive=False))

【2】find

  • find() 方法用于在文档中查找符合条件的tag,并返回第一个匹配的结果。
  • 它可以通过指定name、attrs、recursive和string等参数来过滤查找结果。
find( name , attrs , recursive , string , **kwargs )
  • name: 指定要查找的tag名称,可以是字符串或正则表达式。
  • attrs: 指定tag的属性,可以是字典或字典的列表。
  • recursive: 指定是否递归查找子孙tag,默认为True。
  • string: 指定查找的文本内容,可以是字符串或正则表达式。
  • find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果
  • 比如文档中只有一个标签,那么使用 find_all() 方法来查找标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法
  • 下面两行代码是等价的:
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]

soup.find('title')
# <title>The Dormouse's story</title>
  • 唯一的区别是
    • find_all() 方法的返回结果是值包含一个元素的列表
      • find() 方法直接返回结果
    • find_all() 方法没有找到目标是返回空列表
      • find() 方法找不到目标时,返回 None
  • soup.head.title 是 tag的名字 方法的简写
    • 这个简写的原理就是多次调用当前tag的 find() 方法:
soup.head.title
# <title>The Dormouse's story</title>

soup.find("head").find("title")
# <title>The Dormouse's story</title>

【3】其它方法

(1) find_parents() 和 find_parent()

  • find_parents() 和 find_parent() 方法用于查找当前tag的父级tag。

    • find_parents():

      • 返回所有符合条件的父级tag,结果是一个生成器。
      • 可以传入参数来进一步筛选父级tag。
    • find_parent():

      • 返回第一个符合条件的父级tag。
a_string = soup.find(text="Lacie")
print(a_string)  # Lacie

# 查找当前tag的所有父级tag
print(a_string.find_parent())
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>


print(a_string.find_parents())

# 查找第一个符合条件的父级tag
print(a_string.find_parent("p"))
'''
<p class="story">
    Once upon a time there were three little sisters; and their names were
    <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
    <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
    <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
    and they lived at the bottom of a well.
</p>

'''

(2)find_next_siblings() 和 find_next_sibling()

  • find_next_siblings() 和 find_next_sibling() 方法用于查找当前tag后面的兄弟tag。
    • find_next_siblings():
      • 返回所有符合条件的后续兄弟tag,结果是一个列表。
      • 可以传入参数来进一步筛选兄弟tag。
    • find_next_sibling():
      • 返回第一个符合条件的后续兄弟tag。
first_link = soup.a

# 查找当前tag后面的第一个符合条件的兄弟tag
print(first_link.find_next_sibling("a"))
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>

# 查找当前tag后面的所有符合条件的兄弟tag
print(first_link.find_next_siblings("a"))
'''
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
]
'''
  • find_previous_siblings() 和 find_previous_sibling()的使用类似于find_next_sibling和find_next_siblings。

(3)find_all_next() 和 find_next()

  • find_all_next() 和 find_next() 方法用于在当前tag之后查找符合条件的tag和字符串。
    • find_all_next():
      • 返回所有符合条件的后续tag和文本内容,结果是一个生成器。
      • 可以传入参数来进一步筛选结果。
    • find_next():
      • 返回第一个符合条件的后续tag或文本内容。
first_link = soup.a

# 查找当前tag之后所有的文本内容
print(first_link.find_all_next(string=True))
# ['Elsie', ',\n', 'Lacie', ' and\n', 'Tillie', ';\nand they lived at the bottom of a well.', '\n', '...', '\n']

# 查找当前tag之后第一个符合条件的文本内容
print(first_link.find_next(string=True)) # Elsie
  • find_all_previous() 和 find_previous()的使用类似于find_all_next() 和 find_next()。

【五】Css选择器

【1】select

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title">
    <b>The Dormouse's story</b>
    Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">
        <span>Elsie</span>
    </a>
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
    <div class='panel-1'>
        <ul class='list' id='list-1'>
            <li class='element'>Foo</li>
            <li class='element'>Bar</li>
            <li class='element'>Jay</li>
        </ul>
        <ul class='list list-small' id='list-2'>
            <li class='element'><h1 class='yyyy'>Foo</h1></li>
            <li class='element xxx'>Bar</li>
            <li class='element'>Jay</li>
        </ul>
    </div>
    and they lived at the bottom of a well.
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')

# 选取title元素
soup.select("title")  
# 输出: [<title>The Dormouse's story</title>]

# 选取第三个p元素(class为story)
soup.select("p:nth-of-type(3)")  
# 输出: [<p class="story">...</p>]

# 选取body下的所有a元素
soup.select("body a")
# 输出:
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

# 选取html head title元素
soup.select("html head title")  
# 输出: [<title>The Dormouse's story</title>]

# 选取head下直接子元素title
soup.select("head > title")  
# 输出: [<title>The Dormouse's story</title>]

# 返回所有<p>标签下的直接子级<a>标签
soup.select("p > a")
# 结果:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#        <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
#        <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]


soup.select("p > a:nth-of-type(2)")
# 返回所有<p>标签下第二个<a>标签(直接子级)
# 结果:[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

soup.select("p > #link1")
# 返回所有<p>标签下拥有id="link1"的<a>标签(直接子级)
# 结果:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

# 返回所有<body>标签下的直接子级<a>标签
soup.select("body > a")

# 返回拥有id="link1"的<a>标签之后所有同级的class="sister"的<a>标签
soup.select("#link1 ~ .sister")
# 结果:[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#         <a class="sister" href="http://example.com/tillie"  id="link3">Tillie</a>]

# 返回拥有id="link1"的<a>标签之后紧邻的下一个同级的class="sister"的<a>标签
soup.select("#link1 + .sister")
# 结果:[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

# 返回所有class="sister"的<a>标签
soup.select(".sister")
# 结果:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#         <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#         <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

# 返回所有class属性中包含“sister”的<a>标签
soup.select("[class~=sister]")
# 结果:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#         <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#         <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

# 返回拥有id="link1"的<a>标签
soup.select("#link1")
# 结果:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

# 返回拥有id="link2"且为<a>标签的元素
soup.select("a#link2")
# 结果:[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

# 返回拥有id="link1"或id="link2"的<a>标签
soup.select("#link1,#link2")
# 结果:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#         <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

# 选取包含 href 属性的所有 <a> 标签
soup.select('a[href]')
# 结果为一个列表,包含所有具有 href 属性的 <a> 标签
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

# 选取 href 属性等于 "http://example.com/elsie" 的 <a> 标签
soup.select('a[href="http://example.com/elsie"]')
# 结果为一个列表,只包含 href 属性等于 "http://example.com/elsie" 的 <a> 标签
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

# 选取 href 属性以 "http://example.com/" 开头的 <a> 标签
soup.select('a[href^="http://example.com/"]')
# 结果为一个列表,只包含 href 属性以 "http://example.com/" 开头的 <a> 标签
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

# 选取 href 属性以 "tillie" 结尾的 <a> 标签
soup.select('a[href$="tillie"]')
# 结果为一个列表,只包含 href 属性以 "tillie" 结尾的 <a> 标签
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

# 选取 href 属性中包含 ".com/el" 的 <a> 标签
soup.select('a[href*=".com/el"]')
# 结果为一个列表,只包含 href 属性中包含 ".com/el" 的 <a> 标签
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]


multilingual_markup = """
 <p lang="en">Hello</p>
 <p lang="en-us">Howdy, y'all</p>
 <p lang="en-gb">Pip-pip, old fruit</p>
 <p lang="fr">Bonjour mes amis</p>
"""
multilingual_soup = BeautifulSoup(multilingual_markup)

# 选取 lang 属性以 "en" 开头的 <p> 标签
multilingual_soup.select('p[lang|=en]')
# 结果为一个列表,只包含 lang 属性以 "en" 开头的 <p> 标签
# [<p lang="en">Hello</p>,
#  <p lang="en-us">Howdy, y'all</p>,
#  <p lang="en-gb">Pip-pip, old fruit</p>]

【2】select_one

返回查找到的元素的第一个

【3】获取属性

print(soup.select('#list-2 h1')[0].attrs)

【4】获取内容

print(soup.select('#list-2 h1')[0].get_text())

【六】解析流程

1.实例化一个BeautifulSoup的对象

  • 实例化一个BeautifulSoup的对象,然后把即将被解析的页面源码数据加载到该对象中
    • BeautifulSoup(fp,'lxml'):fp表示本地的一个文件,该种方式是将本地存储的html文件进行数据解析
    • BeautifulSoup(page_text,'lxml'):page_text是网络请求到的页面源码数据,该种方式是直接将网络请求到的页面源码数据进行数据解析

2.调用BeautifulSoup对象

  • 调用BeautifulSoup对象中相关的属性和方法实现标签定位和数据提取

3.具体解析操作详解

  • 在当前目录下新建一个test.html文件,然后将下述内容拷贝到该文件中

    <html lang="en">
    <head>
    	<meta charset="UTF-8" />
    	<title>测试bs4</title>
    </head>
    <body>
    	<div>
    		<p>百里守约</p>
    	</div>
    	<div class="song">
    		<p>李清照</p>
    		<p>王安石</p>
    		<p>苏轼</p>
    		<p>柳宗元</p>
    		<a href="http://www.song.com/" title="赵匡胤" target="_self">
    			<span>this is span</span>
    		宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
    		<a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
    		<img src="http://www.baidu.com/meinv.jpg" alt="" />
    	</div>
    	<div class="tang">
    		<ul>
    			<li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
    			<li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
    			<li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
    			<li><a href="http://www.sina.com" class="du">杜甫</a></li>
    			<li><a href="http://www.dudu.com" class="du">杜牧</a></li>
    			<li><b>杜小月</b></li>
    			<li><i>度蜜月</i></li>
    			<li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
    		</ul>
    	</div>
    </body>
    </html>
    
  • 有了test.html文件后,在练习如下操作

    from bs4 import BeautifulSoup
    #fp就表示本地存储的一个html文件
    fp = open('./test.html','r',encoding='utf-8')
    #解析本地存储的html文件中的内容
    #实例化BeautifulSoup对象,然后把即将被解析的页面源码数据加载到了该对象中
    soup = BeautifulSoup(fp,'lxml') #参数2,lxml是固定形式,表示指定的解析器
    #标签定位
    #方式1:soup.tagName,只会定位到符合条件的第一个标签
    tag1 = soup.title #定位到了title标签
    tag2 = soup.div
    #方式2:属性定位,find函数,findall函数
        #find('tagName',attrName='attrValue'):find只会定位到满足要的第一个标签
    tag3 = soup.find('div',class_='song')#定位class属性值为song的div标签
    tag4 = soup.find('a',id='feng')#定位id属性值为feng的a标签
        #findAll('tagName',attrName='attrValue'):可以定位到满足要求的所有标签
    tag5 = soup.findAll('div',class_='song')
    #方式3:选择器定位:soup.select('选择器')
        #id选择器:#feng  ----id为feng
        #class选择器:.song ----class为song
        #层级选择器:大于号表示一个层级,空格表示多个层级
    tag6 = soup.select('#feng') #定位到所有id属性值为feng的标签
    tag7 = soup.select('.song')#定位到所有class属性值为song的标签
    tag8 = soup.select('.tang > ul > li') #定位到了class为tang下面的ul下面所有的li标签
    tag9 = soup.select('.tang li')
    
    #提取标签中的内容
    #1.提取标签中间的内容:
        #tag.string:只可以提取到标签中直系的文本内容
        #tag.text:可以提取到标签中所有的文本内容
    # p_tag = soup.p
    # print(p_tag.string)
    # print(p_tag.text)
    # div_tag = soup.find('div',class_='song')
    # print(div_tag.text)
    
    #2.提取标签的属性值
        #tag['attrName']
    img_tag = soup.img
    print(img_tag['src']) #提取img标签的src的属性值
    

【七】练习

  • 使用bs4爬取豆瓣电影排行榜信息
from bs4 import BeautifulSoup
soup = BeautifulSoup(s, 'html.parser')

s=soup.find_all(class_="item")
for item in s:
  print(item.find(class_="pic").a.get("href"))
  print(item.find(class_="pic").em.string)
  print(item.find(class_="info").contents[1].a.span.string)
        print(item.find(class_="info").contents[3].contents[3].contents[3].string)
        print(item.find(class_="info").contents[3].contents[3].contents[7].string)

【八】案例集锦

1、小说批量爬取

  • url:https://www.shicimingju.com/book/sanguoyanyi.html

  • 需求:将每一个章节的标题和内容进行爬取然后存储到一个文件中

    • 步骤:

      • 1.请求主页的页面源码数据
      • 2.数据解析:
        • 章节标题
        • 章节详情页的链接
      • 3.解析章节详细内容
      • 4.将解析的章节标题和内容进行存储
      from bs4 import BeautifulSoup
      import requests
      headers = {
          'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'
      }
      #首页地址
      main_url = 'https://www.shicimingju.com/book/sanguoyanyi.html'
      #发起请求,获取了主页页面源码
      response = requests.get(url=main_url,headers=headers)
      response.encoding = 'utf-8'
      page_text = response.text
      #数据解析:章节标题+详情页链接
      soup = BeautifulSoup(page_text,'lxml')
      a_list = soup.select('.book-mulu > ul > li > a')
      fp = open('./sanguo.txt','w',encoding='utf-8')
      for a in a_list:
          title = a.string #章节标题
          detail_url = 'https://www.shicimingju.com'+a['href'] #详情页地址
          #请求详情页的页面源码数据
          response = requests.get(url=detail_url,headers=headers)
          response.encoding = 'utf-8'
          detail_page_text = response.text
          #解析:解析章节内容
          d_soup = BeautifulSoup(detail_page_text,'lxml')
          div_tag = d_soup.find('div',class_='chapter_content')
          content = div_tag.text #章节内容
          fp.write(title+':'+content+'\n')
          print(title,'爬取保存成功!')
      fp.close()
      

2、代理批量爬取

  • url:https://www.kuaidaili.com/free

  • 需求:将前5页的所有id和port解析且存储到文件中

    • 爬取单页内容
    #只爬取了第一页的内容
    from bs4 import BeautifulSoup
    import requests
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'
    }
    url = 'https://www.kuaidaili.com/free'
    page_text = requests.get(url=url,headers=headers).text
    soup = BeautifulSoup(page_text,'lxml')
    trs = soup.select('tbody > tr')
    for tr in trs:
        t1 = tr.findAll('td')[0]
        t2 = tr.findAll('td')[1]
        ip = t1.string
        port = t2.string
        print(ip,port)
    
    • 爬取多页内容
    #爬取多页内容
    from bs4 import BeautifulSoup
    import requests
    import time
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'
    }
    #爬取多页
    #1.创建一个通用的url(可以变换成任意页码的url)
    url = 'https://www.kuaidaili.com/free/inha/%d/'
    #2.通过循环以此生成不同页码的url
    for page in range(1,11):
        print('----------正在爬取第%d页的数据!-----------'%page)
        #format用来格式化字符串的(不可以修改url这个字符串本身)
        new_url = format(url%page)
        #循环发送每一页的请求
        #注意:get方法是一个阻塞方法!
        page_text = requests.get(url=new_url,headers=headers).text
        time.sleep(1)
        soup = BeautifulSoup(page_text,'lxml')
        trs = soup.select('tbody > tr')
        for tr in trs:
            t1 = tr.findAll('td')[0]
            t2 = tr.findAll('td')[1]
            ip = t1.string
            port = t2.string
            print(ip,port)
    
posted @ 2023-08-22 09:09  Chimengmeng  阅读(61)  评论(0编辑  收藏  举报