python爬虫之解析库Beautiful Soup

 

为何要用Beautiful Soup

  Beautiful Soup是一个可以从HTMLXML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式,

  是一个标签的形式,来进行查找的,有点像jquery的形式。提升效率,我们在进行爬虫开发的时候,进程会用到正则来进行查找过滤的操作,纯手动会及其浪费时间。

Beautiful Soup示例摘自官网

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

这里先简单说明Beautiful Soup的查找方式,是一个标签树的形式。

在使用的时候实例化一个对象,这个对象就相当于整个html文件,将标签封装成对象的属性,查找的时候使用“.”

简单操作

from bs4 import BeautifulSoup
soup = BeautifulSoup(open("html_doc.html"),"lxml")
#简单的操作
#打印html文件的title属性
#print(soup.title)
#<title>The Dormouse's story</title>

#打印标签的名字
# print(soup.title.name)
#title

# 打印标签的内容
# print(soup.title.string)
#The Dormouse's story

#打印soup中的p标签,但是这里是能找到的第一个
# print(soup.p)
# <p class="title"><b>The Dormouse's story</b></p>

#打印soup中的p标签class名字,但是这里是能找到的第一个
# print(soup.p['class'],type(soup.p['class']))
# ['title'] <class 'list'> #类型是个列表

# 打印soup中的a标签,但是这里是能找到的第一个
# print(soup.a)
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

#打印所有的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>]

#打印id=link3的标签
# print(soup.find(id="link3"))
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</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

# 从文档中获取所有文字内容:
# print(soup.get_text())
# The Dormouse's story
#
# The Dormouse's story
#
# Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.
#
# ...

Tag

soup1 = BeautifulSoup('<b class="boldest">Extremely bold</b>',"lxml")
tag = soup1.b  
# print(type(tag))
# <class 'bs4.element.Tag'>

Tag的Name属性

# print(tag.name)
# b
# 如果改变了tag的name,那将影响所有通过当前Beautiful Soup对象生成的HTML文档:

# tag.name = "blockquote"
# print(tag)
# <blockquote class="boldest">Extremely bold</blockquote>

Tag的Attributes属性

一个tag可能有很多个属性. tag <b class="boldest"> 有一个 “class” 的属性,值为 “boldest” . tag的属性的操作方法与字典相同:
# print(tag['class'])
# ['boldest']

# 也可以直接”点”取属性, 比如: .attrs :
# print(tag.attrs)
# {'class': ['boldest']}
# print(soup.a.attrs['class'])
# ['sister']
# tag的属性可以被添加,删除或修改. 再说一次, tag的属性操作方法与字典一样

# tag['class'] = 'verybold'
# tag['id'] = 1
# print(tag)
# <blockquote class="verybold" id="1">Extremely bold</blockquote>

# del tag['class']
# del tag['id']
# print(tag)
# <blockquote>Extremely bold</blockquote>

# tag['class']
# KeyError: 'class'
# print(tag.get('class'))
# None
 

子节点操作:

.contents属性

#.contents
# tag的 .contents 属性可以将tag的子节点以列表的方式输出:
# print(soup)
#print(soup.contents) #这里打印的是整个html标签
#print("________")
#print(soup.head.contents) #打印出来的是head下的列表,可以借助元组去重
##['
', <meta charset="utf-8"/>, '
', <title>The Dormouse's story</title>, '
']
#print(len(soup.head.contents))
##5
#print(soup.head.contents[1].name)
##meta


 descendants属性

#descendants属性
# .contents 和 .children 属性仅包含tag的直接子节点.例如,<head>标签只有一个直接子节点<title>
#但如果我们需要递归循环的话, .descendants 属性可以对所有tag的子孙节点进行递归循环 [5] :
# des_tag = soup.head
# print(des_tag)
# for child in des_tag.descendants:
#     print(child,end="")

# 输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:(代替.string)

父节点:

parent

#parent
# title_tag = soup.title
# print(title_tag)
## <title>The Dormouse's story</title>
# print(title_tag.parent)
## <head>
## <meta charset="utf-8"/>
## <title>The Dormouse's story</title>
##</head>

parents

#通过元素的 .parents 属性可以递归得到元素的所有父辈节点,下面的例子使用了 .parents 方法遍历了<a>标签到根节点的所有节点.
#link = soup.a
# link
## <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
# for parent in link.parents:
#     if parent is None:
#         print(parent)
#     else:
#         print(parent.name)
##p
## body
##html
## [document]
## None

兄弟节点:

.next_sibling 和 .previous_sibling 属性

# link2 = soup.find(id='link2')
# print(link2)
# # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
# #实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白. 看看“爱丽丝”文档:
# print(link2.next_sibling) #由于本文是以爱丽丝为版本,所以这里下一个是“and”
# print(link2.previous_sibling) #上一个是一个",".

.next_siblings 和 .previous_siblings属性

