全局设定
%config InteractiveShell.ast_node_interactivity = 'all'
Python 内置了 requests 模块,该模块主要用来发 送 HTTP 请求,requests 模块比 urllib 模块更简洁。
import requests
print(len(dir(requests)))
dir(requests)
69
['ConnectTimeout',
'ConnectionError',
略]
requests是一个Python第三方库,用于发送HTTP请求。以下是requests库中常用的函数及其功能分类和简短功能说明:
1.异常和错误情况[11]
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库中常用的函数及其功能分类和简短功能说明。
- 查看函数帮助文档
# requests.status_codes?
# requests.request?
# requests.get?
# req = requests.request('GET', 'https://httpbin.org/get')
# req
requests.get() 主要用于获取数据,常用于获取网页内容、API 数据等,它将请求参数放在 URL 的查询字符串中发送给服务器。
requests.post() 主要用于向服务器提交数据,常用于提交表单、上传文件等操作,它将请求参数放在请求体中发送给服务器
1.requests.get() 适用场景:
2.requests.post() 适用场景:
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')
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=v2
http://httpbin.org/get?key1=v1&key2=v21&key2=v22
http://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(': 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)
输出:略
# cookie
url9 = '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
[]
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())
输出:
True
200
None
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/html
text/html
# 得到发送到服务器的请求的头部
r12.request.headers
输出:
{'User-Agent': 'python-requests/2.27.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
pip install beautifulsoup4
pip install bs4
将一段文档传入BeautifulSoup 的构造方法,就能得到一个文档的对象, 可以传入一段字符串或一个文件句柄.
BeautifulSoup
第一个参数应该是要被解析的文档字符串或是文件句柄,第二个参数用来标识怎样解析文档,目前支持:“lxml”, “html5lib”, 和 “html.parser”.目前只有 lxml 解析器支持XML文档的解析,在没有安装lxml库的情况下,创建 beautifulsoup
对象时无论是否指定使用lxml,都无法得到解析后的对象。
#加载包
from bs4 import BeautifulSoup
Beautiful Soup为不同的解析器提供了相同的接口,但解析器本身时有区别的.同一篇文档被不同的解析器解析后可能会生成不同结构的树型文档.区别最大的是HTML解析器和XML解析器,看下面片段被解析成HTML结构:
BeautifulSoup("",'html.parser')
BeautifulSoup("",'lxml')
BeautifulSoup("",'xml')
输出:
<a><b></b></a>
<html><body><a><b></b></a></body></html>
<?xml version="1.0" encoding="utf-8"?>
<a><b/></a>
因为空标签不符合HTML标准,所以’html.parser’解析器把它解析成
,'lxml’解析器把它解析出了完整的html文档
同样的文档使用XML解析如下(解析XML需要安装lxml库).注意,空标签依然被保留,并且文档前添加了XML头,而不是被包含在
标签内。
如果被解析的HTML文档是标准格式,那么解析器之间没有任何差别,只是解析速度不同,结果都会返回正确的文档树.
BeautifulSoup("",'html.parser')
BeautifulSoup("",'lxml')
BeautifulSoup("",'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解析错误格式的文档,结果
BeautifulSoup("", "lxml")
输出:
<html><body><a></a></body></html>
使用html5lib库解析相同文档会得到不同的结果:
BeautifulSoup("", "html5lib")
输出:
<html><head></head><body><a><p></p></a></body></html>
html5lib库没有忽略掉
标签.
使用pyhton内置库解析结果如下:
BeautifulSoup("", "html.parser")
输出:
<a></a>
与lxml库类似的,Python内置库忽略掉了
标签内,与lxml不同的是标准库甚至连
标签都没有尝试去添加.
是错误格式,所以以上解析方式都能算作”正确”,html5lib库使用的是HTML5的部分标准,所以最接近”正确”.
不同的解析器可能影响代码执行结果.
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
soup1 = BeautifulSoup(open("test.html"),'html.parser')
print(type(soup1))
soup2 = BeautifulSoup("data",'html.parser')
print(type(soup2))
print(soup2)
输出:
<class 'bs4.BeautifulSoup'>
<class 'bs4.BeautifulSoup'>
<html>data</html>
Tag对象与XML或HTML原生文档中的tag相同:
HTML 标签
和
soup = BeautifulSoup('粗体显示','lxml')
tag = soup.b
type(tag)
输出:
bs4.element.Tag
Tag有很多方法和属性,在 遍历文档树 和 搜索文档树 中有详细解释.现在介绍一下tag中最重要的属性:
Name
每个tag都有自己的名字,通过.name
来获取:
tag.name
输出:
'b'
如果改变了tag的name,那将影响所有通过当前Beautiful Soup对象生成的HTML文档:
tag.name = "blockquote"
tag
输出:
<blockquote class="boldest">粗体显示</blockquote>
Attributes
一个tag可能有很多个属性. tag 有一个 “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('', "lxml")
print(css_soup1.p['class'])
css_soup2 = BeautifulSoup('', "lxml")
print(css_soup2.p['class'])
输出:
['body', 'strikeout']
['body']
如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回
id_soup = BeautifulSoup('', "lxml")
id_soup.p['id']
输出:
'my id'
rel_soup = BeautifulSoup('Back to the homepage
', "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 = ''
css_soup1 = BeautifulSoup(con, "lxml")
print(css_soup1.p['class'])
xml_soup = BeautifulSoup(con, 'xml')
print(xml_soup.p['class'])
输出:
['body', 'strikeout']
body strikeout
soup = BeautifulSoup('粗体显示','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已经执行结束,该对象的输出也会带有对象的引用地址.这样会浪费内存.
Comment对象是一个特殊类型的NavigableString
对象:
%%HTML
<h1 class="boldest">不用粗体用斜体</h1>
<b><!--标题用粗体显示好看吗?--></b>
<b class="boldest">粗体显示</b>
输出:
<h1 class="boldest">不用粗体用斜体</h1>
<b><!--标题用粗体显示好看吗?--></b>
<b class="boldest">粗体显示</b>
markup = ""
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'>
两个 NavigableString
或Tag
对象具有相同的HTML或XML结构时, Beautiful Soup就判断这两个对象相同. 这个例子中, 2个 标签在 BS 中是相同的, 尽管他们在文档树的不同位置, 但是具有相同的表象: “
pizza
”
markup = "I want pizza and more pizza!
"
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_element
second_b.previous_element
输出:
True
False
'I want '
' and more '
如果想判断两个对象是否严格的指向同一个对象可以通过 is
来判断
print(first_b is second_b)
输出:
False
copy.copy()
方法可以复制任意Tag
或 NavigableString
对象
import copy
markup = "I want pizza and more pizza!
"
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)
输出:
True
False
源对象和复制对象的区别是源对象在文档树中, 而复制后的对象是独立的还没有添加到文档树中. 复制后对象的效果和调用了 extract()
方法相同.
print(soup.p.parent)
print(p_copy.parent)
输出:
<p>I want <b>pizza</b> and more <b>pizza</b>!</p>
None
这是因为相等的对象不能同时插入相同的位置.
%%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 BeautifulSoup
html_doc = """
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.
...
"""
soup_alice = BeautifulSoup(html_doc, 'html.parser')
print(soup_alice)
输出:
The Dormouse's storyThe 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.
...
.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,如果想要得到所有的or
标签,或是通过名字得到比一个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对象本身一定会包含子节点,也就是说标签也是
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
.strings
和 stripped_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.'
'...'
全部是空格的行会被忽略掉,段首和段末的空白会被删除
.descendants
.contents
和 .children
属性仅包含tag的直接子节点.例如,标签只有一个直接子节点
head_tag=soup_alice.head
print(head_tag)
输出:
<head><title>The Dormouse's story</title></head>
但是
标签也包含一个子节点:字符串 “The Dormouse’s story”,这种情况下字符串 “The Dormouse’s story”也属于标签的子孙节点。
.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
上面的例子中, 标签只有一个子节点,但是有2个子孙节点:
节点和
的子节点,
BeautifulSoup
有一个直接子节点(节点),却有很多子孙节点:
len(list(soup_alice.children))
list(soup_alice.children)
输出:略
len(list(soup_alice.descendants))
list(soup_alice.descendants)
输出:略
.parent
,.parents
继续分析文档树,每个tag或字符串都有父节点:被包含在某个tag中
通过 .parent
属性来获取某个元素的父节点.在例子“爱丽丝”的文档中,标签是
标签的父节点:
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_tag.string.parent
输出:
<title>The Dormouse's story</title>
文档的顶层节点比如的父节点是
BeautifulSoup
对象:
html_tag = soup_alice.html
type(html_tag.parent)
输出:
bs4.BeautifulSoup
BeautifulSoup
对象的.parent
是None:
print(soup_alice.parent)
输出:
None
.parents
通过元素的 .parents
属性可以递归得到元素的所有父辈节点,下面的例子使用了.parents
方法遍历了标签到根节点的所有节点.
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.a
for parent in link.parents:
print(parent)
print('~~~~~~~~~~~~~~~~~~~~~~`')
parents_list = []
for parent in link.parents:
parents_list.append(parent.name)
print('所有父节点:',parents_list)
输出:略
.next_sibling(s)
.previous_sibling(s)
看一段简单的例子:
soup = BeautifulSoup("text1text2 text3 ","lxml")
print(soup.prettify())
输出:
<html>
<body>
<a>
<b>
text1
</b>
<c>
text2
</c>
<d>
text3
</d>
</a>
</body>
</html>
因为标签和
标签是同一层:他们是同一个元素的子节点,所以和
可以被称为兄弟节点.一段文档以标准格式输出时,兄弟节点有相同的缩进级别.在代码中也可以使用这种关系.
在文档树中,使用 .next_sibling
和.previous_sibling
属性来查询兄弟节点:
soup.b.next_sibling
soup.d.previous_sibling
输出:
<c>text2</c>
<c>text2</c>
标签有
.next_sibling
属性,但是没有.previous_sibling
属性,因为标签在同级节点中是第一个.同理,
标签有 .previous_sibling
属性,却没有 .next_sibling
属性:
print(soup.b.previous_sibling)
print(soup.d.next_sibling)
输出:
None
None
标签既有 .next_sibling
属性,也有.previous_sibling
属性
soup.c.next_sibling
soup.c.previous_sibling
输出:
<d>text3</d>
<b>text1</b>
但是例子中的字符串“text1”和“text2”不是兄弟节点,因为它们的父节点不同:
soup.b.string
print(soup.b.string.next_sibling)
输出:
'text1'
None
实际文档中的tag的.next_sibling
和.previous_sibling
属性通常是字符串或空白. 看看“爱丽丝”文档,如果以为第一个标签的
.next_sibling
结果是第二个标签,那就错了,真实结果是第一个
标签和第二个
标签之间的顿号和换行符:
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_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'
.next_element
.previous_element
看一下“爱丽丝” 文档:
The Dormouse's story
HTML解析器把这段字符串转换成一连串的事件: “打开标签”,”打开一个
标签”,”打开一个
标签”,”添加一段字符串”,”关闭
标签”,关闭标签”,”打开
标签”,等等.Beautiful Soup提供了重现解析器初始化过程的方法.
.next_element
属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling
相同,但通常是不一样的.
这是“爱丽丝”文档中最后一个标签,它的
.next_sibling
结果是一个字符串,因为当前的解析过程因为当前的解析过程因为遇到了标签而中断了:
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.'
但这个标签的
.next_element
属性结果是在标签被解析之后的解析内容,不是
标签后的句子部分,应该是字符串”Tillie”:
last_a_tag.next_element
输出:
'Tillie'
这是因为在原始文档中,字符串“Tillie” 在分号前出现,解析器先进入标签,然后是字符串“Tillie”,然后关闭
标签,然后是分号和剩余部分.分号与
标签在同一层级,但是字符串“Tillie”会被先解析.
.previous_element
属性刚好与.next_element
相反,它指向当前被解析的对象的前一个解析对象:
last_a_tag.previous_element
last_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))
输出:略
Beautiful Soup定义了很多搜索方法,这里着重介绍2个:
find()
和 find_all()
.其它方法的参数和用法类似.
介绍 find_all()
方法前,先介绍一下过滤器的类型,这些过滤器贯穿整个搜索的API.过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中.
最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的标签:
soup_alice.find_all('b')
输出:
[<b>The Dormouse's story</b>]
如果传入字节码参数,Beautiful Soup会当作UTF-8编码,可以传入一段Unicode 编码来避免Beautiful Soup解析编码出错
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 = 'Hello world!
'
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
开头的标签,这表示和
标签都应该被找到:
import re
for tag in soup_alice.find_all(re.compile("^b")):
print(tag.name)
输出:
body
b
#找出所有名字中包含”t”的标签:
for tag in soup_alice.find_all(re.compile("t")):
print(tag.name)
输出:
html
title
如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有标签和
标签:
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>]
True
可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点
for tag in soup_alice.find_all(True):
print(tag.name)
输出:
html
head
title
body
p
b
p
a
a
a
p
如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数,如果这个方法返回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()
方法,将得到所有标签:
soup_alice.find_all(has_class_but_no_id)
输出:
[<p class="title"><b>The Dormouse's story</b></p>, <p class="story">...</p>]
返回结果中只有标签没有
标签,因为
标签还定义了”id”,没有返回
和
,因为
和
中没有定义”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)
输出:
body
p
a
a
a
p
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")
返回的是
标签 ,但find_all("p", "title")
返回的是CSS Class为”title”的标签?
find_all( name , attrs , recursive , string , **kwargs )
name
参数name
参数可以查找所有名字为 name
的tag,字符串对象会被自动忽略掉.
简单的用法如下:
soup_alice.find_all("title")
输出:
[<title>The Dormouse's story</title>]
注意: 搜索name
参数的值可以使任一类型的 过滤器、字符串、正则表达式、列表、自定义方法或是True
.
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字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('foo!')
data_soup.find_all(data-foo="value")
按照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个字符的tag
def has_six_characters(css_class):
return css_class is not None and len(css_class) == 6
soup_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('','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")
输出:
[]
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>]
通过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”的标签:
soup_alice.find_all("a", string="Elsie")
输出:
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
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>]
调用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)
输出:
[]
标签在 标签下, 但并不是直接子节点,
标签才是直接子节点. 在允许查询所有后代节点时 Beautiful Soup 能够查找到
标签. 但是使用了 recursive=False
参数之后,只能查找直接子节点,这样就查不到
标签了.
Beautiful Soup 提供了多种DOM树搜索方法. 这些方法都使用了类似的参数定义. 比如这些方法: find_all()
: name
, attrs
, text
, limit
. 但是只有 find_all()
和 find()
支持 recursive
参数.
像调用 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"]
find( name , attrs , recursive , string , **kwargs )
find_all()
方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个标签,那么使用
find_all()
方法来查找标签就不太合适, 使用
find_all
方法并设置 limit=1
参数不如直接使用 find()
方法.
下面两行代码是等价的:
soup_alice.find_all('title', limit=1)
soup_alice.find('title')
[The Dormouse's story ]
The Dormouse's story
唯一的区别是:
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.title
soup_alice.find("head").find("title")
The Dormouse's story
The Dormouse's story
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")
[Lacie]
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.
[]
文档中的一个标签是是当前叶子节点的直接父节点,所以可以被找到.还有一个
标签是目标叶子节点的间接父辈节点,所以也可以被找到.包含class值为”title”的
标签不是不是目标叶子节点的父辈节点,所以通过
find_parents()
方法搜索不到.
find_parent()
和 find_parents()
方法会让人联想到 .parent
和 .parents
属性.它们之间的联系非常紧密.搜索父辈节点的方法实际上就是对 .parents
属性的迭代搜索.
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.a
first_link
first_link.find_next_siblings("a")
Elsie
[Lacie,
Tillie]
first_story_paragraph = soup_alice.find("p", "story")
first_story_paragraph
first_story_paragraph.find_next_sibling("p")
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.
...
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>
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_link
first_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”也被显示出来,尽管它被包含在我们开始查找的标签的里面.第二个例子中,最后一个
标签也被显示出来,尽管它与我们开始查找位置的
标签不属于同一部分.例子中,搜索的重点是要匹配过滤器的条件,并且在文档中出现的顺序而不是开始查找的元素的位置.
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_link
first_link.find_all_previous("p")
first_link.find_previous("title")
Elsie
[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.
,
The Dormouse's story
]
The Dormouse's story
find_all_previous("p")
返回了文档中的第一段(class=”title”的那段),但还返回了第二段,标签包含了我们开始查找的
标签.不要惊讶,这段代码的功能是查找所有出现在指定
标签之前的
标签,因为这个
标签包含了开始的
标签,所以
标签一定是在
之前出现的.
Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html,
.select()
在 Tag
或 BeautifulSoup
对象的 .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>]
逐级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>]
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>]
[]
soup_alice.select("#link1 ~ .sister")
soup_alice.select("#link1 + .sister")
输出:
[Lacie,
Tillie]
[Lacie]
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>]
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>]
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>]
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>]
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>]
multilingual_markup = """
Hello
Howdy, y'all
Pip-pip, old fruit
Bonjour mes amis
"""
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>]
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.
本章节主要介绍Beautiful Soup4 输出相关内容:
prettify()
方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行
markup = 'I linked to example.com'
soup = BeautifulSoup(markup,'lxml')
soup.prettify()
print('格式化输出:')
print(soup.prettify())
输出:
'\n \n \n I linked to\n \n example.com\n \n \n \n'
格式化输出:
<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>
如果只想得到结果字符串,不重视格式,那么可以对一个BeautifulSoup
对象或 Tag
对象使用Python的 str()
方法:
str(soup)
输出:
'I linked to example.com'
str(soup.a)
输出:
'I linked to example.com'
str()
方法返回UTF-8编码的字符串,可以指定编码的设置.
还可以调用encode()
方法获得字节码或调用decode()
方法获得Unicode.
Beautiful Soup输出是会将HTML中的特殊字符转换成str,比如s双引号:“&lquot;”:
soup = BeautifulSoup("“Dammit!” he said.",'lxml')
soup
str(soup)
输出:
<html><body><p>“Dammit!” he said.</p></body></html>
'“Dammit!” he said.
'
如果只想得到tag中包含的文本内容,那么可以调用get_text()
方法,这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为字符串返回:
markup = '\nI linked to example.com\n'
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']
任何HTML或XML文档都有自己的编码方式,比如ASCII 或 UTF-8,但是使用Beautiful Soup解析后,文档都被转换成了Unicode:
markup = "Sacr\xc3\xa9 bleu!
"
soup = BeautifulSoup(markup,'lxml')
soup.h1
soup.h1.string
输出:
<h1>Sacré bleu!</h1>
'Sacré bleu!'
markup2 = b"\xed\xe5\xec\xf9
"
soup2 = BeautifulSoup(markup2,'lxml')
soup2.h1
输出:
<h1>νεμω</h1>
.original_encoding
这不是魔术(但很神奇),Beautiful Soup用了 编码自动检测
_ 子库来识别当前文档编码并转换成Unicode编码. BeautifulSoup
对象的 .original_encoding
属性记录了自动识别编码的结果
print(soup2.original_encoding)
输出:
ISO-8859-7
from_encoding
通过传入 from_encoding
参数来指定编码方式:
markup2 = b"\xed\xe5\xec\xf9
"
soup3 = BeautifulSoup(markup2,'lxml',from_encoding="iso-8859-8")
soup3.h1
soup3.original_encoding
输出:
<h1>םולש</h1>
'iso-8859-8'
如果仅知道文档采用了Unicode编码, 但不知道具体编码. 可以先自己猜测, 猜测错误(依旧是乱码)时, 可以把错误编码作为 exclude_encodings
参数, 这样文档就不会尝试使用这种编码了解码了.
注:在没有指定编码的情况下, BS会自己猜测编码, 把不正确的编码排除掉, BS就更容易猜到正确编码.
markup2 = b"\xed\xe5\xec\xf9
"
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会把 UnicodeDammit
或BeautifulSoup
对象的 .contains_replacement_characters
属性标记为 True
.这样就可以知道当前文档进行Unicode编码后丢失了一部分特殊内容字符.如果文档中包含�而 .contains_replacement_characters
属性是False
,则表示�就是文档中原来的字符,不是转码失败.
通过Beautiful Soup输出文档时,不管输入文档是什么编码方式,输出编码均为UTF-8编码,下面例子输入文档是Latin-1编码:
markup3 = b'''
Sacr\xe9 bleu!
'''
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>
注意:输出文档中的标签的编码设置已经修改成了与输出编码一致的
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'Sacr\xe9 bleu!
'
b'Sacr\xc3\xa9 bleu!
'