Python-Beautiful Soup库学习
Beautiful Soup 是一个python库,从html和xml文件中提取数据。它通常为程序员节省数小时或数天的工作。
让我们开始吧
这是一个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>
"""
通过我们使用Beautiful Soup,输出了整齐的数据组织:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
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>
这里有简单的方法:
直接获取第一个遇到的元素。
如soup.title 提取
soup.title
# <title>The Dormouse's story</title>
soup.title.name
# u'title'
soup.title.string
# u'The Dormouse's story'
soup.title.parent.name
# u'head'
soup.p
# <p class="title"><b>The Dormouse's story</b></p>
soup.p['class']
# u'title'
soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
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(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
一个通常的任务就是取提取url在页面中的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.
#
# ...
安装Beautiful Soup
直接pip3 install bs4
安装一个解析器
Beautiful Soup即支持python内部安装的html解析器,也支持很多第三方解析器。
这里是几种解析器的优缺点:
创建soup
要解析文档,请将其传递到BeautifulSoup构造函数中。您可以传入一个字符串或一个打开的文件句柄:
from bs4 import BeautifulSoup
with open("index.html") as fp:
soup = BeautifulSoup(fp)
soup = BeautifulSoup("<html>data</html>")
首先,将文档转换为Unicode,将HTML实体转换为Unicode字符:
BeautifulSoup("Sacré bleu!")
<html><head></head><body>Sacré bleu!</body></html>
对象类型
Beautiful Soup将复杂的HTML文档转换为复杂的Python对象树。但是您只需要处理四种对象:Tag、NavigableString、BeautifulSoup和Comment。
标签类:
标签有很多属性和方法,我将在导航树和搜索树的过程中介绍其中的大部分。目前,标签最重要的特性是它的名称和属性。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
Name
tag.name
# u'b'
如果您更改了标签的名称,更改将反映在Beautiful Soup生成的任何HTML标记中:
tag.name = "blockquote"
tag
# <blockquote class="boldest">Extremely bold</blockquote>
Attributes
一个标签可能有很多的属性。标签有一个属性" id “,其值是"boldest”。您可以通过将标签视为字典来访问标签的属性:
tag['id']
# u'boldest'
你可以直接获取字典通过 .attrs:
tag.attrs
# {u'id': 'boldest'}
您可以添加、删除和修改标记的属性。同样,这是通过把标签当作字典来实现的:
tag['id'] = 'verybold'
tag['another-attribute'] = 1
tag
# <b another-attribute="1" id="verybold"></b>
del tag['id']
del tag['another-attribute']
tag
# <b></b>
tag['id']
# KeyError: 'id'
print(tag.get('id'))
# None
Multi-valued attributes
多值属性:常见的就是class
HTML 4定义了一些可以有多个值的属性。HTML 5删除了其中的一些,但定义了更多。最常见的多值属性是class(也就是说,一个标签可以有多个CSS类)。其他包括rel、rev、accept-charset、header和accesskey。Beautiful Soup将多值属性的值表示为列表:
css_soup = BeautifulSoup('<p class="body"></p>')
css_soup.p['class']
# ["body"]
# 这是一个多值属性。
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]
如果一个属性看起来有不止一个值,但它不是HTML标准的任何版本定义的多值属性,Beautiful Soup将不考虑这个属性:
这是一个单值属性:
id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'
当您将标签转换回字符串时,将合并多个属性值:
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>
HTML 标签的 rel 属性
提示:尽管浏览器不会以任何方式使用该属性,不过搜索引擎可以利用该属性获得更多有关链接的信息。
您可以使用’ get_attribute_list来获得一个总是列表、字符串的值,无论它是否是多值属性。
get_attribute_list(' id ')
# [" my id "]
如果将文档解析为XML,则不存在多值属性 。
NavigableString
字符串对应于标记中的一小段文本。Beautiful Soup使用NavigableString类来包含这些文本:
tag.string
# u'Extremely bold'
type(tag.string)
# <class 'bs4.element.NavigableString'>
NavigableString就像Python Unicode字符串一样,只是它还支持在导航树和搜索树时描述的一些特性。您可以使用Unicode()将NavigableString转换为Unicode字符串:
unicode_string = unicode(tag.string)
unicode_string
# u'Extremely bold'
type(unicode_string)
# <type 'unicode'>
您不能就地编辑字符串,但是可以使用**replace_with()**将一个字符串替换为另一个字符串:
tag.string.replace_with("No longer bold")
tag
# <blockquote>No longer bold</blockquote>
NavigableString支持导航树和搜索树时描述的大部分特性,但不是所有特性。特别是,由于字符串不能包含任何内容(就像标记可能包含字符串或其他标记一样),所以字符串不支持.contents或.string属性或find()方法。
如果希望在Beautiful Soup之外使用NavigableString,应该对其调用unicode()将其转换为普通的Python unicode字符串。如果您不这样做,您的字符串将携带对整个Beautiful Soup解析树的引用,即使您已经使用了Beautiful Soup。这是对内存的巨大浪费。
BeautifulSoup
BeautifulSoup对象本身作为一个整体表示文档。在大多数情况下,可以将其视为标记对象。这意味着它支持导航树和搜索树中描述的大多数方法。
因为BeautifulSoup对象不对应于实际的HTML或XML标记,所以它没有名称和属性。但是有时候,查看它的.name是很有用的,所以它被赋予了一个特殊的.name“[document]”:
soup.name
# u'[document]'
Comments and other special strings
Tag、NavigableString和BeautifulSoup几乎涵盖了HTML或XML文件中看到的所有内容,但是还有一些剩余部分。你唯一需要担心的可能就是Comments:
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>
注释。
Comment对象只是NavigableString的一种特殊类型:
comment
# u'Hey, buddy. Want to buy a used parser'
但当它作为HTML文档的一部分出现时,会显示一条特殊格式的注释:
print(soup.b.prettify())
# <b>
# <!--Hey, buddy. Want to buy a used parser?-->
# </b>
Beautiful Soup为XML文档中可能出现的任何其他东西定义了类:CData、ProcessingInstruction、Declaration和Doctype。与Comment一样,这些类也是NavigableString的子类,它们向字符串添加了一些额外的内容。下面是一个用CDATA块替换注释的例子:
from bs4 import CData
cdata = CData("A CDATA block")
comment.replace_with(cdata)
print(soup.b.prettify())
# <b>
# <![CDATA[A CDATA block]]>
# </b>
导航树
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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
标签可以包含字符串和其他标签。这些元素是标记的子元素。Beautiful Soup为导航和遍历标记的子元素提供了许多不同的属性。
注意,Beautiful Soup字符串不支持这些属性,因为字符串不能有子字符串。
通过tag名称来导航。
soup.head
# <head><title>The Dormouse's story</title></head>
soup.title
# <title>The Dormouse's story</title>
.contents 和 .children
一个标签的子属性,可以通过.contents来调用:
head_tag = soup.head
head_tag
# <head><title>The Dormouse's story</title></head>
head_tag.contents
[<title>The Dormouse's story</title>]
title_tag = head_tag.contents[0]
title_tag
# <title>The Dormouse's story</title>
title_tag.contents
# [u'The Dormouse's story']
BeatifulSoup 对象自己也有子属性,在这个例子中,它的子属性是标签。
len(soup.contents)
# 1
soup.contents[0].name
# u'html'
一个字符串没有子属性:
text = title_tag.contents[0]
text.contents
# AttributeError: 'NavigableString' object has no attribute 'contents'
您可以使用.children生成器迭代标记的子元素,而不是将它们作为列表获取:
for child in title_tag.children:
print(child)
# The Dormouse's story
.descendants (子孙后代)
.contents和.children属性只考虑标记的直接子元素。例如,标签只有一个直接子标签—
head_tag.contents
# [<title>The Dormouse's story</title>]
遍历子孙后代:
for child in head_tag.descendants:
print(child)
# <title>The Dormouse's story</title>
# The Dormouse's story
儿子很少,子孙很多:
len(list(soup.children))
# 1
len(list(soup.descendants))
# 25
.string
如果一个tag只有一个子属性,并且它是NavigableString,那么可以通过.string 来获取
title_tag.string
# u'The Dormouse's story'
如果一个标签的唯一子标签是另一个标签,并且这个标签有一个.string,那么父标签被认为和它的子标签有相同的.string:
head_tag.contents
# [<title>The Dormouse's story</title>]
head_tag.string
# u'The Dormouse's story'
如果有多个属性,那么.string 返回None
.strings and stripped_strings
如果标签中有不止一个东西,你仍然可以只看字符串。使用.strings生成器:
读取多个子属性的string
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'
发现有很多空格,换行那么选择:.stripped_strings