解析采集到的网页 学习目标: . 了解网页数据的提取方法; . 掌握使用正则表达式解析网页的方法; . 掌握使用BeautifulSoup解析网页的方法; . 掌握使用lxml工具解析网页的方法。 HTML网页数据的解析和提取是Python网络数据采集开发中非常关键的步骤。 HTML网页的解析和提取有很多方法,本章将从使用正则表达式解析、使用Beautiful Soup解析、使用lxml解析这三个方面进行讲解。 3.1 使用正则表达式解析 简单地说,正则表达式是一种可以用于模式匹配和替换的强大工具。在几乎所有基 于UNIX/Linux系统的软件工具中都能找到正则表达式的痕迹,如Perl或PHP脚本语 言。此外,JavaScript这种脚本语言也提供了对正则表达式的支持。现在,正则表达式已 经成为一个通用的工具,被各类技术人员广泛使用。 3.1.1 基本语法与使用 正则表达式是一个很强大的字符串处理工具,几乎任何关于字符串的操作都可以使 用正则表达式完成。作为一个数据采集工作者,几乎每天都要和字符串打交道,正则表达 式更是不可或缺的技能。正则表达式在不同开发语言中的使用方式可能不一样,不过只 要学会了任意一门语言的正则表达式的用法,其他语言中大部分也只是换了一个函数名 称而已,本质上都是一样的。 首先,Python中的正则表达式大致分为以下几部分。 元字符 模式 函数 re内置对象用法 分组用法 3 48 环视用法 其次,所有关于正则表达式的操作都使用Python标准库中的re模块完成,部分正则 表达式如表3-1所示。 表3-1 部分正则表达式 模式描 述模式描 述 . 匹配任意字符(不包括换行符) \A 匹配字符串开始位置,忽略多行模式 ^ 匹配开始位置,多行模式下匹配每一行 的开始\Z 匹配字符串结束位置,忽略多行模式 $ 匹配结束位置,多行模式下匹配每一行 的结束\b 匹配位于单词开始或结束位置的空字 符串 * 匹配前一个元字符0到多次\B 匹配不在单词开始或结束位置的空字 符串 + 匹配前一个元字符1到多次\d 匹配一个数字,相当于[0-9] ? 匹配前一个元字符0到1次\D 匹配非数字,相当于[^0-9] {m,n} 匹配前一个元字符m 到n次\s 匹配任意空白字符,相当于[\t\n\r\f\v] 3.1.2 Python与正则表达式 正则表达式是一个特殊的字符序列,它能帮助你方便地检查一个字符串是否与某种 模式匹配。Python自1.5版本开始就增加了re模块,提供Perl风格的正则表达式模式。 re模块使Python语言拥有全部的正则表达式功能。compile函数根据一个模式字符串 和可选的标志参数生成一个正则表达式对象,该对象拥有一系列用于正则表达式匹配和 替换的方法。 re模块也提供了与这些方法的功能完全一致的函数,这些函数使用一个模式字符串 作为第一个参数。下面主要介绍Python中常用的正则表达式处理函数。 1. re.match 函数 re.match函数尝试从字符串的起始位置匹配一个模式,如果不是在起始位置匹配成 功,则返回none,该函数的语法如下。 re.match(pattern, string, flags=0) 函数参数说明如表3-2所示。 表3-2 re.match函数参数说明 参 数描 述 pattern 匹配的正则表达式 string 要匹配的字符串 续表 49 参 数描 述 flags 标志位,用于控制正则表达式的匹配方式,如是否区分大小写、多行匹配等。参见:正则 表达式修饰符-可选标志 若匹配成功,则re.match函数返回一个匹配的对象;否则返回None。可以使用 group(num)或groups()方法匹配对象函数以获取匹配表达式。 具体示例代码如下。 #!/usr/bin/python #-*- coding: UTF-8 -*- import re print(re.match('www', 'www.tup.tsinghua.edu.cn').span()) #在起始位置匹配 print(re.match('com', 'www.tup.tsinghua.edu.cn')) #不在起始位置匹配 以上示例运行后的输出结果如下。 (0, 3) None 2. re.search 函数 re.search函数用于扫描整个字符串,并返回第一个成功的匹配。 该函数的语法如下。 re.search(pattern, string, flags=0) 函数参数说明如表3-3所示。 表3-3 re.search函数参数说明 参 数描 述 pattern 匹配的正则表达式 string 要匹配的字符串 flags 标志位,用于控制正则表达式的匹配方式,如是否区分大小写、多行匹配等 若匹配成功,则re.search函数会返回一个匹配的对象;否则返回None。 具体示例代码如下。 #!/usr/bin/python #-*- coding: UTF-8 -*- import re print(re.search('www', 'www.tup.tsinghua.edu.cn').span()) #在起始位置匹配 print(re.search('com', 'www.tup.tsinghua.edu.cn').span()) #不在起始位置匹配 50 以上示例运行后的输出结果如下。 (0, 3) (11, 14) 3. re.match 函数与re.search 函数的区别 re.match函数只匹配字符串的开始位置,如果字符串的开始位置不符合正则表达 式,则匹配失败,函数返回None;而re.search函数匹配整个字符串,直到找到一个匹配。 具体示例代码如下。 #!/usr/bin/python import re line = "Cats are smarter than dogs"; matchObj = re.match( r'dogs', line, re.M|re.I) if matchObj: print ("match --> matchObj.group() : ", matchObj.group()) else: print("No match!!" ) matchObj = re.search( r'dogs', line, re.M|re.I) if matchObj: print("search --> matchObj.group() : ", matchObj.group()) else: print("No match!!" ) 以上示例运行后的输出结果如下。 No match!! search --> matchObj.group() : dogs 下面是正则表达式的相关实例。 #!/usr/bin/python import re line = "Cats are smarter than dogs" matchObj = re.match( r'(.*) are (.*? ) .*', line, re.M|re.I) if matchObj: print("matchObj.group() : ", matchObj.group()) print("matchObj.group(1) : ", matchObj.group(1)) print ("matchObj.group(2) : ", matchObj.group(2)) else: print("No match!!") 其中,正则表达式如下。 51 r'(.*) are (.*? ) .*' 首先,这是一个字符串,前面的一个r表示字符串为非转义的原始字符串,让编译器 忽略反斜杠,也就是忽略转义字符。但是这个字符串中没有反斜杠,所以这个r可有 可无。 “(.*)”为第一个匹配分组,“.*”代表匹配除换行符之外的所有字符。 “(.*?)”为第二个匹配分组,“.*?”后面的多个问号代表非贪婪模式,即只匹配符合 条件的最少字符。 最后的一个“.*”没有被括号包围,所以不是分组,匹配效果和第一个“.*”一样,但 是不计入匹配结果。 matchObj.group()等同于matchObj.group(0),表示匹配到的完整文本字符。 matchObj.group(1)得到第一组匹配结果,也就是“(.*)”匹配到的。 matchObj.group(2)得到第二组匹配结果,也就是“(.*?)”匹配到的。 因为匹配结果中只有两组,所以填3时会报错。 当使用正则表达式爬取面积数据时,首先需要尝试匹配元素中的内容,示例代 码如下所示。 #-*-coding:utf-8-*- import urllib3,re http = urllib3.PoolManager() r = http.request('GET','www.runoob.com/places/default/view/Andorra-6') def scrape(html): area = re.findall('(.*? )',html)[1] return area print(scrape(str(r.data))) 从上述结果可以看出,多个国家属性都使用了标签。要想分 离出面积属性,则可以只选择其中的第二个元素,示例如下。 re.findall('(.*2? ),html)[1] '244,820 square kilometres' 这个迭代版本看起来更好一些,但是网页更新还有很多其他方式,同样使得该正则表 达式无法满足。如将双引号变为单引号,或在标签之间添加多余的空格。下面是 尝试支持这些可能性的改进版本。 re.findall('(.*? )', html ['244,820 square kilometres'] 52 3.2 使用Beautiful Soup 解析 通过Requests库已经可以抓到网页源码了,接下来要从源码中找到并提取数据。 BeautifulSoup是Python的一个库,其主要功能是从网页中爬取数据。BeautifulSoup目 前已经被移植到bs4库中,也就是说,在导入BeautifulSoup时需要先安装bs4库。安装 bs4库后,还需要安装lxml库。如果不安装lxml库,则会使用Python默认的解析器。 下面将介绍BeautifulSoup4的安装和使用方法。 3.2.1 Python网页解析器 网页解析器是用来解析HTML网页的工具,它是一个HTML网页信息提取工具, 就是从HTML网页中解析并提取出“需要的、有价值的数据”或者“新的URL列表”的 工具。网 页解析器将从下载到的URL数据中提取有价值的数据和生成新的URL。对于数 据提取,可以使用正则表达式和BeautifulSoup等方法。正则表达式使用基于字符串的 模糊匹配,对于特点比较鲜明的目标数据具有较好的效果,但通用性不高。使用正则表达 式进行网页解析的过程如图3-1所示。 图3-1 网页解析示意 Python有以下几种网页解析器:正则表达式、html.parser、BeautifulSoup、lxml。常 见的Python网页解析工具有re正则匹配、Python自带的html.parser模块、第三方库 BeautifulSoup及lxm 库。 以上4种网页解析工具属于不同类型的解析器,如图3-2所示。 图3-2 模糊匹配和结构化解析示意 ① 模糊匹配。re正则表达式即为字符串式的模糊匹配模式。 ② 结构化解析。BeautifulSoup、html.parser与lxml为结构化解析模式,它们都以 DOM 树结构为标准进行标签结构信息的提取。 DOM 树即文档对象模型(DocumentObjectModel),其树形标签结构如图3-3所示。 结构化解析是指网页解析器将下载的整个HTML文档当作一个Document对象,然 53 图3-3 DOM 树结构 后利用其上下结构的标签形式对这个对象的上下级标签进行遍历和信息提取操作。 3.2.2 BeautifulSoup第三方库 1. Beautiful Soup 简介 BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库,它能够 通过用户喜欢的转换器实现惯用文档导航、查找和修改文档等操作。在基于Python的 数据采集开发中,主要用到的是BeautifulSoup的查找和提取功能,修改文档方式很少用 到。可以说,BeautifulSoup是一个非常流行的Python模块。 简单来说,BeautifulSoup是Python的一个库,其主要功能是从网页上爬取数据。 BeautifulSoup提供一些简单的、Python式的函数以处理导航、搜索、修改分析树等,它是 一个工具箱,通过解析文档为用户提供需要爬取的数据,不需要多少代码就可以写出一个 完整的应用程序。BeautifulSoup可以自动将输入文档转换为Unicode编码,将输出文档 转换为UTF-8编码,不需要考虑编码方式,除非文档没有指定编码方式,这时,Beautiful Soup就不能自动识别编码方式了,需要说明原始编码方式。BeautifulSoup已成为和 lxml、html6lib一样出色的Python解析器,可以为用户灵活地提供不同的解析策略或更 快的速度。BeautifulSoup会帮助开发人员节省数小时甚至数天的工作时间。 2. 安装Beautiful soup 4 (1)安装方法 接下来介绍BeautifulSoup在Python网络数据采集开发中的使用,该模块可以解析 网页,并提供定位内容的便捷接口。如果还没有安装该模块,则可以使用以下命令安装其 最新版本。 #pip install beautifulsoup4 54 beautifulsoup4(简称bs4)通过PyPi发布,如果无法使用系统包管理安装,那么也可 以通过easy_install或pip进行安装。包的名字是beautifulsoup4,兼容Python2和 Python3。 #yum install libxslt-devel python-devel #pip install lxml 在PyPi中还有一个名字是BeautifulSoup的包,它是BeautifulSoup3的发布版本, 因为很多项目还在使用bs3,所以BeautifulSoup包依然有效。但如果你在编写新项目, 那么你应该安装的是bs4。 如果没有安装easy_install或pip,也可以下载bs4的源码,然后通过setup.py进行 安装。 #python setup.py install 如果上述安装方法都行不通,那么BeautifulSoup的发布协议允许用户将bs4的代 码打包在项目中,这样一来无须安装即可使用。在Python2.7和Python3.2版本下开发 BeautifulSoup,理论上BeautifulSoup应该在所有当前的Python版本中都能正常工作。 BeautifulSoup在发布时被打包成Python2版本的代码,在Python3环境下安装时 会自动转换成Python3的代码,如果没有一个安装的过程,那么代码就不会被转换。如 果代码抛出了ImportError的异常“NomodulenamedHTMLParser”,则表明在Python3 版本中执行了Python2版本的代码。如果代码抛出了ImportError的异常“Nomodule namedhtml.parser”,则表明在Python2版本中执行了Python3版本的代码。如果遇到 上述两种情况,最好的解决方法是重新安装Beautifulsoup4。如果在ROOT_TAG_ NAME = u'[document]'代码处遇到SyntaxError"Invalidsyntax"错误,则需要将bs4的 Python代码版本从Python2转换到Python3,然后重新安装bs4即可。 #python3 setup.py install 或在bs4的目录中执行Python的代码版本转换脚本。 #2to3-3.2 -w bs4 BeautifulSoup支持Python标准库中的HTML解析器,还支持一些第三方解析器, 其中一个是lxml。根据操作系统的不同,可以选择下列方法安装lxml。 #yum install Python-html5lib #pip install html5lib 另一个可供选择的解析器是纯Python实现的html5lib,它的解析方式与浏览器相 同,可以选择下列方法安装html5lib。 55 #apt-get install Python-html5lib #easy_install html5lib pip install html5lib (2)安装测试 安装完成后是一个Python线下自带的、简洁的集成开发环境,测试bs4是否安装成 功的示例如下。 from bs4 import Beautiful Soup import bs4 print bs4 3. Beautiful Soup 语法 使用BeautifulSoup的一般流程有以下3个步骤。 ① 创建BeautifulSoup对象。 ② 使用BeautifulSoup对象的操作方法find_all与find进行解读搜索,示例如下。 soup.find_all('a') soup.find('a') ③ 利用DOM 结构的标签特性进行更为详细的节点信息提取。 4. 使用方法 ① 创建BeautifulSoup对象(即DOM 对象),示例如下。 #引入Beautiful Soup 库 from bs4 import Beatiful Soup #根据HTML 网页字符串结构创建Beatiful Soup 对象 soup=Beautiful Soup(html_doc, #HTML 文档字符串 'html.parser', #HTML 解析器 from_encoding='utf-8' #HTML 文档编码 ) ② 搜索节点(find_all,find),方法如下。 .soup.find_all():查找所有符合查询条件的标签节点,并返回一个列表。 .soup.find():查找符合查询条件的第一个标签节点。 实例1:搜索所有标签,示例如下。 soup.find_all('a') 实例2:查找所有标签名为a且链接符合“/view/123.html”的节点。 实现方法1的示例如下。 56 soup.find_all('a',href='/view/123.html') 实现方法2的示例如下。 soup.find_all('a',href=re.compile(r'/view/\d+\.html')) 实例3:查找所有标签名为a、class属性为abc、文字为Python的节点,示例如下。 soup.findall('a',class_='abc',string='Python') ③ 访问节点信息。 当得到节点IlovePython时,首 先需要获取节点名称,示例如下。 node.name 其次是获取查找到的a节点的href属性,示例如下。 node['href'] 或者示例如下。 node.get('href') 最后是获取查找到的a节点的字符串内容,示例如下。 node.get_text() 5. Beautiful Soup 信息提取实例 相关示例的实现代码如下。 from bs4 import Beautiful Soup html_doc="""The Dormouse's story