# 通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出:
# link2 = soup.find(id='link2')
# for sibling in link2.next_siblings:
#     print(sibling)
#
# # and
#
# # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
# # ;
# # and they lived at the bottom of a well.
# link2 = soup.find(id='link2')
# for sibling in link2.previous_siblings:
#     print(sibling)
#
# # ,
# #
# # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
# # Once upon a time there were three little sisters; and their names were

 搜索文档树:

# #find 和 findall() 唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果.
# find = soup.find('a')
# print(find)
# # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
# print("----------------")
# findall = soup.find_all('a')
# print(findall) #这个是个列表,可以索引取值
# #[<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>]
# findall2 = soup.find_all(attrs={'id':'link2'}) #后面可以加属性来进行过滤
# print("---------")
# print(findall2)
# # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

正则表达式

#如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match()
# 来匹配内容.下面例子中找出所有以b开头的标签,这表示<body>和<b>标签都应该被找到:

# import re
# for tag in soup.find_all(re.compile("^b")):
#     print(tag.name)
# #b
# #b

判断是否有那个属性

# findall可以传入函数作为过滤的参数
# 下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True:
#
# def has_class_but_no_id(tag):
#     return tag.has_attr('class') and not tag.has_attr('id')
# 将这个方法作为参数传入 find_all() 方法,将得到所有<p>标签:
#
# 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>]

findall的属性(find和findall差不多)

# find_all( name , attrs , recursive , string , **kwargs )
#
# soup.find_all("title")
# # [<title>The Dormouse's story</title>]
#
# soup.find_all("p", "title")
# # [<p class="title"><b>The Dormouse's story</b></p>]
#
# 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>]
#
# soup.find_all(id="link2")
# # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
#
# import re
# soup.find(string=re.compile("sisters"))
# # u'Once upon a time there were three little sisters; and their names were
'

findall中 name参数

简单的用法如下:

soup.find_all("title")
# [<title>The Dormouse's story</title>]

重申: 搜索 name 参数的值可以使任一类型的 过滤器 ,字符窜,正则表达式,列表,方法或是 True .
keyword 参数

如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性.

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

如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性:

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

搜索指定名字的属性时可以使用的参数值包括 字符串 , 正则表达式 , 列表, True .

下面的例子在文档树中查找所有包含 id 属性的tag,无论 id 的值是什么:

soup.find_all(id=True)
# [<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>]

使用多个指定名字的参数可以同时过滤tag的多个属性:

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

有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:

data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
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"})
# [<div data-foo="value">foo!</div>]

其他

find_parents()返回所有祖先节点,find_parent()返回直接父节点。
find_next_siblings()返回后面所有兄弟节点,find_next_sibling()返回后面第一个兄弟节点。
find_previous_siblings()返回前面所有兄弟节点,find_previous_sibling()返回前面第一个兄弟节点。
find_all_next()返回节点后所有符合条件的节点, find_next()返回第一个符合条件的节点
find_all_previous()返回节点后所有符合条件的节点, find_previous()返回第一个符合条件的节点

CSS选择器

#Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html [6] , 在 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>]

通过tag标签逐层查找:

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

soup.select("html head title")
# [<title>The Dormouse's story</title>]

找到某个tag标签下的直接子标签 [6] :

soup.select("head > title")
# [<title>The Dormouse's story</title>]

soup.select("p > 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>]

soup.select("p > a:nth-of-type(2)")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

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

soup.select("body > a")
# []

找到兄弟节点标签:

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

soup.select("#link1 + .sister")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

通过CSS的类名查找:

soup.select(".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>]

soup.select("[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>]

通过tag的id查找:

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

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

同时用多种CSS选择器查询元素:

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

通过是否存在某个属性来查找:

soup.select('a[href]')
# [<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>]

通过属性的值来查找:

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

soup.select('a[href^="http://example.com/"]')
# [<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>]

soup.select('a[href$="tillie"]')
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select('a[href*=".com/el"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

通过语言设置来查找:

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")
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

对于熟悉CSS选择器语法的人来说这是个非常方便的方法.Beautiful Soup也支持CSS选择器API,
如果你仅仅需要CSS选择器的功能,那么直接使用 lxml 也可以, 而且速度更快,支持更多的CSS选择器语法,
但Beautiful Soup整合了CSS选择器的语法和自身方便使用API.

解释器种类:

  

解析器使用方法优势劣势
Python标准库 BeautifulSoup(markup, "html.parser")
  • Python的内置标准库
  • 执行速度适中
  • 文档容错能力强
  • Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, "lxml")
  • 速度快
  • 文档容错能力强
  • 需要安装C语言库
lxml XML 解析器

BeautifulSoup(markup, ["lxml-xml"])

BeautifulSoup(markup, "xml")

  • 速度快
  • 唯一支持XML的解析器
  • 需要安装C语言库
html5lib BeautifulSoup(markup, "html5lib")
  • 最好的容错性
  • 以浏览器的方式解析文档
  • 生成HTML5格式的文档
  • 速度慢
  • 不依赖外部扩展

  

  

原文地址:https://www.cnblogs.com/taozizainali/p/9295117.html