> 文章列表 > 统计软件与数据分析Lesson9----爬虫解析库Beautiful Soup

统计软件与数据分析Lesson9----爬虫解析库Beautiful Soup

统计软件与数据分析Lesson9----爬虫解析库Beautiful Soup

统计软件与数据分析Lesson9----爬虫解析库Beautiful Soup知识点总结

  • 1.requests 模块
    • 1.1 查看requests功能函数
    • 1.2 发送请求
    • 1.3 传递URL参数
    • 1.4 获取响应内容
  • 2.Beautiful Soup模块
    • 2.1 解析器
    • 2.2 对象类型
      • 2.2.1 Beautiful Soup
      • 2.2.2 标签Tag
      • 2.2.3 可遍历的字符串NavigableString
      • 2.2.4 注释Comment
      • 2.2.5 对象比较
      • 2.2.6 复制对象
    • 2.3 遍历
      • 2.3.1 子节点 `.contents` , `.children`
      • 2.3.2 孙节点`.descendants`
      • 2.3.3 父节点`.parent`,`.parents`
      • 2.3.4 兄弟节点 `.next_sibling(s)` ` .previous_sibling(s)`
      • 2.3.5 回退和前进`.next_element` `.previous_element`
    • 2.4 搜索
      • 2.4.1 过滤器
        • 2.4.1.1字符串
        • 2.4.1.2 正则表达式
        • 2.4.1.3 列表
        • 2.4.1.4 True
        • 2.4.1.5 自定义方法
      • 2.4.2搜索`find_all()`
        • 2.4.2.1 `name` 参数
        • 2.4.2.2 keyword 参数
        • 2.4.2.3 CSS搜索
        • 2.4.2.4 attrs参数
        • 2.4.2.5 string 参数
        • 2.4.2.6 limit 参数
        • 2.4.2.7 recursive 参数
        • 2.4.2.8 调用tag
      • 2.4.3 搜索find()
        • 2.4.3.1 find()
        • 2.4.3.2 find_parents() 和 find_parent()
        • 2.4.3.3 find_next_siblings() 和 find_next_sibling()
        • 2.4.3.4 find_previous_siblings() 和 find_previous_sibling()
        • 2.4.3.5 find_all_next() 和 find_next()
        • 2.4.3.6 find_all_previous() 和 find_previous()
      • 2.4.4 CSS选择器
        • 2.4.4.1 `.select()`
        • 2.4.4.2 通过tag标签逐层查找
        • 2.4.4.3 找某个tag标签下的直接子标签
        • 2.4.4.4 找兄弟节点标签
        • 2.4.4.5 通过CSS的类名查找
        • 2.4.4.6 通过tag的id查找
        • 2.4.4.7 同时用多种CSS选择器查询元素:
        • 2.4.4.8 通过是否存在某个属性来查找
        • 2.4.4.9 通过属性的值来查找
        • 2.4.4.10通过语言设置来查找
        • 2.4.4.11 返回查找到的第一个元素
    • 2.5 输出
      • 2.5.1 格式化输出
      • 2.5.2 压缩输出
      • 2.5.3 输出格式
      • 2.5.4 get_text()
    • 2.6 编码
      • 2.6.1 自动识别编码`.original_encoding`
      • 2.6.2 指定编码方式 `from_encoding`
      • 2.6.3 猜测编码方式
      • 2.6.4 输出编码
全局设定
%config InteractiveShell.ast_node_interactivity = 'all'

1.requests 模块

Python 内置了 requests 模块,该模块主要用来发 送 HTTP 请求,requests 模块比 urllib 模块更简洁。

1.1 查看requests功能函数

import requests
print(len(dir(requests)))
dir(requests)
69['ConnectTimeout','ConnectionError',
略]

requests是一个Python第三方库,用于发送HTTP请求。以下是requests库中常用的函数及其功能分类和简短功能说明:

1.异常和错误情况[11]

  • requests.exceptions.RequestException:所有异常的基类。
  • requests.exceptions.HTTPError:HTTP错误异常,如404、500等。
  • requests.exceptions.ConnectionError:连接错误异常,如DNS解析失败、拒绝连接等。
  • requests.exceptions.Timeout:请求超时异常。
  • requests.exceptions.TooManyRedirects:重定向次数超过了最大值。
  • requests.exceptions.SSLError:SSL证书验证失败异常。
  • requests.exceptions.ProxyError:代理错误异常,如无法连接代理等。
  • requests.exceptions.InvalidURL:无效的URL异常。
  • requests.exceptions.InvalidHeader:无效的请求头异常。
  • requests.exceptions.ChunkedEncodingError:分块编码错误异常。
  • requests.exceptions.ContentDecodingError:内容解码错误异常。

2.请求(Request)[17]

  • 1.发送请求[1]
    requests.request() 发送一个HTTP请求,可以指定请求的方法、URL、请求头、请求体、参数和cookies。

  • 2.HTTP方法[7]
    requests.get() 发送一个HTTP GET请求。
    requests.post() 发送一个HTTP POST请求。
    requests.put() 发送一个HTTP PUT请求。
    requests.delete() 发送一个HTTP DELETE请求。
    requests.head() 发送一个HTTP HEAD请求。
    requests.patch() 发送一个HTTP PATCH请求。
    requests.options() 发送一个HTTP OPTIONS请求。

  • 3.请求参数[9]
    params 用于传递URL查询参数。
    data 用于传递表单数据。
    json 用于传递JSON格式的数据。
    files 用于传递文件数据。
    headers 用于设置请求头。
    cookies 用于设置cookies。
    auth 用于设置认证信息。
    timeout 用于设置请求超时时间。
    proxies 用于设置代理。

3.响应(Response)[8]

  • 1.响应内容[5]
    response.text 获取响应内容,以Unicode编码解析。
    response.content 获取响应内容,以字节形式返回。
    response.json() 将响应内容解析为JSON格式。
    response.raw 获取原始响应内容。
    response.raise_for_status() 如果响应状态码不是200,则抛出异常。

  • 2.响应信息[3]

  • response.status_code 获取响应状态码。

  • response.headers 获取响应头。

  • response.cookies 获取响应的cookies。

4.进阶------会话(Session)[15]

  • 1.创建会话[1]
    requests.Session() 创建一个会话对象。

  • 2.会话方法[8]
    session.get() 发送一个HTTP GET请求。
    session.post() 发送一个HTTP POST请求。
    session.put() 发送一个HTTP PUT请求。
    session.delete() 发送一个HTTP DELETE请求。
    session.head() 发送一个HTTP HEAD请求。
    session.patch() 发送一个HTTP PATCH请求。
    session.options() 发送一个HTTP OPTIONS请求。
    session.request() 发送一个HTTP请求,可以指定请求的方法、URL、请求头、请求体、参数和cookies。

  • 3.会话特性[6]
    session.auth 会话级别的认证信息。
    session.headers 会话级别的请求头。
    session.cookies 会话级别的cookies。
    session.proxies 会话级别的代理。
    session.verify SSL证书验证。
    session.cert SSL证书。

以上是requests库中常用的函数及其功能分类和简短功能说明。

  1. 查看函数帮助文档
# requests.status_codes?
# requests.request?
# requests.get?
# req = requests.request('GET', 'https://httpbin.org/get')
# req

1.2 发送请求

  • requests.get() 主要用于获取数据,常用于获取网页内容、API 数据等,它将请求参数放在 URL 的查询字符串中发送给服务器。

  • requests.post() 主要用于向服务器提交数据,常用于提交表单、上传文件等操作,它将请求参数放在请求体中发送给服务器

