遍历文档树

还拿”爱丽丝梦游仙境”的文档来做例子:

  1. html_doc = """
  2. <html><head><title>The Dormouse's story</title></head>
  3. <body>
  4. <p class="title"><b>The Dormouse's story</b></p>
  5.  
  6. <p class="story">Once upon a time there were three little sisters; and their names were
  7. <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
  8. <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
  9. <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
  10. and they lived at the bottom of a well.</p>
  11.  
  12. <p class="story">...</p>
  13. """
  14.  
  15. from bs4 import BeautifulSoup
  16. soup = BeautifulSoup(html_doc, 'html.parser')

通过这段例子来演示怎样从文档的一段内容找到另一段内容

子节点

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

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

tag的名字

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

  1. soup.head
  2. # <head><title>The Dormouse's story</title></head>
  3.  
  4. soup.title
  5. # <title>The Dormouse's story</title>

这是个获取tag的小窍门,可以在文档树的tag中多次调用这个方法.下面的代码可以获取<body>标签中的第一个<b>标签:

  1. soup.body.b
  2. # <b>The Dormouse's story</b>

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

  1. soup.a
  2. # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

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

  1. soup.find_all('a')
  2. # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
  3. # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
  4. # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

.contents 和 .children

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

  1. head_tag = soup.head
  2. head_tag
  3. # <head><title>The Dormouse's story</title></head>
  4.  
  5. head_tag.contents
  6. [<title>The Dormouse's story</title>]
  7.  
  8. title_tag = head_tag.contents[0]
  9. title_tag
  10. # <title>The Dormouse's story</title>
  11. title_tag.contents
  12. # [u'The Dormouse's story']

BeautifulSoup 对象本身一定会包含子节点,也就是说<html>标签也是 BeautifulSoup 对象的子节点:

  1. len(soup.contents)
  2. # 1
  3. soup.contents[0].name
  4. # u'html'

字符串没有 .contents 属性,因为字符串没有子节点:

  1. text = title_tag.contents[0]
  2. text.contents
  3. # AttributeError: 'NavigableString' object has no attribute 'contents'

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

  1. for child in title_tag.children:
  2. print(child)
  3. # The Dormouse's story

.descendants

.contents 和 .children 属性仅包含tag的直接子节点.例如,<head>标签只有一个直接子节点<title>

  1. head_tag.contents
  2. # [<title>The Dormouse's story</title>]

但是<title>标签也包含一个子节点:字符串 “The Dormouse’s story”,这种情况下字符串 “The Dormouse’s story”也属于<head>标签的子孙节点. .descendants 属性可以对所有tag的子孙节点进行递归循环 [5] :

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

上面的例子中, <head>标签只有一个子节点,但是有2个子孙节点:<head>节点和<head>的子节点, BeautifulSoup 有一个直接子节点(<html>节点),却有很多子孙节点:

  1. len(list(soup.children))
  2. # 1
  3. len(list(soup.descendants))
  4. # 25

.string

如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点:

  1. title_tag.string
  2. # u'The Dormouse's story'

如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同:

  1. head_tag.contents
  2. # [<title>The Dormouse's story</title>]
  3.  
  4. head_tag.string
  5. # u'The Dormouse's story'

如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None :

  1. print(soup.html.string)
  2. # None

.strings 和 stripped_strings

如果tag中包含多个字符串 [2] ,可以使用 .strings 来循环获取:

  1. for string in soup.strings:
  2. print(repr(string))
  3. # u"The Dormouse's story"
  4. # u'\n\n'
  5. # u"The Dormouse's story"
  6. # u'\n\n'
  7. # u'Once upon a time there were three little sisters; and their names were\n'
  8. # u'Elsie'
  9. # u',\n'
  10. # u'Lacie'
  11. # u' and\n'
  12. # u'Tillie'
  13. # u';\nand they lived at the bottom of a well.'
  14. # u'\n\n'
  15. # u'...'
  16. # u'\n'

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

  1. for string in soup.stripped_strings:
  2. print(repr(string))
  3. # u"The Dormouse's story"
  4. # u"The Dormouse's story"
  5. # u'Once upon a time there were three little sisters; and their names were'
  6. # u'Elsie'
  7. # u','
  8. # u'Lacie'
  9. # u'and'
  10. # u'Tillie'
  11. # u';\nand they lived at the bottom of a well.'
  12. # u'...'

全部是空格的行会被忽略掉,段首和段末的空白会被删除

父节点

继续分析文档树,每个tag或字符串都有父节点:被包含在某个tag中

.parent

通过 .parent 属性来获取某个元素的父节点.在例子“爱丽丝”的文档中,<head>标签是<title>标签的父节点:

  1. title_tag = soup.title
  2. title_tag
  3. # <title>The Dormouse's story</title>
  4. title_tag.parent
  5. # <head><title>The Dormouse's story</title></head>

文档title的字符串也有父节点:<title>标签

  1. title_tag.string.parent
  2. # <title>The Dormouse's story</title>

文档的顶层节点比如<html>的父节点是 BeautifulSoup 对象:

  1. html_tag = soup.html
  2. type(html_tag.parent)
  3. # <class 'bs4.BeautifulSoup'>

BeautifulSoup 对象的 .parent 是None:

  1. print(soup.parent)
  2. # None

.parents

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

  1. link = soup.a
  2. link
  3. # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
  4. for parent in link.parents:
  5. if parent is None:
  6. print(parent)
  7. else:
  8. print(parent.name)
  9. # p
  10. # body
  11. # html
  12. # [document]
  13. # None

兄弟节点

看一段简单的例子:

  1. sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
  2. print(sibling_soup.prettify())
  3. # <html>
  4. # <body>
  5. # <a>
  6. # <b>
  7. # text1
  8. # </b>
  9. # <c>
  10. # text2
  11. # </c>
  12. # </a>
  13. # </body>
  14. # </html>

因为<b>标签和<c>标签是同一层:他们是同一个元素的子节点,所以<b>和<c>可以被称为兄弟节点.一段文档以标准格式输出时,兄弟节点有相同的缩进级别.在代码中也可以使用这种关系.

.next_sibling 和 .previous_sibling

在文档树中,使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点:

  1. sibling_soup.b.next_sibling
  2. # <c>text2</c>
  3.  
  4. sibling_soup.c.previous_sibling
  5. # <b>text1</b>

<b>标签有 .next_sibling 属性,但是没有 .previous_sibling 属性,因为<b>标签在同级节点中是第一个.同理,<c>标签有 .previous_sibling 属性,却没有 .next_sibling 属性:

  1. print(sibling_soup.b.previous_sibling)
  2. # None
  3. print(sibling_soup.c.next_sibling)
  4. # None

例子中的字符串“text1”和“text2”不是兄弟节点,因为它们的父节点不同:

  1. sibling_soup.b.string
  2. # u'text1'
  3.  
  4. print(sibling_soup.b.string.next_sibling)
  5. # None

实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白. 看看“爱丽丝”文档:

  1. <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
  2. <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
  3. <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>

如果以为第一个<a>标签的 .next_sibling 结果是第二个<a>标签,那就错了,真实结果是第一个<a>标签和第二个<a>标签之间的顿号和换行符:

  1. link = soup.a
  2. link
  3. # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
  4.  
  5. link.next_sibling
  6. # u',\n'

第二个<a>标签是顿号的 .next_sibling 属性:

  1. link.next_sibling.next_sibling
  2. # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>

.next_siblings 和 .previous_siblings

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

  1. for sibling in soup.a.next_siblings:
  2. print(repr(sibling))
  3. # u',\n'
  4. # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
  5. # u' and\n'
  6. # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
  7. # u'; and they lived at the bottom of a well.'
  8. # None
  9.  
  10. for sibling in soup.find(id="link3").previous_siblings:
  11. print(repr(sibling))
  12. # ' and\n'
  13. # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
  14. # u',\n'
  15. # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
  16. # u'Once upon a time there were three little sisters; and their names were\n'
  17. # None

回退和前进

看一下“爱丽丝” 文档:

  1. <html><head><title>The Dormouse's story</title></head>
  2. <p class="title"><b>The Dormouse's story</b></p>

HTML解析器把这段字符串转换成一连串的事件: “打开<html>标签”,”打开一个<head>标签”,”打开一个<title>标签”,”添加一段字符串”,”关闭<title>标签”,”打开<p>标签”,等等.Beautiful Soup提供了重现解析器初始化过程的方法.

.next_element 和 .previous_element

.next_element 属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling 相同,但通常是不一样的.

这是“爱丽丝”文档中最后一个<a>标签,它的 .next_sibling 结果是一个字符串,因为当前的解析过程 [2]因为当前的解析过程因为遇到了<a>标签而中断了:

  1. last_a_tag = soup.find("a", id="link3")
  2. last_a_tag
  3. # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
  4.  
  5. last_a_tag.next_sibling
  6. # '; and they lived at the bottom of a well.'

但这个<a>标签的 .next_element 属性结果是在<a>标签被解析之后的解析内容,不是<a>标签后的句子部分,应该是字符串”Tillie”:

  1. last_a_tag.next_element
  2. # u'Tillie'

这是因为在原始文档中,字符串“Tillie” 在分号前出现,解析器先进入<a>标签,然后是字符串“Tillie”,然后关闭</a>标签,然后是分号和剩余部分.分号与<a>标签在同一层级,但是字符串“Tillie”会被先解析.

.previous_element 属性刚好与 .next_element 相反,它指向当前被解析的对象的前一个解析对象:

  1. last_a_tag.previous_element
  2. # u' and\n'
  3. last_a_tag.previous_element.next_element
  4. # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

.next_elements 和 .previous_elements

通过 .next_elements 和 .previous_elements 的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样:

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

bs4--官文--遍历文档树的更多相关文章

  1. 使用requests爬取梨视频、bilibili视频、汽车之家,bs4遍历文档树、搜索文档树,css选择器

    今日内容概要 使用requests爬取梨视频 requests+bs4爬取汽车之家 bs4遍历文档树 bs4搜索文档树 css选择器 内容详细 1.使用requests爬取梨视频 # 模拟发送http ...

  2. 使用Python爬虫库BeautifulSoup遍历文档树并对标签进行操作详解(新手必学)

    为大家介绍下Python爬虫库BeautifulSoup遍历文档树并对标签进行操作的详细方法与函数下面就是使用Python爬虫库BeautifulSoup对文档树进行遍历并对标签进行操作的实例,都是最 ...

  3. bs4--官文--搜索文档树

    搜索文档树 Beautiful Soup定义了很多搜索方法,这里着重介绍2个: find() 和 find_all() .其它方法的参数和用法类似,请读者举一反三. 再以“爱丽丝”文档作为例子: ht ...

  4. bs4--官文--修改文档树

    修改文档树 Beautiful Soup的强项是文档树的搜索,但同时也可以方便的修改文档树 修改tag的名称和属性 在 Attributes 的章节中已经介绍过这个功能,但是再看一遍也无妨. 重命名一 ...

  5. jsonp 遍历文档

    遍历文档 将html解析成一个Document后,就可以使用类似Dom的方法进行操作 File input = new File("/tmp/input.html"); Docum ...

  6. Python爬虫系列(六):搜索文档树

    今天早上,写的东西掉了.这个烂知乎,有bug,说了自动保存草稿,其实并没有保存.无语 今晚,我们将继续讨论如何分析html文档. 1.字符串 #直接找元素soup.find_all('b') 2.正则 ...

  7. 一文搞懂B树、B-树、B+树

    前言 B树和B-树是同一种数据结构,如果不清楚的话,会被面试官忽悠,所以本文介绍两种数据结构,B树和B+树,废话不多数咱们开干. B树 介绍 在计算机科学中,B树是一种自平衡的树,能够保持数据有序.这 ...

  8. 老李推荐:第14章9节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-遍历控件树查找控件

    老李推荐:第14章9节<MonkeyRunner源码剖析> HierarchyViewer实现原理-遍历控件树查找控件   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员 ...

  9. childNodes遍历DOM节点树

    childNodes遍历DOM节点树 var s = ""; function travel(space,node) { if(node.tagName){ s += space ...

随机推荐

  1. Codeforces Round #402 (Div. 2) A

    Description In Berland each high school student is characterized by academic performance — integer v ...

  2. override和overload的小笔记

    override是覆盖的意思,也就是我们的重写.可以重写覆盖父类的方法,然后实现接口的方法也可以叫做override. 几个要注意的点: 重写一定要用和被重写方法同样的方法名还有参数列表. 抛出的异常 ...

  3. Spark Mllib里如何将trainDara训练数据文件里提取第M到第N字段(图文详解)

    不多说,直接上干货! 具体,见 Hadoop+Spark大数据巨量分析与机器学习整合开发实战的第13章 使用决策树二元分类算法来预测分类StumbleUpon数据集

  4. Javaoo学习数组

  5. [转]Ioc容器Autofac

    本文转自:http://www.cnblogs.com/hkncd/archive/2012/11/21/2780041.html Ioc容器Autofac系列(1)-- 初窥   前言 第一次接触A ...

  6. asp.net 图表

    感谢csdn深南大道,文章转自http://blog.csdn.net/smartsmile2012/article/details/17356673 前台代码 <div> <asp ...

  7. Java删除List指定元素

    List<String> lists = new ArrayList<>(); list.add("123"); list.add("456&qu ...

  8. IE6常见CSS解析Bug和hack

    第一:图片间隙 a:div中的图片间隙: 描述:在div中插入图片时,图片会将div下方撑大3像素 hack1:将<div>和<img>写在一行 hack2:将<img& ...

  9. android布局不带参数返回

    package com.example.lesson3_4; import java.util.ArrayList; import java.util.List; import android.app ...

  10. Sublime Text 3 使用小记

    快捷键: [ // 代码对齐插件 { "keys": ["shift+alt+a"], "command": "alignment ...