Python爬虫利器 | Beautiful Soup4遍历文档

Beautiful Soup介绍

Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。BeautifulSoup会帮你节省数小时甚至数天的工作时间。
以上摘自官网

Beautiful Soup 安装

$ easy_install beautifulsoup4
# or
$ pip install beautifulsoup4

安装解析器

Beautiful Soup 不仅支持 Python 标准库中的 HTML 解析器,还支持很多第三方的解析器,比如 lxmlhtml5lib 等。可以选择下列三种方法来安装lxml:

$ apt-get install Python-lxml

$ easy_install lxml

$ pip install lxml

另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib:

$ apt-get install Python-html5lib

$ easy_install html5lib

$ pip install html5lib

各解析器优缺点如下:
在这里插入图片描述
推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxmlhtml5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.

注意: 如果一段HTML或XML文档格式不正确的话,那么在不同的解析器中返回的结果可能是不一样的,查看

Beautiful Soup 使用

Beautiful Soup 使用来起来非常简单,将一段文档(一段字符串或一个文件句柄)传入BeautifulSoup 的构造方法,就能得到一个文档的对象,有了该对象之后,就可以对该文档做一些我们想做的操作了。而传入的文本大都是通过爬虫爬取过来的,所以 Beautiful Soup 和 requests 库结合使用体验效果更佳。

from bs4 import BeautifulSoup

soup = BeautifulSoup(open("index.html"))

soup = BeautifulSoup("<html>data</html>")

首先,文档被转换成Unicode,并且HTML的实例都被转换成Unicode编码

BeautifulSoup("Sacr&eacute; bleu!")
<html><head></head><body>Sacré bleu!</body></html>

然后,Beautiful Soup选择最合适的解析器来解析这段文档,如果手动指定解析器那么Beautiful Soup会选择指定的解析器来解析文档.

对象的种类

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:Tag , NavigableString, BeautifulSoup, Comment.
其中:

  • Tag 就是 HTML 的一个标签,比如 div,p ,h1~h6标签等,也是用的最多的一个对象。

  • NavigableString 指标签内部的文字,直译就是可遍历的字符串。

  • BeautifulSoup 指一个文档的全部内容,可以当成一个 Tag 来处理。

  • Comment 是一个特殊的 NavigableString,其输出内容不包括注释内容。

Tag

Tag 对象与XML或HTML原生文档中的tag相同:

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

tag中重要的属性

tag中最重要的属性有:nameattributes

Name

每个tag都有自己的名字,通过.name来获取:

tag.name
# u'b'

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

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

Attributes

一个tag可能有很多个属性. tag <b class="boldest">有一个“class” 的属性,值为 “boldest” . tag的属性的操作方法与字典相同:tag的属性可以被添加,删除或修改

tag['class']
# u'boldest'

# tag的属性可以被添加,删除或修改
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>

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

也可以直接”点”取属性, 比如: .attrs :

tag.attrs
# {u'class': u'boldest'}

字符串常被包含在tag内.Beautiful SoupNavigableString类来包装tag中的字符串:

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

tag中包含的字符串不能编辑,但是可以被替换成其它的字符串,用replace_with()方法:

tag.string.replace_with("No longer bold")
tag
# <blockquote>No longer bold</blockquote>

BeautifulSoup

BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作Tag 对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法.

因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有nameattribute属性.但有时查看它的 .name属性是很方便的,所以 BeautifulSoup 对象包含了一个值为“[document]”的特殊属性.name

注释及特殊字符串:
Tag,NavigableString , BeautifulSoup 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象:如文档的注释部分,这就需要Comment对象会使用特殊的格式输出注释部分:

markup = "<b><!--这是一段注释--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>


# Comment 对象会使用特殊的格式输出注释部分
comment
# u'这是一段注释'

遍历文档树

先定义一串 HTML 文本,下面做解析:

html_doc = """
<html><head><title>index</title></head>

<p class="title"><b>商城</b></p>

<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a> 
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>

<p class="welcome">...</p>
"""

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)

子节点

一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点.Beautiful Soup提供了许多操作和遍历子节点的属性.

注意: Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点

获取Tag的名字

操作文档树最简单的方法就是告诉它你想获取的tag的name.如果想获取<head> 标签,只要用 soup.head :

soup.head
# <head><title>index</title></head>

soup.title
# <title>商城</title>

可以在文档树的tag中多次调用这个方法.例如获取<body>标签中的第一个<b>标签:

soup.body.b
# <b>商城</b>

通过点取属性的方式只能获得当前名字的第一个tag:

soup.a
# <a href="http://cityShop.com/elsie" class="city" id="home">home</a>

如果想要得到所有的<a>标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到 find_all():

soup.find_all('a')
# [<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
# <a href="http://cityShop.com/lacie" class="city" id="design">design</a> 
# <a href="http://cityShop.com/tillie" class="city" id="products">products</a>]

.contents 和 .children

tag的.contents 属性可以将tag的子节点以列表的方式输出:

head_tag = soup.head
head_tag
# <head><title>index</title></head>

head_tag.contents
[<title>index</title>]

