Featured image of post BeautifulSoup

BeautifulSoup

主要介绍爬虫技术中重要的组件BeautifulSoup的使用方法和注意事项

BeautifulSoup

简介

BeautifulSoup 是一个可以把 HTML 或 XML 文档“变成结构化树”,方便你用Python 来查找、遍历、提取内容的工具。

解析器

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

具体使用代码展示如下:

1
2
3
4
from bs4 import BeautifulSoup

soup = BeautifulSoup('<p>Hello world</p>', 'lxml')
print(soup.p)

基本用法

当传入不标准的HTML字符串,BeautifulSoup可以自动更正格式。

  1. soup.prettify(): 用于将 HTML 或 XML 文档格式化输出为带缩进的字符串,即会把原始网页内容重新排版,输出一个带有层级缩进的漂亮 HTML 字符串,例:

    1
    2
    3
    4
    5
    6
    7
    
    from bs4 import BeautifulSoup
    
    html = '<html><head><title>Test</title></head><body><h1>Hi</h1><p>Hello</p></body></html>'
    soup = BeautifulSoup(html, 'html.parser')
    
    pretty_html = soup.prettify()
    print(pretty_html)
    

    输出:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    <html>
     <head>
      <title>
       Test
      </title>
     </head>
     <body>
      <h1>
       Hi
      </h1>
      <p>
       Hello
      </p>
     </body>
    </html>
    
  2. .title:获取title标签

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    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对象 解析器为lxml
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.title)
    #output-><title>The Dormouse's story</title>
    
  3. .name:返回的是当前节点的“标签名称”

    1
    2
    3
    4
    5
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.title.name)
    print(soup.name)
    #output->title
    #[document]
    
  4. .string/.text:获取标签中的文字内容

    1
    2
    3
    4
    5
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.title.string)
    print(soup.title.text)
    #output->The Dormouse's story
    #The Dormouse's story
    
  5. .p:访问HTML中第一个

    标签

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.p)
    #output-><p class="title"><b>The Dormouse's story</b></p>
    
  6. .find_all():查找文档中所有符合条件的标签元素,返回一个列表

    用法 示例 说明
    查找所有某种标签 soup.find_all(‘p’) 找出所有 <p>
    根据 class 属性 soup.find_all(‘p’, class_=‘story’) class 要用 class_ 表示
    查找多个标签 soup.find_all([‘p’, ‘a’]) 所有 <p><a> 标签
    查找包含特定字符串的标签 soup.find_all(string=‘Hello’) 内容匹配为 ‘Hello’ 的标签
    使用属性字典 soup.find_all(‘a’, {‘id’: ’link1’}) 属性筛选
    限制返回数量 soup.find_all(‘p’, limit=2) 最多返回 2 个 <p> 标签
    1
    2
    3
    4
    
    '''
    基本语法:
    soup.find_all(name=None, attrs={}, recursive=True, string=None, limit=None, **kwargs)
    '''
    
  7. .find():获取第一次匹配条件的元素

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.find(id="link1"))
    #output-><a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>
    
  8. .parent:获取父级标签

    1
    2
    3
    4
    5
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.title)
    print(soup.title.parnt)
    #output-><title>The Dormouse's story</title>
    #<head><title>The Dormouse's story</title></head>
    
  9. .p[‘class’]:获取某个 <p> 标签的 class 属性的值

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.p["class"])
    #output->['title']
    
  10. .get_text():获取文档中所有文字内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
soup = BeautifulSoup(html_doc, 'lxml')
print(soup.get_text())
'''
output->
The Dormouse's story

The Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well.
...
'''
  1. .get():用于安全地获取某个属性的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    '''
    tag.get('属性名') 相当于 tag['属性名'],但如果属性不存在,不会报错,而是返回 None
    '''
    a_tags = soup.find_all('a')
    for a_tag in a_tags:
        print(a_tag.get("href"))
    #output->https://example.com/elsie
    #https://example.com/lacie
    #https://example.com/tillie
    

BeautifulSoup的对象种类

主要有四种主要类型:Tag,NavigableString,BeautifulSoup,Comment。

  1. Tag:该对象与HTML或XML原生文档中的标签相同,该对象可以包含其他标签,文本内容和属性。

    1
    2
    3
    4
    
    soup = BeautifulSoup(html_doc, 'lxml')
    tag = soup.title
    print(type(tag))
    #output-><class 'bs4.element.Tag'>
    
  2. NavigableString:标签中的文本内容,是一个不可变字符串,可以由Tag对象的.string获取

    1
    2
    3
    4
    
    soup = BeautifulSoup(html_doc, 'lxml')
    tag = soup.title
    print(type(tag.string))
    #output-> <class 'bs4.element.NavigableString'>
    
  3. BeautifulSoup:整个文档的根对象,即整个文档的根内容,可以被视为一个特殊的Tag对象,但没有名称和属性,其提供对整个文档的遍历,搜索和修改

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(type(soup))
    #output-> <class 'bs4.BeautifulSoup'>
    
  4. Comment:对象是一个特殊类型的NavigableString对象,表示HTML和XML中的注释部分

    1
    2
    3
    4
    
    # <b><!--This is a comment--></b>
    soup = BeautifulSoup(html_doc, 'lxml')
    print(type(soup.b.string))
    #output-> <class 'bs4.element.NavigableString'>
    

BeautifulSoup遍历文档树