1.requests.get() 适用场景:

  • 获取数据,如获取网页内容、API 数据等;
  • 请求不需要修改服务器状态的操作,如查询操作;
  • 请求数据量较小,可以放在 URL 的查询字符串中发送

2.requests.post() 适用场景:

  • 向服务器提交数据,如表单数据、JSON 数据、上传文件等;
  • 请求需要修改服务器状态的操作,如添加、修改、删除等;
  • 请求数据量较大,超过 URL 最大长度限制的情况。
r_get = requests.get('https://tsxy.zuel.edu.cn/')
r_get
输出:<Response [200]>

# 提交数据
url = 'http://httpbin.org/post'
data = {'key1': 'value1', 'key2': 'value2'}
r_post1 = requests.post(url, data=data)
print(r_post1)# 上传文件
url_2 = 'http://example.com/upload'
files = {'file': open('./data/test.txt', 'rb')}
r_post2 = requests.post(url_2, files=files)
print(r_post2)#添加请求头
url_3 = 'http://example.com/api'
data = {'key1': 'value1', 'key2': 'value2'}
headers = {'User-Agent': 'Mozilla/5.0'}
r_post3 = requests.post(url_3, data=data, headers=headers)
print(r_post3)
输出:略
输出:<Response [200]>
r_put = requests.put('http://httpbin.org/put', data = {'key':'value'})
r_delete = requests.delete('http://httpbin.org/delete')
r_head = requests.head('http://httpbin.org/get')
r_options = requests.options('http://httpbin.org/get')

1.3 传递URL参数

