什么是Beautiful Soup?
是一个可以从HTML或XML文件中提取数据的Python库,它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。
安装
pip install beautifulsoup4
解析器
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,比如lxml。
pip install lxml
如何使用
将一段文档传入Beautiful Soup的构造方法,就能得到一个文档的对象,可以传入一个字符串或一个文件句柄。
>>> BeautifulSoup("Sacrébleu!")
Sacr\xe9bleu!
首先文档被转换成Unicode,并且HTML的实例都被转换成Unicode编码,然后Beautiful Soup选择最合适的解析器来分析这段文档。
对象的种类
####Tag
Tag对象与XML和HTNL原生文当中的相同:
>>> soup=BeautifulSoup('<b class="boldest">Extremely bold</b>')
>>> type(soup.b)
<class 'bs4.element.Tag'>
Tag对象的属性:name和attributes
- Name
>>> tag=soup.b
>>> tag.name
u'b'
#如果改变tag的名字,将会影响通过当前BeautifulSoup对象生成的HTML文档
>>> tag.name='blockquote'
>>> tag
<blockquote class="boldest">Extremely bold</blockquote>
Attributes
tag的属性的操作方法与字典相同:
>>> tag['class']
[u'boldest']
>>> tag.attrs
{u'class': [u'boldest']}
tag的属性可以被添加,删除或修改。
#修改添加属性
>>> 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>
>>> tag['class']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/kiosk/.local/lib/python2.7/site-packages/bs4/element.py", line 1071, in __getitem__
return self.attrs[key]
KeyError: 'class'
多值属性
有时候一个属性可能对应多个值
>>> css_soup=BeautifulSoup('<p class="body strikeout"></p>')
>>> css_soup.p['class']
[u'body', u'strikeout']
>>> css_soup=BeautifulSoup('<p class="body"></p>')
>>> css_soup.p['class']
[u'body']
如果某个属性看起来有多个值,但任何版本的HTML定义中都没有定义它为对值属性,那么将会返回一个字符串。
#id是唯一的,不会有多个id
>>> id_soup=BeautifulSoup('<p id="my id"></p>')
>>> id_soup.p['id']
u'my id'
将tag转换成字符串时,多值属性会合并为一个值
>>> from bs4 import BeautifulSoup
>>> rel_soup=BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
>>> rel_soup.a['rel']
>>> rel_soup.a['rel']=['index','context']
>>> rel_soup.p
<p>Back to the <a rel="index context">homepage</a></p>
遍历字符串
字符串常被包含在tag内,Beautiful Soup用NavigableString类来包装tag中的字符串:
>>> soup=BeautifulSoup('<b class="boldest">Extremely bold</b>')
>>> tag=soup.b
>>> tag.string
u'Extremely bold'
>>> type(tag.string)
<class 'bs4.element.NavigableString'>
一个NavigableString字符串与Python中的Unicode字符串相同,通过 unicode() 方法可以直接将 NavigableString 对象转换成Unicode字符串。
tag中包含的字符串不能编辑,但是可以使用replace_with()方法替换:
>>> tag.string.replace_with("No longer bold")
u'Extremely bold'
>>> tag
<b class="boldest">No longer bold</b>
####BeautifulSoup
BeautifulSoup对象表示的时一个文档的全部内容。大部分时候可以把它当作Tag对象。
Tag , NavigableString , BeautifulSoup几乎覆盖了htnl和xml中的所有内容,但还有一些特殊对象,比如文当中的注释
注释以及特殊字符串
>>> markup="<b><!--Hello World--></b>"
>>> soup=BeautifulSoup(markup)
>>> comment=soup.b.string
>>> comment
u'Hello World'
>>> type(comment)
#Comment对象时一个特殊类型的NavigableString 对象
<class 'bs4.element.Comment'>
用CDATA来替换注释
>>> from bs4 import CData
>>> cdata=CData("Hello World")
>>> comment.replace_with(cdata)
u'Hello World'
>>> print(soup.b.prettify())
<b>
<![CDATA[Hello World]]>
</b>
遍历文档树
子节点
一个Tag可能包含多个字符串或其他Tag,这些都是这个Tag的子节点。
- tag的名字
html_doc="""
<html><head><title>The Dormouse's story</title></head>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">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>
"""
from bs4 import BeautifulSoup
#指定解析器lxml,默认为HTML
soup=BeautifulSoup(html_doc,'lxml')
print soup
#解析后
<html><head><title>The Dormouse's story</title></head>
<body><p class="title"><b>The Dormouse's story</b></p>
<p class="story">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>
print soup.head
# <head><title>The Dormouse's story</title></head>
print soup.title
# <title>The Dormouse's story</title>
#获得<p>标签中的一个<b>标签
print soup.p.b
#<b>The Dormouse's story</b>
通过点取属性的方式只能获得当前名字的第一个tag;如果想获得所有的,则用find_all().返回一个列表。
#获得所有的<a>标签
print soup.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>]
.contents和.children
.contents可以将子节点以列表形式输出,如果没有子节点就没有这个属性;
.children子节点变成生成器,可以for遍历;
.descendants
可以对所有tag的子孙节点进行递归循环;
.string
如果只有一个子节点,可以获得子节点。如果有多个,则输出None
.stripped_strings
可以去除多余空白内容
父节点
.parent获取父节点
title_tag=soup.title
print title_tag
print title_tag.parent
# <title>The Dormouse's story</title>
# <head><title>The Dormouse's story</title></head>
BeautifulSoup 对象的 .parent 是None:
.parents
可以递归得到元素的所有父辈节点。
link=soup.a
print link
for parent in link.parents:
if parent is None:
print parent
else:
print parent.name
"""
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
p
html
[document]
"""
兄弟节点
如果两个标签处于同一层,成为兄弟节点
.next_sibling 和 .previous_sibling
.next_siblings 和 .previous_siblings
对当前所有的兄弟节点迭代输出。
回退和前进
.next_element 和 .previous_element
next_element 属性指向解析过程中下一个被解析的对象。
last_a_tag=soup.find('a',id='link3')
print last_a_tag
print last_a_tag.next_sibling
print last_a_tag.next_element
"""
#.next_sibling和.next_element的区别
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
#与<a>处于同层的下一个要被解析的
;
and they lived at the bottom of a well.
#处于<a>内层的
Tillie
"""
.next_elements 和 .previous_elements
可以向前或向后访问文档的解析内容
搜索文档树
过滤器
- 字符串:传入一个字符串,查找与字符串完整匹配的内容。
print soup.find_all('b')
# [<b>The Dormouse's story</b>]
- 正则表达式
找出所有以b开头的标签
import re
for tag in soup.find_all(re.compile("^b")):
print tag.name
# b
- 列表
如果传入列表参数,将与列表中的任意元素匹配的内容返回;
print soup.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
匹配任何值,但不会返回字符串节点
for tag in soup.find_all(True):
print tag.name
"""
html
head
title
p
b
p
a
a
a
p
"""
方法
包含class属性不包含id属性
这里写代码片
find_all方法祥解
find_all(self, name=None, attrs={}, recursive=True, text=None,
limit=None, **kwargs):
搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件;
name参数
可以查找名字为name的tag,字符串对象会被自动忽略
print soup.find_all("title")
# [<title>The Dormouse's story</title>]
搜索name参数的值可以是任意类型的过滤器,字符串,正则,列表,方法或者True;
keyword参数
如果一个指定名字的参数你是搜索内置的参数名,搜索时会把该参数当作指定名tag的属性来搜索;
print soup.find_all(id='link2')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
print soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
搜索指定名字的属性时可以是字符串,正则,列表,True
有些属性在搜索不能使用:
data_soup=BeautifulSoup('<div data-foo="value">foo!</div>')
print data_soup.find_all(data_foo="value")
# []
#可以通过attrs参数定义一个字典参数来搜索
print data_soup.find_all(attrs={"data-foo":"value"})
# [<div data-foo="value">foo!</div>]
按CSS搜索:
使用class_参数来指定,而不是class;
print soup.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_参数同样接受字符串,过滤器等
print soup.find_all(class_=re.compile("itl"))
# [<p class="title"><b>The Dormouse's story</b></p>]
def has_six_class(css_class):
return css_class is not None and len(css_class)==6
print soup.find_all(class_=has_six_class)
# [<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是多值的也可以指定任意一个值都能搜索到,也可以通过多个值匹配到.但是多值搜索如果顺序不符将搜索不到;
text参数
搜索文当中的字符串内容.还可以与其他参数混合使用:
print soup.find_all("a",text="Elsie")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
limit参数
指定搜索返回的数量
print soup.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>]
print soup.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>]
recursive参数
调用tag的find_all方法时.会检索当前的所有子孙节点,如果只想搜索tag的直接子节点,可以将recursive=False
定义find_all的简便方法
soup.find_all("a")
soup("a")
find()方法
find只返回一个结果.find_all以列表的形式返回所有结果
find_parents() 和 find_parent()
搜索当前节点的父辈节点
a_string=soup.find(text="Lacie")
print a_string
# Lacie
#当前叶子节点的直接父节点
print a_string.find_parents("a")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
#当前叶子的间接父节点
print a_string.find_parents("p")
# [<p class="story">Once upon a time there were three little sisters; and their names were\n
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,\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>;\n
# and they lived at the bottom of a well.</p>]
find_next_siblings() 合 find_next_sibling()
对后面解析的兄弟tag进行迭代,只返回单符合条件的第一个兄弟节点
find_previous_siblings() 和 find_previous_sibling()
和上面的一样,匹配当前节点的前面的节点
find_all_next() 和 find_next()
对当前tag的之后的tag和字符串进行迭代
find_all_previous() 和 find_previous()
对当前tag的之前的tag和字符串进行迭代
修改文档树
修改tag的名称和属性
获取到以后重新赋值,跟修改字典的value值一样
修改.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"
print tag
# <a href="http://example.com/">New link text</a>
如果当前tag包含其他的tag,那么.string属性会覆盖掉原有的所有内容.包括tag
append()
soup=BeautifulSoup("<a>Foo</a>")
soup.a.append("Bar")
print soup
# <a>FooBar</a>
print soup.a.contents
# [u'Foo', u'Bar']
BeautifulSoup.new_string() 和 .new_tag()
添加一段文本内容到文档
soup=BeautifulSoup("<b></b>")
tag=soup.b
tag.append("Hello")
new_string=soup.new_string("there")
tag.append(new_string)
print tag
print tag.contents
# <b>Hellothere</b>
# [u'Hello', u'there']
如果创建一段注释,或者 NavigableString 的任何子类,姜子类作为new_string方法的第二个参数传入:
soup=BeautifulSoup("<b></b>")
tag=soup.b
tag.append("Hello")
new_string=soup.new_string("there")
tag.append(new_string)
new_comment=soup.new_string("Nice to see you",Comment)
tag.append(new_comment)
print tag
print tag.contents
# <b>Hellothere<!--Nice to see you--></b>
# [u'Hello', u'there', u'Nice to see you']
创建一个tag最好的方法是调用工厂方法 BeautifulSoup.new_tag() :
soup=BeautifulSoup("<b></b>")
first_tag=soup.b
new_tag=soup.new_tag("a",href="http://www.example.com")
first_tag.append(new_tag)
print first_tag
new_tag.string="Link text"
print first_tag
# <b><a href="http://www.example.com"></a></b>
# <b><a href="http://www.example.com">Link text</a></b>
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")
print tag
print tag.contents
# <a href="http://example.com/">I linked to but did not endorse<i>example.com</i></a>
# [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)
print soup.b
# <b><i>Don't</i>stop</b>
insert_before() 方法在当前tag或文本节点后插入内容
soup.b.i.insert_after(soup.new_string("ever"))
print soup.b
# <b><i>Don't</i>everstop</b>
clear()
移除当前tag的内容
extract()
移除当前tag的内容,并作为结果返回
decompose()
将当前节点移除文档树并完全销毁
replace_with()
替换文档树中的某段内容
wrap()
对指定的tag元素进行包装,并返回包装后的借果;
soup=BeautifulSoup("<p>I wish I was bold.</p>")
print soup.p.string.wrap(soup.new_tag("b"))
# <b>I wish I was bold.</b>
print soup.p.wrap(soup.new_tag("div"))
# <div><p><b>I wish I was bold.</b></p></div>
unwrap()
移除tag内的所有tag标签.
CSS选择器
Beautiful Soup支持大部分的选择器,在Tag或者Beautiful Soup对象的.select()方法中传入字符串参数,即可使用CSS选择器的语法找到tag:
通过标签选择
#选择title标签
print soup.select("title")
# [<title>The Dormouse's story</title>]
#在所有p标签中,选择第三个标签
print soup.select("p:nth-of-type(3)")
# [<p class="story">...</p>]
#选择body 下的a标签,全部显示
print soup.select('body 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>]
#选择id=link1后的所有兄弟节点标签
print soup.select("#link1 ~ .sister")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
#选择id=link1后的下一个兄弟节点标签
print soup.select("#link1 + .sister")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
通过类名查找
#选择a标签,类属性为sister的标签
print soup.select('a.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>]
通过id查找
#选择a标签id为link1的标签
print soup.select('a#link1')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
通过属性查找
#选择a标签,其属性中存在id的所有标签
print soup.select("a[id]")
# [<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标签,其属性href="http://example.com/lacie"的所有标签
print soup.select('a[href="http://example.com/lacie"]')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
#选择a标签,其属性href以http开头的
print soup.select('a[href^="http"]')
# [<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标签,其属性href以lacie结尾的
print soup.select('a[href$="lacie"]')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
#选择a标签,其属性href包含tillie的
print soup.select('a[href*="tillie"]')
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
#从html中排除某标签,此时soup中不再有这个标签
[s.extract() for s in soup('a')]
print soup.select('a')
# []
#排除多个
[s.extract() for s in soup('a','p')]