The Dormouse's story

Once upon a time there were three little sisters; and their names were Elsie, Lacieand Tillie; and they lived at the bottom of a well.

57

""" links=Beautiful Soup(html_doc,'lxml') print(links.name,links.a['href'],links.get_text()) 将一段文档传入BeautifulSoup的构造方法就能得到一个文档的对象。下面是传入 一段字符串或一个文件句柄的示例。 from bs4 import Beautiful Soup soup = Beautiful Soup(open("index.html")) soup = Beautiful Soup("data") 首先,文档被转换成Unicode,并且HTML的实例都被转换成了Unicode编码,代码 如下。 Beautiful Soup("Sacré bleu!") Sacrébleu! 然后,BeautifulSoup选择最合适的解析器解析这段文档。如果手动指定解析器,那 么BeautifulSoup会选择指定的解析器解析文档。 下面的一段HTML代码将作为例子在本书后续章节中被多次用到,这是《爱丽丝梦 游仙境》中的一段内容(后文中简称这段内容为“爱丽丝”)。 html_doc = """ 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.

使用BeautifulSoup解析这段代码,能够得到一个BeautifulSoup对象,并能按照标 准的缩进格式进行输出,具体如下。 soup = Beautiful Soup(html_doc,'lxml') 58 The Dormouse's story

