https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/#
https://beautifulsoup.cn/
-
beautifulsoup4
库也称为Beautiful Soup
库或bs4
库,用于解析 HTML 或 XML 文档
-
beautifulsoup4
库是一个第三方库
-
beautifulsoup4
库可以自动将输入文档转换为 Unicode 编码,将输出文档转换为 UTF-8 编码,故不需要考虑编码方式
-
beautifulsoup4
库在解析网页时需要依赖解析器,支持的解析器:
解析器 |
使用方法 |
优点 |
缺点 |
python 标准库中的 HTML 解析器 |
BeautifulSoup(markup,'html.parser') |
python 的内置标准库,执行速度适中,文档容错能力强 |
python 2.73 或3.2.2 前的版本文档容错能力差 |
lxml HTML 解析器 |
BeautifulSoup(markup,'lxml') |
速度快,文档容错能力强 |
需要安装 C 语言库 |
lxml XML 解析器 |
BeautifulSoup(markup,'xml') |
速度快,唯一支持 XML 的解析器 |
需要安装 C 语言库 |
html5lib |
BeautifulSoup(markup,'html5lib') |
容错性最好,以浏览器的方式解析文档,生成 HTML5 格式的文档 |
速度慢,不依赖外部扩展 |
4 个对象
- beautiful soup 将复杂 HTML 文档转换成一个复杂的树形结构,每个节点都是 python 对象,所有对象可归纳为 4 种:Tag, NavigableString, BeautifulSoup, Comment
Tag 对象
-
通俗来讲, Tag 就是 HTML 中的一个个标签
-
可以利用 Tag 对象.标签名
的方式获取标签的内容,查找的是所有内容中第一个符合要求的标签
-
可以利用 Tag 对象[属性名]
的方式获取标签的属性值,返回一个 list
-
Tag 对象有 4 个常用属性:
属性 |
说明 |
name |
标签的名字,如 head、title 等,返回一个字符串 |
string |
标签所包围的文字,网页中真实的文字,返回一个字符串 |
attrs |
字典,包含了页面标签的所有属性,返回一个字典,可通过attrs['属性名'] 获取属性值 |
contents |
这个标签下所有子标签的内容(HTML 源码),返回一个列表 |
- 按照 HTML 语法,可以在标签中嵌套其他标签,因此
string
属性的返回值遵遵循如下原则:
- 如果标签内部没有其他标签, string 属性返回其中的内容
- 如果标签内部还有其他标签,但只有一个标签, string 属性返回最里面标签的内容
- 如果标签内部还有其他标签,且不止一个标签, string 属性返回 None
此外,还有一些属性:
属性 |
说明 |
children |
返回所有的子节点 |
desecndants |
返回所有子孙节点 |
parent |
返回父节点 |
parents |
返回父节点的父节点 |
next_siblings |
|
previous_siblings |
|
- .content 属性和 .childern 属性获取直接子节点,前者返回一个 list,后者返回一个 list 生成器对象,可以通过遍历获取所有子节点:
for child in soup.body.children:
print(child)
.desecndants 属性也返回一个 list 生成器对象
NavigableString 对象
- Tag 对象的 string 属性返回的内容就是 NavigableString 对象
BeautifulSoup 对象
- BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候可以当作 Tag 对象,是一个特殊的 Tag
- Comment 注释对象是一个特殊类型的 NavigableString 对象,其内容不包括注释符号
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup('<b><!--This is comment--></b>')
>>> print(type(soup.find('b').string))
<class 'bs4.element.Comment'>
-
然后可通过对象名.属性名
的形式获取该对象的第一个属性值(即节点)
> BeautifulSoup
对象的属性名与 HTML 的标签名相同
搜索文档树
find_all
find_all(name, attrs, recursive, string, limit)
find_all() 方法搜索当前 tag 的所有 tag 子节点,并判断是否符合过滤器的条件
- name:通过 HTML 标签名直接查找节点。如果 name 参数是正则表达式,则通过正则表达式的 match() 来匹配内容
- attrs:通过 HTML 标签的属性查找结点(需列出属性名和值),可以同时设置多个属性
- recursive:搜索层次,默认查找当前标签的所有子孙节点,如果只查找标签的子节点,可以使用参数
recursive=False
- string:通过关键字检索 string 属性内容,传入的形式可以是字符串,也可以是正则表达式对象
- limit:返回结果的个数,默认返回全部结果
find_all()
方法通过属性查找结点时,对于一些常用的属性(如 id 和 class 等),可以不用 attrs 字典形式来传递,而用复制的形式直接传入参数(如find_all(class_='hotnews')
,由于 class 在 python 中是一个关键字,所以后面需要加一个下划线
- 由于 find_all() 方法非常常用,所以在 bs4 中,BeautifulSoup 对象和 tag 对象可以被当作一个 find_all() 方法使用,即 bs.find_all('a') 和 bs('a') 是等效的,soup.title.find_all(text='abc') 和 soup.title(text='abc') 是等效的
import requests
from bs4 import BeautifulSoup
import re
url = ''
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
print('所有 span 节点个数:', len(soup.find_all('span')))
print('class 属性值为 “amount” 的所有 span 节点:')
for node in soup.find_all('span', attrs={'class': 'amount'}):
print(node)
print('前 3 个 class 属性值为 “amount” 的 span 节点:\n', soup.find_all('span', attrs={'class': 'amount'}, limits=3))
print('string 属性值包含 “2室1厅” 的前三个节点的文本:', soup.find_all(string=re.compile('2室1厅'), limit=3))
find
find()
方法的用法与find_all()
方法相似,但其返回结果是第一个匹配的节点
其他查询方法
find_parents()
和find_parent()
:前者返回所有祖先节点,后者返回直接父节点
find_next_siblings()
和find_next_sibling()
:前者返回后面所有的兄弟节点,后者返回后面的第一个兄弟节点
find_previous_siblings()
和find_previous_sibling()
:前者返回前面所有的兄弟节点,后者返回前面的第一个兄弟节点
find_all_previous()
和find_previous()
:前者返回节点前所有符合条件的节点,后者返回节点前第一个符合条件的节点
多值属性
-
HTML 中定义了一系列可以包含多个值的属性,最常见的多值属性是 class ,还有 rel, rev, accept-charset, headers, accesskey
-
在 Beautiful Soup 中多值属性的返回类型是 list
soup = BeautifulSoup('<p class="body strikeout"></p>')
soup.p['class']
# ["body", "strikeout"]
soup = BeautifulSoup('<p class="body"></p>')
soup.p['class']
# ["body"]
-
如果某个属性值看起来好像有多个值,但在任何版本的 HTML 定义中都没有被定义为多值属性,那么 Beautiful Soup 会将这个属性组为字符串返回
id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'
-
find_all()
和find()
方法中,根据属性筛选节点时,若某个节点的多值属性的一个属性值与给出的属性值相同,也会出现在结果中
CSS 选择器
选择器 |
示例 |
示例说明 |
.class |
.intro |
选择class="intro" 的所有节点 |
#id |
#firstname |
选择`id="firstname"的所有节点 |
* |
* |
选择所有节点 |
element |
p |
选择所有<p> 节点 |
element,element |
div,p |
选择所有<dic> 节点和所有<p> 节点 |
element element |
div p |
选择<div> 节点内部的所有<p> 节点 |
element>element |
div>p |
选择父节点为<div> 节点的所有<p> 节点 |
[attribute] |
[target] |
选择带有 target 属性的所有节点 |
[attribute=valur] |
[target=blank] |
选择 target="blank" 的所有节点 |
:link |
a:link |
选择所有未访问的链接 |
:visited |
a:visited |
选择所有已访问的链接 |
:active |
a:active |
选择活动链接 |
:first-line |
p:first-line |
选择每个<p> 节点的首行 |
element1~element2 |
p~ul |
选择前面有<p> 节点的所有<ul> 节点 |
[attribute^=value] |
a[src^="https"] |
选择其 src 属性值以 “https” 开头的所有<a> 节点 |
[attribute$=value] |
a[src$=".pdf"] |
选择其 src 属性值以 “.pdf” 结尾的所有<a> 节点 |
[attribute*=valur] |
a[src*="abc"] |
选择其 src 属性值中包含 “abc” 字串的所有<a> 节点 |
:enabled |
input:enabled |
选择每个启用的<input> 节点 |
:disabled |
input:disabled |
选择每个禁用的<input> 节点 |
:checked |
input:checked |
选择每个被选中的<input> 节点 |
import requests
from bs4 import BeautifulSoup
url = ''
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
print('div 节点下所有 a 结点的个数:', len(soup.select('div a')))
# 获取第一个 calss 属性值为 “list-main-header” 的节点下所有 a 节点
node_a = soup.select('.list-main-header')[0].select('a')
print('第一个 a 节点的 href 属性值:', node_a[0]['href'])
print('第一个 a 节点的文本:', node_a[0].string)
实例
- 在 当当网 搜索 python,爬取该网页中图书的书名、作者、出版社和价格,保存到本地
import requests
from bs4 import BeautifulSoup
def req(url):
headersvalue = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.52'}
r = requests.get(url, headers=headersvalue)
soup = BeautifulSoup(r.text, 'lxml')
return soup
url = 'http://search.dangdang.com/?key=python&act=input'
node_li = req(url).find('ul', {'class': 'bigimg'}).select('li')
book_detail = []
for li in node_li:
name = li.a.attrs['title']
author = li.find('p', class_='search_book_author').span.a.attrs['title']
publisher = li.find('a', dd_name = '单品出版社').string
price = li.find('span', class_='search_now_price').string
book_detail.append('{}, {}, {}, {}'.format(name, author, publisher, price))
with open('d:/book_detail.txt', 'w', encoding='utf-8') as f:
f.write('\n'.join(book_detail))