python3爬虫(二):解析库之Beautiful Soup

python3爬虫(二):解析库之Beautiful Soup

Beautiful Soup是一个可以从HTML或XML中提取数据的python库,了解了HTML或XML的结构,能很方便地获取数据

一、准备

1、安装库和解析器

这里使用的是 beautifulsoup4 以及 lxml 作为解析器

pip install beautifulsoup4
pip install lxml

HTML例子

下面一段HTML代码将作为例子被多次用到

html_doc = '''
<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 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>
'''

二、对象

1、对象获取

使用 BeautifulSoup() 解析html代码,能够得到一个 BeautifulSoup 对象,并能按照标准的锁紧格式的结构输出
BeautifulSoup(doc, 解析器 [, 解析方式])

  • doc:HTML或XML文档
  • 解析方式: 可选,默认为html
import bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')

print(soup.prettify())
# <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="link2">
#     Tillie
#    </a>
#    ; and they lived at the bottom of a well.
#   </p>
#   <p class="story">
#    ...
#   </p>
#  </body>
# </html>

2、对象的类别

Beautiful Soup将HTML文档转换成复杂的树结构,每个节点都是python对象,可分为4类:TagNavigableStringBeautifulSoupComment

(1)Tag类

  • Tag 对象与HTML或XML原生文档中的标签Tag相同,如:html、body、title、p、div、span 等等
  • 获取:soup.tagName
soup = BeautifulSoup('<p class="boldest">Extremely bold</p>')
tag = soup.p
type(tag)		# <class 'bs4.element.Tag'>

Tag的属性:

  • Name:tag的名字, tag.name
    • 如果改变了 tag 的 name,则会影响所有通过当前Beautiful Soup对象生成的HTML文档
print(tag.name)		# u'p'
tag.name = 'span'		
#  <span class="boldest">Extremely bold</span>
  • Attributes:tab的属性
    • 一个 tag 可能有多个属性
    • 操作方法:与字典相同
      • 获取:方括号 tag[‘attr’],点 tag.attr
      • 可以被添加、删除、修改
    • 多值属性:
      • HTML中有很多多值属性,而XML不包含
      • 获取多值属性时,返回类型是list
      • 获取不是多值属性时,作为字符串返回
      • 修改多值属性时,赋值list,会将多个属性值合并为一个值
# 获取
print(tag['class'])		# u'boldest'
print(tag.class)		# u'boldest'

# 添加、修改
tag['class'] = 'verybold'
tag['id'] = 1
tag
# <spanclass="verybold" id="1">Extremely bold</span>

# 删除
del tag['class']
del tag['id']
tag
# <span>Extremely bold</span>

# 获取不存在
tag['class']
# KeyError: 'class'
print(tag.get('class'))
# None

# 获取多值属性时,返回类型是list
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]

# 获取不是多值属性时,作为字符串返回
id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'

# 修改多值属性时,赋值list,会将多个属性值合并为一个值
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
rel_soup.a['rel']
# ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
# <p>Back to the <a rel="index contents">homepage</a></p>

# xml不包含多值属性
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
xml_soup.p['class']
# u'body strikeout'

(2)NavigableString类

  • 字符串常被包含在tag内,用NavigableString类来包装tag中的字符串,tag.string
soup = BeautifulSoup('<p class="boldest">Extremely bold</p>')
tag = soup.p
tag.string		# u'Extremely bold'
type(tag.string)		# <class 'bs4.element.NavigableString'>
  • 与python中Unicode字符串相同,通过unicode()进行转换
unicode_string = unicode(tag.string)		# u'Extremely bold'
type(unicode_string)		# <type 'unicode'>
  • 不能被编辑、但能被替换:replace_with()
tag.string.replace_with('hello world')
tag 		# <p class="boldest">hello world</p>
  • 支持 遍历文档树 和 搜索文档树 中定义的大部分属性和方法,不支持.contents, .string, find()

注意:NavigableString类仅包含字符串,不包含其他内容(如tag)

(3)Beautiful Soup对象

  • 表示一个文档的全部内容
  • 支持 遍历文档树 和 搜索文档树 中的大部分方法
  • name属性值为 u’[document]'
  • 没有attribute属性

(4)Comment类

  • 文档的注释部分, 是一个类型特殊的NavigableString对象
  • 出现在文档时,会使用特殊的格式输出
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>

三、遍历文档树

1、子节点

一个 tag 可能包含多个字符串或其他的 tag,其都是 tag 的子节点;而字符串没有子节点

(1).tagName

  • 直接通过标签 tag 的 name 获取标签
  • 只能获取到当前标签名的第一个tag
  • 想要获取所有的当前标签名,使用搜索文档树中的 find_all()
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)
soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

(2).contents

  • 将tag的子节点以列表的方式输出
head_tag = soup.head
head_tag.contents
# [<title>The Dormouse's story</title>]