title_tag = head_tag.contents[0]
title_tag
# <title>index</title>
title_tag.contents
# [u'index']

tag的.children 生成器,可以对tag的子节点进行循环遍历:

for child in title_tag.children:
    print(child)
    # index

.children只可以获取 tag 的直接节点,而获取不到子孙节点,.descendants可以满足。

.contents.children属性仅包含tag的直接子节点.例如,<head>标签只有一个直接子节点<title>
但是<title>标签也包含一个子节点:字符串 “index”,这种情况下字符串“index”也属于<head>标签的子孙节点. .descendants属性可以对所有tag的子孙节点进行递归循环 :

for child in head_tag.descendants:
    print(child)
    # <title>index</title>
    # index

父节点

每个tag或字符串都有父节点:被包含在某个tag中

.parent

通过 .parent属性来获取某个元素的父节点.title的父标签是 headhtml的父标签是 BeautifulSoup 对象,而 BeautifulSoup 对象的父标签是 None。

title_tag = soup.title
title_tag
# <title>index</title>
title_tag.parent
# <head><title>index</title></head>


# 文档title的字符串也有父节点:<title>标签
title_tag.string.parent
# <title>index</title>


# 文档的顶层节点比如<html>的父节点是 BeautifulSoup 对象:
html_tag = soup.html
type(html_tag.parent)
# <class 'bs4.BeautifulSoup'>


# BeautifulSoup 对象的 .parent 是None:
print(soup.parent)
# None

.parents

通过元素的 .parents属性可以递归得到元素的所有父辈节点,下面的例子使用了.parents方法遍历了<a>标签到根节点的所有节点.

link = soup.a
link
# <a href="http://cityShop.com/elsie" class="city" id="home">home</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
兄弟节点即同一级别的兄弟元素,在文档树中,使用.next_sibling.previous_sibling属性来查询兄弟节点:

soup = BeautifulSoup(html_doc, "lxml");
p_tag=soup.p

print(p_tag.next_sibling)
print(p_tag.next_sibling.next_sibling)

# 输出结果

<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a> 
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>
p 的第一个 next_sibling 是p 和 p 之间的换行符。

.next_siblings 和 .previous_siblings
通过 .next_siblings.previous_siblings属性可以对当前节点的兄弟节点迭代输出:

soup = BeautifulSoup(html_doc, "lxml");
p_tag=soup.p

for p_tag in p_tag.previous_siblings:
	print( p_tag)

# 输出结果
<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a> 
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>

# 输出结果

<p class="title"><b>商城</b></p>

前进和后退

通过.next_element.previous_element获取指定标签的前一个或者后一个被解析的对象,注意这个和兄弟节点是有所不同的,兄弟节点是指有相同父亲节点的子节点,而这个前一个或者后一个是按照文档的解析顺序来计算的。
比如在实例的文本 html_doc 中,head的兄弟节点是body(不考虑换行符),因为他们具有共同的父节点 html,但是 head的下一个节点是 title。即soup.head.next_sibling=title soup.head.next_element=title`。

soup = BeautifulSoup(html_doc, "lxml");

head_tag=soup.head
print(head_tag.next_element)

title_tag=soup.title
print(title_tag.next_element)

# 输出结果
<title>index</title>
index

还需要注意的是title下一个解析的标签不是body,而是 title标签内的内容,因为 html的解析顺序是打开 title 标签,然后解析内容,最后关闭title标签。
.previous_element属性刚好与.next_element相反,它指向当前被解析的对象的前一个解析对象:

last_a_tag.previous_element
# u' and\n'
last_a_tag.previous_element.next_element
# <a href="http://cityShop.com/tillie" class="city" id="products">products</a>

另外,同样可以通过.next_elements.previous_elements来迭代文档树。换行符同样会占用解析顺序,与迭代兄弟节点效果一致。

for element in last_a_tag.next_elements:
    print(repr(element))
# u'Tillie'
# u';\欢迎来参观.'
# u'\n\n'
# <p class="story">...</p>
# u'...'
# u'\n'
# None

搜索文档树

Beautiful Soup定义了很多搜索方法,其中比较重要常用的是: find()find_all().
定义一个文档实例:

html_doc = """
<html><head><title>index</title></head>

<p class="title"><b>商城首页</b></p>

<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a> 
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>

<p class="story">...</p>
"""

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)

使用find_all()类似的方法可以查找到想要查找的文档内容:

soup.find_all('b')

# 结果
# [<b>商城首页</b>]

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

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

找出所有名字中包含”t”的标签:

for tag in soup.find_all(re.compile("t")):
    print(tag.name)
    
# 结果
# html
# title

传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:

soup.find_all(["a", "b"])
# [<b>商城首页</b>
#  <a href="http://cityShop.com/elsie" class="city" id="home">home</a>
#  <a href="http://cityShop.com/lacie" class="city" id="design">design</a> 
#  <a href="http://cityShop.com/tillie" class="city" id="products">products</a>]

更详细文档: https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#

猜你喜欢

转载自blog.csdn.net/weixin_43853746/article/details/108015080
今日推荐