Beautifulsoup 库 -- 02 -- 遍历文档树

x33g5p2x  于2021-09-19 转载在 其他  
字(8.3k)|赞(0)|评价(0)|浏览(719)

1. 遍历文档树

  • 测试文档:
  1. 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> """
  2. from bs4 import BeautifulSoup
  3. soup = BeautifulSoup(html_doc, 'html.parser')
  • 怎样从文档的一段内容找到另一段内容

1.1 子节点

  • 一个 Tag 可能包含多个字符串或其它的 Tag,这些都是这个 Tag 的子节点;
  • Beautiful Soup 提供了许多操作和遍历子节点的属性。
    注意: Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点

1.1.1 tag 的名字

  • 操作文档树最简单的方法就是告诉它你想获取的 tag 的 name。

  • 如果想获取 <head> 标签,只要用 soup.head

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

  • soup.body.b

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

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

  1. print(soup.p)
  2. print(soup.p)
  3. print(soup.find_all('a'))

输出:

  1. <p class="title"><b>The Dormouse's story</b></p> <p class="title"><b>The Dormouse's story</b></p>
  2. [<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>]

1.1.2 contents 和 children 属性

  • tag 的 contents 属性可以将 tag 的子节点以列表的方式输出:
  1. tag_head = soup.head
  2. print(tag_head)
  3. print(tag_head.contents)
  4. tag_title = tag_head.contents[0]
  5. print(tag_title)
  6. print(tag_title.contents)

输出:

  1. <head><title>The Dormouse's story</title></head> [<title>The Dormouse's story</title>]
  2. <title>The Dormouse's story</title> ["The Dormouse's story"]
  • BeautifulSoup 对象本身一定会包含子节点,也就是说 <html> 标签也是 BeautifulSoup 对象的子节点:
  1. print(len(soup.contents))
  2. print(soup.contents[0].name)
  3. print(soup.contents[1].name)

输出:

  1. 2
  2. None
  3. html

为什么会和教程不一样呢?正常情况下只会有一个子节点,也就是 html,这里为什么会有两个而且,第一个为 None;
答:因为,我们输入文本文档时,这种格式默认前边有空行(空格),所以删除后,即是 1;

  • 字符串没有 contents 属性,因为字符串没有子节点;
  • 通过 tag 的 children 生成器,可以对 tag 的子节点进行循环:
  1. tag_title = soup.title
  2. for child in tag_title.children:
  3. print(child)

输出:

  1. The Dormouse's story

1.1.3 descendants

  • contentschildren 属性仅包含 tag 的直接子节点。

  • 例如:<head> 标签只有一个直接子节点 <title>

  • <title> 标签也包含一个子节点:字符串 “The Dormouse’s story”;

  • 这种情况下字符串 “The Dormouse’s story”也属于 <head> 标签的子孙节点;

  • descendants 属性可以对所有 tag 的子孙节点进行递归循环;

  1. tag_head = soup.head
  2. for child in tag_head.descendants:
  3. print(child)

输出:

  1. title>The Dormouse's story</title> The Dormouse's story

1.1.4 string

  • 如果 tag 只有一个 NavigableString 类型子节点,那么这个 tag 可以使用 string 得到子节点:
  1. tag_title = soup.title
  2. print(tag_title.string)

输出:

  1. The Dormouse's story
  • 如果一个 tag 仅有一个子节点,那么这个 tag 也可以使用 string 方法,输出结果与当前唯一子节点的 string 结果相同:
  • 如果 tag 包含了多个子节点,tag 就无法确定 string 方法应该调用哪个子节点的内容, string 的输出结果是 None:

1.1.5 strings 和 stripped_strings

  • 如果 tag 中包含多个字符串,可以使用 strings 来循环获取:
  1. soup = BeautifulSoup(html_doc, 'html.parser')
  2. for string in soup.strings:
  3. print(repr(string)) #repr(将对象转化为供解释器读取的形式)

输出:

  1. u'\n'
  2. u"The Dormouse's story"
  3. u'\n'
  4. u'\n'
  5. u"The Dormouse's story"
  6. u'\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'
  15. u'...'
  16. u'\n'
  • 输出的字符串中包含了很多空格或空行,使用 stripped_strings 可以去除多余空白内容:

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

  1. soup = BeautifulSoup(html_doc, 'html.parser')
  2. for string in soup.stripped_strings:
  3. print(repr(string)) #repr(将对象转化为供解释器读取的形式)

输出:

  1. u"The Dormouse's story"
  2. u"The Dormouse's story"
  3. u'Once upon a time there were three little sisters; and their names were'
  4. u'Elsie'
  5. u','
  6. u'Lacie'
  7. u'and'
  8. u'Tillie'
  9. u';\nand they lived at the bottom of a well.'
  10. u'...'

1.2 父节点

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

1.2.1 parent

  • 通过 parent 属性来获取某个元素的父节点;
  • 在例子“爱丽丝”的文档中, <head> 标签是 <title> 标签的父节点;
  1. tag_title = soup.title
  2. print(tag_title)
  3. print(tag_title.parent)

输出

  1. <title>The Dormouse's story</title> <head><title>The Dormouse's story</title></head>
  • 文档的顶层节点比如 <html> 的父节点是 BeautifulSoup 对象:
  1. tag_html = soup.html
  2. print(type(tag_html.parent))tag_html = soup.html
  3. print(type(tag_html.parent))

输出:

  1. <class 'bs4.BeautifulSoup'>
  • BeautifulSoup 对象的 parent 是None;

1.2.2 parents

  • 通过元素的 parents 属性可以递归得到元素的所有父辈节点;
  • 下面的例子使用了 parents 方法遍历了 <a>标签到根节点的所有节点:
  1. tag_a = soup.a
  2. print(tag_a)
  3. for parent in tag_a.parents:
  4. if parent is None:
  5. print(parent)
  6. else:
  7. print(parent.name)

输出:

  1. <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
  2. p
  3. body
  4. html
  5. [document]

1.3 兄弟节点

  • 一段文档以标准格式输出时,兄弟节点有相同的缩进级别。
  • 在代码中也可以使用这种关系。
  • 栗子:<b> 标签和 <c> 标签是同一层:他们是同一个元素的子节点,所以 <b><c> 可以被称为兄弟节点:
  1. from bs4 import BeautifulSoup
  2. sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
  3. print(sibling_soup.prettify())

输出:

  1. # <html>
  2. # <body>
  3. # <a>
  4. # <b>
  5. # text1
  6. # </b>
  7. # <c>
  8. # text2
  9. # </c>
  10. # </a>
  11. # </body>
  12. # </html>

1.3.1 next_sibling 和 previous_sibling

  • 在文档树中,使用 next_siblingprevious_sibling 属性来查询兄弟节点;
  1. from bs4 import BeautifulSoup
  2. brother_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
  3. print(brother_soup.b.next_sibling)
  4. print()
  5. print(brother_soup.c.previous_sibling)

输出:

  1. <c>text2</c>
  2. <b>text1</b>
  • 实际文档中的 tag 的 next_siblingprevious_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> 标签和第二个 <a> 标签之间的顿号和换行符;

1.3.2 next_siblings 和 previous_siblings

  • 通过 next_siblingsprevious_siblings 属性可以对当前节点的兄弟节点迭代输出;
  • 栗子1:next_siblings
  1. from bs4 import BeautifulSoup
  2. 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> """
  3. soup = BeautifulSoup(html_doc, 'html.parser')
  4. for sibling in soup.a.next_siblings:
  5. print(repr(sibling))