(3).children

通过 tag的 .children 生成器,可以对子节点进行循环

for child in head_tag.contents[0].children:
	print(child)		# The Dormouse's story

(4).descendants

.descendants 对所有tag的子孙节点进行递归循环

for child in head_tag.descendants:
    print(child)
    # <title>The Dormouse's story</title>
    # The Dormouse's story

(5).string

  • 如果tag只有一个NavigableString类型的子节点,可通过 .string 获取子节点
  • 如果tag只有一个子节点,且子节点仅有一个NavigableString类型的子节点,tag可以直接使用 .string 输出内容
  • 如果tag包含多个子节点,.string的输出结果为None
head_tag.contents[0].string		# u'The Dormouse's story'
head_tag.string		# u'The Dormouse's story'
soup.html.string		# None

(6).strings

循环获取tag中包含的所有字符串(包括空格和空行)

for string in soup.strings:
    print(repr(string))
    # u"The Dormouse's story"
    # u'\n\n'
    # u"The Dormouse's story"
    # u'\n\n'
    # u'Once upon a time there were three little sisters; and their names were\n'
    # u'Elsie'
    # u',\n'
    # u'Lacie'
    # u' and\n'
    # u'Tillie'
    # u';\nand they lived at the bottom of a well.'
    # u'\n\n'
    # u'...'
    # u'\n'

(7).stripped_strings

循环获取tag中包含的所有字符串(全部是空格的行被忽略,段首、段末的空白被删除)

for string in soup.stripped_strings:
    print(repr(string))
    # u"The Dormouse's story"
    # u"The Dormouse's story"
    # u'Once upon a time there were three little sisters; and their names were'
    # u'Elsie'
    # u','
    # u'Lacie'
    # u'and'
    # u'Tillie'
    # u';\nand they lived at the bottom of a well.'
    # u'...'

2、父节点

  • 父节点为包含当前tag的节点
  • 字符串也有父节点
  • 文档的顶层节点的父节点是BeautifulSoup对象
  • BeautifulSoup对象的.parent是None

(1).parent: 获取某个元素的父节点

soup.title.parent		# <head><title>The Dormouse's story</title></head>
soup.title.string.parent		# <title>The Dormouse's story</title>
type(soup.html.parent(			# <class 'bs4.BeautifulSoup'>
soup.parent			# None

(2).parents:通过递归获得元素的所有父辈节点

link = soup.a
for parent in link.parents:
    if parent is None:
        print(parent)
    else:
        print(parent.name)
# p
# body
# html
# [document]
# None

3、兄弟节点

同一节点下的所有子节点互为兄弟节点(不一定是同一类标签,可以是tag或字符串)

(1).next_sibling:

  • 获取上一兄弟节点
  • 当节点是同级节点中的最后一个时,为None
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
sibling_soup .b.next_sibling		# <c>text2</c>
sibling_soup .c.next_sibling		# None

(2).previous_sibling:

  • 获取下一兄弟节点
  • 当节点是同级节点中的第一个时,为None
sibling_soup .c.precious_sibling			# <b>text1</b>
sibling_soup .b.precious_sibling		# None

(3).next_siblings,.previous_siblings

迭代获取当前节点的兄弟节点

4、回退和前进

  • 根据HTML的解析过程,获取上一个、下一个被解析对象
  • 解析过程:类似树的深度遍历,即标签内部有标签或字符串,则先解析内部,再继续解析下一个

(1).next_element,.previous_element

last_a_tag		# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
last_a_tag.next_sibling		# '; and they lived at the bottom of a well.'
last_a_tag.next_element		# u'Tillie'
last_a_tag.previous_element		# u' and\n'

(2).next_elements,.previous_elements

for element in last_a_tag.next_elements:
    print(repr(element))
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# <p class="story">...</p>
# u'...'
# u'\n'
# None

四、搜索文档树

Beautiful Soup定义了很多搜索方法,主要介绍 find()find_all() ,其他方法与其类似

1、过滤器

过滤器可被用于 tag 的 name 、节点属性、字符串或其混合中

(1)字符串

  • 在搜索方法中传入字符串参数,查找与字符串完整匹配的内容
  • 若传入字节码参数,会被当做UTF-8编码,所以可以传入 Unicode编码来避免解析编码出错
soup.find_all('b')
# [<b>The Dormouse's story</b>]

(2)正则表达式

  • 传入正则表达式作为参数,会通过正则表达式的match()来匹配内容
import re
for tag in soup.find_all(re.compile("^b")):
    print(tag.name)
# body
# b

(3)列表

传入列表参数,返回与列表中任一元素匹配的内容

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>]

(4)True

传入True,则匹配所有tag,不包括字符串节点

for tag in soup.find_all(True):
    print(tag.name)
# html
# head
# title
# body
# p
# b
# p
# a
# a
# a
# p

(5)方法

传入方法,方法被定义为只接受一个元素参数,若方法返回True则当前元素被匹配并且被找到,否则返回False

def has_class_but_no_id(tag):
    return tag.has_attr('class') and not tag.has_attr('id')

soup.find_all(has_class_but_no_id)
# [<p class="title"><b>The Dormouse's story</b></p>,
#  <p class="story">Once upon a time there were...</p>,
#  <p class="story">...</p>]

2、find_all()

  • 返回当前节点的所有子孙节点中符合条件的所有 tag,返回的是列表
  • 没有找到目标时,返回空列表

(1)name参数

  • 查找所有名字为 name 的 tag,字符串对象被忽略
  • name参数的值可以是任一类型的 过滤器(字符串、正则表达式、列表、方法、True)
soup.find_all("title")
# [<title>The Dormouse's story</title>]

(2)keyword参数

  • 如果一个指定名字的参数不是内置的参数名,则将该参数当做 tag 的属性来搜索
  • 指定名字的属性使用的参数值包括:字符串、正则表达式、列表、True
  • 部分 tag 属性不能使用,如data-*属性
  • 但可以使用attrs参数定义一个字典参数来搜索包含特殊属性的tag
soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
data_soup.find_all(attrs={"data-foo": "value"})
# [<div data-foo="value">foo!</div>]

(3)CSS类名搜索

  • 通过 class_ 参数搜索有指定CSS类名的 tag
  • 同样接受不同类型的过滤器:字符串、正则表达式、方法、True
  • 当tag有多个类名时,进行css类名搜索时,可以分别搜索
  • 也可通过CSS值完全匹配,但顺序不符,则搜索不到
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>]

