前面使用过BeautifulSoup来处理返回的html文档,这个库可以让我们不需要依赖正则而找到我们所需要的内容
基础内容
基本对象
首先要了解一下一些基础的属性和方法
BeautifulSoup将html解析为树形结构
from bs4 import BeautifulSoup
# 以文件形式解析html文档
soup = BeautifulSoup(open("filePath"))
# 以字符串形式解析html文档
soup = BeautifulSoup("<html>....<html/>")
解析完成后整个文档会被转换为树形结构,每一个节点都是一个对象
整个结构中有四种对象
- Tag
Tag对象对应Html文档中的标签(tag)例如<p> <h1> <a>
等,因此Tag对象也具有名称和属性,对应标签的名字和属性
我们可以直接采用点操作符来取整个结构中的Tag对象,这种方法只会取找到的第一个符合要求的对象
soup = BeautifulSoup("<p id="p1" >This is a Tag <p/>")
# name指明了标签的名称
print(soup.p.name)
# 标签的属性则可以使用键值对的取法
print(soup.p['id'])
# 如果要取多个属性则可以使用attrs
print(soup.p.attrs)
如果同一个属性有多个值,则会返回列表
- NavigableString
可遍历的字符串,包含在Tag内部的字符串,通常就是标签所包含的文本内容
这部分不可编辑,但是可以使用replace_with()
方法替换
print(soup.p.string)
# 替换文本内容
soup.p.string.replace_with("This is a another tag")
print(soup.p.string)
在BeautifulSoup之外使用NavigableString对象时,最好unicode()转换
- BeautifulSoup
BeautifulSoup指的是整个文档的全部内容,基本可以类似为Tag,但并不是真正的Tag,所以没有name和attrs
# 仅仅包含一个值为document的特殊name属性
print(soup.name)
- Comment
上述对象几乎包含了所有的html的内容,但是有一些特殊的对象,比如文档的注释,他属于一种特殊的NavigableString类型
soup = BeautifulSoup("<b><!--Hey, buddy. Want to buy a used parser?--></b>")
comment = soup.b.string
type(comment)
基本方法
遍历文档树
子节点
一个Tag可能包含其他的Tag或者字符串,这都是这个Tag的子节点,下面我们就讨论一下遍历并操作这些子节点的方法
- 点取子节点
最简单的方法就是使用name来取我们想要的子节点,例如获取某个标签只要使用这个标签的名字
# 获取title
print(soup.title)
这个方法可以多次调用,进而获取更加内层的子孙节点
print(soup.body.p)
这种方式只会获取当前名字的第一个Tag,如果想要获得所有的当前名字Tag,则要使用搜索文档树中的方法find_all(),后续再说
- contents和children取直接子节点
Tag具有contents和children属性,也可用于获得子节点,但是只能获取其直接子节点
contents:将Tag的子节点以列表的方式输出,返回子节点列表
children:返回子节点列表的迭代器,可用于循环遍历
print(soup.body.contents)
for child in soup.body.children:
print(child)
- descendants取子孙节点
上述第二个方法只能取直接节点,这自然是不够方便的,如果我们想要遍历所有的子孙节点,就需要用到descendants属性
for child in soup.descentants:
print(child)
这个方法可以遍历所有的子孙节点
-
string取字符串节点
如果这个Tag下只有一个string类型的节点,无论他在第几个或是第几层,string属性都能够唯一的取到这个节点,相反如果有多个string类型的子节点,则会返回None -
strings或stripped_strings取多个字符串节点
strings可以获得所有字符串节点的列表
stripped_strings则可以去除空格和空白的节点,在去除换行Tag时十分有用
父节点
文档树中的Tag或者字符串都有父节点
- parent取父节点
BeautifulSoup的父节点是None
print(soup.parent)
print(soup.p.parent)
- parents递归所有父辈节点
此属性可以递归遍历到根节点,返回迭代器
兄弟节点
同一层,同一个节点的子节点互称兄弟节点,具有相同的缩进级别
- next_sibling和previous_sibling查询兄弟节点
注意自动生成的换行等也会被认为是兄弟节点,例如
<a>
标签的下一个兄弟节点一定是换行符
- next_siblings和previous_siblings对兄弟节点迭代输出
回退和前进
重现解析器初始化过程的方法,解析器把文档当作字符串连续解析
- next_element和previous_element获得解析顺序前后的节点
这两个方法会分别按照解析顺序取得之后和之前解析的节点,因为解析按照字符串顺序解析,因此这个方法获得的节点和兄弟节点可能不同 - next_elements和previous_elements获得解析前后的迭代器
搜索文档树
主要介绍两个搜索方法:find()
和find_all()
要使用这两个方法我们就需要了解他们的过滤器参数
- 字符串
可以传入一个字符串作为过滤参数,搜索时会查找完全匹配字符串的内容 - 正则表达式
也可以使用正则表达式,会使用search方法来查找正则匹配的内容 - 列表
传入列表参数,则会查找与列表中任意一项匹配的内容 - True
返回所有的Tag,但是不会返回字符串节点 - 方法
如果没有合适的过滤器,也可以定义一个方法,这个方法接受一个参数,当方法返回True时表示匹配成功,否则返回False
find_all
:
find_all(name, attrs, recursive, string, **kwargs)
搜索当前Tag的所有tag子节点并判断是否符合过滤条件
- name:可以查找所有名字为name的Tag,字符串对象自动忽略
- keword:对于指定参数名传入的参数,如果参数名不是内置的参数名,则会作为指定名字的tag的属性来搜索,例如
soup.find_all(id="1")
# 指定的参数名id并不是内置的参数名(name, attrs, recursive, string)
# 所以这个参数名会被当做tag的属性来搜索,所以这个方法会搜索所有的tag的id属性
指定参数名传入的参数可以是字符串,正则,列表,True
True表示所有包含该参数名的tag
也可以使用多个指定的名字的参数来过滤多个属性,对于h5的某些属性也可能不支持搜索,这时可以使用attrs参数来指定一个包含这些属性的字典来解决
3. css:可以按照css类名来搜索tag,但是class是保留字,因此使用class_
作为参数名
class_ 接受字符串,正则,方法,True
因为类名是多值属性,一个tag可能指定了多个css类,因此匹配时可以仅仅匹配其中的某一个类,也可以用空格分隔多个类名进行完全匹配,完全匹配时顺序敏感
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.find_all("p", class_="strikeout")
# [<p class="body strikeout"></p>]
css_soup.find_all("p", class_="body")
# [<p class="body strikeout"></p>]
css_soup.find_all("p", class_="body strikeout")
# [<p class="body strikeout"></p>]
- string:string可以搜索文档中的字符串内容,接受字符串,正则,列表,True
当string和搜索tag的参数一同使用时,会转而搜索string()方法的值与传入的string值相同的tag - limit:find_all()方法返回搜索到的全部内容,因此有可能因为文档树过大而导致执行缓慢,如果不需要全部搜索结果,则可以使用limit参数限制返回的结果数量
soup.find_all("a", limit=2)
- recursive参数
find_all()方法会搜索当前tag的所有子孙节点,如果只想搜索直接子节点,则可以用recursive=False来实现
find_all()方法有简写形式,即省略方法名
soup("a")
与soup.find_all("a")
是等价的
find
:
find_all(name, attrs, recursive, string, **kwargs)
和find_all()唯一的区别就是find()仅返回所搜到的第一个结果,而find_all()返回列表,同样搜索失败时,find()返回None,find_all()返回空列表
对于其他的方法,其参数和区别与上述两个方法类似,同时看名字也能明白其作用,不做赘述
find_parents() 和 find_parent()
搜索当前节点的父辈节点
find_next_siblings() 和 find_next_sibling()
返回所有符合条件的后面的兄弟节点,只返回符合条件的后面的第一个tag节点
find_previous_siblings() 和 find_previous_sibling()
返回所有符合条件的前面的兄弟节点,返回第一个符合条件的前面的兄弟节点:
find_all_next() 和 find_next()
返回之后所有符合条件的节点,返回之前第一个符合条件的节点
find_all_previous() 和 find_previous()
返回之前所有符合条件的节点,返回之前第一个符合条件的节点
css选择器
这是另外一个用于搜索文档树的重要方法
使用select()
方法可以用css选择器的语法来找到tag,具体的语法需要另外学习,但是确实相当方便
其实一般情况下可以在浏览器控制台直接获得css选择器字符串,这个我在之前的文章中提到过
修改文档树
- 修改Tag的名称和属性
tag.name = "another name"
tag['class'] = 'another class'
del tag['class']
del tag['id']
-
修改string
赋值即为修改 -
append()
向Tag中添加内容,追加到contents的最后 -
NavigableString()和new_tag()
添加一段文本内容到文档中也可以使用NavigableString的构造方法
soup = BeautifulSoup("<b></b>")
tag = soup.b
tag.append("Hello")
new_string = NavigableString(" there")
tag.append(new_string)
tag
# <b>Hello there.</b>
tag.contents
# [u'Hello', u' there']
还可以用来添加注释
new_comment = soup.new_string("Nice to see you.", Comment)
tag.append(new_comment)
tag
# <b>Hello there<!--Nice to see you.--></b>
tag.contents
# [u'Hello', u' there', u'Nice to see you.']
创建一个Tag使用BeautifulSoup.new_tag()
soup = BeautifulSoup("<b></b>")
original_tag = soup.b
new_tag = soup.new_tag("a", href="http://www.example.com")
original_tag.append(new_tag)
original_tag
# <b><a href="http://www.example.com"></a></b>
new_tag.string = "Link text."
original_tag
# <b><a href="http://www.example.com">Link text.</a></b>
- insert()
这个方法与append类似,区别就是可以指定插入的位置
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a
tag.insert(1, "but did not endorse ")
tag
# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
tag.contents
# [u'I linked to ', u'but did not endorse', <i>example.com</i>]
-
insert_before() 和 insert_after()
与上一个方法不同,上一个insert是在当前tag内部指定位置插入子节点,而这个方法则是在当前tag的前后插入同级别节点 -
clear()
移除当前tag的内容(全部子节点) -
extract()
将当前tag移除文档树,并作为结果返回(删除当前节点并返回) -
decompose()
将当前tag移除文档树并销毁 -
replace_with()
移除某段内容(当前节点)并用新tag或者文本节点代替 -
warp()
对指定元素进行包装并返回包装的结果
soup = BeautifulSoup("<p>I wish I was bold.</p>")
soup.p.string.wrap(soup.new_tag("b"))
# <b>I wish I was bold.</b>
soup.p.wrap(soup.new_tag("div"))
# <div><p><b>I wish I was bold.</b></p></div>
- unwarp()
移除tag内的所有的tag标签,解包装
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a
a_tag.i.unwrap()
a_tag
# <a href="http://example.com/">I linked to example.com</a>
输出
-
格式化输出
prettify()
将文档树格式化后输出,每个标签占一行,BeautifulSoup和Tag均可调用 -
压缩输出
仅仅得到文档的字符串,而不需要注重格式,可以直接将文档树对象字符串化
str(soup)
unicode(soup)
- 输出格式
略…没咋看明白,也不是很重要 - get_text()
只想得到tag包含的文本,可以调用这个方法来获取所有子孙节点的unicode格式字符串
可以指定分割字符串get_text("\")
可以指定去除空白get_text(strip=True)
可以获得列表形式的文本内容soup.stripped_strings
指定文档解析器
不同的解析器对于不标准的html文档有不同的解析结果,但是对于标准的文档解析结构都是一样,常用的有lxml,html5lib,html.parser
通常使用lxml的居多,现在对于这个无需做过多了解