学习bs4模块
学习bs4模块
直接在官方网站上面学习
介绍
bs4是一个格式化读取xml,html文档的库
可以使xml、html按照标签的形式操作,具体是将HTML文档转换成一个复杂的树形结构。
官方推荐lxml来解析HTML
所有节点对象可以归纳为4种: Tag , NavigableString , BeautifulSoup , Comment .简单使用操作
from bs4 import BeautifulSoup html_doc = '' soup = BeautifulSoup(html_doc, 'html.parser') print(soup.prettify()) #美化输出,格式化缩进。
一般属性
soup.title
:获取页面的标题,包含标签soup.title.name
:获取页面标题标签的内容soup.title.string
:获取页面标题的内容soup.title.parent.name
:父标签名字soup.p
:p
是标签名字soup.p['class']
:第一个匹配p的标签,成员classsoup.a
:同上
一般方法
soup.find_all('a')
:找到所以的a
标签
查找所有的a标签
for link in soup.find_all('a'):
print(link.get('href'))
# http://example.com/elsie
# http://example.com/lacie
# http://example.com/tillie
soup.get_text()
:拿到文档中所有的文字内容,实际测试新浪的首页存在问题,使用html5lib也一致。
对象的种类
Tag
Tag
对象和XML或HTML原生文档中的tag相同
Tag重要的属性
name
每个Tag都有自己的名字,通过name属性来获取,比如xxx ,Tag名称就是title
如果改变了某个tag的名称,那么将影响当前生成的hml对象文档tag.name = 'xxx'
Attributes
一个Tag
可能存在属性,属性的操作和字典相同
可以直接使用attrs
获取全部属性
tag的属性可以添加删除或者修改。操作和字典一致。
多值属性
一个属性可能有多个值,这里返回其列表。
<p class="body strikeout"></p>
:将会返回body和strikeout的列表
如果存在多个值,但是HTML定义中并没有定义为多值属性,那么还是返回字符串
并且可以传入列表以修改多值属性,传入列表为多值。
xml中不包含多值
可以遍历的字符串
字符串常被包含在tag内.Beautiful Soup用
NavigableString
类来包装tag中的字符串
节点下的字符串不可以编辑,但是可以替换,使用replace_with(‘xxx’)
字符串不支持:contents,string属性或find()方法
需要在此之外使用字符串,使用unicode将其转换为普通的Unicode字符串,减少内存占用。
子节点
soup.body.b
:body节点下的b节点
tag.contents
:可以将节点下的子节点以列表方式输出。tag.children
:子节点生成器,descendants
:后裔,多所有的子孙节点递归循环,这样会将string也单独提取出来- 节点下的
string
:如果节点下面存在子节点,并且子节点不是内容节点,那么返回Nonestrings and stripped_strings
:解决了上面的问题,使用这个将打印节点下的所以字符串,其中stripped_strings去除空格
BeautifulSoup对象
表示文档的全部内容大部分时候可以将它当做
Tag
对象。
搜索文档树
很多搜索方法,这里解释:
find()
和find_all()
其它操作方法类似
过滤器
- 字符串
使用字符串来过滤查找,比如soup.find_all('a')
来找到a
标签- 正则表达式
支持正则表达式,比如:soup.find_all(re.compile("^b"))
- 列表参数
传入列表参数:['a', 'b']
找出a
b
的标签- True
传入True
值,那么会找到所有节点,但是没有字符串。- 方法
如果没有合适的过滤器,那么可以传入方法,方法接收一个参数
```
def has_class_but_no_id(tag):
return tag.has_attr(‘class’) and not tag.has_attr(‘id’)
def not_lacie(href):
return href and not re.compile(“lacie”).search(href)
soup.find_all(href=not_lacie)
from bs4 import NavigableString
def surrounded_by_strings(tag):
return (isinstance(tag.next_element, NavigableString)
and isinstance(tag.previous_element, NavigableString))
> 具体参数 find_all( name , attrs , recursive , string , **kwargs )
> * name 用来查找所以tag
> * attrs 传入字典
> * recursive 传入False只便利直接的子节点
> * string 查找内容,页面上的文本内容,可以传入文字,列表,正则表达式,True
> * **kwargs 传入关键字参数 比如:id='xxx' 同时可以过滤多个这样的属性
> * limit 限制查找的数量
> 有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:
data_soup = BeautifulSoup(‘
data_soup.find_all(data-foo=”value”)
SyntaxError: keyword can’t be an expression
> 但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag:
data_soup.find_all(attrs={“data-foo”: “value”})
[
> 但标识CSS类名的关键字 class 在Python中是保留字,使用 class 做参数会导致语法错误.从Beautiful Soup的4.1.1版本开始,可以通过 class_ 参数搜索有指定CSS类名的tag:
### 子节点
> 一个节点可能包含一个或者多个子节点
> * contents and children:返回子节点的列表,后者返回迭代器。
> * descendants:后裔,会将字符串也打印出来。
> * srting:得到节点下的NavigableString对象
> * strings and stripped_strings:得到节点下的所以NString对象,后者会去除空格和空行。
### 父节点
> 每个节点或者字符串都有父节点
> * parent:得到直接父节点
> * parents:得到所有的父节点
### 兄弟节点
> * next_sipling and previous_sibling:类似遍历兄弟节点
> * next_siplings and previous_siblings:当前节点之前或者之后的所有兄弟节点迭代输出
### 回退和前进
> * next_element and previous_element:返回当前节点上一个或者下一个被解析对象
> * next_elements and previous_elements:对应的就是前后所有被解析的对象,就像HTML对象正在被解析一样
### 像调用 find_all() 一样调用tag
soup.find_all(“a”)
soup(“a”)
> 这两行是等价的
### find()
> find_all是得到所有的结果,有时候我们只需要一个结果,那么直接使用find就行。
> find_all 加上参数limit = 1,也可以实现,但是返回的结果是列表
> find没有找到结果的时候返回`None`
> find方法多次调用:
html.find(‘head’).find(‘title’)
### find_parents() 和 find_parent()
> 搜索当前节点的父节点
s = html.find(string=’Lacie’) # 找到字符串Lacie
s.find_parents(‘a’) # 字符串节点的父节点
> 其实方法就是使用了parent和parents属性进行迭代搜索的
### find_next_siblings() 和 find_next_sibling()
> 通过next_siblings属性对当前tag之后的兄弟节点进行迭代搜索
first_link = html.a
first_link
Elsie
first_link.find_next_siblings()
[Lacie, Tillie]
```
find_previous_siblings() 和 find_previous_sibling()
和上面类似
find_all_next() 和 find_next()
通过next_elements属性对当前tag进行迭代搜索
>> f_link = html.a >> f_link <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> >> f_link.find_all_next('a') [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a clas s="sister" href="http://example.com/tillie" id="link3">Tillie</a>] >> f_link.find_all_next(string=True) ['Elsie', ',\n', 'Lacie', ' and\n', 'Tillie', ';\nand they lived at the bottom o f a well.', '\n', '...', '\n']
find_all_previous() 和 find_previous()
通过previous_element对当前tag进行迭代搜索
>> f_link = html.a >> f_link <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> >> f_link.find_all_previous('title') [<title>The Dormouse's story</title>]
CSS选择器
bs4支持大部分的CSS选择器:CSS Selector w3school
在
Tag
或BeautifulSoup
对象的.select()
方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:
soup.select("title")
# [<title>The Dormouse's story</title>]
soup.select("p nth-of-type(3)")
# [<p class="story">...</p>]
不是很懂这个选择器,大概看起来可以根据CSS选择器文档来书写。
修改文档树
修改tag的名称和属性
tag.name = 'blockquote'
tag['class'] = 'modify value'
tag['id'] = 'newid'
修改
.string
tag.string = 'New link text'
append()
在.string上面追加内容
NavigableString() 和 .new_tag()
NavigableString()
是构造一个字符串,然后将其放入需要修改的地方。new_string = NavigableString('Hello')
html.b.append(new_string)
from bs4 import Comment
new_comment = html.new_string('Nice to see you', Comment)
html.b.append(new_comment)
创建一个Tag的最好的方法是调用工厂方法,new_tag()new_tag = html.new_tag('a', href='test')
html.b.append(new_tag)
# 在b标签的下面追加一个taga
insert()
选择插入的位置,这个和append类似,不过可以选择位置插入。
insert_before, insert_after
看名字就知道,这是在当前tag或者string后边插入对象。
定位到tag或者string,然后使用方法进行插入。‘clear()’
移除tag下的内容
‘extract()’
移除当前tag,将tag移除。
该方法移除tag的时候,会返回移除的tag信息
>>> html = '<h1><a href="www.text.com">wwwww</a></h1>'
>>> h = bs(html, 'lxml')
>>> h
<html><body><h1><a href="www.text.com">wwwww</a></h1></body></html>
>>> h.body.a
<a href="www.text.com">wwwww</a>
>>> tag = h.body.a.extract()
>>> tag
<a href="www.text.com">wwwww</a>
>>> h
<html><body><h1></h1></body></html>
>>>
‘decompose()’
这个更为激进,移除tag之后,不返回。
‘replace_with()’
移除内容并替换新的tag或者string
return:方法返回被替换的内容‘wrap()’
对元素进行包装,并且返回包装后的内容。
>> tag = <a>xxx</a> >> wrap_tag = BeautifulSoup.new_tag('p') >> tag.wrap(wrap_tag) >> tag <p><a>xxx</a></p>
‘unwrap()’
和wrap的方法相反
```
markup = ‘I linked to example.com‘
soup = BeautifulSoup(markup)
a_tag = soup.a
a_tag.i.unwrap()
a_tag
I linked to example.com
#### 格式化输出
> prettify() : 格式化为unicode之后,每个xml、html独占一行。
html.prettify()
\n \n I linked to\n \n
‘\n
example.com\n \n \n \n‘
print(html.prettify())
I linked to
example.com
```
压缩输出
unicode(bs)、str(bs)和encode
这应该属于其它操作了输出格式
bs会将html里边的特殊字符转换成unicode的格式
>> soup = bs("“Dammit!” he said.") unicode(soup) # u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
python3上面没有unicode,直接使用str替换。
并且3上面bs4是直接将&再次转义了,其它没有变化。‘get_text()’
如果想要获取tag里边的所以文本内容,可以使用这个方法。
‘get_text(‘|’) # 第一个参数指定分隔符号,不太明白这个分隔符具体操作,单词???’
‘get_text(‘|’, trip=True) # 这个会处理文本两端的空格’
还可以使用stripped_string生成器,获取文本列表分别处理。
指定解析器
‘BeautifulSoup()’ 第一个参数是文本或者是文件,第二个参数是解析器,Python3应该不自动选择解析器。
目前支持解析:html、xml和html5
解析器有:lxml、html5lib和html.parser
- 解析器之间的区别
使用了不同的解析器会产生不同的结果
具体还是去官网查看API,这里就这样了。
存在一些加标签和保留特定的标签的差异:
比如lxml会将单结束标志’‘去掉,而html5lib会将其补齐为’‘
一般不全的都会补齐基本标签,比如:’‘
官方给的建议是在代码上面标注使用了什么解析器。
编码
任何文档都有其编码方式,使用bs4之后都将其解析为unicode
bs使用了自动编码检测来将文档的编码转换为unicode
‘original_encoding’属性记录了自动识别的编码
bs采用了逐字节的方式来识别编码的,存在效率问题,并且还会猜错。
所以可以使用from_encoding参数来指定文档的编码:
BeautifulSoup(xxx, ‘lxml’, from_encoding=’utf-8’)
当编码比较接近或者是其子集的时候,可能会猜测错误,并且我们也只是知道大致编码的时候,这个时候bs提供exclude_encoding参数来排除错误的编码。
相当于猜测这个文档编码的一个集合,然后取其最可能的编码方式,但是如果猜测错误,那么说明猜测的这个编码是最可能的,但是是错误的,所以提供这个方法来排除,然后相当于让其猜测第二个可能的编码方式。
例如:’BeautifulSoup(xxx, ‘lxml’, exclude_encoding=[‘utf-8’])’
原文:少数情况下(通常是UTF-8编码的文档中包含了其它编码格式的文件),想获得正确的Unicode编码就不得不将文档中少数特殊编码字符替换成特殊Unicode编码,“REPLACEMENT CHARACTER” (U+FFFD, �) [9] . 如果Beautifu Soup猜测文档编码时作了特殊字符的替换,那么Beautiful Soup会把 UnicodeDammit 或 BeautifulSoup 对象的 .contains_replacement_characters 属性标记为 True .这样就可以知道当前文档进行Unicode编码后丢失了一部分特殊内容字符.如果文档中包含�而 .contains_replacement_characters 属性是 False ,则表示�就是文档中原来的字符,不是转码失败.
输出编码
bs将输入的文档都转换为utf-8输出,并且会将源数据的meta里边的编码也改成utf-8
在’prettify(‘utf-8’)’可以指定打印其编码
并且调研bs对象或者节点的方式来使用encode()方法
‘tag.encode(‘utf-8’)’
如果文档中包含当前编码不支持的字符,那么会进行转码。>> markup = u"<b>\N{SNOWMAN}</b>" >> snowman_soup = BeautifulSoup(markup) >> tag = snowman_soup.b >> print(tag.encode("utf-8")) # <b>☃</b> >> print tag.encode("latin-1") # <b>☃</b> >> print tag.encode("ascii") # <b>☃</b>
‘UnicodeDammit’
这个是bs的内置库,可以用来猜测文档的编码。
UnicodeDammit(‘xxx’).unicode_markup.original_encoding
就可以查看猜测的编码了。
UnicodeDammit.detwingle():可以将编码进行统一,比如一些网站包含了另外的网站内容,但是编码却不一样,一个是utf-8编码,一个是另外的编码
这样直接使用是存在问题的。所以在处理之前最好使用这个方法进行操作一次。
判断对象是否相同
使用等于不严格的方式来判断是否相同,如下图不同位置b对象是相同的
>> markup = "<p>I want <b>pizza</b> and more <b>pizza</b>!</p>" >> soup = BeautifulSoup(markup, 'html.parser') >> first_b, second_b = soup.find_all('b') >> print(first_b == second_b) # True >> print(first_b.previous_element == second_b.previous_element) # False
使用is来严格判断是否相同
>> print(first_b is second_b) False
复制Beautiful Soup对象
copy.copy()
可以复制任意的Tag
或NavigableString
import copy p_copy = copy.copy(soup.p)
解析部分文档
如果仅仅需要获取页面中的
a
标签的内容,但是如果全部都去解析,就太耗费性能和内存了。最快的方法就在一开始就把a
标签以外的都忽略掉。
SoupStrainer类可以定义文档的某段内容,只会解析SoupStrainer中定义的内容,创建一个SoupStrainer对象并作为parse_only参数给BeautifulSoup的构造方法就行。
SoupSTrainer类接受与典型搜索方法相同的参数:name、attr、recursive、string、**kw,下面源教程的列子,最后的short_strings运行错误,可能是参数导致的,显示NoneType
无len。
```
from bs4 import SoupStrainer
only_a_tags = SoupStrainer(“a”) #
only_tags_with_id_link2 = SoupStrainer(id=”link2”)
def is_short_string(string):
return len(string) < 10
only_short_strings = SoupStrainer(string=is_short_string)
BeautifulSoup(html_doc, “html.parser”, parse_only=only_a_tags)
```
其他说明(官方教程为代码诊断)
from bs4.diagnose import diagnose
这个可以打印出解析器处理文档的过程。使用了一下,貌似是使用了不同的解析器对文档进行解析,然后分别给出结果。解析错误
解析崩溃或者异常基本都不是bs4的问题,因为他并不包含解析器,官方给于的解释是换解析器
最常见的解析错误是HTMLParser.HTMLParseError: malformed start tag
和HTMLParser.HTMLParseError: bad end tag
.这都是由Python内置的解析器引起的,解决方法是 安装lxml
或html5lib
find_all()或者find返回都是空,但是文档中也存在这个Tag,还是换解析器。解析器错误
一般解析器解析之后都是转为小写,如果要保留大小写,那么使用
xml
解析器效率问题
使用
lxml
解析器会较快一点
安装cchardet
之后,文档编码检测速度会更快。