css_soup.find_all("p", class_="body strikeout")
# [<p class="body strikeout"></p>]

(4)text参数

  • 搜索文档中字符串内容
  • 接受字符串、正则表达式、列表、方法、True
  • 与其他参数混合使用来过滤 tag,找到 .string 方法与 text 参数值相符的 tag
soup.find_all(text=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']

soup.find_all("a", text="Elsie")
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]

(5)limit参数

限制返回的搜索结果的数量

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>]

(6)recursive参数

  • 为 True 时,检索当前 tag 的所有子孙节点
  • 为 False 时,只搜索 tag 的直接子节点
ss = '''
<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
'''
soup.html.find_all("title")
# [<title>The Dormouse's story</title>]

soup.html.find_all("title", recursive=False)
# []

(7)简写方法

像调用find_all()一样调用tag

# 以下两行代码是等价的
soup.title.find_all(text=True)
soup.title(text=True)

3、find()

  • 参数与 find_all() 类似
  • 使用情况:只想得到一个结果(与find_all()设置limit=1类似)
  • 返回:返回当前节点的所有子孙节点中符合条件的一个结果
  • 直接返回结果,而不是列表
  • 找不到目标时,返回 None
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]

soup.find('title')
# <title>The Dormouse's story</title>

print(soup.find("nosuchtag"))
# None

4、find_parents()、find_parent()

  • 与 find_all() 和 find() 类似,仅搜索文档的部分不同
  • 返回当前节点符合条件的的父辈节点(直接与间接)
a_string = soup.find(text="Lacie")
a_string
# u'Lacie'

a_string.find_parents("a")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

a_string.find_parent("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>

5、find_next_siblings()、find_next_sibling()

  • 与 find_all() 和 find() 类似
  • 返回当前节点符合条件的后面的兄弟节点
first_link = soup.a
first_link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

first_link.find_next_siblings("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>]

6、find_previous_siblings()、find_previous_sibling()

  • 与 find_all() 和 find() 类似
  • 返回当前节点符合条件的前面的兄弟节点
last_link = soup.find("a", id="link3")
last_link
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

last_link.find_previous_siblings("a")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

7、find_all_next()、find_next()

  • 返回当前 tag 之后所有符合条件的节点,返回当前 tag 之后第一个符合条件的节点
  • 返回的节点包括 tag 和字符串
first_link = soup.a
first_link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

first_link.find_all_next(text=True)
# [u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
#  u';\nand they lived at the bottom of a well.', u'\n\n', u'...', u'\n']

8、find_all_previous()、find_previous()

与find_all_next()、find_next()类似,只是是查找当前 tag 之前的节点

9、CSS选择器

soup.select("title")		# 标签查找
soup.select("body a")		# 标签逐层查找
soup.select("p > a:nth-of-type(2)")			# 直接子标签
soup.select("#link1 + .sister")		# 兄弟节点
soup.select(".sister")				# 类名
soup.select("a#link2")			# id
soup.select('a[href]')		# 是否有某属性
soup.select('a[href$="tillie"]')		# 属性值

五、修改文档树

六、输出

猜你喜欢

转载自blog.csdn.net/Song_Lynn/article/details/82950771