更新时间:2021-06-21 来源:黑马程序员 浏览量:
使用lxml库时需要编写和测试XPath语句,显然降低了开发效率。除了lxml库之外,还可以使用Beautiful
Soup来提取HTML/XML数据。虽然这两个库的功能相似,但是Beautiful Soup使用起来更加简洁方便,受到开发人员的推崇。
截止到目前,BeautifulSoup(3.2.1版本)已经停止开发,官网推荐现在的项目使用beautifulsoup4( Beautiful Soup4版本,简称bs4)开发。
bs4是一个HTML/XML的解析器,其主要功能是解析和提取HTML/XML数据。它不仅支持CSS选择器,而且支持Python标准库中的HTML解析器,以及lxml的XML解析器。通过使用这些转化器,实现了惯用的文档导航和查找方式,节省了大量的工作时间,提高了开发项目的效率。
bs4库会将复杂的HTML文档换成树结构(HITML DoM),这个结构中的每个节点都是一个Pyhon对象。这些对象可以归纳为如下4种:
(1) bs4.element.Tag类:表示HTML中的标签,是最基本的信息组织单元,它有两个非常重要的属性,分别是表示标签名字的name属性和表示标签属性的attrs属性。
(2) bs4.element.NavigableString类:表示HTML中标签的文本(非属性字符串)。
(3) bs4.BeautifulSoup类:表示HTML DOM中的全部内容,支持遍历文档树和搜索文档树的大部分方法。
(4) bs4.element.Comment类:表示标签内字符串的注释部分,是一种特殊的Navigable String对象。
使用bs4的一般流程如下:
(1)创建一个BeautifulSoup类型的对象。
根据HTML或者文件创建BeautifulSoup 对象。
(2)通过BeautifulSoup对象的操作方法进行解读搜索。
根据DOM树进行各种节点的搜索( 例如,find_all()方法可以搜索出所有满足要求的节点,find()方法只会搜索出第一个满足要求的节点),只要获得了一个节点,就可以访问节点的名称、属性和文本。
(3)利用DOM树结构标签的特性,进行更为详细的节点信息提取。
在搜索节点时,也可以按照节点的名称、节点的属性或者节点的文字进行搜索。上述流程如下图所示。
通过一个字符串或者类文件对象(存储在本地的文件句柄或Web网页句柄)可以创建BauifulSoup类的对象。 BeautifulSoup类中构造方法的语法如下:
def_init_(self, markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encodings=None, **kwargs)
上述方法的一些参数含义如下:
(1) markup:表示要解析的文档字符串或文件对象。
(2) features:表示解析器的名称。
(3) builder:表示指定的解析器。
(4) from_encoding:表示指定的编码格式。
(5) exclude _encodings:表示排除的编码格式。 例如,根据字符串html_doc创建一个BeautifulSoup对象:
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc, 'lxml')
上述示例中,在创建BeautifulSoup实例时共传入了两个参数。其中,第一个参数表示包含被解析HTML文档的字符串;第二个参数表示使用lxml解析器进行解析。
目前,bs4 支持的解析器包括Python标准库、lxml和html5lib。为了让用户更好地选择合适的解析器,下面列举它们的使用方法和优缺点,如表所示。
解析器 | 使用方法 | 优势 | 劣势 |
lxml HTML解析器 | BeautifulSoup(markup,"lxml") | (1)速度快; (2)文档容错能力强 | 需要安装C语言库 |
Python标准库 | BeautifulSoup(markup, "html.parser") | (1) Python的内置标准库; (2)执行速度适中; (3)文档容错能力强 | Python 2.7.3或3.2.2之前的版本中文档容错能力差 |
lxml XML解析器 | BeautifulSoup(markup, [<<lxml-xml>>]) BeautifulSoup(markup, "xml") | (1)速度快; (2)唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | (1)最好的容错性; (2)以浏览器的方式解析文档 (3)生成HTML5格式的文档 | (1)速度慢; (2)不依赖外部扩展 |
在创建BeautifulSoup对象时,如果没有明确地指定解析器,那么BeautifulSoup对象会根据当前系统安装的库自动选择解析器。解析器的选择顺序为:lxml、html5lib、Python标准库。在下面两种情况下,选择解析器的优先顺序会发生变化:
(1)要解析的文档是什么类型,目前支持html、xml和html5。
(2)指定使用哪种解析器。
如果明确指定的解析器没有安装,那么BeautifulSoup对象会自动选择其他方案。但是,目前只有lxml解析器支持解析XML文档,一且没有安装lxml库,就无法得到解析后的对象。
使用print()函数输出刚创建的BeantifulSoup对象soup,代码如下:
print(soup.prettify())
上述示例中调用了petif()方法进行打印,既可以为HTML标签和内容增加换行符,又可以对标签做相关的处理,以便于更加友好地显示HTML内容。为了直观地比较这两种情况,下面分别列出直接打印和调用prettify()方法后打印的结果。直接使用print()函数进行输出,示例结果如下:
<html><head><title>The Dormouse's story</title></head> <body> </body></html>
调用prettify()方法后进行输出,示例结果如下:
<html> <head> <title> The Dormouse's story </title> </head> <body> </body> </html>
实际上,网页中有用的信息都存在于网页中的文本或者各种不同标签的属性值,为了能获得这些有用的网页信息,可以通过一些查找方法获取文本或者标签属性。因此,bs4库内置了一些查找方法,其中常用的两个方法功能如下:
(1) find()方法:用于查找符合查询条件的第一 个标签节点。
(2) find_all()方法:查找所有符合查询条件的标签节点,并返回一个列表。
这两个方法用到的参数是一样的,这里以find_all()方法为例,介绍在这个方法中这些参数的应用。find_all()方法的定义如下:
find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
上述方法中一些重要参数所表示的含义如下:
1.name参数
在找所有名字为name的标签,但字符串会被自动忽略。下面是 name参数的几种情况:
(1) 传人字符串:在搜索的方法中传入一个字符串,BeautifuSoup对象会查找与字符事无全匹配的内容。例如:
soup.find_all('b')
上述示例用于查找文档中所有的<b>标签。
(2)传人正则表达式:如果传入一个正则表达式,那么BautifulSoup对象会通过re模块的match()函数进行匹配。下面的示例中,使用正则表达式"^b"匹配所有以字母b开头的标签。
import re for tag in soup.find_all(re.compile("^b")) : print(tag.name) #输出结果如下 body
(3)传人列表:如果传入一个列表,那么BeautifulSoup对象会将与列表中任一元索匹配的内容返回。在下面的示例中,找到了文档中所有的<a>标签和<b>标签。
soup.find_all(["a", "b"]) # 部分输出结果如下: [<b>The Dormouse's story</b>, <a classm"sister" href="http://example.com/elsie" 1d="link1">E1sle</a>,
2.attrs参数
如果某个指定名字的参数不是搜索方法中内置的参数名,那么在进行搜索时,会把该参数当作指定名称的标签中的属性来搜索。在下面的示例中,在find_all()方法中传人名称为id的参数,这时BeautiflSoup对象会搜索每个标签的id属性。
soup.find_all(id='link2') # 输出的结果可能是: [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
若传入多个指定名字的参数,则可以同时过滤出标签中的多个属性。在下面的示例中,既可以搜索每个标签的id属性,同时又可以搜索href属性。
import re soup.find_all(href=re.compile("elsie"), id='link1') # 输出的结果可能是: [<a class="sister" href="http://example.com/elsie" id="linkl">Elsie</a>]
如果要搜索的标签名称为class,由于class属于Python的关键字,所以可在class的后面加上一个下画线。例如:
soup.find_all("a", class_="sister") # 部分输出结果如下: # [<a href="http: //example.com/elsie" id="link1">Elsie</a>,
但是,有些标签的属性名称是不能使用的,例如HTML5中的“data-”属性,在程序中使用时,会出现SyntaxError异常信息。这时,可以通过find_all()方法的attrs参数传入一个字典来搜索包含特殊属性的标签。例如:
data_soup=BeautifulSoup('<div data-foo="value">foo!</div>', 'lxml') data_soup.find_all(data-foo="value") # 程序输出如下报错信息: # SyntaxError: keyword can't be an expression data_soup.find_all(attrs={"data-foo": "value"}) # 程序可匹配的结果 # [<div data-foo="value">foo!</div>]
3.text参数
通过在find_all()方法中传人text参数,可以搜索文档中的字符串内容。与name参数的可选值一样,text参数也可以接受字符串、正则表达式和列表等。例如:
soup.find_all(text="Elsie") # [u'Elsie'] soup.find_all(text=["Tillie", "Elsie", "Lacie"]) # [u'Elsie', u'Lacie', u'Tillie']
limit参数 在使用find_all()方法返回匹配的结果时,倘若DOM树非常大,那么搜索的速度会相当慢。这时,如果不需要获得全部的结果,就可以使用limit参数限制返回结果的数量,其效果与SQL语句中的limit关键字所产生的效果类似。一旦搜索到结果的数量达到了limit的限制,就会停止搜索。例如:
soup.find_all("a", limit=2)
上述示例会搜索到最多两个符合搜索条件的标签。
recursive参数 在调用find_all()方法时,Beutifuloup对象会检索当前节点的所有子节点。这时,如果只想搜索当前节点的直接子节点,就可以使用参数recursive=False。例如:
soup.html.find_all("title") # [<title>The Dormouse's story</title>] soup.html.find_all("titile", recursive=False) # []
除了上述两个常用的方法以外,bs4库中还提供了一些通过 节点间的关系进行查我的方法。由于这些方法的参数和用法跟fnd, alll 方法类似,这里就不再另行介绍。
除了bs4库提供的操作方法以外,还可以使用CSS选择器进行查找。什么是CSS呢? CSS (Cascading Style Sheets,层叠样式表)是一种用来表现HTML或XML等文件样式的计算机语言,它不仅可以静态地修饰网页,而且可以配合各种脚本语言动态地对网页各元索进行格式化。
要想使用Css对HTML页面中的元素实现一对一、一对多或多对一的控制,需要用到CSS选择器。 每一条CSS样式定义均由两部分组成,形式如下:
[code]选择器{样式}[/code]
其中,在{} 之前的部分就是“选择器”。选择器指明了}中样式的作用对象,也就是“样式”作用于网页中的哪些元素。
为了使用CSS选择器达到筛选节点的目的,在bs4库的BeautifulSoup类中提供了一个select()方法,该方法会将搜索到的结果放到列表中。 CSS选择器的查找方式可分为如下几种:
1.通过标签查找
在编写CSS时,标签的名称不用加任何修饰。调用select0方法时,可以传人包含某个标签的字符串。使用CSS选择器查找标签的示例如下:
soup.select("title") # 查找的结果可能为 # [<title>The Dormouse's story</title>]
2.通过类名查找
在编写CSS时,需要在类名的前面加上“.” 。例如,查找类名为sister的标签,示例如下:
soup.select('.sister') # 并查找的结果可能为 # [<a href="http://example.com/elsie" id="linkl"><!-- Elsie --></a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a> ]
3.通过id名查找
在编写CSS时,需要在id名称的前面加上“#”。例如,查找id名为link1的标签,具体示例如下;
soup.select("#link1") # 查找的结果可能为 # [<a href-"http://example.com/elsie" id="link1">Elsie</a>]
4.通过组合的形式查找
组合查找与编写CLASS文件时标签名、类名、id
名的组合原理一样,二者需要用空格分开。例如,在标签p中,查找id值等于link1的内容。
soup.select('p #link1') # 手查找的结果可能为 # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
可以使用“>”将标签与子标签分隔,从而找到某个标签下的直接子标签。例如:
soup.select("head > title") # 查找的结果可能为 # [<title>The Dormouse's story</title>]
5.通过属性查找
可以通过属性元素进行查找,属性需要用中括号括起来。但是,属性和标签属于同一个结点,它们中间不能加空格,否则将无法匹配到。例如:
soup.select('a[href="http://example.com/elsie"]') # 查找的结果可能为 # [<a href="http: //example. com/elsie" id="link1">Elsie</a>]
同样,属性仍然可以与上述查找方式组合,即不在同一节点的属性使用空格隔开,同一节点的属性之间不加空格。例如:
soup.select('P a[href="http://example.com/elsie"]') # 查找的结果可能为 # [<a href="http://example.com/elsie" id="link1">Elsie</a>]
上述这些查找方式都会返回一个列表。遍历这个列表,可以调用get _text() 方法来获取节点 的内容。例如:
<br class="Apple-interchange-newline"><div></div> soup=BeautifulSoup(html_doc, 'lxml') for element in soup.select('a'): print(element.get_text()) # 获取节点的内容 # 获取到节点的内容可能为 Elsie Lacie Tillie