The Dormouse's story

Once upon a time there were three little sisters; and their names were < a class =" sister" href =" http://www. tup. tsinghua. edu. cn/elsie" id =" link1"> Elsie , < a class =" sister" href =" http://www. tup. tsinghua. edu. cn/lacie" id =" link2"> Lacie and < a class =" sister" href =" http://www. tup. tsinghua. edu. cn/tillie" id =" link2"> Tillie

以下是几个简单的浏览结构化数据的方法。 soup.title The Dormouse's story print(soup.title.name) u'title' print(soup.t itle.string) u'The Dormouse's story' print(soup.title.parent.name) u'head' 59 print(soup.p)

The Dormouse's story

print(soup.p['class']) u'title' print(soup.a) Elsie print(soup.find_all('a')) [Elsie, Lacie, Tillie] print(soup.find(id="link3")) 标签的链接的代码如下。 for link in soup.find_all('a'): print(link.get('href')) http://www.tup.tsinghua.edu.cn/elsie http://www.tup.tsinghua.edu.cn/lacie http://www.tup.tsinghua.edu.cn/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. … 6. Beautiful Soup 对象的种类 BeautifulSoup将复杂的HTML文档转换成了一个复杂的树形结构,每个节点都是 Python对象。所有对象可以归纳为4种:Tag、NavigableString、BeautifulSoup、Comment。 (1)Tag对象 Tag对象与XML或HTML原生文档中的Tag(标签)相同,相关示例如下。 60 soup = Beautiful Soup('Extremely bold') tag = soup.b pring(type(tag)) Tag对象有很多方法和属性,在遍历文档树和搜索文档树中有详细解释。现在介绍 Tag对象中最重要的属性:name和attributes。 ① name。每个Tag对象都有自己的名字,通过name获取,相关示例如下。 tag.name u'b' 如果改变了Tag对象的name,则将影响所有通过当前BeautifulSoup对象生成的 HTML文档,相关示例如下。 tag.name = "blockquote" tag
Extremely bold
②attributes。一个Tag对象可能有多个属性,tag 有一个 class的属性,值为boldest。Tag属性的操作方法与字典相同。 Tag['class'] ['boldest'] 也可以直接“点”取属性,比如.attrs。 Tag.attrs {'class': ['boldest']} Tag对象的属性可以被添加、删除或修改。Tag对象的属性操作方法与字典相同。 tag['class'] = 'verybold' tag['id'] = 1 print(Tag) Extremely bold del tag['class'] del tag['id'] print(Tag) Extremely bold print(Tag['class']) Traceback (most recent call last): File "", line 1, in print(Tag['class']) 61 File "/usr/local/lib/python3.5/site-packages/bs4/element.py", line 997, in __getitem__ return self.attrs[key] KeyError: 'class' print(tag.get('class')) None HTML4定义了一系列可以包含多个值的属性,虽然在HTML5中移除了一些,但 HTML5却增加了更多的其他属性。最常见的多值属性是class(一个Tag对象可以有 多个CSS的class),还有一些其他属性,如rel、rev、accept-charset、headers、accesskey。在 BeautifulSoup中,多值属性的返回类型是list,相关示例如下。 css_soup = Beautiful Soup('

') css_soup.p['class'] ["body", "strikeout"] css_soup = Beautiful Soup('

') css_soup.p['class'] ["body"] 如果某个属性看起来好像有多个值,但它在任何版本的HTML定义中都没有被定 义为多值属性,那么BeautifulSoup会将这个属性作为字符串返回,相关示例如下。 id_soup = Beautiful Soup('

') id_soup.p['id'] 'my id' 将Tag对象转换成字符串时,多值属性会合并为一个值,相关示例如下。 rel_soup = Beautiful Soup('

Back to the homepage

') rel_soup.a['rel'] ['index'] rel_soup.a['rel'] = ['index', 'contents'] print(rel_soup.p)

Back to the homepage

如果转换的文档是XML格式,那么相关标签中就不包含多值属性,相关示例如下。 xml_soup = Beautiful Soup('

', 'xml') xml_soup.p['class'] u'body strikeout' (2)NavigableString对象 BeautifulSoup使用NavigableString类包装Tag对象中的字符串,相关示例如下。 62 print(tag.string) u'Extremely bold' print(type(tag.string)) NavigableString字符串与Python中的Unicode字符串相同,并且支持包含在遍历 文档树和搜索文档树中的一些特性。通过unicode()方法可以直接将NavigableString对 象转换成Unicode字符串,相关示例如下。 unicode_string = unicode(tag.string) unicode_string u'Extremely bold' print(type(unicode_string)) Tag对象中包含的字符串不能编辑,但可以被替换成其他字符串。替换时会用到 replace_with()方法,相关示例如下。 tag.string.replace_with("No longer bold") print(tag)
No longer bold
NavigableString对象支持遍历文档树和搜索文档树中定义的大部分属性,但并非全 部。尤其是一个字符串不能包含其他内容(Tag对象能够包含字符串或其他Tag对象), 字符串不支持contents或string属性或find()方法。如果想在BeautifulSoup之外使用 NavigableString对象,则需要调用unicode()方法,将该对象转换成普通的Unicode字符 串;否则就算BeautifulSoup的方法已经执行结束,该对象的输出也会带有对象的引用地 址,这样会浪费内存。 (3)BeautifulSoup对象 BeautifulSoup对象表示一个文档的全部内容,大多情况下可以把它当作Tag对象。 BeautifulSoup对象支持遍历文档树和搜索文档树中描述的大部分方法。因为Beautiful Soup对象并不是真正的HTML或XML的Tag对象,所以它没有name和attributes属 性,但查看它的name属性却是很方便的,所以BeautifulSoup 对象包含一个值为 “[document]”的特殊属性name。 print(soup.name) u'[document]' BeautifulSoup 中定义的其他类型都可能出现在XML 文档中,如CData、 ProcessingInstruction、Declaration、Doctype。与Comment 对象类似,这些类都是 NavigableString的子类,只是添加了一些额外的方法。下面是使用CData替代注释的 例子。 63 from bs4 import CData cdata = CData("A CDATA block") comment.replace_with(cdata) print(soup.b.prettify()) (4)Comment对象 Comment对象是一个特殊类型的NavigableString对象,相关示例如下。 print(comment) u'Hey, buddy. Want to buy a used parser' 当在HTML文档中出现时,Comment对象会使用特殊的格式输出,相关示例如下。 print(soup.b.prettify()) 7. 遍历文档树 下面用“爱丽丝”文档举例,文档的结构如下。 html_doc = """ 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.

""" from bs4 import Beautiful Soup soup = Beautiful Soup(html_doc) 下面演示怎样从文档的一段内容中找到另一段内容。 64 (1)子节点 一个Tag对象可能包含多个字符串或其他Tag对象,这些都是这个Tag对象的子节 点。BeautifulSoup提供了许多操作和遍历子节点的属性。 注意:在BeautifulSoup中,字符串节点不支持遍历子节点的属性,这是因为字符串 没有子节点。 操作文档树最简单的方法就是告诉它想要获取的Tag对象的name。如果想获取 标签,则只需要用到soup.head,相关示例如下。 print(soup.head) The Dormouse's story print(soup.title) The Dormouse's story 上述代码是获取Tag对象标签的小窍门,可以在文档树的标签中多次调用这个方 法。下面的代码可以获取标签中的第一个标签。 print(soup.body.b) The Dormouse's story 通过点取属性的方式只能获得当前名字的第一个节点,相关示例如下。 print(soup.a) Elsie 如果想要得到所有标签,或是通过名字得到比一个标签更多的内容时,就需要 用到遍历文档树中描述的方法,如find_all()方法。 print(soup.find_all('a')) [Elsie, Lacie, Tillie] (2)父节点 继续分析文档树,每个Tag对象或字符串都有父节点:被包含在某个Tag对象中, 可以通过parent属性获取某个元素的父节点。在“爱丽丝”文档中,标签是 标签的父节点。 title_tag = soup.title print(title_tag) 65 <title>The Dormouse's story print(title_tag.parent) The Dormouse's story 文档title的字符串也有父节点,即标签,相关代码如下。 title_tag.string.parent <title>The Dormouse's story 文档的顶层节点,如的父节点是BeautifulSoup对象,相关代码如下。 html_tag = soup.html print(type(html_tag.parent)) BeautifulSoup对象的parent是None,相关代码如下。 print(soup.parent) None 通过元素的parents属性可以递归地得到元素的所有父节点。下面的例子使用 parents属性遍历了从标签到根节点的所有节点。 link = soup.a print(link) Elsie for parent in link.parents: if parent is None: print(parent) else: print(parent.name) pb ody html [document] None (3)兄弟节点 对于兄弟节点,可以通过下面的例子理解,具体如下。 sibling_soup = Beautiful Soup("text1text2") print(sibling_soup.prettify()) 66 text1 text2 因为标签和标签属于同一层(它们是同一个元素的子节点),所以标 签和标签可以被称为兄弟节点。当一段文档以标准格式输出时,兄弟节点有相同的 缩进级别,在代码中也可以使用这种关系。 在文档树中,使用next_sibling和previous_sibling属性查询兄弟节点,相关代码 如下。 print(sibling_soup.b.next_sibling) text2 print(sibling_soup.c.previous_sibling) text1 标签有next_sibling属性,但是没有previous_sibling属性,因为标签在 同级节点中是第一个。同理,标签有previous_sibling属性,却没有next_sibling属 性,相关代码如下。 print(sibling_soup.b.previous_sibling) None print(sibling_soup.c.next_sibling) None 例子中的字符串text1和text2不是兄弟节点,因为它们的父节点不同,相关代码 如下。 Print(sibling_soup.b.string) u'text1' print(sibling_soup.b.string.next_sibling) None 另外,Tag对象的next_sibling和previous_sibling属性通常是字符串或空白,首先 看以下代码。