param1 = {'key1': 'v1', 'key2': 'v2'}
r1 = requests.get("http://httpbin.org/get", params=param1)
print(r1.url)param2 = {'key1': 'v1', 'key2': ['v21', 'v22']}
r2 = requests.get("http://httpbin.org/get", params=param2)
print(r2.url)param3 = {'key1': 'v1', 'key2':None}
r3 = requests.get("http://httpbin.org/get", params=param3)
print(r3.url)
输出:http://httpbin.org/get?key1=v1&key2=v2http://httpbin.org/get?key1=v1&key2=v21&key2=v22http://httpbin.org/get?key1=v1
# 二进制数据
from PIL import Image
from io import BytesIO
# %matplotlib inline
url_img='https://tsxy.zuel.edu.cn/_upload/article/images/eb/3e/c1313d8b4e63bc6c7f63bc3a8b41/70332b8b-83cb-4bd8-9969-fc732ec041cb.jpg'
r4 = requests.get(url_img)
image = Image.open(BytesIO(r4.content))
# image.save('./data/tsxy1.jpg')
image.show()
# json处理
import json
r5 = requests.get('https://github.com/timeline.json')
print(type(r5.json))
print(r5.text)
输出:---------------------------------------------------------------------------**TimeoutError**                            Traceback (most recent call last)**ConnectionError:** HTTPSConnectionPool(host='github.com', port=443): Max retries exceeded with url: /timeline.json (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x0000020154C063C8>: Failed to establish a new connection: [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。',))
# 原始数据处理
url_img2='https://tsxy.zuel.edu.cn/_upload/article/images/5a/e7/cdd4223e4a9d92a5966553e99b14/4a6e9d9f-1c27-4e2a-af2e-d48833d76932.jpg'
r6 = requests.get(url_img2, stream = True)
with open('./data/tsxy2.jpg', 'wb+') as f:for d in r6.iter_content(1024):f.write(d)
# 提交表单form = {'username':'user', 'password':'pass'}
r7 = requests.post('http://httpbin.org/post', data = form)
print(r7.text)
print('~~~~~~~~~~~~~~~~~~~~~~~~~')
r8 = requests.post('http://httpbin.org/post', data = json.dumps(form))
print(r8.text)
输出:略
# cookieurl9 = 'http://www.baidu.com'
r9 = requests.get(url9)
cookies = r9.cookies
for k, v in cookies.get_dict().items():print(k, v)print('~~~~~~~~~~~~~~~~~~~~~~~~~')
cookies = {'c1':'v1', 'c2': 'v2'}
r10 = requests.get('http://httpbin.org/cookies', cookies = cookies)
print(r10.text)
输出:BDORZ 27315~~~~~~~~~~~~~~~~~~~~~~~~~{"cookies": {"c1": "v1", "c2": "v2"}}
# 重定向和重定向历史r11 = requests.head('https://tsxy.zuel.edu.cn/', allow_redirects = True)
print(r11.url)
print(r11.status_code)
print(r11.history)
输出:https://tsxy.zuel.edu.cn/200[]

1.4 获取响应内容

url = 'https://tsxy.zuel.edu.cn/4804/list.htm'
r12 = requests.get(url)
# r.encoding = 'utf-8'
print(r12.text)
print('状态码:',r12.status_code)
print('编码方式:',r12.encoding)
print('头文件:\\n',r12.headers)
print('cookies:\\n',r12.cookies)
print(r12.content)
输出:略
print(r5.json())
print('')
print(r6.raw)
print('')
print(r6.raw.read(5))
输出:略
# 为方便引用,Requests还附带了一个内置的状态码查询对象:
r12.status_code == requests.codes.ok
print(r12.status_code)
print(r12.raise_for_status())
输出:True200None
r13 = requests.get('http://httpbin.org/status/404')
print(r13.status_code)
# r13.raise_for_status()
输出:404
# 访问服务器返回给我们的响应头部信息
print(r12.headers)
#HTTP 头部是大小写不敏感的,不区分大小写
print(r12.headers['Content-Type'])
print(r12.headers.get('content-type'))
输出:{'Date': 'Wed, 12 Apr 2023 03:39:20 GMT', 'Content-Type': 'text/html', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=30', 'X-Frame-Options': 'SAMEORIGIN', 'Frame-Options': 'SAMEORIGIN', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '1;mode=block', 'Content-Security-Policy': "default-src * 'unsafe-inline' 'unsafe-eval'", 'Referrer-Policy': 'unsafe-url', 'X-Download-Options': 'noopen', 'STRICT-TRANSPORT-SECURITY': 'max-age=16070400; includeSubDomains', 'X-Permitted-Cross-Domain-Policies': 'master-only', 'Server': '******', 'Expires': 'Wed, 12 Apr 2023 03:49:20 GMT', 'Cache-Control': 'max-age=600', 'X-Cache': 'EXPIRED'}text/htmltext/html
# 得到发送到服务器的请求的头部
r12.request.headers
输出:{'User-Agent': 'python-requests/2.27.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

2.Beautiful Soup模块

  • 安装:

pip install beautifulsoup4

pip install bs4

  • 引用:from bs4 import BeautifulSoup

2.1 解析器

统计软件与数据分析Lesson9----爬虫解析库Beautiful Soup

将一段文档传入BeautifulSoup 的构造方法,就能得到一个文档的对象, 可以传入一段字符串或一个文件句柄.

BeautifulSoup第一个参数应该是要被解析的文档字符串或是文件句柄,第二个参数用来标识怎样解析文档,目前支持:“lxml”, “html5lib”, 和 “html.parser”.目前只有 lxml 解析器支持XML文档的解析,在没有安装lxml库的情况下,创建 beautifulsoup 对象时无论是否指定使用lxml,都无法得到解析后的对象。

#加载包
from bs4 import BeautifulSoup

Beautiful Soup为不同的解析器提供了相同的接口,但解析器本身时有区别的.同一篇文档被不同的解析器解析后可能会生成不同结构的树型文档.区别最大的是HTML解析器和XML解析器,看下面片段被解析成HTML结构:

BeautifulSoup("<a><b /></a>",'html.parser')
BeautifulSoup("<a><b /></a>",'lxml')
BeautifulSoup("<a><b /></a>",'xml')
输出:<a><b></b></a><html><body><a><b></b></a></body></html><?xml version="1.0" encoding="utf-8"?><a><b/></a>

因为空标签<b />不符合HTML标准,所以’html.parser’解析器把它解析成<b></b>,'lxml’解析器把它解析出了完整的html文档

同样的文档使用XML解析如下(解析XML需要安装lxml库).注意,空标签<b />依然被保留,并且文档前添加了XML头,而不是被包含在<html>标签内。

如果被解析的HTML文档是标准格式,那么解析器之间没有任何差别,只是解析速度不同,结果都会返回正确的文档树.

BeautifulSoup("<html><body><a><b></b></a></body></html>",'html.parser')
BeautifulSoup("<html><body><a><b></b></a></body></html>",'lxml')
BeautifulSoup("<html><body><a><b></b></a></body></html>",'xml')
输出:<html><body><a><b></b></a></body></html><html><body><a><b></b></a></body></html><?xml version="1.0" encoding="utf-8"?><html><body><a><b/></a></body></html>

但是如果被解析文档不是标准格式,那么不同的解析器返回结果可能不同.下面例子中,使用lxml解析错误格式的文档,结果</p>标签被直接忽略掉了:

BeautifulSoup("<a></p>", "lxml")
输出:<html><body><a></a></body></html>
使用html5lib库解析相同文档会得到不同的结果:
BeautifulSoup("<a></p>", "html5lib")
输出:<html><head></head><body><a><p></p></a></body></html>

html5lib库没有忽略掉</p>标签,而是自动补全了标签,还给文档树添加了<head>标签.

使用pyhton内置库解析结果如下:

BeautifulSoup("<a></p>", "html.parser")
输出:<a></a>

与lxml库类似的,Python内置库忽略掉了</p>标签,与html5lib库不同的是标准库没有尝试创建符合标准的文档格式或将文档片段包含在<body>标签内,与lxml不同的是标准库甚至连<html>标签都没有尝试去添加.

因为文档片段<a></p>是错误格式,所以以上解析方式都能算作”正确”,html5lib库使用的是HTML5的部分标准,所以最接近”正确”.

不同的解析器可能影响代码执行结果.

2.2 对象类型

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:

  • BeautifulSoup
  • Tag
  • NavigableString
  • Comment

2.2.1 Beautiful Soup

soup1 = BeautifulSoup(open("test.html"),'html.parser')
print(type(soup1))
soup2 = BeautifulSoup("<html>data</html>",'html.parser')
print(type(soup2))
print(soup2)
输出:<class 'bs4.BeautifulSoup'><class 'bs4.BeautifulSoup'><html>data</html>

2.2.2 标签Tag

Tag对象与XML或HTML原生文档中的tag相同:

HTML 标签

  • HTML 标记标签通常被称为 HTML 标签 (HTML tag)。
  • HTML 标签是由尖括号包围的关键词,比如 <html>
  • HTML 标签通常是成对出现的,比如 <b></b>
  • 标签对中的第一个标签是开始标签,第二个标签是结束标签
  • 开始和结束标签也被称为开放标签和闭合标签
soup = BeautifulSoup('<b class="boldest">粗体显示</b>','lxml')
tag = soup.b
type(tag)
输出:bs4.element.Tag

Tag有很多方法和属性,在 遍历文档树 和 搜索文档树 中有详细解释.现在介绍一下tag中最重要的属性:

  • name
  • attributes

Name

每个tag都有自己的名字,通过.name 来获取:

tag.name
输出:'b'

如果改变了tag的name,那将影响所有通过当前Beautiful Soup对象生成的HTML文档:

tag.name = "blockquote"
tag
输出:<blockquote class="boldest">粗体显示</blockquote>

Attributes

一个tag可能有很多个属性. tag <b class="boldest"> 有一个 “class” 的属性,值为 “boldest” . tag的属性的操作方法与字典相同:

tag['class']
输出:['boldest']

也可以直接”点”取属性, 比如:.attrs :

tag.attrs
输出:{'class': ['boldest']}

tag的属性可以被添加,删除或修改. 再说一次, tag的属性操作方法与字典一样:

# 修改属性值
tag['class'] = 'verybold'
print(tag)
输出:<blockquote class="verybold">粗体显示</blockquote>
# 添加属性
tag['id'] = 1
tag
输出:<blockquote class="verybold" id="1">粗体显示</blockquote>
# 删除属性
del tag['class']
del tag['id']
tag
输出:<blockquote>粗体显示</blockquote>
tag['class']
print(tag.get('class'))
输出:None

多值属性

HTML 4定义了一系列可以包含多个值的属性.在HTML5中移除了一些,却增加更多.最常见的多值的属性是 class (一个tag可以有多个CSS的class). 还有一些属性 rel , rev , accept-charset , headers , accesskey . 在Beautiful Soup中多值属性的返回类型是list:

css_soup1 = BeautifulSoup('<p class="body strikeout"></p>', "lxml")
print(css_soup1.p['class'])css_soup2 = BeautifulSoup('<p class="body"></p>', "lxml")
print(css_soup2.p['class'])
输出:['body', 'strikeout']['body']

如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回

id_soup = BeautifulSoup('<p id="my id"></p>', "lxml")
id_soup.p['id']
输出:'my id'
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>', "lxml")
print(rel_soup.a['rel'])rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.a)
print(rel_soup.a['rel'])
print(rel_soup.p)
输出:['index']<a rel="index contents">homepage</a>['index', 'contents']<p>Back to the <a rel="index contents">homepage</a></p>

如果转换的文档是XML格式,那么tag中不包含多值属性

con = '<p class="body strikeout"></p>'css_soup1 = BeautifulSoup(con, "lxml")
print(css_soup1.p['class'])xml_soup = BeautifulSoup(con, 'xml')
print(xml_soup.p['class'])
输出:['body', 'strikeout']body strikeout

2.2.3 可遍历的字符串NavigableString

soup = BeautifulSoup('<b class="boldest">粗体显示</b>','lxml')
tag = soup.b
tag_content = tag.string
print(tag_content)
print(type(tag_content))
输出:粗体显示<class 'bs4.element.NavigableString'>

一个 NavigableString字符串与Python中的字符串相同,并且还支持包含在 遍历文档树 和 搜索文档树 中的一些特性. 通过 str() 方法可以直接将 NavigableString对象转换成str字符串:


tag_str = str(tag.string)
print(tag_str)print(type(tag_str))
输出:粗体显示<class 'str'>
tag中包含的字符串不能编辑,但是可以被替换成其它的字符串,用 replace_with() 方法:
tag.string = "不用粗体用斜体"
print(tag)
print(soup.b)

输出:
不用粗体用斜体
不用粗体用斜体

tag.string.replace_with("不用粗体,用斜体")
print(tag)
print(soup.b)
输出:'不用粗体用斜体'<b class="boldest">不用粗体,用斜体</b><b class="boldest">不用粗体,用斜体</b>

NavigableString 对象支持 遍历文档树搜索文档树 中定义的大部分属性, 并非全部.尤其是,一个字符串不能包含其它内容(tag能够包含字符串或是其它tag),字符串不支持 .contents.string 属性或find() 方法.

如果想在Beautiful Soup之外使用NavigableString 对象,需要调用str() 方法,将该对象转换成普通的str字符串,否则就算Beautiful Soup已经执行结束,该对象的输出也会带有对象的引用地址.这样会浪费内存.

2.2.4 注释Comment

Comment对象是一个特殊类型的NavigableString 对象:

%%HTML
<h1 class="boldest">不用粗体用斜体</h1>
<b><!--标题用粗体显示好看吗?--></b>
<b class="boldest">粗体显示</b>
输出:
<h1 class="boldest">不用粗体用斜体</h1>
<b><!--标题用粗体显示好看吗?--></b>
<b class="boldest">粗体显示</b>
markup = "<b><!--标题用粗体显示好看吗?--></b>"
soup_comment = BeautifulSoup(markup,'lxml')
print(soup_comment)
comment = soup.b.string
print(type(comment))
输出:<html><body><b><!--标题用粗体显示好看吗?--></b></body></html><class 'bs4.element.NavigableString'>

2.2.5 对象比较

两个 NavigableStringTag 对象具有相同的HTML或XML结构时, Beautiful Soup就判断这两个对象相同. 这个例子中, 2个 <b> 标签在 BS 中是相同的, 尽管他们在文档树的不同位置, 但是具有相同的表象: “<b>pizza</b>


markup = "<p>I want <b>pizza</b> and more <b>pizza</b>!</p>"soup = BeautifulSoup(markup, 'html.parser')first_b, second_b = soup.find_all('b')print(first_b == second_b)print(first_b.previous_element == second_b.previous_element)first_b.previous_elementsecond_b.previous_element
输出:TrueFalse'I want '' and more '

如果想判断两个对象是否严格的指向同一个对象可以通过 is来判断

print(first_b is second_b)
输出:False

2.2.6 复制对象

copy.copy()方法可以复制任意TagNavigableString 对象

import copymarkup = "<p>I want <b>pizza</b> and more <b>pizza</b>!</p>"
soup = BeautifulSoup(markup, 'html.parser')p_copy = copy.copy(soup.p)
print(p_copy)
输出:<p>I want <b>pizza</b> and more <b>pizza</b>!</p>
#复制后的对象跟与对象是相等的, 但指向不同的内存地址
print(soup.p == p_copy)print(soup.p is p_copy)
输出:TrueFalse

源对象和复制对象的区别是源对象在文档树中, 而复制后的对象是独立的还没有添加到文档树中. 复制后对象的效果和调用了 extract() 方法相同.

print(soup.p.parent)print(p_copy.parent)
输出:<p>I want <b>pizza</b> and more <b>pizza</b>!</p>None

这是因为相等的对象不能同时插入相同的位置.

2.3 遍历

%%HTML
<html><head><title>The Dormouse's story</title></head><body>
<p class="title"><b>The Dormouse's story</b></p><p class="story" id="link">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></body>
</html>

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.

...

通过下面的例子来演示怎样从文档的一段内容找到另一段内容

from bs4 import BeautifulSouphtml_doc = """
<html><head><title>The Dormouse's story</title></head><body>
<p class="title"><b>The Dormouse's story</b></p><p class="story" id="link">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></body>
</html>
"""soup_alice = BeautifulSoup(html_doc, 'html.parser')
print(soup_alice)

输出:

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.

...

2.3.1 子节点 .contents , .children

一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点.Beautiful Soup提供了许多操作和遍历子节点的属性.

注意: Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点

#获取故事题目
print(soup_alice.head)
print(soup_alice.title)print(soup_alice.b)
print(soup_alice.p.b)
print(soup_alice.body.p.b)
输出:<head><title>The Dormouse's story</title></head><title>The Dormouse's story</title><b>The Dormouse's story</b><b>The Dormouse's story</b><b>The Dormouse's story</b>

通过点取属性的方式只能获得当前名字的第一个tag,如果想要得到所有的<p>or<a>标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到 Searching the tree 中描述的方法,比如: find_all()

print(soup_alice.p)
print(soup_alice.find_all('p'))
输出:<p class="title"><b>The Dormouse's story</b></p>[<p class="title"><b>The Dormouse's story</b></p>, <p class="story" id="link">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>, <p class="story">...</p>]
print(soup_alice.a)
print(soup_alice.find_all('a'))
输出:<a class="sister" href="http://example.com/elsie" id="link1">Elsie</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>]

.contents.children

tag的.contents属性可以将tag的子节点以列表的方式输出:

soup_head = soup_alice.head
print(soup_head.contents)
soup_title = soup_alice.title
print(soup_title.contents)
输出:[<title>The Dormouse's story</title>]["The Dormouse's story"]
soup_a = soup_alice.find_all('a')
for a in soup_a:print(a.contents)
输出:['Elsie']['Lacie']['Tillie']

BeautifulSoup对象本身一定会包含子节点,也就是说<html>标签也是 BeautifulSoup对象的子节点:

print(soup_alice.contents)
len(soup_alice.contents)
输出:略
soup_alice.contents[1]
输出:略
print(soup_alice.contents[0].name)
输出:None
soup_alice.contents[1].name
输出:'html'
# 通过tag的 `.children` 生成器,可以对tag的子节点进行循环:
title_tag = soup_alice.head.contents[0]
print(type(title_tag))
for child in title_tag.children:print(child)
输出:<class 'bs4.element.Tag'>The Dormouse's story
p_tag = soup_alice.p.contents[0]
print(type(p_tag))
for child in p_tag.children:print(child)
输出:<class 'bs4.element.Tag'>The Dormouse's story

.string
如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点:

title_tag = soup_alice.head.contents[0]
title_tag.string
输出:"The Dormouse's story"

如果一个tag仅有一个子节点,那么这个tag也可以使用.string方法,输出结果与当前唯一子节点的 .string 结果相同:

head_tag =  soup_alice.head
head_tag.string
输出:"The Dormouse's story"

如果tag包含了多个子节点,tag就无法确定.string方法应该调用哪个子节点的内容,.string 的输出结果是None :

print(soup_alice.html.string)
输出:None

.stringsstripped_strings

如果tag中包含多个字符串,可以使用 .strings来循环获取:

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

输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings可以去除多余空白内容:

for string in soup_alice.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.''...'

全部是空格的行会被忽略掉,段首和段末的空白会被删除

2.3.2 孙节点.descendants

.contents.children 属性仅包含tag的直接子节点.例如,<head>标签只有一个直接子节点<title>

head_tag=soup_alice.head
print(head_tag)
输出:<head><title>The Dormouse's story</title></head>

但是<title>标签也包含一个子节点:字符串 “The Dormouse’s story”,这种情况下字符串 “The Dormouse’s story”也属于<head>标签的子孙节点。.descendants 属性可以对所有tag的子孙节点进行递归循环:

for child in head_tag.children:print(child)
输出:<title>The Dormouse's story</title>
for child in head_tag.descendants:print(child)
输出:<title>The Dormouse's story</title>The Dormouse's story

上面的例子中, <head>标签只有一个子节点,但是有2个子孙节点:<head>节点和<head>的子节点, BeautifulSoup 有一个直接子节点(<html>节点),却有很多子孙节点:

len(list(soup_alice.children))
list(soup_alice.children)
输出:略
len(list(soup_alice.descendants))
list(soup_alice.descendants)
输出:略

2.3.3 父节点.parent,.parents

继续分析文档树,每个tag或字符串都有父节点:被包含在某个tag中

通过 .parent 属性来获取某个元素的父节点.在例子“爱丽丝”的文档中,<head>标签是<title>标签的父节点:

title_tag = soup_alice.title
title_tag
输出:<title>The Dormouse's story</title>
title_tag.parent
输出:
<head><title>The Dormouse's story</title></head>

文档title的字符串也有父节点:<title>标签

title_tag.string.parent
输出:
<title>The Dormouse's story</title>

文档的顶层节点比如<html>的父节点是BeautifulSoup对象:

html_tag = soup_alice.html
type(html_tag.parent)
输出:bs4.BeautifulSoup

BeautifulSoup 对象的.parent 是None:

print(soup_alice.parent)
输出:None

.parents

通过元素的 .parents 属性可以递归得到元素的所有父辈节点,下面的例子使用了.parents方法遍历了<a>标签到根节点的所有节点.

a_tag = soup_alice.find_all('a')
a_tag
输出:[<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>]
link = soup_alice.afor parent in link.parents:   print(parent)print('~~~~~~~~~~~~~~~~~~~~~~`')parents_list = []   
for parent in link.parents:parents_list.append(parent.name)
print('所有父节点:',parents_list)
输出:略

2.3.4 兄弟节点 .next_sibling(s) .previous_sibling(s)

看一段简单的例子:

soup = BeautifulSoup("<a><b>text1</b><c>text2</c><d>text3</d></b></a>","lxml")
print(soup.prettify())
输出:<html><body><a><b>text1</b><c>text2</c><d>text3</d></a></body></html>

因为<b>标签和<c>标签是同一层:他们是同一个元素的子节点,所以<b><c>可以被称为兄弟节点.一段文档以标准格式输出时,兄弟节点有相同的缩进级别.在代码中也可以使用这种关系.

在文档树中,使用 .next_sibling.previous_sibling属性来查询兄弟节点:

soup.b.next_siblingsoup.d.previous_sibling
输出:<c>text2</c><c>text2</c>

<b>标签有 .next_sibling属性,但是没有.previous_sibling属性,因为<b>标签在同级节点中是第一个.同理,<d>标签有 .previous_sibling 属性,却没有 .next_sibling 属性:

print(soup.b.previous_sibling)
print(soup.d.next_sibling)
输出:NoneNone

<c>标签既有 .next_sibling属性,也有.previous_sibling属性

soup.c.next_siblingsoup.c.previous_sibling
输出:<d>text3</d><b>text1</b>

但是例子中的字符串“text1”和“text2”不是兄弟节点,因为它们的父节点不同:

soup.b.stringprint(soup.b.string.next_sibling)
输出:'text1'None

实际文档中的tag的.next_sibling.previous_sibling属性通常是字符串或空白. 看看“爱丽丝”文档,如果以为第一个<a>标签的 .next_sibling 结果是第二个<a>标签,那就错了,真实结果是第一个<a>标签和第二个<a>标签之间的顿号和换行符:

a_tag = soup_alice.a
print(a_tag)a_tag.next_sibling
输出:<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>',\\n'

第二个<a>标签是顿号的 .next_sibling属性:

a_tag.next_sibling.next_sibling
输出:<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>

.next_siblings.previous_siblings

通过 .next_siblings.previous_siblings属性可以对当前节点的兄弟节点迭代输出:

for sibling in soup_alice.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.'
for sibling in soup_alice.find(id="link3").previous_siblings:print(repr(sibling))
 输出:略' and\\n'<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>',\\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'

2.3.5 回退和前进.next_element .previous_element

看一下“爱丽丝” 文档:

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

HTML解析器把这段字符串转换成一连串的事件: “打开<html>标签”,”打开一个<head>标签”,”打开一个<title>标签”,”添加一段字符串”,”关闭<title>标签”,关闭<head>标签”,”打开<p>标签”,等等.Beautiful Soup提供了重现解析器初始化过程的方法.

.next_element属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling相同,但通常是不一样的.

这是“爱丽丝”文档中最后一个<a>标签,它的.next_sibling 结果是一个字符串,因为当前的解析过程因为当前的解析过程因为遇到了<a>标签而中断了:

last_a_tag = soup_alice.find("a", id="link3")
last_a_tag
输出:
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
last_a_tag.next_sibling
输出:';\\nand they lived at the bottom of a well.'

但这个<a>标签的 .next_element 属性结果是在<a>标签被解析之后的解析内容,不是<a>标签后的句子部分,应该是字符串”Tillie”:

last_a_tag.next_element
输出:'Tillie'

这是因为在原始文档中,字符串“Tillie” 在分号前出现,解析器先进入<a>标签,然后是字符串“Tillie”,然后关闭</a>标签,然后是分号和剩余部分.分号与<a>标签在同一层级,但是字符串“Tillie”会被先解析.

.previous_element属性刚好与.next_element 相反,它指向当前被解析的对象的前一个解析对象:

last_a_tag.previous_elementlast_a_tag.previous_element.next_element
输出:' and\\n'<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

.next_elements.previous_elements

通过 .next_elements.previous_elements 的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样:

for element in last_a_tag.next_elements:print(repr(element))
 输出:略
for element in last_a_tag.previous_elements:print(repr(element))
 输出:略

2.4 搜索

Beautiful Soup定义了很多搜索方法,这里着重介绍2个:

find()find_all() .其它方法的参数和用法类似.

2.4.1 过滤器

介绍 find_all() 方法前,先介绍一下过滤器的类型,这些过滤器贯穿整个搜索的API.过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中.

2.4.1.1字符串

最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的<b>标签:

soup_alice.find_all('b')
输出:[<b>The Dormouse's story</b>]

如果传入字节码参数,Beautiful Soup会当作UTF-8编码,可以传入一段Unicode 编码来避免Beautiful Soup解析编码出错

2.4.1.2 正则表达式

Python中的正则表达式是一个强大的工具,可以用于对字符串进行匹配和搜索。以下是关于Python正则表达式的知识点总结和示例:

基本语法

正则表达式是由一些特殊字符和普通字符组成的模式,用于描述要匹配的字符串的规则。在Python中,可以使用re模块来操作正则表达式。

下面是一些常用的正则表达式特殊字符:

字符 描述
. 匹配任意字符,除了换行符
^ 匹配字符串开头
$ 匹配字符串结尾
* 匹配前一个字符0次或多次
+ 匹配前一个字符1次或多次
? 匹配前一个字符0次或1次
{n} 匹配前一个字符n次
{n,} 匹配前一个字符至少n次
{n,m} 匹配前一个字符至少n次,但不超过m次
[…] 匹配字符集中的任意一个字符
[^…] 不匹配字符集中的任意一个字符
( ) 匹配括号内的任意表达式

re模块中常用的函数如下:

函数 描述
re.search(pattern, string, flags=0) 在字符串中查找匹配的模式,返回一个匹配对象
re.match(pattern, string, flags=0) 在字符串开头匹配模式,返回一个匹配对象
re.findall(pattern, string, flags=0) 返回字符串中所有匹配的模式,返回一个列表
re.sub(pattern, repl, string, count=0, flags=0) 用指定字符串替换字符串中所有匹配的模式
re.compile(pattern, flags=0) 将正则表达式编译成一个对象,可以重复使用

示例
下面是一些Python正则表达式的示例:

import re# 匹配字符串中的数字
pattern = r'\\d+'
string = 'hello123world456'
result = re.findall(pattern, string)
print(result)  # ['123', '456']# 匹配字符串中的日期
pattern = r'\\d{4}-\\d{2}-\\d{2}'
string = 'Today is 2023-04-11, and tomorrow is 2023-04-12.'
result = re.findall(pattern, string)
print(result)  # ['2023-04-11', '2023-04-12']# 匹配HTML标签中的文本内容
pattern = r'<.*?>(.*?)</.*?>'
string = '<h1>Hello world!</h1>'
result = re.findall(pattern, string)
print(result)  # ['Hello world!']
输出:['123', '456']['2023-04-11', '2023-04-12']['Hello world!']

以上是关于Python正则表达式的知识点总结和示例。需要注意的是,在使用正则表达式时需要注意模式的准确性,否则可能导致匹配错误或不完全。另外,在处理复杂的正则表达式时,可以使用一些在线工具或软件来辅助编写和调试正则表达式,如Regex101、PyRegex等。

如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出所有以b开头的标签,这表示<body><b>标签都应该被找到:

import re
for tag in soup_alice.find_all(re.compile("^b")):print(tag.name)
输出:bodyb
#找出所有名字中包含”t”的标签:
for tag in soup_alice.find_all(re.compile("t")):print(tag.name)
输出:htmltitle

2.4.1.3 列表

如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:

soup_alice.find_all(["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>]

2.4.1.4 True

True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点

for tag in soup_alice.find_all(True):print(tag.name)
输出:htmlheadtitlebodypbpaaap

2.4.1.5 自定义方法

如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数,如果这个方法返回True 表示当前元素匹配并且被找到,如果不是则反回 False

下面方法校验了当前元素,如果包含class属性却不包含 id 属性,那么将返回 True:

def has_class_but_no_id(tag):return tag.has_attr('class') and not tag.has_attr('id')

将这个方法作为参数传入find_all() 方法,将得到所有<p>标签:

soup_alice.find_all(has_class_but_no_id)
输出:[<p class="title"><b>The Dormouse's story</b></p>, <p class="story">...</p>]

返回结果中只有<p>标签没有<a>标签,因为<a>标签还定义了”id”,没有返回<html><head>,因为<html><head>中没有定义”class”属性.

通过一个方法来过滤一类标签属性的时候, 这个方法的参数是要被过滤的属性的值, 而不是这个标签. 下面的例子是找出href 属性不符合指定正则的 a 标签.

soup_alice
输出:
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story" id="link">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>
<p class="story">...</p>
</body>
</html>
def not_lacie(href):return href and not re.compile("lacie").search(href)
soup_alice.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):return (isinstance(tag.next_element, NavigableString)and isinstance(tag.previous_element, NavigableString))for tag in soup_alice.find_all(surrounded_by_strings):print(tag.name)
输出:bodypaaap

2.4.2搜索find_all()

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

find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.这里有几个例子:

soup_alice.find_all("title")soup_alice.find_all("p", "title")soup_alice.find_all("a")soup_alice.find_all(id="link2")import re
soup_alice.find(string=re.compile("sisters"))
输出:[<title>The Dormouse's story</title>][<p class="title"><b>The Dormouse's story</b></p>][<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>][<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]'Once upon a time there were three little sisters; and their names were\\n'

对比soup_alice.find_all("title")soup_alice.find_all("p", "title") 为什么find_all("title")返回的是<title>标签 ,但find_all("p", "title") 返回的是CSS Class为”title”的<p>标签?

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

2.4.2.1 name 参数

name参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉.

简单的用法如下:

soup_alice.find_all("title")
输出:[<title>The Dormouse's story</title>]

注意: 搜索name参数的值可以使任一类型的 过滤器、字符串、正则表达式、列表、自定义方法或是True.

2.4.2.2 keyword 参数

如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为id 的参数,Beautiful Soup会搜索每个tag的”id”属性.

soup_alice.find_all(id='link2')
输出:[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

如果传入href 参数,Beautiful Soup会搜索每个tag的”href”属性:

soup_alice.find_all(href=re.compile("elsie"))
输出:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

搜索指定名字的属性时可以使用的参数值包括:字符串、 正则表达式、 列表,、 True .

下面的例子在文档树中查找所有包含 id 属性的tag,无论 id 的值是什么:

soup_alice.find_all(id=True)
输出:[<p class="story" id="link">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>,<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>]

使用多个指定名字的参数可以同时过滤tag的多个属性:

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

有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:

data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
data_soup.find_all(data-foo="value")

2.4.2.3 CSS搜索

按照CSS类名搜索tag的功能非常实用,但标识CSS类名的关键字class 在Python中是保留字,使用class做参数会导致语法错误.从Beautiful Soup的4.1.1版本开始,可以通过class_ 参数搜索有指定CSS类名的tag:

soup_alice.find_all("a", class="sister")
soup_alice.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>]

class_ 参数同样接受不同类型的过滤器:字符串、正则表达式、自定义方法或 True :

soup_alice.find_all(class_=re.compile("itl"))
输出:[<p class="title"><b>The Dormouse's story</b></p>]
#查找"class"值为6个字符的tagdef has_six_characters(css_class):return css_class is not None and len(css_class) == 6soup_alice.find_all(class_=has_six_characters)
输出:[<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>]

tag的class 属性是 多值属性 .按照CSS类名搜索tag时,可以分别搜索tag中的每个CSS类名:

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

搜索 class 属性时也可以通过CSS值完全匹配:

css_soup.find_all("p", class_="body strikeout")
输出:[<p class="body strikeout"></p>]

完全匹配 class 的值时,如果CSS类名的顺序与实际不符,将搜索不到结果:

css_soup.find_all("p", class_="strikeout body")
输出:[]

2.4.2.4 attrs参数

soup_alice.find_all("a", attrs={"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>]

2.4.2.5 string 参数

通过string 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样,string 参数接受字符串、正则表达式、列表、True . 看例子:

soup_alice.find_all(string="Elsie")soup_alice.find_all(string=["Tillie", "Elsie", "Lacie"])soup_alice.find_all(string=re.compile("Dormouse"))
输出:['Elsie']['Elsie', 'Lacie', 'Tillie']["The Dormouse's story", "The Dormouse's story"]
def is_the_only_string_within_a_tag(s):"""Return True if this string is the only child of its parent tag."""return (s == s.parent.string)soup_alice.find_all(string=is_the_only_string_within_a_tag)
输出:["The Dormouse's story","The Dormouse's story",'Elsie','Lacie','Tillie','...']

虽然 string参数用于搜索字符串,还可以与其它参数混合使用来过滤tag.Beautiful Soup会找到 .string 方法与string参数值相符的tag.下面代码用来搜索内容里面包含“Elsie”的<a>标签:

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

2.4.2.6 limit 参数

find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用limit参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到limit 的限制时,就停止搜索返回结果.

文档树中有3个tag符合搜索条件,但结果只返回了2个,因为我们限制了返回数量:

soup_alice.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>]
soup_alice.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>]

2.4.2.7 recursive 参数

调用tag的find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数recursive=False .

soup_alice
   输出:<html><head><title>The Dormouse's story</title></head><body><p class="title"><b>The Dormouse's story</b></p><p class="story" id="link">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><p class="story">...</p></body></html>
soup_alice.html.find_all("title", recursive=False)
输出:[]

<title>标签在 <html> 标签下, 但并不是直接子节点, <head> 标签才是直接子节点. 在允许查询所有后代节点时 Beautiful Soup 能够查找到 <title> 标签. 但是使用了 recursive=False 参数之后,只能查找直接子节点,这样就查不到 <title> 标签了.

Beautiful Soup 提供了多种DOM树搜索方法. 这些方法都使用了类似的参数定义. 比如这些方法: find_all(): name, attrs, text, limit. 但是只有 find_all()find() 支持 recursive参数.

2.4.2.8 调用tag

像调用 find_all() 一样调用tag
find_all() 几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all()方法相同,下面两行代码是等价的:

soup_alice.find_all("a")
soup_alice("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>][<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_alice.title.find_all(string=True)
soup_alice.title(string=True)
输出:["The Dormouse's story"]["The Dormouse's story"]

2.4.3 搜索find()

2.4.3.1 find()

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

find_all()方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个<body>标签,那么使用 find_all()方法来查找<body>标签就不太合适, 使用 find_all方法并设置 limit=1 参数不如直接使用 find() 方法.

下面两行代码是等价的:

soup_alice.find_all('title', limit=1)soup_alice.find('title')
[<title>The Dormouse's story</title>]
<title>The Dormouse's story</title>

唯一的区别是:

  • find_all() 方法的返回结果是值包含一个元素的列表,而 find()方法直接返回结果.

  • find_all()方法没有找到目标是返回空列表,find() 方法找不到目标时,返回None .

print(soup_alice.find_all("nosuchtag"))
print(soup_alice.find("nosuchtag"))
[]
None

soup_alice.head.title 是 tag的名字方法的简写.这个简写的原理就是多次调用当前tag的find()方法:

soup_alice.head.titlesoup_alice.find("head").find("title")
<title>The Dormouse's story</title>
<title>The Dormouse's story</title>

2.4.3.2 find_parents() 和 find_parent()

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

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

我们已经用了很大篇幅来介绍 find_all()find() 方法,Beautiful Soup中还有10个用于搜索的API.其中有五个用的是与find_all() 相同的搜索参数,另外5个与find()方法的搜索参数类似.区别仅是它们搜索文档的不同部分.

注意: find_all()find()只搜索当前节点的所有子节点,孙子节点等. find_parents()find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容. 我们从一个文档中的一个叶子节点开始:

a_string = soup_alice.find(string="Lacie")
a_string
输出:'Lacie'
a_string.find_parents("a")
a_string.find_parent("p")
a_string.find_parents("p", class_="title")
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]<p class="story" id="link">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>[]

文档中的一个<a>标签是是当前叶子节点的直接父节点,所以可以被找到.还有一个<p>标签是目标叶子节点的间接父辈节点,所以也可以被找到.包含class值为”title”的<p>标签不是不是目标叶子节点的父辈节点,所以通过find_parents()方法搜索不到.

find_parent()find_parents()方法会让人联想到 .parent .parents 属性.它们之间的联系非常紧密.搜索父辈节点的方法实际上就是对 .parents 属性的迭代搜索.

2.4.3.3 find_next_siblings() 和 find_next_sibling()

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

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

这2个方法通过 .next_siblings 属性对当tag的所有后面解析的兄弟tag节点进行迭代,其中,

  • find_next_siblings() 方法返回所有符合条件的后面的兄弟节点,
  • find_next_sibling() 只返回符合条件的后面的第一个tag节点.
first_link = soup_alice.afirst_linkfirst_link.find_next_siblings("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>]
first_story_paragraph = soup_alice.find("p", "story")
first_story_paragraph
first_story_paragraph.find_next_sibling("p")
<p class="story" id="link">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><p class="story">...</p>

2.4.3.4 find_previous_siblings() 和 find_previous_sibling()

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

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

这2个方法通过 .previous_siblings 属性对当前tag的前面解析的兄弟tag节点进行迭代,其中,

  • find_previous_siblings() 方法返回所有符合条件的前面的兄弟节点
  • find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点
last_link = soup_alice.find("a", id="link3")
last_link
last_link.find_previous_siblings("a")
输出:<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
first_story_paragraph = soup_alice.find("p", "story")
first_story_paragraph
first_story_paragraph.find_previous_sibling("p")
输出:<p class="story" id="link">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><p class="title"><b>The Dormouse's story</b></p>

2.4.3.5 find_all_next() 和 find_next()

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

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

这2个方法通过 .next_elements 属性对当前tag的之后的 tag 和字符串进行迭代, 其中,

  • find_all_next()方法返回所有符合条件的节点
  • find_next() 方法返回第一个符合条件的节点
first_link = soup_alice.a
first_linkfirst_link.find_all_next(string=True)first_link.find_next("p")
输出:<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>['Elsie',',\\n','Lacie',' and\\n','Tillie',';\\nand they lived at the bottom of a well.','\\n','...','\\n','\\n','\\n']<p class="story">...</p>

第一个例子中,字符串 “Elsie”也被显示出来,尽管它被包含在我们开始查找的<a>标签的里面.第二个例子中,最后一个<p>标签也被显示出来,尽管它与我们开始查找位置的<a>标签不属于同一部分.例子中,搜索的重点是要匹配过滤器的条件,并且在文档中出现的顺序而不是开始查找的元素的位置.

2.4.3.6 find_all_previous() 和 find_previous()

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

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

这2个方法通过 .previous_elements 属性对当前节点前面的tag和字符串进行迭代, find_all_previous()方法返回所有符合条件的节点, find_previous()方法返回第一个符合条件的节点.

first_link = soup_alice.a
first_linkfirst_link.find_all_previous("p")first_link.find_previous("title")
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>[<p class="story" id="link">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>,<p class="title"><b>The Dormouse's story</b></p>]<title>The Dormouse's story</title>

find_all_previous("p") 返回了文档中的第一段(class=”title”的那段),但还返回了第二段,<p>标签包含了我们开始查找的<a>标签.不要惊讶,这段代码的功能是查找所有出现在指定<a>标签之前的<p>标签,因为这个<p>标签包含了开始的<a>标签,所以<p>标签一定是在<a>之前出现的.

2.4.4 CSS选择器

Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html,

2.4.4.1 .select()

TagBeautifulSoup 对象的 .select() 方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:

soup_alice.select("title")
soup_alice.select("p")
输出:[<title>The Dormouse's story</title>][<p class="title"><b>The Dormouse's story</b></p>,<p class="story" id="link">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>,<p class="story">...</p>]

2.4.4.2 通过tag标签逐层查找

逐级tag之间用空格连接

soup_alice.select("body a")soup_alice.select("html head title")
输出:[<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>][<title>The Dormouse's story</title>]

2.4.4.3 找某个tag标签下的直接子标签

soup_alice.select("head > title")soup_alice.select("p > a")soup_alice.select("p > a:nth-of-type(2)")#nth-of-type(2)定义输出所有a标签的第二个标签soup_alice.select("p > #link1")soup_alice.select("body > a")
输出:[<title>The Dormouse's story</title>][<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>][<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>][<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>][]

2.4.4.4 找兄弟节点标签

soup_alice.select("#link1 ~ .sister")soup_alice.select("#link1 + .sister")

输出:
[Lacie,
Tillie]

[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

2.4.4.5 通过CSS的类名查找

soup_alice.select(".sister")soup_alice.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>][<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>]

2.4.4.6 通过tag的id查找

soup_alice.select("#link1")soup_alice.select("a#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>]

2.4.4.7 同时用多种CSS选择器查询元素:

soup_alice.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>]

2.4.4.8 通过是否存在某个属性来查找

soup_alice.select('a[href]')
输出:[<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>]

2.4.4.9 通过属性的值来查找

soup_alice.select('a[href="http://example.com/elsie"]')soup_alice.select('a[href^="http://example.com/"]')
输出:[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</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_alice.select('a[href$="tillie"]')soup_alice.select('a[href*=".com/el"]')
输出:[<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>][<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

2.4.4.10通过语言设置来查找

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,'lxml')multilingual_soup.select('p[lang|=en]')
输出:[<p lang="en">Hello</p>,<p lang="en-us">Howdy, y'all</p>,<p lang="en-gb">Pip-pip, old fruit</p>]

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

soup_alice.select_one(".sister")
输出:<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

对于熟悉CSS选择器语法的人来说这是个非常方便的方法.Beautiful Soup也支持CSS选择器API, 如果你仅仅需要CSS选择器的功能,那么直接使用 lxml 也可以, 而且速度更快,支持更多的CSS选择器语法,但Beautiful Soup整合了CSS选择器的语法和自身方便使用API.

2.5 输出

本章节主要介绍Beautiful Soup4 输出相关内容:

  • 格式化输出
  • 压缩输出
  • 输出格式
  • get_text()

2.5.1 格式化输出

prettify()方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup,'lxml')
soup.prettify()
print('格式化输出:')
print(soup.prettify())
输出:'<html>\\n <body>\\n  <a href="http://example.com/">\\n   I linked to\\n   <i>\\n    example.com\\n   </i>\\n  </a>\\n </body>\\n</html>'格式化输出:<html><body><a href="http://example.com/">I linked to<i>example.com</i></a></body></html>
# `BeautifulSoup` 对象和它的tag节点都可以调用`prettify()` 方法:print(soup.a.prettify())
输出:<a href="http://example.com/">I linked to<i>example.com</i></a>

2.5.2 压缩输出

如果只想得到结果字符串,不重视格式,那么可以对一个BeautifulSoup 对象或 Tag 对象使用Python的 str()方法:

str(soup)
输出:'<html><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'
str(soup.a)
输出:'<a href="http://example.com/">I linked to <i>example.com</i></a>'

str() 方法返回UTF-8编码的字符串,可以指定编码的设置.

还可以调用encode()方法获得字节码或调用decode()方法获得Unicode.

2.5.3 输出格式

Beautiful Soup输出是会将HTML中的特殊字符转换成str,比如s双引号:“&lquot;”:

soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.",'lxml')
soup
str(soup)
输出:<html><body><p>“Dammit!” he said.</p></body></html>'<html><body><p>“Dammit!” he said.</p></body></html>'

2.5.4 get_text()

如果只想得到tag中包含的文本内容,那么可以调用get_text()方法,这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为字符串返回:

markup = '<a href="http://example.com/">\\nI linked to <i>example.com</i>\\n</a>'
soup = BeautifulSoup(markup,'lxml')
soup.get_text()
soup.i.get_text()
输出:'\\nI linked to example.com\\n''example.com'
#可以通过参数指定tag的文本内容的分隔符:
soup.get_text(" # ")
输出:'\\nI linked to  # example.com # \\n'
#还可以去除获得文本内容的前后空白:
soup.get_text("#", strip=True)
输出:'I linked to#example.com'
# 或者使用 .stripped_strings 生成器,获得文本列表后手动处理列表:[text for text in soup.stripped_strings]
输出:['I linked to', 'example.com']

2.6 编码

任何HTML或XML文档都有自己的编码方式,比如ASCII 或 UTF-8,但是使用Beautiful Soup解析后,文档都被转换成了Unicode:

markup = "<h1>Sacr\\xc3\\xa9 bleu!</h1>"soup = BeautifulSoup(markup,'lxml')soup.h1soup.h1.string
输出:<h1>Sacré bleu!</h1>'Sacré bleu!'
markup2 = b"<h1>\\xed\\xe5\\xec\\xf9</h1>"
soup2 = BeautifulSoup(markup2,'lxml')
soup2.h1
输出:<h1>νεμω</h1>

2.6.1 自动识别编码.original_encoding

这不是魔术(但很神奇),Beautiful Soup用了 编码自动检测_ 子库来识别当前文档编码并转换成Unicode编码. BeautifulSoup对象的 .original_encoding属性记录了自动识别编码的结果

print(soup2.original_encoding)
输出:ISO-8859-7

2.6.2 指定编码方式 from_encoding

通过传入 from_encoding 参数来指定编码方式:

markup2 = b"<h1>\\xed\\xe5\\xec\\xf9</h1>"
soup3 = BeautifulSoup(markup2,'lxml',from_encoding="iso-8859-8")soup3.h1
soup3.original_encoding
输出:<h1>םולש</h1>'iso-8859-8'

2.6.3 猜测编码方式

如果仅知道文档采用了Unicode编码, 但不知道具体编码. 可以先自己猜测, 猜测错误(依旧是乱码)时, 可以把错误编码作为 exclude_encodings 参数, 这样文档就不会尝试使用这种编码了解码了.

:在没有指定编码的情况下, BS会自己猜测编码, 把不正确的编码排除掉, BS就更容易猜到正确编码.

markup2 = b"<h1>\\xed\\xe5\\xec\\xf9</h1>"
soup4 = BeautifulSoup(markup2,'lxml', exclude_encodings=["ISO-8859-7"])
soup4.h1
soup4.original_encoding
输出:<h1>íåìù</h1>'windows-1252'

猜测结果是 Windows-1255 编码, 猜测结果可能不够准确, 但是 Windows-1255 编码是 ISO-8859-8 的扩展集, 所以猜测结果已经十分接近了, 并且不影响使用. (exclude_encodings 参数是 4.4.0版本的新功能)

少数情况下(通常是UTF-8编码的文档中包含了其它编码格式的文件),想获得正确的Unicode编码就不得不将文档中少数特殊编码字符替换成特殊Unicode编码,“REPLACEMENT CHARACTER” (U+FFFD, �). 如果Beautifu Soup猜测文档编码时作了特殊字符的替换,那么Beautiful Soup会把 UnicodeDammitBeautifulSoup 对象的 .contains_replacement_characters 属性标记为 True .这样就可以知道当前文档进行Unicode编码后丢失了一部分特殊内容字符.如果文档中包含�而 .contains_replacement_characters 属性是False ,则表示�就是文档中原来的字符,不是转码失败.

2.6.4 输出编码

通过Beautiful Soup输出文档时,不管输入文档是什么编码方式,输出编码均为UTF-8编码,下面例子输入文档是Latin-1编码:

markup3 = b'''
<html><head><meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" /></head><body><p>Sacr\\xe9 bleu!</p></body>
</html>
'''soup5 = BeautifulSoup(markup3,'lxml')
print(soup5.prettify())
输出:<html><head><meta content="text/html; charset=utf-8" http-equiv="Content-type"/></head><body><p>Sacré bleu!</p></body></html>

注意:输出文档中的<meta>标签的编码设置已经修改成了与输出编码一致的UTF-8.

如果不想用UTF-8编码输出,可以将编码方式传入 prettify()函数:

print(soup5.prettify("latin-1"))

还可以调用 BeautifulSoup对象或任意节点的 encode()方法,就像Python的字符串调用encode() 方法一样:

soup5.p.encode("latin-1")soup5.p.encode("utf-8")
输出:b'<p>Sacr\\xe9 bleu!</p>'b'<p>Sacr\\xc3\\xa9 bleu!</p>'