Python爬虫学习笔记 (4) [初级] 学习BeautifulSoup bs4 靓汤

本文介绍了Python解析库BeautifulSoup (bs4) 的基本概念和核心功能,包括如何构造bs4实例、遍历文档、搜索目标标签以及获取标签信息。学习内容涵盖Tag对象、NavigableString对象、BeautifulSoup对象及其遍历、搜索方法,如.find_all()。通过实例展示了如何使用bs4解析HTML和XML文档,帮助读者掌握网页信息提取的关键技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

更新日期: 2021.04.11

本节学习内容 :掌握解析库 bs4 的常用方法。

目录

  • 1. bs4 是什么?能帮我完成什么任务?
  • 2. 学习资料来源 (官网)
  • 3. 为什么使用 bs4 需指定解析器?怎么选择?
  • 4. 准备工作
  • 5. 开始使用 - 构造 bs4 实例
  • 6. bs4 实例中有哪些可操控的对象?
    • 6.1 Tag 标签对象
    • 6.2 NavigableString 字符串对象
    • 6.3 BeautifulSoup 文档级别对象
    • 6.4 其他
  • 7. 获取目标标签 - 遍历文档
    • 7.1 获取当前节点信息 .name
    • 7.2 向下遍历
      • 7.2.1 列表形式子节点 .contents
      • 7.2.2 生成器形式子节点 .children
      • 7.2.3 所有子孙节点 .descendants
      • 7.2.4 子孙节点的字符串信息 .strings & stripped_strings
    • 7.3 向上遍历
      • 7.3.1 父节点 .parent
      • 7.3.2 所有上级节点 .parents
    • 7.4 横向遍历
      • 7.4.1 相邻的一个兄弟节点 .next_sibling & .previous_sibling
      • 7.4.2 相邻的多个兄弟节点 .next_siblings & .previous_siblings
    • 7.5 按解析顺序遍历 .next_element(s) & .previous_element(s)
  • 8. 获取目标标签 - 直接搜索
    • 8.1 辅助搜索工具
      • 8.1.1 字符串
      • 8.1.2 正则表达式
      • 8.1.3 列表
      • 8.1.4 True
      • 8.1.5 自定义搜索方法 ~
    • 8.2 开始搜索大法 .find_all
      • 8.2.1 标签名字
      • 8.2.2 关键字/ 属性名称
      • 8.2.3 CCS (类名)
      • 8.2.4 字符串
      • 8.2.5 混合筛选
      • 8.2.6 限制返回数量
      • 8.2.7 仅返回子节点
    • 8.3. 搜索大法 - 除 find_all 之外的 find 方法
    • 8.4. 搜索大法 - 使用CSS选择器
  • 9. 获取标签的信息
    • 9.1 获取标签内的字符串信息
    • 9.2 获取标签的属性和属性值
  • 10. 其他
    • 10.1 省略 .find_all() 直接调用标签
    • 10.2 优雅的输出 prettify()
    • 10.3 bs4 怎样确定解码规则
    • 10.4 解析部分文档 - SoupStrainer
    • 10.5 破解乱码 - UnicodeDammit
    • 10.6 常见的解析错误 (这些全都不是我的错,哈哈~)
    • 10.7 解析xml文档
    • 10.8 处理重复的属性
    • 10.9 bs4 的其他功能
  • 11. 总结~

1. bs4 是什么?能帮我完成什么任务?

BeautifulSoup是Python的一个用于解析网页 html 和 xml 代码的库。

主要工作原理是先定位目标标签,再获取目标字符串或属性值。是获取网页信息的利器。

2. 学习资料来源 (官网)

打开官网找到 BeautifulSoup (bs4),当前版本是4.9.3 (Oct 3, 2020), 英文版介绍是基于4.9.0, 链接: https://www.crummy.com/software/BeautifulSoup/bs4/doc/

中文版介绍是基于4.4.0 (Sep 29, 2015), 链接: https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/

先学习中文版, 再看看英文版的变化,不同之处以 4.9 开头作为标记在这里插入图片描述

3. 为什么使用 bs4 需指定解析器?怎么选择?

bs4 支持使用多种解析器,可以解析html, xml 和 html5 格式的文档,不同解析器的主要特点见下表。

