学习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.pp是标签名字
  • soup.p['class']:第一个匹配p的标签,成员class
  • soup.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:如果节点下面存在子节点,并且子节点不是内容节点,那么返回None
  • strings 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(‘

foo!
‘)
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”})
[

foo!
]

> 但标识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

TagBeautifulSoup 对象的 .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_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标签的下面追加一个tag a

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 \n I linked to\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("&ldquo;Dammit!&rdquo; 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>&#9731;</b>

>> print tag.encode("ascii")
# <b>&#9731;</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()可以复制任意的TagNavigableString

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 tagHTMLParser.HTMLParseError: bad end tag.这都是由Python内置的解析器引起的,解决方法是 安装lxmlhtml5lib
find_all()或者find返回都是空,但是文档中也存在这个Tag,还是换解析器。

解析器错误

一般解析器解析之后都是转为小写,如果要保留大小写,那么使用xml解析器

效率问题

使用lxml解析器会较快一点
安装cchardet之后,文档编码检测速度会更快。