BeautifulSoup提供很多方法来遍历解析后的文档树

  1. 导航父节点:.parent和.parents。其中.parent可以获取当前节点的上一级父节点,.parents可以遍历获取当前节点的所有父辈节点

    1
    2
    3
    4
    
    soup = BeautifulSoup(html_doc, 'lxml')
    title_tag = soup.title
    print(title_tag.parent)
    #<head><title>The Dormouse's story</title></head>
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    soup = BeautifulSoup(html_doc, 'lxml')
    body_tag = soup.body
    for parent in body_tag.parents:
        print(parent)
    #<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 class="sister" href="https://example.com/elsie" id="link1">Elsie</a>,
    #....
    
  2. 导航子节点:.contents可以获取当前节点的所有子节点;.children可以遍历当前节点的所有子节点,返回一个list。字符串没有这两个属性。这两个仅包含tag直接子结点,字符串”The Dormouse’s story”是<head>标签的子孙结点

    1
    2
    3
    4
    
    soup = BeautifulSoup(html_doc, 'lxml')
    head_contents = soup.head.contents
    print(head_contents)
    #output-> [<title>The Dormouse's story</title>]
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    soup = BeautifulSoup(html_doc, 'lxml')
    body_children = soup.body.children
    
    for child in body_children:
        print(child)
    #output-><p class="title"><b>The Dormouse's story</b></p>
    #<a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>,
    #<a class="sister" href="https://example.com/tillie" id="link3">Tillie</a>;
    #and they lived at the bottom of a well.</p>
    #.....
    
  3. .descendants:可以遍历当前节点的所有后代节点(层遍历)

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    for descendant in soup.descendants:
        print(descendant)
    
  4. 节点内容:.string,.strings,.stripped_strings。.string如果如果tag只有一个NavigableString类型子节点,那么这个tag可以使用.string得到其子节点。但若tag中包含了多个子节点,tag就无法确定string方法应该调用哪一个字节的内容,则会输出None

    1
    2
    3
    4
    5
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.head.string)
    #The Dormouse's story
    print(soup.title.string)
    #The Dormouse's story
    
    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.body.string)
    #None
    

    .strings可以遍历获取标签中的所有文本内容,.stripped_strings可以去除多余的空白字符

    1
    2
    3
    4
    5
    6
    7
    
    soup = BeautifulSoup(html_doc, 'lxml')
    for string in soup.strings:
        print(string)
    #The Dormouse's story
    ......
    
    #The Dormouse's story
    

BeautifulSoup搜索文档树

  1. find_all():搜索当前tag的所有tag子节点

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.find_all("title"))  # 查找所有的title标签
    print(soup.find_all("p", "title"))  # 查找p标签中class为title的标签
    
    print(soup.find_all("a"))  # 查找所有的a标签
    
    print(soup.find_all(id="link2"))  # 查找id为link2的标签
    #[<title>The Dormouse's story</title>]
    #[<p class="title"><b>The Dormouse's story</b></p>]
    #[<a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="https://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="https://example.com/tillie" id="link3">Tillie</a>]
    #[<a class="sister" href="https://example.com/lacie" id="link2">Lacie</a>]
    
  2. find_parent():只返回最接近的父标签

    1
    2
    3
    4
    
    soup = BeautifulSoup(html_doc, 'lxml')
    a_string = soup.find(string='Lacie')
    print(a_string.find_parent())  # 查找父节点
    #<a class="sister" href="https://example.com/lacie" id="link2">Lacie</a>
    
  3. find_parents():返回所有符合条件的祖先标签,按从近到远的顺序排列

    1
    2
    3
    4
    5
    6
    7
    
    soup = BeautifulSoup(html_doc, 'lxml')
    a_string = soup.find(string='Lacie')
    print(a_string.find_parents())  # 查找父节点
    #[<a class="sister" href="https://example.com/lacie" id="link2">Lacie</a>, <p class="story">Once upon a time there were three little sisters; and their names were
    #<a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>,
    #<a class="sister" href="https://example.com/lacie" id="link2">Lacie</a> and
    #and they lived at the bottom of a well.</p>, <body>....]
    

BeautifulSoup的CSS选择器

我们在写CSS时,标签名不加任何修饰,类名前加点,id名前加#,BeautifulSoup中也可以使用类似的方法来筛选元素。BeautifulSoup中的select()方法允许使用CSS选择器来查找HTML文档元素,其返回一个包含所有匹配元素的列表类似于find_all()方法。

  1. 通过标签名查找:

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.select('b'))
    #[<b>The Dormouse's story</b>, <b><!--This is a comment--></b>]
    
  2. 通过类名查找:

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.select('.title'))
    #[<p class="title"><b>The Dormouse's story</b></p>]
    
  3. id名查找:

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.select('#link1'))
    #[<a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>]
    
  4. 组合查找:组合查找即与写class时一致,标签名与类名id名进行组合的原理一样

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.select('p #link1'))
    #[<a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>]
    
  5. 属性查找:选择具有特定属性或属性值的标签

  • 简单属性选择器:

    1
    2
    3
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.select("a[href='https://example.com/elsie']"))  # 选择a标签中href属性为https://example.com/elsie的标签
    #[<a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>]
    
  • 属性值选择器

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    
    '''
    精确匹配:[attribute="value"]
    部分匹配
    包含特定值:[attribute~="value"] 选择属性值包含特定单词的标签。
    以特定值开头:[attribute^="value"] 选择属性值以特定字符串开头的标签
    以特定值结尾:[attribute$="value"] 选择属性值以特定字符串结尾的标签。
    包含特定子字符串:[attribute*="value"] 选择属性值包含特定子字符串的标签
    '''
    
    soup = BeautifulSoup(html_doc, 'lxml')
    print(soup.select('a[href^="https://example.com"]'))  # 选择href以https://example.com开头的a标签
    #[<a class="sister" href="https://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="https://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="https://example.com/tillie" id="link3">Tillie</a>]
    
恍如昨日,嗤笑今朝
使用 Hugo 构建
主题 StackJimmy 设计