如果文档格式不正确, 其被不同的解析器解析后返回的结果可能是不一样的。因为对于错误的格式,各解析器有自己的容错和解析原则。即使是格式正确的文档,如果没有使用相应的解析器,也可能无法获得预期结果。

在公布源代码时应标明所依赖的解析器类型,以避免给阅读者带来不必要的麻烦。
在这里插入图片描述

4. 准备工作

pip 安装 bs4 及相关的解析器 (lxml, html5)。

5. 开始使用 - 构造 bs4 实例

导入 bs4 库后,使用BeautifulSoup() 命令将目标文档构造为 bs4 实例,然后针对这个实例对象进行操作。

目标文档可以是一个网页代码的片段,也可以从文件中读取。

from bs4 import BeautifulSoup

# 从文件中读取。练习新技能时,先把网页代码存下来再玩,不要反复地爬取~~
with open("index.html") as html: 
    soup = BeautifulSoup(html, 'html.parser')

如不指定解析器,bs4 会自动选择它认为合适的解析器来解析。见下例,bs4 选择了 lxml 作为解析器,并提示了可能出现的问题,以及解决方法。然后输出了解析结果。

from bs4 import BeautifulSoup
soup = BeautifulSoup("<html>Alice</html>")
print(soup)
'''
GuessedAtParserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). 
This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.
<html><body><p>Alice</p></body></html>
'''

如指定解析器,bs4 会直接使用指定的解析器来解析,如下。

soup = BeautifulSoup("Sacr&eacute!","lxml")
print(soup) # <html><body><p>Sacré!</p></body></html>

6. bs4 实例中有哪些可操控的对象?

bs4 实例化文档后将其转换为树形结构, 主要有三类对象:Tag , NavigableString , BeautifulSoup。

6.1 Tag 标签对象

Tag 对象与网页源文档中的 tag 相同。即一对尖括号之内的全部内容,含尖括号和标签的名称,以及这个标签内部的全部信息,如属性名称和值,字符串等。

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>',"lxml")
print(type(soup.b)) # <class 'bs4.element.Tag'>

标签最重要的属性是 name (名字)和 attributes (属性)。

name属性

每个标签都有名字 (可以重名…), 通过 .name 方法获取相应名字的标签。还可以给标签改名。

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>',"lxml")
print(soup.b.name) # b

tag = soup.b
print(tag) # <b class="boldest">Extremely bold</b>

tag.name = "a"
print(tag) # <a class="boldest">Extremely bold</blockquote>
# 改名之后,b标签就消失了,变成了a标签...

attributes 属性

一个标签可有多个属性。针对属性的操作方法与字典相同。

可使用 tag[‘属性名称’] 获取属性相应的值,也可使用 .attrs 获取属性的所有 “名称~值” 信息。

soup = BeautifulSoup('<b class="boldest">Extremely Black</b>',"lxml")
tag = soup.b

print(tag['class']) # ['boldest']
print(tag.attrs) # {'class': ['boldest']}

标签的属性可被添加, 删除和修改。

soup = BeautifulSoup('<b class="boldest">Extremely Black</b>',"lxml")
tag = soup.b

tag['class'] = 'verybold'
tag['id'] = 1

print(tag) # <b class="verybold" id="1">Extremely Black</b>

del tag['class']
del tag['id']
print(tag) # <b>Extremely Black</b>

多值属性:

  • HTML4和 HTML5都支持多值属性,最常见的多值属性是 class。
  • 一个标签可拥有多个CSS的 class 属性,并同时拥有其他属性,如 rel , rev , accept-charset , headers , accesskey等。
  • 4.9.0 可通过 “multi_valued_attributes=None” 在解析时禁止识别多值属性。
soup = BeautifulSoup('<p class="body strikeout"></p>','lxml')
print(soup.p['class']) # ['body', 'strikeout'] 多值属性的返回类型是列表list

soup = BeautifulSoup('<p class="body strikeout"></p>','lxml',multi_valued_attributes=None)
print(soup.p['class']) # body strikeout,禁止识别多值属性

soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>','lxml')
soup.a['rel'] = ['index', 'contents'] # 可以通过列表同时更新标签的多个属性
print(soup.a) # <a rel="index contents">homepage</a>

