python

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.htmlTagBeautifulSoup对象的.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>
 <head>
 </head>
 <body>
  <a href="http://example.com/">
...'

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>xe2x80x9cDammit!xe2x80x9d he said.</body></html>'

<get_text()>

如果只想得到tag中包含的文本内容,那么可以使用 get_text() 方法,这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为Unicode字符串返回:

markup = '<a href="http://example.com/">
I linked to <i>example.com</i>
</a>'
soup = BeautifulSoup(markup)

soup.get_text()
u'
I linked to example.com
'
soup.i.get_text()
u'example.com'

可以通过参数指定tag的文本内容的分隔符:

# soup.get_text("|")
u'
I linked to |example.com|
'

还可以去除获得文本内容的前后空白:

# 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']  
原文地址:https://www.cnblogs.com/1328497946TS/p/11016489.html