输出:

  1. ',\n'
  2. <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
  3. ' and\n'
  4. <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
  5. ';\nand they lived at the bottom of a well.'
  • 栗子2:previous_siblings
  1. for sibling in soup.find(id="link3").previous_siblings:
  2. print(repr(sibling))

输出:

  1. ' and\n'
  2. <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
  3. ',\n'
  4. <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
  5. 'Once upon a time there were three little sisters; and their names were\n'

1.4 回退和前进

1.4.1 next_element 和 previous_element

  • next_element 属性指向解析过程中下一个被解析的对象(字符串或 tag );
  • 结果可能与 next_sibling 相同,但通常是不一样的。
  • 栗子:
  1. tag_a_last = soup.find("a", id="link3")
  2. print(tag_a_last)
  3. print("-------------")
  4. print(tag_a_last.next_sibling)
  5. print("-------------")
  6. print(tag_a_last.next_element)

输出:

  1. <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
  2. -------------
  3. ;
  4. and they lived at the bottom of a well.
  5. -------------
  6. Tillie

next_sibling 属性得到的是一串字符串,因为它解析时,遇到 <a> 标签会中断;
next_element 属性得到的是在 <a> 标签解析之后的内容,不是 <a>标签后的句子部分;
这是因为在原始文档中,字符串“Tillie” 在分号前出现,解析器先进入<a>标签,然后是字符串“Tillie”,然后关闭</a>标签,然后是分号和剩余部分。分号与<a>标签在同一层级,但是字符串“Tillie”会被先解析。

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

相关文章