单值属性:

  • 即使某个属性看起来有多个值, 但在解析所得的文档中没有发现它的属性被定义为多值,bs4就不会认为这是一个多值属性,并会将这个属性作为一个字符串返回。
  • 例如XML不支持多值属性,如使用XML解析,而原文档是HTML4或HTML5 编写的,解析得到的文档中标签将不会包含多值属性。所以,注意:选择合适的解析器~~
  • 4.9.0 可使用 “multi_valued_attributes argument” 强制解析器识别为多值属性,即使是在使用XML解析器。
id_soup = BeautifulSoup('<p id="my id"></p>','lxml') # lxml以为id是单值属性
print(id_soup.p['id']) # my id

xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml') # XML不支持多属性
print(xml_soup.p['class']) # body strikeout

id_soup = BeautifulSoup('<p id="my id"></p>','xml',multi_valued_attributes={ '*' : 'id'})
print(id_soup.p['id']) # ['my', 'id'] # 两个属性~

6.2 NavigableString 字符串对象

NavigableString 是 bs4 用来管理标签内的字符串的类。

是 Python 中字符串类型的子类,继承了所有字符串的属性和方法。

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
tag = soup.b

print(type(tag.string)) # <class 'bs4.element.NavigableString'>
print(tag.string) # Extremely bold

NavigableString 可以被替换,使用.replace_with 或直接给 tag.string 赋值。

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
tag = soup.b

tag.string.replace_with("No longer bold")
print(tag) # <b class="boldest">No longer bold</b>

tag.string = "It is black"
print(tag) # <b class="boldest">It is black</b>

tag.string = tag.string + " revised"
print(tag) # <b class="boldest">It is black revised</b>

6.3 BeautifulSoup 文档级别对象

BeautifulSoup 对象表示一个文档的全部内容,也可以把它看作一个标签对象。

它支持遍历和搜索中的大部分方法,但由于它并不是一个具体的标签, 所以仅有一个特殊的 .name 属性,值为 [document]。

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
print(soup.name) # [document]

4.9.0 把 BeautifulSoup 作为一个标签,可以在更高的层面上操作文档,例如使用一个 BeautifulSoup 对象替换另一个 BeautifulSoup 对象的部分内容。

doc = BeautifulSoup("<document><content/>INSERT FOOTER HERE</document", "lxml")
footer = BeautifulSoup("<footer>Here's the footer</footer>", "lxml")

doc.find(text="INSERT FOOTER HERE").replace_with(footer)
print(doc)
# <?xml version="1.0" encoding="utf-8"?>
# <document><content/><footer>Here's the footer</footer></document>

6.4 其他

以上三类对象几乎涵盖了网页文档中的所有内容, 还有一些特殊的对象类型,如文档的注释 (Comment 对象)。

Comment 对象是一个特殊类型的 NavigableString对象,也可以通过 print (soup.b.string) 方法输出,不过格式比较特别。

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup, 'lxml')

print(type(soup.b.string)) # <class 'bs4.element.Comment'>
print(soup.b.string) # Hey, buddy. Want to buy a used parser?

print(soup.b.prettify())
'''
<b>
 <!--Hey, buddy. Want to buy a used parser?-->
</b>
'''

4.9.0 bs4 还定义了其他的类,这些类使用方法和 NavigableString 一样,作用都是使我们可以方便的通过标签获取信息。(html5lib 解析器不含这些元素)

  • Style 类: CCS样式表
  • Script 类:Javascript
  • Template 类:html 模板
  • CData, ProcessingInstruction, Declaration, 以及 Doctype

以下例子使用 CDATA block 替换了 comment 部分的内容。具体用途还不清楚~~

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup, 'html.parser')
comment = soup.b.string
type(comment) # <class 'bs4.element.Comment'>

cdata = CData("A CDATA block")
comment.replace_with(cdata)

print(soup.b) # <b><![CDATA[A CDATA block]]></b>

7. 获取目标标签 - 遍历文档

