python - bs4
b44中文文档地址:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
《BeautifulSoup使用》
对象的种类
基本元素 | 说明 |
Tag | 标签,最基本的信息组织单元,分别是<>和</>标明开头和结尾 |
Name | 标签的名字,<p></p>的名字是/'p',格式:<tag>.name |
Attributes | 标签的属性,字典形式组织,格式:<tag>.attrs |
NavigableString | 标签内非属性字符串,<></>中字符串,格式:<tag>.string |
Comment | 标签内字符串的注释部分,一种特殊的Comment类型 |
<<BeautifulSoup的主要函数以及用法
1.创建BeautifulSoup对象
import lxml import requests from bs4 import BeautifulSoup
2.解析器的选择
python标准库 | BeautifulSoup(markup, "html.parser") | python内置的标准库 ,执行速度适中,文档容错能力强 | python2.7以及python3.2。2之前的文档容错能力差 |
lxml HTML解析器 | BeautifulSoup(markup, "lxml") | 速度快,文档容错能力强 | 需要安装C语言库需要安装C语言库 |
lxml XML解析器 | BeautifulSoup(markup, "xml") | 速度快,唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 | 速度慢,不依赖外部扩展 |
3.遍历文档树
- .contents 返回当前节点的所有子节点 返回类型是列表
- .children 返回当前节点的所有子节点 返回类型是list生成器对象
- .descendants 返回当前节点的所有子孙节点 返回类型是list生成器对象
- .parent 返回当前节点的父亲节点 返回类型是节点Tag
- .parents 返回当前节点的所有父亲节点 返回类型是list生成器对象
- .next_sibling 返回当前节点的下一个兄弟节点
- .previous_sibling 返回当前节点的所有兄弟节点 返回类型是list生成器对象
- .next_element 返回当前节点的下一个Tag
- .previous_sibling 返回当前节点的上一个节点
- .next_siblings 返回当前节点后的所有兄弟节点
- .previous_siblings 返回当前节点前的所有节点
- .string 返回当前节点标签内的内容
- 如果当前Tag包含了多个子节点Tag就无法确定 string方法应该调用哪个节点的内容
- .strings 返回多个内容 需要遍历获取
4.搜索文档
>>过滤器
介绍 find_all()
方法前,先介绍一下过滤器的类型 ,这些过滤器贯穿整个搜索的API.过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中。
>>字符串
最简单的过滤器是字符串,在搜索方法中传入一个字符串参数,BeautifulSoup会查找与字符串完整匹配的内容,下面的梨子用于查找文档中所有的<b>标签:
soup.find_all('b')
如果传入字节码参数,BeautifulSoup会当作UTF-8编码,可以传入一段Unicode编码来避免BeautifulSoup解析编码错误。
>>正则表达式
如果传入正则表达式作为参数,BeautifulSoup会通过正则表达式的math()来匹配内容,下面例子中找出所有以b开头的标签,这表示<body>和<b>标签都应该该被找到:
import re for tag in soup.find_all(re.compile("^b")): print(tag.name)
>>列表
如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:
soup.find_all(["a", "b"])
>>Ture
True
可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点:
for tag in soup.find_all(True): print(tag.name)
>>方法
如果有没有合适过滤器,那么还可以定义一个方法,只接收一个元素参数,如果这个方法返回True,表示当前元素匹配并且被找到,如果不是则返回False
下面方法检验了当前元素,如果包含class属性去不包含id属性,那么将返回Ture:
def has_class_but_no_id(tag): return tag.has_attr('class') and not tag.has_attr('id)
将这个方法作为参数传入 find_all()
方法,将得到所有<p>标签
>>find_all
find_all(name, attrs, recursive, string, **keyargs)
find_all方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件,这里有几个例子:
soup.find_all("title") soup.find_all("p", "title") soup.find_all("a") soup.find_all(id="link2") import re soup.find(string=re.compile("sisters"))
<name参数>
简单的用法如下:
soup.find_all("title")
重申:搜索name参数的值可以使任意类型的过滤器,字符串,正则表达式,列表,方法或是Ture。
<keyword参数>
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id
的参数,Beautiful Soup会搜索每个tag的”id”属性。
soup.find_all(id='link2')
有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:
但是可以通过 find_all()
方法的 attrs
参数定义一个字典参数来搜索包含特殊属性的tag:
data_soup.find_all(attrs={"data-foo": "value"})
<按Css搜索>
按照CSS类名搜索tag的功能非常实用,但标识CSS类名的关键字 class
在Python中是保留字,使用 class
做参数会导致语法错误.从Beautiful Soup的4.1.1版本开始,可以通过 class_
参数搜索有指定CSS类名的tag:
soup.find_all("a", class_="sister")
class_
参数同样接受不同类型的 过滤器
,字符串,正则表达式,方法或 True
:
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>]
搜索 class
属性时也可以通过CSS值完全匹配:
css_soup.find_all("p", class_="body strikeout") # [<p class="body strikeout"></p>]
完全匹配 class
的值时,如果CSS类名的顺序与实际不符,将搜索不到结果:
<string参数>
通过string参数可以搜索文档中的字符串内容,与name参数的可选值一样,string参数接受字符换,正则表达,列表,True,看例子:
soup.find_all(string="Elsie") soup.find_all(string=["Tillie", "Elsie", "Lacie"]) soup.find_all(string=re.compile("Dormouse")) 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.find_all(string=is_the_only_string_within_a_tag)
虽然 string
参数用于搜索字符串,还可以与其它参数混合使用来过滤tag.Beautiful Soup会找到 .string
方法与 string
参数值相符的tag.下面代码用来搜索内容里面包含“Elsie”的<a>标签:
soup.find_all("a", string="Elsie")
<limit参数>
find_all()
方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit
参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit
的限制时,就停止搜索返回结果.
soup.find_all("a", limit=2)
<recursive参数>
调用tag的 find_all()
方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False
<像调用 find_all()
一样调用tag>
find_all()
几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup
对象和 tag
对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all()
方法相同,下面各自两行代码是等价的:
soup.find_all("a") soup("a") soup.title.find_all(string=True) soup.title(string=True)
<<find
find( name , attrs , recursive , string , **kwargs )
find_all()
方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个<body>标签,那么使用 find_all()
方法来查找<body>标签就不太合适, 使用 find_all
方法并设置 limit=1
参数不如直接使用 find()
方法.下面两行代码是等价的:
soup.find_all('title', limit=1) soup.find('title')
find_all()
方法的返回结果是值包含一个元素的列表,而 find()
方法直接返回结果.find_all()
方法没有找到目标是返回空列表, find()
方法找不到目标时,返回 None
.
<<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的搜索方法相同,搜索文档搜索文档包含的内容.
<<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节点.
<<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()
方法返回第一个符合条件的前面的兄弟节点.
<<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()
方法返回第一个符合条件的节点.
<<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()
方法返回第一个符合条件的节点.
CSS选择器
Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html在Tag
或BeautifulSoup
对象的.select()
方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:
soup.select("title") soup.select("p nth-of-type(3)")
通过tag标签逐层查找:
soup.select("body a") soup.select("html head title")
找到某个tag标签下的直接子标签
soup.select("head > title") soup.select("p > a") soup.select("p > a:nth-of-type(2)") soup.select("p > #link1") soup.select("body > a")
找到兄弟节点标签:
soup.select("#link1 ~ .sister") # ~ 表示所有其他兄弟标签 soup.select("#link1 + .sister")
# + 表示第一个其他兄弟标签
通过CSS的类名查找:
soup.select(".sister") soup.select("[class~=sister]")
通过tag的id查找:
soup.select("#link1") soup.select("a#link2")
同时用多种CSS选择器查询元素:
soup.select("#link1,#link2")
通过是否存在某个属性来查找:
soup.select('a[href]')
通过属性的值来查找:
soup.select('a[href="http://example.com/elsie"]') soup.select('a[href^="http://example.com/"]') soup.select('a[href$="tillie"]') soup.select('a[href*=".com/el"]')
通过语言设置来查找:
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) 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.select_one(".sister")
对于熟悉CSS选择器语法的人来说这是个非常方便的方法.Beautiful Soup也支持CSS选择器API, 如果你仅仅需要CSS选择器的功能,那么直接使用 lxml
也可以, 而且速度更快,支持更多的CSS选择器语法,但Beautiful Soup整合了CSS选择器的语法和自身方便使用API.
5.修改文档树
Beautiful Soup的强项是文档树的搜索,但同时也可以方便的修改文档树
<修改tag的名称和属性>
重命名一个tag,改变属性的值,添加或删除属性:
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') tag = soup.b tag.name = "blockquote" tag['class'] = 'verybold' tag['id'] = 1 tag # <blockquote class="verybold" id="1">Extremely bold</blockquote> del tag['class'] del tag['id'] tag # <blockquote>Extremely bold</blockquote>
<修改 .string>
给tag的 .string
属性赋值,就相当于用当前的内容替代了原来的内容:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup) tag = soup.a tag.string = "New link text." tag # <a href="http://example.com/">New link text.</a>
<append()>
Tag.append()
方法想tag中添加内容,就好像Python的列表的 .append()
方法:
soup = BeautifulSoup("<a>Foo</a>") soup.a.append("Bar") soup # <html><head></head><body><a>FooBar</a></body></html> soup.a.contents # [u'Foo', u'Bar']
使用append() 方法之后Tag.string方法便不可用
可以使用tag.strings获取tag的内容
<NavigableString() 和 .new_tag()>
如果想添加一段文本内容到文档中也没问题,可以调用Python的 append()
方法 或调用 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']
如果想要创建一段注释,或 NavigableString
的任何子类, 只要调用 NavigableString 的构造方法:
from bs4 import Comment 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.']
# 这是Beautiful Soup 4.2.1 中新增的方法
创建一个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>
第一个参数作为tag的name,是必填,其它参数选填
<insert()>
Tag.insert()
方法与 Tag.append()
方法类似,区别是不会把新元素添加到父节点 .contents
属性的最后,而是把元素插入到指定的位置.与Python列表总的 .insert()
方法的用法下同:
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_before()
方法在当前tag或文本节点前插入内容:
soup = BeautifulSoup("<b>stop</b>") tag = soup.new_tag("i") tag.string = "Don't" soup.b.string.insert_before(tag) soup.b # <b><i>Don't</i>stop</b>
insert_after()
方法在当前tag或文本节点后插入内容:
soup.b.i.insert_after(soup.new_string(" ever ")) soup.b # <b><i>Don't</i> ever stop</b> soup.b.contents # [<i>Don't</i>, u' ever ', u'stop']
<clear()>
Tag.clear()
方法移除当前tag的内容:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup) tag = soup.a tag.clear() tag # <a href="http://example.com/"></a>
<extract()>
PageElement.extract()
方法将当前tag移除文档树,并作为方法结果返回:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup) a_tag = soup.a i_tag = soup.i.extract() a_tag # <a href="http://example.com/">I linked to</a> i_tag # <i>example.com</i> print(i_tag.parent) None
这个方法实际上产生了2个文档树: 一个是用来解析原始文档的 BeautifulSoup
对象,另一个是被移除并且返回的tag.被移除并返回的tag可以继续调用 extract
方法.
<decompose()>
Tag.decompose()
方法将当前节点移除文档树并完全销毁:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup) a_tag = soup.a soup.i.decompose() a_tag # <a href="http://example.com/">I linked to</a>
<replace_with()>
PageElement.replace_with()
方法移除文档树中的某段内容,并用新tag或文本节点替代
它:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup) a_tag = soup.a new_tag = soup.new_tag("b") new_tag.string = "example.net" a_tag.i.replace_with(new_tag) a_tag # <a href="http://example.com/">I linked to <b>example.net</b></a>
<wrap()>
PageElement.wrap()
方法可以对指定的tag元素进行包装,并返回包装后的结果:
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>
该方法在 Beautiful Soup 4.0.5 中添加
<unwrap()>
Tag.unwrap()
方法与 wrap()
方法相反.将移除tag内的所有tag标签,该方法常被用来进行标记的解包:
6.输出
<格式化输出>
prettify()
方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行.
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>' soup = BeautifulSoup(markup) soup.prettify() # '<html>\n <head>\n </head>\n <body>\n <a href="http://example.com/">\n...' print(soup.prettify()) # <html> # <head> # </head> # <body> # <a href="http://example.com/"> # I linked to # <i> # example.com # </i> # </a> # </body> # </html>
BeautifulSoup
对象和它的tag节点都可以调用 prettify()
方法.
<压缩输出>
如果只想得到结果字符串,不重视格式,那么可以对一个 BeautifulSoup
对象或 Tag
对象使用Python的 unicode()
或 str()
方法:
str(soup) # '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>' unicode(soup.a) # u'<a href="http://example.com/">I linked to <i>example.com</i></a>'
str()
方法返回UTF-8编码的字符串,可以指定编码的设置.
还可以调用 encode()
方法获得字节码或调用 decode()
方法获得Unicode.
<输出格式>
Beautiful Soup输出是会将HTML中的特殊字符转换成Unicode,比如“&lquot;”:
soup = BeautifulSoup("“Dammit!” he said.") unicode(soup) # u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
如果将文档转换成字符串,Unicode编码会被编码成UTF-8.这样就无法正确显示HTML特殊字符了:
str(soup) # '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'
<get_text()>
如果只想得到tag中包含的文本内容,那么可以使用 get_text()
方法,这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为Unicode字符串返回:
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>' soup = BeautifulSoup(markup) soup.get_text() u'\nI linked to example.com\n' soup.i.get_text() u'example.com'
可以通过参数指定tag的文本内容的分隔符:
# soup.get_text("|") u'\nI linked to |example.com|\n'
还可以去除获得文本内容的前后空白:
# soup.get_text("|", strip=True) u'I linked to|example.com'
或者使用 .stripped_strings 生成器,获得文本列表后手动处理列表:
[text for text in soup.stripped_strings] # [u'I linked to', u'example.com']