本节演示主要采用 ”爱丽丝梦游仙境” 文档的片段,如下,后续不再重复文档代码。

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

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>
"""

7.1 获取当前节点信息 .name

以下两种方法效果相同,因为 find_all 是 bs4 中最常用的方法,可以省略不写。

  • 通过标签名字使用 .name 方法获取目标标签。
  • 使用 find_all (“name”) 方法获取全部同名标签。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')

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

print(soup.head.title) # <title>The Dormouse's story</title> # 可嵌套使用多层标签名字

print(soup.find_all("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>]
'''

7.2 向下遍历

一个标签里可包含多层及多个标签,统称为这个标签的子孙节点。

7.2.1 列表形式子节点 .contents

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

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

print(soup.head.contents) # [<title>The Dormouse's story</title>]
print(soup.head.contents[0]) # <title>The Dormouse's story</title>

7.2.2 生成器形式子节点 .children

head_tag = soup.head
print(head_tag.contents) # [<title>The Dormouse's story</title>]

title_tag = head_tag.contents[0]
print(title_tag) # <title>The Dormouse's story</title>

for child in title_tag.children:
    print(child) # The Dormouse's story

7.2.3 所有子孙节点 .descendants

head_tag = soup.head

for descendant in head_tag.descendants:
    print(descendant)
    # <title>The Dormouse's story</title>
    # The Dormouse's story

print(len(list(soup.children))) # 1
print(len(list(soup.descendants))) # 25

7.2.4 子孙节点的字符串信息 .strings & stripped_strings

  • 如标签内只含一个 NavigableString 类节点, 不论是否为其子节点,都可以使用 .string 直接获取到字符串。
  • 如标签内的多个节点都包含字符串, 需使用 .strings 循环获取。
  • 使用 .stripped_strings 去除字符串中可能包含的空格或空行。
head_tag = soup.head
title_tag = head_tag.contents[0]

for string in soup.strings: # 输出略
    print(repr(string))

for string in soup.stripped_strings: # 输出如下
    print(repr(string))
'''
"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'
';\nand they lived at the bottom of a well.'
'...'
'''

7.3 向上遍历

7.3.1 父节点 .parent

  • “title”标签的父节点是"head’’ 标签。
  • “title”标签包含的字符串也有父节点,就是“title”标签。(上一级,就是加一层尖括号)。
  • 根节点 “html” 的父节点是 BeautifulSoup 对象,即 html 文档本身。
  • BeautifulSoup 的父节点是 None。
title_tag = soup.title

print(title_tag) # <title>The Dormouse's story</title>
print(title_tag.parent) # <head><title>The Dormouse's story</title></head>

print(title_tag.string.parent) # <title>The Dormouse's story</title>

print(type(soup.html.parent)) # <class 'bs4.BeautifulSoup'>
print(soup.parent) # None

7.3.2 所有上级节点 .parents

link = soup.a
print(link) # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

for i in link.parents:
    if i is not None: # BeautifulSoup对象的.parent是None
        print(i.name)
'''
p
body
html
[document]
'''

7.4 横向遍历

接下来使用以下简单的文档片段。

sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")

7.4.1 相邻的一个兄弟节点 .next_sibling & .previous_sibling

实际情况中,很多标签的 .next_sibling 和 .previous_sibling 通常是无意义的字符, 比如是两个标签之间的顿号或换行符。

sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>", 'lxml')

print(sibling_soup.b.next_sibling) # <c>text2</c>
print(sibling_soup.c.previous_sibling) # <b>text1</b>

7.4.2 相邻的多个兄弟节点 .next_siblings & .previous_siblings

获得 a 节点之后的所有同级节点。

for sibling in soup.a.next_siblings:
    print(repr(sibling))
'''
',\n'
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
' and\n'
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
';\nand they lived at the bottom of a well.'
'''

带条件搜索 (id=“link2”) 兄弟节点。

for sibling in soup.find(id="link2").previous_siblings:
    print(repr(sibling))
'''
',\n'
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
'Once upon a time there were three little sisters; and their names were\n'
'''    

7.5 按解析顺序遍历 .next_element(s) & .previous_element(s)

解析器解析文档一般遵循以下过程。
以下面的 “爱丽丝” 文档为例,解析器首先打开 html 标签, 再打开一个head标签, 接下来打开一个title标签, 添加一段字符串, 然后关闭 title 标签, 再关闭 head 标签,打开 p 标签…

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

# 获取所有id="link3"的a节点之后的节点,不论是子孙还是兄弟
last_a_tag = soup.find("a", id="link3")
for element in last_a_tag.next_elements:
    print(repr(element))
'''
'Tillie'
';\nand they lived at the bottom of a well.'
'\n'
<p class="story">...</p>
'...'
'\n'
'''

8. 获取目标标签 - 直接搜索

8.1 辅助搜索工具

8.1.1 字符串

在搜索方法中传入一个字符串参数, bs4 会据此比对所有标签的名字,并以列表形式返回名字与该字符串完全匹配的标签。

print(soup.find_all('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>]

8.1.2 正则表达式

import re
for tag in soup.find_all(re.compile("^b")): # 找出所有以 b开头的标签
    print(tag.name)
# body
# b

for tag in soup.find_all(re.compile("t")): # 找出所有名字中包含't'的标签
    print(tag.name)
# html
# title

8.1.3 列表

bs4 会将名字与列表中任一字符串匹配的标签返回。

print(soup.find_all(["a", "b"])) # 返回所有名字包含'a'和'b'的标签
# [<b>The Dormouse's story</b>, 
# <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>]

8.1.4 True

True 可以匹配任何值, 所以下面代码返回所有有名字的标签的名字。

for tag in soup.find_all(True):
    print(tag.name)
# html
# head
# title
# body
# p
# 其他输出略

8.1.5 自定义搜索方法 ~

自定义方法如果匹配失败,会返回 False。

# 找出所有符合 “含有 class 及 id 属性” 条件的标签
def has_class_but_no_id(tag):
    return tag.has_attr('class') and tag.has_attr('id')
print(soup.find_all(has_class_but_no_id))
# [<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中不含"lacie"的标签
import re
def not_lacie(href):
    return href and not re.compile("lacie").search(href)
print(soup.find_all(href=not_lacie))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

# 找出所有符合“前后紧邻的元素都是字符串”条件的标签
from bs4 import NavigableString
def surrounded_by_strings(tag):
    if (isinstance(tag.next_element, NavigableString) and isinstance(tag.previous_element, NavigableString)):
        return tag

for tag in soup.find_all(surrounded_by_strings):
    print(tag.name)
'''
body
p
a
输出略
'''

8.2 开始搜索大法 .find_all

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

find_all () 方法搜索当前标签中的所有标签, 并返回符合搜索条件的标签。

  • 搜索条件可以是自定义的过滤器 , 字符串,正则表达式, True,列表等。
  • 可以在一个搜索方法中同时设置多个属性的搜索条件。

8.2.1 标签名字

find_all 接收到的参数如果不是关键字或属性名称,比如在处理 ”<a class=“sister” href="http…“ 片段时,提供的不是 class 或 href ,搜索时会把该参数当作标签的名称处理,并返回符合条件的标签。

print(soup.find_all('title')) # [<title>The Dormouse's story</title>]

8.2.2 关键字/ 属性名称

find_all 接收到的参数如果含属性名称,搜索时将直接找到该属性所在的标签,并返回符合筛选条件的标签。

print(soup.find_all(id ="link3")) # 返回所有id ="link3"的标签
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

import re
print(soup.find_all(href = re.compile("elsie"))) # 返回所有href含有"elsie"的标签
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

print(soup.find_all(id=True)) # 返回所有含id 属性的标签
# [<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>]

print(soup.find_all(href=re.compile("elsie"), id='link1'))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

有些属性在搜索时不能直接使用, 比如以下的 data-* 属性。需通过 attrs 参数定义一个字典来搜索。

尝试更改 data-foo,把中划线改为下划线或者去掉都可以搜索,把中划线替换为*/等特殊字符都不可以搜索。比较像Python变量的命名规则。

data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
print (data_soup.find_all(data-foo="value"))
'''
  File "bs4_study.py", line 20
    print (data_soup.find_all(data-foo="value"))
                              ^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
'''

data_soup.find_all(attrs={"data-foo": "value"}) # [<div data-foo="value">foo!</div>]

4.9.0 同样,由于 name 是 bs4 的保留关键字,也不能直接搜索,需构造词典来解决。

name_soup = BeautifulSoup('<input name="email"/>', 'html.parser')
print(name_soup.find_all(name="email")) # []
print(name_soup.find_all(attrs={"name": "email"})) # [<input name="email"/>]

8.2.3 CCS (类名)

CSS类名的关键字 class 在Python中是保留字, 不能做为搜索的关键字,需在后面加下划线再使用,即class_

print(soup.find_all("a", 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>]

print(soup.find_all(class_=re.compile("itl"))) 
# [<p class="title"><b>The Dormouse's story</b></p>]

CCS中标签的 class 属性是多值属性, 可以通过其中一个或全部属性值获取该标签,但不能通过一部分属性值获取。

css_soup = BeautifulSoup('<p class="body strikeout"></p>','lxml')
print(css_soup.find_all("p", class_="strikeout")) # [<p class="body strikeout"></p>]
print(css_soup.find_all("p", class_="body")) # [<p class="body strikeout"></p>]
print(css_soup.find_all("p", class_="body strikeout")) # [<p class="body strikeout"></p>]

# 提供三个属性值当中的两个作为查询条件,返回为空,搜索失败
css_soup = BeautifulSoup('<p class="body strikeout added"></p>','lxml')
print(css_soup.find_all("p", class_="body strikeout")) # []

8.2.4 字符串

搜索文档中的字符串内容,可提供一个参数或参数列表进行匹配,也可以使用正则,自定义搜索等方法。

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

print(soup.find_all(string=["Tillie", "Elsie", "Lacie"])) # ['Elsie', u'Lacie', u'Tillie']
print(soup.find_all(string=["lie", "Elsie", "Lacie"])) # ['Elsie', 'Lacie'], 无法匹配Tillie

print(soup.find_all(string=re.compile("mouse"))) 
# ["The Dormouse's story", u"The Dormouse's story"], 使用正则还是可以匹配到~ 

def is_the_only_string_within_a_tag(s):
    return (s == s.parent.string)
print(soup.find_all(string=is_the_only_string_within_a_tag))
# ["The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']

8.2.5 混合筛选

print(soup.find_all("a", string="Elsie")) 
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]

8.2.6 限制返回数量

如果文档信息量很大,而我们只需要其中的一部分~

print(soup.find_all("a",limit=2))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

8.2.7 仅返回子节点

使用 find_all() 方法会返回所有满足条件的子孙节点, 如只需搜索子节点, 可设置参数 recursive=False。

只有 find_all() 和 find() 支持 recursive 参数。

8.3. 搜索大法 - 除 find_all 之外的 find 方法

以下搜索方法的使用和 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()

8.4. 搜索大法 - 使用CSS选择器

4.9.0 4.7.0版在 bs4 中加入了 SoupSieve 库,可以更方便的处理CSS选择器。

在 Elements 选项卡上点击某个标签的右键, 即可得到以下三种方式的路径。

bs4 支持大部分CSS选择器, 如果已经熟悉了CSS选择器的语法,使用起来会非常方便。不过,如果仅需要使用CSS选择器的功能, 应直接使用 lxml 解析器而不是 bs4,那样速度更快。

  • CCS selector path: #su
  • xPath path: //*[@id=“su”
  • JS path: document.querySelector(’#su’)
    在这里插入图片描述

9. 获取标签的信息

9.1 获取标签内的字符串信息

# 获取当前标签内的所有文本信息,含子孙节点内的文本
print(soup.python_study.text)
print(soup.python_study.get_text())

# 获取当前标签内的直接文本信息。
# 如果当前标签内没有直接文本信息,其子孙节点中有唯一的文本信息,也可以获取到
print(soup.python_study.string)

9.2 获取标签的属性和属性值

# 获取标签的所有属性信息
print (tag.attrs) 
# {'star': '255', 'class': ['M', 'L'], 'teacher': 'alice zhang   '}

# 获取某一属性的值
print(target_tag['star']) # 255

10. 其他

10.1 省略 .find_all() 直接调用标签

以下两组代码等价~

soup.find_all("a")
soup("a")

soup.title.find_all(string=True)
soup.title(string=True)

10.2 优雅的输出 prettify()

prettify () 方法将 bs4 的文档树格式化后以Unicode 编码输出, 每个XML/HTML标签都独占一行。很好看,查看代码寻找规律时可以考虑看 prettify 版本~

10.3 bs4 怎样确定解码规则

默认情况下,bs4 会将文档转换为Unicode再处理。

通过 bs4 输出到文档时, 不论之前的输入文档是什么编码方式, 默认输出编码均为UTF-8。

10.4 解析部分文档 - SoupStrainer

如仅需要查找并解析文档的一部分,可使用 SoupStrainer 类定义bs4 的处理范围。

创建一个 SoupStrainer 对象并作为 parse_only 参数提交给 bs4 处理。parse_only可以接受多种类型的参数。

解析部分文档不会节省多少解析时间 (因为本来就只需要解析这一部分~), 但可以节省内存, 而且搜索也会变快。

4.9.0 html5lib 解析器不支持部分解析,因为其工作原理不支持仅获取部分文档,具体原因请百度。

from bs4 import BeautifulSoup
from bs4 import SoupStrainer

only_parse_id_link2 = SoupStrainer(id="link2")
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_parse_id_link2).prettify())
'''
<a class="sister" href="http://example.com/lacie" id="link2">
 Lacie
</a>

def is_short_string(string):
    return len(string) < 10
only_short_strings = SoupStrainer(string=is_short_string)

10.5 破解乱码 - UnicodeDammit

以下案例中 snowmen 和 quote 的编码比较特殊且不同,因此人工猜测两部分的编码,然后使用不同的规则处理,再合并输出。结果很不好看,哈哈~

UnicodeDammit.detwingle () 方法可以智能的使用不同规则同时处理一篇文档的不同部分,然后转换为UTF-8编码输出。

现阶段 UnicodeDammit.detwingle () 方法只能解码包含在UTF-8编码中的Windows-1252编码内容, 据说这已经可以解决我们最常遇到的问题了。

snowmen = (u"\N{SNOWMAN}" * 3)
quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")

doc = snowmen.encode("utf8") + quote.encode("windows_1252")
print(doc) # b'\xe2\x98\x83\xe2\x98\x83\xe2\x98\x83\x93I like snowmen!\x94'
print(doc.decode("windows-1252")) # ☃☃☃“I like snowmen!”

from bs4 import UnicodeDammit
new_doc = UnicodeDammit.detwingle(doc)
print(new_doc.decode("utf8")) # ☃☃☃“I like snowmen!”

10.6 常见的解析错误 (这些全都不是我的错,哈哈~)

解析错误有两种,一种是崩溃, bs4 尝试解析一段文档结果抛出了异常,通常是 HTMLParser.HTMLParseError 。另一种是 bs4 解析后的文档树看起来与原来的内容相差很多。

通常情况下,这些异常产生于所依赖的解析器。如果一个解析器不能很好的解析出当前的文档, 最好的办法就是换一个解析器。

10.7 解析xml文档

解析 xml 文档时,记得选择 xml 解析器。

10.8 处理重复的属性

4.9.1版新规

html.parser 解析器默认返回最后一个属性值,可通过设置 “on_duplicate_attribute” 改变。

html5lib 和 lxml 解析器默认返回第一个属性值。

from bs4 import BeautifulSoup

markup = '<a href="http://url1/" href="http://url2/">'

soup = BeautifulSoup(markup, 'html.parser')
print(soup.a['href']) # http://url2/

soup = BeautifulSoup(markup, 'html.parser', on_duplicate_attribute='ignore')
print(soup.a['href']) # http://url1/

soup = BeautifulSoup(markup, 'lxml')
print(soup.a['href']) # http://url1/

10.9 bs4 的其他功能

以下内容暂时不学,知道有这些功能就可以了~

  • 修改文档
  • 比较对象是否相同(==, is)
  • 复制BeautifulSoup对象 (copy.copy())
  • 4.9.0 html.parser 和 html5lib 解析器可以返回目标的行数和位置

11. 总结~

  • 终于看完了 … 感觉用起来会很方便,特别想说:这真是给人类使用的语言啊~~~
  • 继续学习 lxml 等解析器的动力有点不足,因为 bs4 这么强大,想快点去操练下,哈哈~
  • bs4 可以获取属性值和字符串信息,但是,如果需要对字符串做进一步的解析,精准的获取信息,就需要使用正则了,接下来学习 re~~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值