第四部分 解析库的使用(XPath、Beautiful Soup、PyQuery)

在网页节点中,可以定义id、class或其他属性。节点间有层次关系,网页中要通过XPath或CSS选择器定位一个或多个节点。在页面解析时,可利用XPath或CSS选择器提取某个节点,再调用相应方法获取它的正文内容或者属性,就可提取到想要的信息。在python中常用的解析库有lxml、Beautiful Soup、pyquery等。使用这些库可以很大程度上提高效率。

一     使用XPath解析库

XPath,全称XML Path Language,即XML路径语言,是一门在XML文档中查找信息的语言。适用于XML文档和HTML文档搜索。

XPath的选择功能十分强大,提供了非常简洁明了的路径选择表达式。另外,还提供了超过100个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。几乎所有想要定位的节点,都可以用XPath来选择。

XPath于1999年11月16日成为W3C标准,被设计为供XSLT、XPointer以及其他XML解析软件使用,更多的文档可以访问其官方网站:https://www.w3.org/TR/xpath/。

XPath对应的库是lxml库。

1、 XPath的常用规则
下表是XPath的几个常用规则
表4-1 XPath常用规则
表达式               描述
nodename        选取此节点的所有子节点
/                        从当前节点选取直接子节点
//                      从当前节点选取子孙节点
.                        选取当前节点
..                       选取当前节点的父节点
@                     选取属性

下面是XPath的匹配示例,如下所示:
//title[@lang='eng']
这个实例是XPath规则 ,代表选择所有名称为title,同时属性lang的值为eng的节点。

2、 第一个lxml库实例

下面用一个简单的代码了解下XPath对网页的解析过程,代码如下:

 1 from lxml import etree
 2 text = '''
 3 <div>
 4     <ul>
 5         <li class="item-0"><a href="link1.html">first item</a></li>
 6         <li class="item-1"><a href="link2.html">second item</a></li>
 7         <li class="item-inactive"><a href="link2.html">third item</a></li>
 8         <li class="item-1"><a href="link4.html">fourth item</a></li>
 9         <li class="item-0"><a href="link5.html">fifth item</a>
10     </ul>
11 </div>
12 '''
13 html = etree.HTML(text)         # 调用HTML类初始化,自动修正HTML文本
14 result = etree.tostring(html)   # 输出修正后的HTML代码,是bytes类型
15 print(result.decode("utf-8"))

在上面代码中,首先导人lxml库的etree模块,然后声明了一段HTML文本,调用HTML类进行初始化,这样就成功构造了一个XPath解析对象。注意HTML文本中的最后一个li节点是没有闭合的,但是etree模块可以自动修正HTML文本。

调用tostring()方法即可输出修正后的HTML代码,结果是bytes类型。利用decode()方法将其转成str类型,在输出结果中,经过处理后,li节点的标签被补全,并且还自动添加了body、html节点。输出结果如下:

 1 <html><body><div>
 2     <ul>
 3         <li class="item-0"><a href="link1.html">first item</a></li>
 4         <li class="item-1"><a href="link2.html">second item</a></li>
 5         <li class="item-inactive"><a href="link2.html">third item</a></li>
 6         <li class="item-1"><a href="link4.html">fourth item</a></li>
 7         <li class="item-0"><a href="link5.html">fifth item</a>
 8     </li></ul>
 9 </div>
10 </body></html>

还可以直接读取文本文件进行解析,示例如下:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))

上面代码中test.html的内容是前面例子中的HTML代码。这次的输出结果中多了DOCTYPE的声明,对解析没有任何影响。输出如下所示:

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
 2 <html><body><div>&#13;
 3     <ul>&#13;
 4         <li class="item-0"><a href="link1.html">first item</a></li>&#13;
 5         <li class="item-1"><a href="link2.html">second item</a></li>&#13;
 6         <li class="item-inactive"><a href="link2.html">third item</a></li>&#13;
 7         <li class="item-1"><a href="link4.html">fourth item</a></li>&#13;
 8         <li class="item-0"><a href="link5.html">fifth item</a>&#13;
 9     </li></ul>&#13;
10 </div></body></html>

 

 

3、 所有节点
可用 // 开头的XPath规则来选取所有符合要求的节点。用前面的HTML文本,如果要选取所有节点,可以这样实现:

 1 from lxml import etree
 2 html = etree.parse('./test.html', etree.HTMLParser())
 3 result = html.xpath('//*')  # *表示选取所有节点
 4 print(result)   # 输出如下所示:
 5 [<Element html at 0x2555e650208>, <Element body at 0x2555e650308>,
 6 <Element div at 0x2555e650348>, <Element ul at 0x2555e650388>,
 7 <Element li at 0x2555e6503c8>, <Element a at 0x2555e650448>,
 8 <Element li at 0x2555e650488>, <Element a at 0x2555e6504c8>,
 9 <Element li at 0x2555e650508>, <Element a at 0x2555e650408>,
10 <Element li at 0x2555e650548>, <Element a at 0x2555e650588>,
11 <Element li at 0x2555e6505c8>, <Element a at 0x2555e650608>]
View Code

这里使用 * 代表匹配所有节点,也就是整个HTML文本中的所有节点都会被获取。输出返回形式是一个列表,每个元素是Element类型,其后跟了节点的名称,如html、body、div、ul、li、a等,所有节点都包含在列表中。

也可以匹配指定节点名称。如果想获取所有li节点,示例如下:

1 from lxml import etree
2 html = etree.parse('./test.html', etree.HTMLParser())
3 result = html.xpath('//li')  # 匹配所有的li节点
4 print(result)
5 print(result[0])    #输出如下所示:
6 [<Element li at 0x199d8940308>, <Element li at 0x199d8940348>, <Element li at 0x199d8940388>,
7 <Element li at 0x199d89403c8>, <Element li at 0x199d8940408>]
8 <Element li at 0x199d8940308>

要选取所有li节点,可以使用 //,直接加上节点名称即可,调用时直接使用xpath()方法即可。输出结果是列表,每个元素是一个Element对象,要取出某个对象,使用索引即可,如[0]。

4、 子节点
通过 / 或 // 即可查找元素的子节点或子孙节点。假如现在想选择li节点的所有直接a子节点,可以这样实现:

1 from lxml import etree
2 html = etree.parse('./test.html', etree.HTMLParser())
3 result = html.xpath('//li/a')  # 匹配所有的li节点下的a节点
4 print(result)   # 输出如下所示:
5 
6 [<Element a at 0x1e2198b0308>, <Element a at 0x1e2198b0348>,
7 <Element a at 0x1e2198b0388>, <Element a at 0x1e2198b03c8>, <Element a at 0x1e2198b0408>]

这里通过追加 /a 即选择所有li节点的所有直接a子节点。因为 //li 用于选中所有li节点,/a 用于选中li节点的所有直接子节点a,二者组合在一起即获取所有li节点的所有直接a子节点。

此处的 / 用于选取直接子节点,如果要获取所有子孙节点,可以使用 // 。例如,要获取ul节点下的所有子孙a节点,可以这样实现:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul//a')          # 匹配所有的li节点下的a节点
print(result)                                    # 输出结果与前面相同

如果这里用 //ul/a ,就不能获取任何结果。因为 / 用于获取直接子节点,而在 ul 节点下没有直接的a子节点,只有li节点,所以无法获取任何匹配结果。例如:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul/a')
print(result)                                 # 输出结果为空列表:[]

所以要注意 / 和 // 的区别,其中 / 用于获取直接子节点, // 用于获取子孙节点。

5、 父节点

用/或者//可以查找子节点或子孙节点。如果知道一个子节点要查找父节点,可用 .. 来实现。

比如,现在首先选中href属性为link4.html的a节点,然后再获取其父节点,然后再获取其class属性,相关代码如下:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath("//a[@href='link4.html']/../@class")
print(result)                  # 输出是:['item-1']

输出结果正是获取的目标li节点的class属性。也可以通过 parent:: 来获取父节点,代码如下:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath("//a[@href='link4.html']/parent::*/@class")
print(result)              # 输出结果与前面一样:['item-1']

6、 属性匹配

在选取的时候,还可以用 @ 符号进行属性过滤。比如,这里如果要选取class为item-0的li节点,可以这样实现:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath("//li[@class='item-0']")
print(result)                  # 输出如下所示:
[<Element li at 0x1e53b240308>, <Element li at 0x1e53b240348>]

通过加入 [@class='item-0'] ,限制了节点的class属性为item-0 ,而HTML文本中符合条件的li节点有两个,所以结果返回两个匹配到的元素。

7、 获取文本

用XPath中的text()方法可获取节点中的文本,下面尝试获取前面li节点中的文本,相关代码如下:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
print(result)                 # 输出是:['\r\n ']

在输出结果中,没有获取到文本内容,获取到是一个换行符。在上面代码中,XPath中text()前面是 / ,在此处 / 的含义是选取直接子节点,而li的直接子节点都是a节点,文本是在a节点内部,所以这里匹配到的结果是被修正的li节点内部的换行符,因为自动修正的li节点的尾标签换行了。即选中的是这两个节点:
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li>

因其中一个节点自动修正, li节点的尾标签添加的时候换行了,所以提取文本得到的唯一结果是li节点的尾标签和a节点的尾标签之间的换行符。

要获取li节点内部的文本,有两种方式,一是先选取a节点再获取文本,二是使用 // 。下面看下这两个的区别:

from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')           # 获取li节点下的a子节点文本
print(result)            # 输出如下所示:
['first item', 'fifth item']
上面的输出返回了两个值,内容是属性为item-0的li节点的文本,这说明前面的属性匹配是正确的。这里是逐层选取的,先选取li节点,又利用 / 选取其直接子节点 a ,再选取其文本。得到的结果是预期的结果。

接下来用另一种方式(使用 //)选取结果,如下所示:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')             # 获取li节点下的文本
print(result)                # 输出如下所示:
['first item', 'fifth item', '\r\n ']

输出的结果有3个,这里选取了所有子孙节点的文本,其中前两个是li的子节点a节点内部的文本,另一个是最后一个li节点内部的文本,即换行符。

通过上面分析得出,要获取某个节点下的子孙节点内部的所有文本,可直接用 // 加text()的方式,这样获取到最全面的文本信息,但可能会有一些换行符和特殊字符。如果要获取某些特定的子孙节点下的所有文本,可以先选取到特定的子孙节点,再利用text()方法获取其内部文本,这样获取的结果是整洁的。

8、 属性获取

用text()方法可以获取节点内部文本,用 @ 符号可以获取节点的属性值。例如要获取所有li节点下所有a节点的href属性,代码如下:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)                # 输出结果如下:
['link1.html', 'link2.html', 'link2.html', 'link4.html', 'link5.html']

输出结果是以列表形式返回,成功的获取了所有li节点下的a节点的href属性。在代码中,通过 @href获取节点的href属性。要注意的是,此处和属性匹配方法不同,属性匹配是中括号加属性名和值来限定某个属性,如 [@href="link1.html"],而此处的 @href 指的是获取节点的某个属性值,注意二者的区分

9、 属性多值匹配

一个节点的某个属性可能有多个值,用单纯的属性方式获取,会无法匹配。例如:
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[@class="li"]')
print(result)           # 输出结果为空:[]

因为在HTML文本中li节点的class属性有两个值li和li-first,用前面的属性匹配方式就不能匹配到结果。这时要用contains()函数,修改代码如下:
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)           # 输出结果是:['first item']

通过contains()方法,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配。在上面代码中,先匹配到li节点,接着匹配子节点a的文本。在节点有多个属性值时经常用到contains()方法,因为class属性值通常有多个。

10、 多属性匹配

根据多个属性确定一个节点,需要同时匹配多个属性,可使用运行符 and 来连接。示例如下:
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)               # 输出是:['first item']

这里的li节点增加了一个属性name。要确定这个节点,需要同时根据class和name属性来选择,一个条件是class属性里面包含li字符串,另一个条件是name属性为item字符串,二者需要同时满足,需要用and操作符相连,相连之后置于中括号内进行条件筛选。

上面代码中的and是XPath中的运算符。此处,还有很多运算符,如or, mod等。表4-2做了一简短总结。

表4-2   运算符及其介绍

关于此表可参考:http://www.w3school.com.cn/xpath/xpath_operators.asp

11、 按序选择

在一些匹配任务中,可能在某些属性会同时匹配多个节点,但是只想要其中的某个节点,如第二个节点或第一个节点,这时可利用中括号传入索引的方法获取特定次序的节点。注意这里的索引是从1开始。代码如下:

 1 from lxml import etree
 2 
 3 text = '''
 4 <div>
 5     <ul>
 6         <li class="item-0"><a href="link1.html">first item</a></li>
 7         <li class="item-1"><a href="link2.html">second item</a></li>
 8         <li class="item-inactive"><a href="link3.html">third item</a></li>
 9         <li class="item-1"><a href="link4.html">fourth item</a></li>
10         <li class="item-0"><a href="link5.html">fifth item</a>
11     </ul>
12 </div>
13 '''
14 html = etree.HTML(text)
15 result = html.xpath('//li[1]/a/text()')      # [1]表示第一个li节点
16 print(result)                                # 输出:['first item']
17 result = html.xpath('//li[last()]/a/text()') # 选取最后一个li节点
18 print(result)                                # 输出:['fifth item']
19 result = html.xpath('//li[position()<3]/a/text()') # 选取位置(索引)小于3的节点
20 print(result)                                # 输出:['first item', 'second item']
21 result = html.xpath('//li[last()-2]/a/text()')  # 选取最后一个节点减2的节点
22 print(result)                                # 输出:['third item']

在上面代码中,第一次选择的是第一个li节点,中括号传入的是数字1。注意这里的序号是从1开始。第二次选择的是最后一个li节点,中括号传入的是last(),输出是最后一个li节点。第三次选择时,选取位置小于3的li节点,也就是序号为1和2的节点,输出的是前两个li节点。第四次选择时,中括号传入的参数是last()-2,表示选取倒数第三个li节点,因为last()是最后一个,last()-2就是倒数第三个。

这里使用了last()、position()等函数。在XPath中,提供了100多个函数,包括存取、数值、字符串、逻辑、节点、序列等处理功能,它们的具体作用可以参考:http://www.w3school.com.cn/xpath/xpath_functions.asp 。

12、 节点轴选择

XPath提供了很多节点轴选择方法,包括获取子元素、兄弟元素、父元素、祖先元素等,示例如下:

 1 from lxml import etree
 2 
 3 text = '''
 4 <div>
 5     <ul>
 6         <li class="item-0"><a href="link1.html"><span>first item</span></a></li>
 7         <li class="item-1"><a href="link2.html">second item</a></li>
 8         <li class="item-inactive"><a href="link3.html">third item</a></li>
 9         <li class="item-1"><a href="link4.html">fourth item</a></li>
10         <li class="item-0"><a href="link5.html">fifth item</a>
11     </ul>
12     <a></a>
13 </div>
14 '''
15 html = etree.HTML(text)
16 result = html.xpath('//li[1]/ancestor::*')
17 print(result)   # 第一个li节点的所有父节点
18 result = html.xpath('//li[1]/ancestor::div')
19 print(result)   # 第一个li节点的div父节点
20 result = html.xpath('//li[1]/attribute::*')
21 print(result)   # 第一个li节点的所有属性
22 result = html.xpath('//li[1]/child::a[@href="link1.html"]')
23 print(result)   # 第一个li节点的子节点a且属性是href="link1.html"的子节点
24 result = html.xpath('//li[1]/descendant::span')
25 print(result)   # 第一个li节点的所有span子孙节点
26 result = html.xpath('//li[1]/following::*[2]')
27 print(result)   # 第一个li节点后的结束标签之后的所有节点,但限定索引为第二个
28 result = html.xpath('//li[1]/following-sibling::*')
29 print(result)   # 第一个li节点的所有同级(兄弟)节点
30 输出如下所示:
31 [<Element html at 0x1a26413e2c8>, <Element body at 0x1a26413e248>, <Element div at 0x1a26413e208>, <Element ul at 0x1a26413e308>]
32 [<Element div at 0x1a26413e208>]
33 ['item-0']
34 [<Element a at 0x1a26413e308>]
35 [<Element span at 0x1a26413e208>]
36 [<Element a at 0x1a26413e308>]
37 [<Element li at 0x1a26413e248>, <Element li at 0x1a26413e348>, <Element li at 0x1a26413e388>, <Element li at 0x1a26413e3c8>]

上面代码中,第一次选择时,调用ancestor轴,可以获取当前节点的所有祖先节点。其后需要跟两个冒号,然后是节点的选择器,这里直接使用*,表示匹配所有节点,因此返回结果是第一个li节点的所有祖先节点,包括html、body、div和ul。

第二次选择时,又加限定条件,在冒号后面加div,得到的结果就只有div这个祖先节点了。

第三次选择时,调用了attribute轴,可以获取所有属性值,其后跟的选择器还是 * ,这代表获取节点的所有属性,返回值就是li 节点的所有属性值。

第四次选择时,调用了child轴,可以获取所有直接子节点。这里又加了限定条件,选取href属性为link1.html的a节点。

第五次选择时,调用了descendant轴,可以获取所有子孙节点。这里又加了限定条件获取span节点,所以返回的结果只包含span节点而不包含a节点。

第六次选择时,调用了following轴,选取文档中当前节点的结束标签之后的所有节点。这里虽然使用的是 * 匹配,但又加了索引选择,所以只获取了第二个后续节点。

第七次选择时,调用了following-sibling轴,可以获取当前节点之后的所有同级(兄弟)节点。这里使用 * 匹配,所以获取了所有后续同级节点。

XPath轴常用轴选择表

上面是XPath的简单用法,更多轴的用法参考:http://www.w3school.com.cn/xpath/xpath_axes.asp

XPath功能强大,内置函数多,熟练后,可大大提升HTML信息的提取效率。
更多XPath的用法:http://www.w3school.com.cn/xpath/index.asp
如果想查询更多Python lxml库的用法,可以查看http://lxml.de/

二     使用Beautiful Soup

Beautiful Soup,借助网页的结构和属性等特性来解析网页

Beautiful Soup是Python的一个HTML或XML的解析库,用它可方便地从网页中提取数据。官方解释如下:

Beautiful Soup提供一些简单的、Python式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,由于简单,不需要多少代码就可写出一个完整的应用程序。

Beautful Soup自动将输入文档转换为Unicode编码,输出文档转换为UTF-8编码。不需要考虑编码方式,除非文档没有指定一个编码方式,这时仅需要说明一下原始编码方式就可行。

Beautiful Soup 已成为和lxml、html6lib一样出色的Python解释器,为用户灵活地提供不同的解析策略或强劲的速度。

在使用之前确保已经安装了Beautiful Soup和lxml。

1、 解析器

Beautiful Soup在解析时要依赖解析器,支持Python标准库中的HTML解析器,还支持一些第三方解析器(比如lxml)。表4-3列出了Beautiful Soup支持的解析器。

表4-3 Beautiful Soup支持的解析器

通过以上对比可以看出,lxml解析器有解析HTML和XML的功能,而且速度快,容错能力强。要使用lxml,在初始化Beautiful Soup时,第二个参数设为lxml即可,例如:
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)        # 输出:Hello

下面的介绍就使用这个lxml解析器。

2、 基本用法

先通过实例了解Beautiful Soup的基本用法:

 1 html = """
 2 <html><head><title>The Dormouse's story</title></head>
 3     <body>
 4         <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
 5         <p class="story">Once upon a time there were three little sisters; and their names were
 6         <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
 7         <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
 8         <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
 9         and they lived at the bottom of a well.</p>
10         <p class="story">...</p>
11 """
12 from bs4 import BeautifulSoup as bs
13 soup = bs(html, 'lxml')
14 print(soup.prettify())
15 print(soup.title.string)
16 
17 运行结果如下:
18 <html>
19  <head>
20   <title>
21    The Dormouse's story
22   </title>
23  </head>
24  <body>
25   <p class="title" name="dromouse">
26    <b>
27     The Dormouse's story
28    </b>
29   </p>
30   <p class="story">
31    Once upon a time there were three little sisters; and their names were
32    <a class="sister" href="http://example.com/elsie" id="link1">
33     <!-- Elsie -->
34    </a>
35    ,
36    <a class="sister" href="http://example.com/lacie" id="link2">
37     Lacie
38    </a>
39    and
40    <a class="sister" href="http://example.com/tillie" id="link3">
41     Tillie
42    </a>
43    ;
44         and they lived at the bottom of a well.
45   </p>
46   <p class="story">
47    ...
48   </p>
49  </body>
50 </html>
51 The Dormouse's story

在代码中声明的html变量,是一个HTML字符串。但它并不是一个完整的HTML字符串,因为body和html节点没有闭合。接着将它作为第一个参数传递给BeautifulSoup对象,该对象的第二个参数为解析器类型(这里使用lxml)此时完成了BeautifulSoup对象的初始化。然后将这个对象赋值给soup变量。下面就可以调用soup的各个方法和属性解析这串HTML代码。

首先,调用prettify()方法。这个方法可以把要解析的字符串以标准的缩进格式输出。这里需要注意的是,输出结果里面包含body和html节点,也就是说对于不标准的HTML字符串BeautifolSoup,可以自动更正格式。这一步不是由prettify()方法做的,而是在初始化BeautifolSoup时就完成了。

然后调用soup.title.string,输出的是HTML中title节点的文本内容。所以soup.title可以选出HTML中的title节点,再调用string属性就可以得到里面的文本,所以可以通过简单调用几个属性完成文本提取,非常方便。

3、 节点选择器

直接调用节点的名称就可以选择节点元素,再调用string属性就可以得到节点内的文本,这种选择方式速度非常快。如果单个节点结构层次非常清晰,可以选用这种方式来解析。

2.3.1   选择元素
用一个例子详细说明选择元素的方法:

 1 html = """
 2 <html><head><title>The Dormouse's story</title></head>
 3     <body>
 4         <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
 5         <p class="story">Once upon a time there were three little sisters; and their names were
 6         <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
 7         <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
 8         <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
 9         and they lived at the bottom of a well.</p>
10         <p class="story">...</p>
11 """
12 from bs4 import BeautifulSoup as bs
13 soup = bs(html, 'lxml')
14 print(soup.title)   # 输出title标签及内容
15 print(type(soup.title))  # 是bs4的标签元素类型
16 print(soup.title.string)  # 输出title标签文本内容
17 print(soup.head)    # 输出head标签及其下面的子标签
18 print(soup.p)   # 输出p标签及其下面的子标签
19 
20 输出结果如下:
21 <title>The Dormouse's story</title>
22 <class 'bs4.element.Tag'>
23 The Dormouse's story
24 <head><title>The Dormouse's story</title></head>
25 <p class="title" name="dromouse"><b>The Dormouse's story</b></p>

选用前面相同的HTML代码,这次首先打印输出title节点的选择结果,输出结果正是title节点加里面的文字内容。接下来,输出它的类型,是bs4.element.Tag类型,这是BeautifulSoup中一个重要的数据结构。经过选择器选择后,选择结果都是这种Tag类型。Tag具有一些属性,比如string属性,调用该属性,可以得到节点的文本内容,所以接下来的输出结果正是节点的文本内容。

接着,又尝试选择head节点,结果也是节点加其内部的所有内容。最后,选择p节点。不过这次情况比较特殊,结果是第一个p节点的内容,后面的几个p节点并没有选到。也就是说,当有多个节点时,这种选择方式只会选择到第一个匹配的节点,其他的后面节点都会忽略。

2.3.2 提取信息
前面用到的string属性可以获取文本的值。要获取节点名称和节点属性,就要使用name、attrs属性

获取名称:利用name属性获取节点名称。以前面的HTML代码为例,选取title节点,调用其name属性就可得到节点名称:
print(soup.title.name)            # 输出:title

获取属性:每个节点可能有多个属性,比如id和class等,选择这个节点元素后,可调用attrs获取所有属性:
print(soup.p.attrs)              # 获取第一个p节点的所有属性,以字典形式输出
print(soup.p.attrs['name']) # 输出第一个p节点的name属性值
输出结果如下:
{'class': ['title'], 'name': 'dromouse'}
dromouse

从上面输出可知,attrs是以字典形式返回结果,它把选择的节点的所有属性和属性值组合成一个字典。如果要获取name属性,向字典提供键名即可。所以要获取name属性,可通过attrs['name']方式得到。这种方式获取属性相对比较麻烦。另一种方式是不用写attrs,直接在节点元素后面加中括号,传入属性名就可以获取属性值。例如:
print(soup.p['name'])      # 输出name属性值,是字符串类型
print(soup.p['class'])       # 输出class属性值,是列表类型
输出如下所示:
dromouse
['title']

要注意的是,返回结果有的是字符串,有的是字符串组成的列表。在上面代码中,name属性的值是唯一的,返回结果就是单个字符串。对于class属性可以有多个属性值,所以返回的是列表。注意这点区别。

获取内容:用string属性可获取节点元素包含的文本内容,比如要获取第一个p节点的文本:
print(soup.p.string)      # 输出是:The Dormouse's story
注意这里选择的是第一个p节点,文本也是第一个p节点的文本。

2.3.3    嵌套选择
在前面的输出中已经知道每个返回结果都是bs4.element.Tag类型,该类型可以继续调用节点进行下一步的选择。比如,获取了head节点元素,可以继续调用head来选取其内部的head节点元素:
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
"""
from bs4 import BeautifulSoup as bs
soup = bs(html, 'lxml')
print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)

输出如下所示:
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story

第一个输出结果是调用head之后再次调用title而选择的title节点元素。接着输出类型仍然是bs4.element.Tag类型。所以可以继续做嵌套选择。最后输出它的string属性,就是节点里的文本内容。

2.3.4     关联选择
在做选择时,不能一步就选到想要的节点元素,需要先选中某一个节点元素,然后以它为基准再选择它的子节点、父节点、兄弟节点等。

选择子节点和子孙节点:要获取直接子节点,调用contents属性,例如:

 1 html = """
 2 <html>
 3     <head>
 4         <title>The Dormouse's story</title>
 5     </head>
 6     <body>
 7         <p class="story">
 8             Once upon a time there were three little sisters; and their names were
 9             <a href="http://example.com/elsie" class="sister" id="link1">
10                 <span>Elsie</span>
11             </a>
12             <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
13             and
14             <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
15             and they lived at the bottom of a well.
16         </p>
17         <p class="story">...</p>
18 """
19 from bs4 import BeautifulSoup as bs
20 soup = bs(html, 'lxml')
21 print(soup.p.contents)
22 
23 运行结果如下:
24 ['\n            Once upon a time there were three little sisters; and their names were\n            ',
25 <a class="sister" href="http://example.com/elsie" id="link1">
26 <span>Elsie</span>
27 </a>, '\n', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, '\n            and\n            ',
28 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>,
29 '\n            and they lived at the bottom of a well.\n        ']

从输出可知,结果是列表形式。在p节点里有文本、节点,输出将它们以列表统一返回。要注意的是,列表中的每个元素都是p节点的直接子节点。比如第一个a节点里面包含一层span节点,这相当于孙节点了,但是返回结果并没有单独把span节点选出来。所以说,contents属性得到的结果是直接子节点的列表。

还可以调用children属性得到相应的结果:

 1 from bs4 import BeautifulSoup as bs
 2 soup = bs(html, 'lxml')
 3 print(soup.p.children)      # 结果是一个迭代器类型
 4 for i, child in enumerate(soup.p.children):
 5     print(i, child)
 6 
 7 运行结果如下:
 8 <list_iterator object at 0x0000014EEF1662B0>
 9 0
10             Once upon a time there were three little sisters; and their names were
11 
12 1 <a class="sister" href="http://example.com/elsie" id="link1">
13 <span>Elsie</span>
14 </a>
15 2
16 
17 3 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
18 4
19             and
20 
21 5 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
22 6
23             and they lived at the bottom of a well.

调用children属性选择器,结果是生成器类型。接着用for循环输出相应内容。但子孙节点没有单独选出。要得到所有的子孙节点,可以调用descendants属性

 1 from bs4 import BeautifulSoup
 2 soup = BeautifulSoup(html, 'lxml')
 3 print(soup.p.descendants)       # 选取子孙节点,结果是一个生成器类型
 4 for i, child in enumerate(soup.p.descendants):
 5     print(i, child)
 6 
 7 运行结果如下:
 8 <generator object descendants at 0x000001D287BEC360>
 9 0
10             Once upon a time there were three little sisters; and their names were
11 
12 1 <a class="sister" href="http://example.com/elsie" id="link1">
13 <span>Elsie</span>
14 </a>
15 2
16 
17 3 <span>Elsie</span>
18 4 Elsie
19 5
20 
21 6
22 
23 7 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
24 8 Lacie
25 9
26             and
27 
28 10 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
29 11 Tillie
30 12
31             and they lived at the bottom of a well.

descendants属性返回的结果仍然是生成器。遍历输出可以看到,这次输出结果包含了span节点。descendants会递归查询所有子孙节点,得到所有的子孙节点。

父节点和祖先节点
调用parent属性可以获取某个节点元素的父节点。例如:

 1 html = """
 2 <html>
 3     <head>
 4         <title>The Dormouse's story</title>
 5     </head>
 6     <body>
 7         <p class="story">
 8             Once upon a time there were three little sisters; and their names were
 9             <a href="http://example.com/elsie" class="sister" id="link1">
10                 <span>Elsie</span>
11             </a>
12         </p>
13         <p class="story">...</p>
14 """
15 from bs4 import BeautifulSoup as bs
16 soup = bs(html, 'lxml')
17 print(soup.a.parent)
18 
19 运行结果如下:
20 <p class="story">
21             Once upon a time there were three little sisters; and their names were
22             <a class="sister" href="http://example.com/elsie" id="link1">
23 <span>Elsie</span>
24 </a>
25 </p>

上面代码中选择的是第一个a节点的父节点元素。它的父节点是p节点,输出结果是p节点及其内部的内容。parent属性仅仅是找某个节点的直接父节点,没有继续向外层寻找父节点的祖先节点。要获取祖先节点,就调用parents属性

 1 html = """
 2 <html>
 3     <body>
 4         <p class="story">
 5             <a href="http://example.com/elsie" class="sister" id="link1">
 6                 <span>Elsie</span>
 7             </a>
 8         </p>
 9 """
10 from bs4 import BeautifulSoup as bs
11 soup = bs(html, 'lxml')
12 print(soup.a.parents)       # 结果是一个生成器类型
13 print(list(enumerate(soup.p.parents)))
14 
15 运行结果如下:
16 <generator object parents at 0x0000017E847CC360>
17 [(0, <body>
18 <p class="story">
19 <a class="sister" href="http://example.com/elsie" id="link1">
20 <span>Elsie</span>
21 </a>
22 </p>
23 </body>),
24 (1, <html>
25 <body>
26 <p class="story">
27 <a class="sister" href="http://example.com/elsie" id="link1">
28 <span>Elsie</span>
29 </a>
30 </p>
31 </body></html>), (2, <html>
32 <body>
33 <p class="story">
34 <a class="sister" href="http://example.com/elsie" id="link1">
35 <span>Elsie</span>
36 </a>
37 </p>
38 </body></html>)]

输出可知,parents返回结果也是生成器类型。这里用列表输出它的索引和内容,列表中的元素就是a节点的祖先节点。

兄弟节点
前面的方法是获取当前节点的子节点或者父节点。如要获取当前节点的同级节点(兄弟节点),可使用next_sibling、
previous_siblings等属性
。例如:

 1 html = """
 2 <html>
 3     <body>
 4         <p class="story">
 5             Once upon a time there were three little sisters; and their names were
 6             <a href="http://example.com/elsie" class="sister" id="link1">
 7                 <span>Elsie</span>
 8             </a>
 9             Hello
10             <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
11             and
12             <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
13             and they lived at the bottom of a well.
14         </p>
15 """
16 from bs4 import BeautifulSoup as bs
17 soup = bs(html, 'lxml')
18 print('Next Sibling', soup.a.next_sibling)
19 print('Prev Sibling', soup.a.previous_sibling)
20 print('Next Siblings', list(enumerate(soup.a.next_siblings)))
21 print('Prev Siblings', list(enumerate(soup.a.previous_siblings)))
22 
23 输出如下所示:
24 Next Sibling
25             Hello
26 
27 Prev Sibling
28             Once upon a time there were three little sisters; and their names were
29 
30 Next Siblings [(0, '\n            Hello\n            '),
31                (1, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>),
32                (2, '\n            and\n            '),
33                (3, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>),
34                (4, '\n            and they lived at the bottom of a well.\n        ')]
35 Prev Siblings [(0, '\n            Once upon a time there were three little sisters; and their names were\n            ')]

在上面代码中,调用了4个属性,其中next_sibling和previous_sibling分别获取节点的下一个和上一个兄弟元素,next_siblings和previous_siblings则分别获取所有后面和前面的兄弟节点的生成器。

提取信息
要获取相关节点的一些信息,比如文本、属性等,也可用同样的方法。例如:

 1 html = """
 2 <html>
 3     <body>
 4         <p class="story">
 5             Once upon a time there were three little sisters; and their names were
 6             <a href="http://example.com/elsie" class="sister" id="link1">Bob</a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
 7         </p>
 8 """
 9 from bs4 import BeautifulSoup as bs
10 soup = bs(html, 'lxml')
11 print('Next Sibling:')
12 print(type(soup.a.next_sibling))    # 单个节点类型是bs4.element.Tag
13 print(soup.a.next_sibling)          # 输出下一个兄弟节点
14 print(soup.a.next_sibling.string)   # 获取下一个兄弟节点的文本
15 print('Parent:')
16 print(type(soup.a.parents))         # 第一个a节点的所有父节点类型是一个生成器类型,注意和单个节点类型区分
17 print(list(soup.a.parents)[0])      # 需要先将生成类型转换为列表方可获取元素。获取第一个
18 print(list(soup.a.parents)[0].attrs['class'])   # 获取第一个元素的class属性值,因class属性值可以有多个,所以结果是列表。
19 
20 运行结果如下:
21 Next Sibling:
22 <class 'bs4.element.Tag'>
23 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
24 Lacie
25 Parent:
26 <class 'generator'>
27 <p class="story">
28             Once upon a time there were three little sisters; and their names were
29             <a class="sister" href="http://example.com/elsie" id="link1">Bob</a><a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
30 </p>
31 ['story']

如果返回的是单个节点,可直接调用string、attrs等属性获取其文本和属性值;如果返回结果是多个节点的生成器,可先转为列表后取出某个元素,然后再利用string、attrs等属性获取其对应节点的文本和属性。

4、 方法选择器

属性选择的优点是速度快,但对复杂的选择不灵活。Beautiful Soup还提供一些查询方法,如find_all()、find()等,调用这些方法,传入相应参数就可灵活查询。

2.4.1   find_all()方法

find_all查询所有符合条件的元素。给它传入一些属性或文本,就可以得到符合条件的元素,功能强大。对应的参数如下:
find_all(name, attrs, recursive, text, **kwargs)

name参数:可根据节点名查询元素,如name="ul"查找所有ul节点,示例如下:

 1 html='''
 2 <div class="panel">
 3     <div class="panel-heading">
 4         <h4>Hello</h4>
 5     </div>
 6     <div class="panel-body">
 7         <ul class="list" id="list-1">
 8             <li class="element">Foo</li>
 9             <li class="element">Bar</li>
10             <li class="element">Jay</li>
11         </ul>
12         <ul class="list list-small" id="list-2">
13             <li class="element">Foo</li>
14             <li class="element">Bar</li>
15         </ul>
16     </div>
17 </div>
18 '''
19 from bs4 import BeautifulSoup as bs
20 soup = bs(html, 'lxml')
21 print(soup.find_all(name='ul'))   # 查找所有的ul节点
22 print(type(soup.find_all(name='ul')[0]))    # 类型是bs4.element.Tag类型
23 
24 运行结果如下所示:
25 [<ul class="list" id="list-1">
26 <li class="element">Foo</li>
27 <li class="element">Bar</li>
28 <li class="element">Jay</li>
29 </ul>, <ul class="list list-small" id="list-2">
30 <li class="element">Foo</li>
31 <li class="element">Bar</li>
32 </ul>]
33 <class 'bs4.element.Tag'>

这里调用find_all()方法,传入参数name,参数值是ul。这样可查询所有ul节点,返回结果是列表类型,长度为2。每个元素依然是bs4.element.Tag类型。

由于还是Tag类型,还可以进行嵌套查询。下面查询出所有ul节点后,再查询其内部的li节点:

1 for ul in soup.find_all(name='ul'):
2     print(ul.find_all(name='li'))
3 输出如下所示:
4 [<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
5 [<li class="element">Foo</li>, <li class="element">Bar</li>]

输出结果是2个列表,列表的每个元素还是Tag类型。再一次遍历每个li,就可获取它的文本:

 1 for ul in soup.find_all(name='ul'):
 2     print(ul.find_all(name='li'))
 3     for li in ul.find_all(name='li'):
 4         print(li.string)
 5 输出如下所示:
 6 [<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
 7 Foo
 8 Bar
 9 Jay
10 [<li class="element">Foo</li>, <li class="element">Bar</li>]
11 Foo
12 Bar

attrs参数:在做查询时,还可以用属性来查询,如attrs={'id':'i1'},示例如下:

 1 html='''
 2 <div class="panel">
 3     <div class="panel-heading">
 4         <h4>Hello</h4>
 5     </div>
 6     <div class="panel-body">
 7         <ul class="list" id="list-1" name="elements">
 8             <li class="element">Foo</li>
 9             <li class="element">Bar</li>
10             <li class="element">Jay</li>
11         </ul>
12         <ul class="list list-small" id="list-2">
13             <li class="element">Foo</li>
14             <li class="element">Bar</li>
15         </ul>
16     </div>
17 </div>
18 '''
19 from bs4 import BeautifulSoup as bs
20 soup = bs(html, 'lxml')
21 print(soup.find_all(attrs={'id': 'list-1'}))
22 print(soup.find_all(attrs={'name': 'elements'}))
23 
24 运行结果如下:
25 [<ul class="list" id="list-1" name="elements">
26 <li class="element">Foo</li>
27 <li class="element">Bar</li>
28 <li class="element">Jay</li>
29 </ul>]
30 [<ul class="list" id="list-1" name="elements">
31 <li class="element">Foo</li>
32 <li class="element">Bar</li>
33 <li class="element">Jay</li>
34 </ul>]

这里查询时用的是attrs参数,参数的类型是字典。如传入attrs={'id': 'list-1'}表示查询id为list-1的节点,结果是列表形式,内容就是符合id为list-1的所有节点。在这里符合条件的元素个数是1,所以结果是长度为1的列表。

对于常用属性,如id和class等,可不用attrs来传递,可以直接传入id这个参数。用上面上HTML代码换种方式查询:

 1 from bs4 import BeautifulSoup as bs
 2 soup = bs(html, 'lxml')
 3 print(soup.find_all(id='list-1'))
 4 print(soup.find_all(class_='element'))
 5 
 6 输出如下所示:
 7 [<ul class="list" id="list-1" name="elements">
 8 <li class="element">Foo</li>
 9 <li class="element">Bar</li>
10 <li class="element">Jay</li>
11 </ul>]
12 [<li class="element">Foo</li>, <li class="element">Bar</li>,
13  <li class="element">Jay</li>, <li class="element">Foo</li>,
14  <li class="element">Bar</li>]

直接传入id='list-1',可查询id为list-1的节点元素。对于class来说,由于class在Python里是一个关键字,所以后面需要加一个下划线,即class_='element',返回的结果依然还是Tag组成的列表。

text参数:可匹配节点的文本,传入的形式可以是字符串,可以是正则表达式对象,示例如下:

 1 import re
 2 html='''
 3 <div class="panel">
 4     <div class="panel-body">
 5         <a>Hello, this is a link</a>
 6         <a>Hello, this is a link, too</a>
 7     </div>
 8 </div>
 9 '''
10 from bs4 import BeautifulSoup as bs
11 soup = bs(html, 'lxml')
12 print(soup.find_all(text=re.compile('link')))
13 输出如下所示:
14 ['Hello, this is a link', 'Hello, this is a link, too']

这里在find_all()方法中传入text参数,该参数为正则表达式对象,结果返回所有匹配正则表达式的节点文本组成的列表。

2.4.2    find()方法

find()方法返回的是第一个匹配的元素。find_all()方法返回所有匹配的元素组成的列表。find()方法同样有name, attrs, text参数选项。例如:

 1 html='''
 2 <div class="panel">
 3     <div class="panel-heading">
 4         <h4>Hello</h4>
 5     </div>
 6     <div class="panel-body">
 7         <ul class="list" id="list-1">
 8             <li class="element">Foo</li>
 9             <li class="element">Bar</li>
10             <li class="element">Jay</li>
11         </ul>
12         <ul class="list list-small" id="list-2">
13             <li class="element">Foo</li>
14             <li class="element">Bar</li>
15         </ul>
16     </div>
17 </div>
18 '''
19 from bs4 import BeautifulSoup as bs
20 soup = bs(html, 'lxml')
21 print(soup.find(name='ul'))         # 查找第一个ul节点
22 print(type(soup.find(name='ul')))   # 查看类型,仍然是:bs4.element.Tag类型
23 print(soup.find(class_='list'))     # 查找第一个class属性为list的节点
24 
25 运行结果如下所示:
26 <ul class="list" id="list-1">
27 <li class="element">Foo</li>
28 <li class="element">Bar</li>
29 <li class="element">Jay</li>
30 </ul>
31 <class 'bs4.element.Tag'>
32 <ul class="list" id="list-1">
33 <li class="element">Foo</li>
34 <li class="element">Bar</li>
35 <li class="element">Jay</li>
36 </ul>

这次的返回结果不再是列表形式, 而是第一个匹配的节点元素,类型依然是Tag类型。

除了find_all() 和 find()外,还有其它查询方法。用法与它们完全相同,但查询范围不同。还有这些方法:
find_parents():返回所有祖先节点。
find_parent():返回所有直接父节点。
find_next_siblings():返回后面所有的兄弟节点。
find_next_sibling():返回后面第一个兄弟节点。
find_previous_siblings():返回前面所有的兄弟节点。
find_previous_sibling():返回前面第一个兄弟节点。
find_all_next():返回节点后所有符合条件的节点。
find_next():返回第一个符合条件的节点。
find_all_previous():返回当前节点前所有符合条件的节点。
find_previous():返回当前节点前第一个符合条件的节点。

5、 CSS选择器

Beautiful Soup还提供了css选择器。可参考下面这个网站:
http://www.w3school.com.cn/cssref/css_selectors.asp

使用CSS选择器时,调用select()方法,传入相应的CSS选择器即可。例如下面示例:

 1 html='''
 2 <div class="panel">
 3     <div class="panel-heading">
 4         <h4>Hello</h4>
 5     </div>
 6     <div class="panel-body">
 7         <ul class="list" id="list-1">
 8             <li class="element">Foo</li>
 9             <li class="element">Bar</li>
10             <li class="element">Jay</li>
11         </ul>
12         <ul class="list list-small" id="list-2">
13             <li class="element">Foo</li>
14             <li class="element">Bar</li>
15         </ul>
16     </div>
17 </div>
18 '''
19 from bs4 import BeautifulSoup as bs
20 soup = bs(html, 'lxml')
21 print(soup.select('.panel .panel-heading'))     # 层级选择器
22 print(soup.select('ul li'))                     # 层级选择器,ul节点下的所有li节点
23 print(soup.select('#list-2 .element'))
24 print(type(soup.select('ul')[0]))               # 仍然是:bs4.element.Tag类型
25 
26 输出如下所示:
27 [<div class="panel-heading">
28  <h4>Hello</h4>
29  </div>]
30 [<li class="element">Foo</li>, <li class="element">Bar</li>,
31  <li class="element">Jay</li>, <li class="element">Foo</li>,
32  <li class="element">Bar</li>]
33 [<li class="element">Foo</li>, <li class="element">Bar</li>]
34 <class 'bs4.element.Tag'>

在上面代码使用了3次CSS选择器,输出结果均是符合CSS选择器节点组成的列表。最后一个输出类型仍然是bs4.element.Tag类型

2.5.1    嵌套选择
select()方法支持嵌套选择。例如,可先选择全部的ul节点,遍历每个ul节点,选择其下的li节点。例如:

1 from bs4 import BeautifulSoup as bs
2 soup = bs(html, 'lxml')
3 for ul in soup.select('ul'):    # 先遍历所有的ul节点
4     print(ul.select('li'))  # 输出每个li节点
5 输出如下所示:
6 [<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
7 [<li class="element">Foo</li>, <li class="element">Bar</li>]

2.5.2      获取属性
由于节点类型是Tag类型,获取属性还可以用原来的方法。例如要获取每个li节点的id属性,可以直接使用中括号传入属性名(例:ul['id']),也可使用attrs属性方式(例:ul.attrs['id'])。如下面代码所示:

 1 from bs4 import BeautifulSoup as bs
 2 soup = bs(html, 'lxml')
 3 for ul in soup.select('ul'):
 4     print(ul['id'])         # 直接使用中括号获取属性
 5     print(ul.attrs['id'])   # 通过attrs获取属性
 6 输出如下所示:
 7 list-1
 8 list-1
 9 list-2
10 list-2

 

 

2.5.3     获取文本
除了可用string属性外,还可以用get_text()方法。例如获取li节点的文本可用:li.get_text()。

 1 from bs4 import BeautifulSoup as bs
 2 soup = bs(html, 'lxml')
 3 for li in soup.select('li'):
 4     print("Get Text:", li.get_text())
 5     print("String:", li.string)
 6 输出如下所示:
 7 Get Text: Foo
 8 String: Foo
 9 Get Text: Bar
10 String: Bar
11 Get Text: Jay
12 String: Jay
13 Get Text: Foo
14 String: Foo
15 Get Text: Bar
16 String: Bar

在使用Beautiful Soup解析库时,注意以下几点:
尽量使用lxml解析库,必要时可用html.parser。
节点选择筛选功能弱但是速度快。
多使用find()或者find_all()查询匹配单个结果或者多个结果。
熟悉CSS选择器,可用select()方法选择。

三     使用pyquery解析库

Beautiful Soup是强大的网页解析库,但对CSS选择器功能不够强大。另外一个对CSS选择器更适合的解析库是pyquery,它与jQuery有一定的相似性。

在使用pyquery前先正确安装pyquery。

3.1     初始化

初始化pyquery时,需要传入HTML文本初始化一个PyQuery对象。初始化方式有多种,可直接传入字符串,传入URL,传入文件名等

3.1.1     字符串方式初始化
首先引入PyQuery对象,声明HTML字符串,并将其当做参数传递给PyQuery类,这样就完成了初始化。接着将初始化对象传入CSS选择器,就可根据选择器选择所有相应的节点。下面代码中选择所有的li节点,例如:

 1 html = '''
 2 <div>
 3     <ul>
 4          <li class="item-0">first item</li>
 5          <li class="item-1"><a href="link2.html">second item</a></li>
 6          <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 7          <li class="item-1 active"><a href="link4.html">fourth item</a></li>
 8          <li class="item-0"><a href="link5.html">fifth item</a></li>
 9      </ul>
10  </div>
11 '''
12 from pyquery import PyQuery as pq
13 doc = pq(html)
14 print(doc('li'))    # 输出如下所示:
15 <li class="item-0">first item</li>
16          <li class="item-1"><a href="link2.html">second item</a></li>
17          <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
18          <li class="item-1 active"><a href="link4.html">fourth item</a></li>
19          <li class="item-0"><a href="link5.html">fifth item</a></li>

 

3.1.2     URL初始化
初始化参数可以传入网址的URL,需要指定参数为url即可。例如:
from pyquery import PyQuery as pq
doc = pq(url='https://www.baidu.com', encoding="utf-8")
print(doc('title'))      # 获取title标签,输出结果如下所示:
<title>百度一下,你就知道</title>

当传入URL时,PyQuery对象会先请求这个URL,然后用得到的HTML内容完成初始化,就是用网页的源代码以字符串的形式传递给PyQuery类来初始化。与下面的作用是相同的:
from pyquery import PyQuery as pq
import requests
text = requests.get('https://www.baidu.com').text
doc = pq(text)
print(doc('title'))       # 输出中文是乱码,后面找解决方法

3.1.3     文件初始化
指定参数filename可以传递本地文件名。本地文件要求是html文件,内容是待解析HTML字符串。先读取文件内容,用文件内容以字符串形式传递给PyQuery类初始化。例如:
from pyquery import PyQuery as pq
doc = pq(filename='jq1.html')
print(doc('title'))      # 输出中文仍然是乱码

3.2      CSS选择器

先用实例来了解pyquery的CSS选择器用法:

 1 html = '''
 2 <div id="container">
 3     <ul class="list">
 4          <li class="item-0">first item 成都</li>
 5          <li class="item-1"><a href="link2.html">second item</a></li>
 6          <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 7          <li class="item-1 active"><a href="link4.html">fourth item</a></li>
 8          <li class="item-0"><a href="link5.html">fifth item</a></li>
 9      </ul>
10  </div>
11 '''
12 from pyquery import PyQuery as pq
13 doc = pq(html)
14 print(doc('#container .list li'))
15 print(type(doc('#container .list li')))
16 输出如下所示:
17 <li class="item-0">first item 成都</li>
18          <li class="item-1"><a href="link2.html">second item</a></li>
19          <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
20          <li class="item-1 active"><a href="link4.html">fourth item</a></li>
21          <li class="item-0"><a href="link5.html">fifth item</a></li>
22 <class 'pyquery.pyquery.PyQuery'>

上面代码中初始化PyQuery对象后,传入一个CSS选择器#container .list li,该选择器的意思是先取id为container的节点,然后再选取其内部的class为list的节点内部的所有li节点。第二个输出可知类型是PyQuery类型。

3.3     查找节点
可以查找子节点、父节点、兄弟节点等,对应着相关的查询函数,使用方法与jQuery中函数用法相同。

3.3.1     子节点
用find()方法查找子节点,参数是CSS选择器。以前面的HTML为例:

 1 from pyquery import PyQuery as pq
 2 doc = pq(html)
 3 items = doc('.list')
 4 print(type(items))      # 输出是PyQuery对象
 5 print(items)
 6 lis = items.find('li')
 7 print(type(lis))        # 输出是PyQuery对象
 8 print(lis)
 9 输出如下所示:
10 <class 'pyquery.pyquery.PyQuery'>
11 <ul class="list">
12          <li class="item-0">first item 成都</li>
13          <li class="item-1"><a href="link2.html">second item</a></li>
14          <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
15          <li class="item-1 active"><a href="link4.html">fourth item</a></li>
16          <li class="item-0"><a href="link5.html">fifth item</a></li>
17      </ul>
18 
19 <class 'pyquery.pyquery.PyQuery'>
20 <li class="item-0">first item 成都</li>
21          <li class="item-1"><a href="link2.html">second item</a></li>
22          <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
23          <li class="item-1 active"><a href="link4.html">fourth item</a></li>
24          <li class="item-0"><a href="link5.html">fifth item</a></li>

首先,选取class为list的节点,调用find()方法,传入css选择器,选取其内部的li节点,最后打印输出。输出中find()方法将符合条件的所有节点选择出来,结果的类型是PyQuery类型。

find()查找范围是节点的所有子孙节点,只想查找子节点,可以用children()方法

1 lis = items.children()
2 print(type(lis))        # 输出是PyQuery对象
3 print(lis)      # 输出如下所示:
4 <class 'pyquery.pyquery.PyQuery'>
5 <li class="item-0">first item 成都</li>
6          <li class="item-1"><a href="link2.html">second item</a></li>
7          <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
8          <li class="item-1 active"><a href="link4.html">fourth item</a></li>
9          <li class="item-0"><a href="link5.html">fifth item</a></li>

还可以向children()方法传入CSS选择器,做进一步筛选。如选出子节点中class为active的节点:

1 lis = items.children('.active')
2 print(lis)  # 输出如下所示:
3 <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
4          <li class="item-1 active"><a href="link4.html">fourth item</a></li>


3.3.2     父节点和祖先节点
用parent()方法可以获取某个节点的直接父节点,用parents()方法可以获取某个节点的祖先节点。例如:

 1 html = '''
 2 <div class="wrap">
 3     <div id="container">
 4         <ul class="list">
 5              <li class="item-0">first item</li>
 6              <li class="item-1"><a href="link2.html">second item</a></li>
 7              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 8              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
 9              <li class="item-0"><a href="link5.html">fifth item</a></li>
10          </ul>
11      </div>
12  </div>
13 '''
14 from pyquery import PyQuery as pq
15 doc = pq(html)
16 items = doc('.list')
17 print('以下是直接父节点')
18 container = items.parent()  # 找直接父节点
19 print(type(container))      # 类型是PyQuery类型
20 print(container)
21 print('以下是祖先节点')
22 parents = items.parents()   # 查找祖先节点
23 print(type(parents))        # 类型是PyQuery类型
24 print(parents)
25 
26 输出如下所示:
27 以下是直接父节点
28 <class 'pyquery.pyquery.PyQuery'>
29 <div id="container">
30         <ul class="list">
31              <li class="item-0">first item</li>
32              <li class="item-1"><a href="link2.html">second item</a></li>
33              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
34              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
35              <li class="item-0"><a href="link5.html">fifth item</a></li>
36          </ul>
37      </div>
38 以下是祖先节点
39 <class 'pyquery.pyquery.PyQuery'>
40 <div class="wrap">
41     <div id="container">
42         <ul class="list">
43              <li class="item-0">first item</li>
44              <li class="item-1"><a href="link2.html">second item</a></li>
45              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
46              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
47              <li class="item-0"><a href="link5.html">fifth item</a></li>
48          </ul>
49      </div>
50  </div><div id="container">
51         <ul class="list">
52              <li class="item-0">first item</li>
53              <li class="item-1"><a href="link2.html">second item</a></li>
54              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
55              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
56              <li class="item-0"><a href="link5.html">fifth item</a></li>
57          </ul>
58      </div>

在祖先节点的输出中有两个:一个是class为wrap的节点,一个是id为container的节点。parents()方法会返回所有的祖先节点。要单独提取某个祖先节点,可向parents()方法传入CSS选择器,只查找满足CSS选择器的节点。例如:
parent = items.parents('.wrap')
print(parent)      # 输出省略,参考上面的输出

3.3.3      兄弟节点
使用siblings()方法可以查找某个节点的所有兄弟节点。向siblings()方法传入CSS选择器,可以查找特定条件的兄弟节点。使用前面的HTML代码为例:

 1 from pyquery import PyQuery as pq
 2 doc = pq(html)
 3 li = doc('.list .item-0.active')    # 查找满足“.list .item-0.active”这个条件的兄弟节点,有4个
 4 print("查找满足‘.list .item-0.active’这个条件的兄弟节点")
 5 print(li.siblings())
 6 print("在兄弟节点中找有class属性为active属性的节点")
 7 print(li.siblings(".active"))
 8 
 9 运行结果如下:
10 查找满足‘.list .item-0.active’这个条件的兄弟节点
11 <li class="item-1"><a href="link2.html">second item</a></li>
12              <li class="item-0">first item</li>
13              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
14              <li class="item-0"><a href="link5.html">fifth item</a></li>
15 在兄弟节点中找有class属性为active属性的节点
16 <li class="item-1 active"><a href="link4.html">fourth item</a></li>

在上面代码中选择class为list的节点内部class为item-0和active的节点,就是第三个li节点。它的兄弟
节点有4个,是第一、二、四、五个li节点。接着向兄弟节点传入CSS选择器,选择了class为active的节点。
此时在兄弟节点中找到一个,就是第三个兄弟节点。所以输出的是第三个兄弟节点。

3.4     遍历
pyquery选择的结果可能有多个节点,也可能是单个节点,它们的类型都是PyQuery类型。不是像Beautiful Soup那样返回列表。对于单个节点可直接打印输出,也可以直接转成字符串。例如:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')     # 获取满足“.item-0.active”这个条件的节点
print(li)
print(str(li))     # 转换成字符串后输出,结果和直接输出是一样的
输出结果如下:
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>

对于多个节点的结果,可使用遍历来获取。例如,把每一个li节点进行遍历,调用items()方法,此时的类型是生成器类型。例如:
from pyquery import PyQuery as pq
doc = pq(html)
lis = doc('li').items()     # 转换为生成器类型,即可迭代
print(type(lis))             # 生成器类型
for li in lis:
        print(str(li).strip(),type(li), sep='\n')
输出结果如下所示:
<class 'generator'>
<li class="item-0">first item</li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-1"><a href="link2.html">second item</a></li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-0"><a href="link5.html">fifth item</a></li>
<class 'pyquery.pyquery.PyQuery'>

由输出可知,调用items()方法后,得到一个生成器,遍历生成器可以得到每个li节点对象。每个li节点对象依然是PyQuery类型,该对象可以调用前面的方法进行选择,如查询子节点、祖先节点等。

3.5     获取信息
获取到节点后,接着就要提取节点包含的信息。有两类重要的信息:获取属性和获取文本

3.5.1     获取属性
获取到某个节点后,可调用attr()方法获取属性:

 1 html = '''
 2 <div class="wrap">
 3     <div id="container">
 4         <ul class="list">
 5              <li class="item-0">first item</li>
 6              <li class="item-1"><a href="link2.html">second item</a></li>
 7              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 8              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
 9              <li class="item-0"><a href="link5.html">fifth item</a></li>
10          </ul>
11      </div>
12  </div>
13 '''
14 from pyquery import PyQuery as pq
15 doc = pq(html)
16 a = doc('.item-0.active a')     # 获取满足条件的a节点
17 print(a, type(a), sep=',')
18 print(a.attr('href'))
19 
20 输出如下所示:
21 <a href="link3.html"><span class="bold">third item</span></a>, <class 'pyquery.pyquery.PyQuery'>
22 link3.html

在上面代码中选取的是class为item-0和active的li节点内的a节点,类型是PyQuery类型。接着调用attr()方法传入属性名称参数,就得到了属性值。还可以直接调用attr属性来获取属性值,例如:
print(a.attr.href)     # 输出:link3.html

a.attr('href')和a.attr.herf获取到的结果是完全一样的。

如果选中的是多个元素,调用attr()方法只能获取到第一个节点的属性值,例如下面代码所示:
a = doc('a')
print(a, type(a))
print(a.attr('href'))
print(a.attr.href)
输出如下所示:
<a href="link2.html">second item</a>
<a href="link3.html"><span class="bold">third item</span></a>
<a href="link4.html">fourth item</a>
<a href="link5.html">fifth item</a> <class 'pyquery.pyquery.PyQuery'>
link2.html
link2.html

在上面的输出中,找到了4个a节点,但属性值只获取到第一个节点的。如果要获取所有的a节点属性,需使用遍历:
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('a')
for item in a.items():
        print(item.attr('href'))
输出如下所示:
link2.html
link3.html
link4.html
link5.html

通过属性获取节点时要注意,如果返回的节点有多个,就需要遍历才能获取每个节点的属性。

3.5.2     获取文本
前面的方法是获取节点和属性,要获取节点内部的文本,可使用text()方法。例如下面代码所示:

 1 html = '''
 2 <div class="wrap">
 3     <div id="container">
 4         <ul class="list">
 5              <li class="item-0">first item</li>
 6              <li class="item-1"><a href="link2.html">second item</a></li>
 7              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 8              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
 9              <li class="item-0"><a href="link5.html">fifth item</a></li>
10          </ul>
11      </div>
12  </div>
13 '''
14 from pyquery import PyQuery as pq
15 doc = pq(html)
16 a = doc('.item-0.active a')
17 print(a)
18 print(a.text())
19 
20 输出如下所示:
21 <a href="link3.html"><span class="bold">third item</span></a>
22 third item

在上面代码中,先获取满足条件的a节点,然后调用text()方法获取其内部的文本信息。该方法忽略掉节点内部包含的所有HTML,只返回纯文字内容。要获取这个节点内部的HTML文本,就使用html()方法。如下所示:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(li.html())
输出如下所示:
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<a href="link3.html"><span class="bold">third item</span></a>

上面的输出可知,获取到第三个li节点,调用html()方法获取li节点内的所有HTML文本。如果获取到的结果是多个节点,text()或html()返回的内容会有所不一样。html()方法返回第一个获取到节点的内部HTML文本,text()则返回所有获取到的节点内部的纯文本,中间用空格隔开,即返回结果是一个字符串。如下面代码所示:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('li')        # 获取所有的li节点
print(li.html())   # 输出的是第一个li节点的html文本
print(li.text())    # 输出的是所有节点的文本
print(type(li.text()))     # 输出是字符串类型
输出如下所示:
first item
first item second item third item fourth item fifth item
<class 'str'>

所以当获取到多个节点时,要获取每个节点内部的HTML文本,需要遍历每个节点。而text()方法不用遍历,该方法将获取的所有文本合并成一个字符串。

3.6     节点操作
pyquery有一系列方法来对节点进行动态修改,比如为某个节点添加一个class,移除某个节点等,这些操作有时候会为提取信息带来极大的便利。节点操作方法太多,下面说几个常用的。

3.6.1     addClass和removeClass
通过下面的实例进行理解:

 1 html = '''
 2 <div class="wrap">
 3     <div id="container">
 4         <ul class="list">
 5              <li class="item-0">first item</li>
 6              <li class="item-1"><a href="link2.html">second item</a></li>
 7              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 8              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
 9              <li class="item-0"><a href="link5.html">fifth item</a></li>
10          </ul>
11      </div>
12  </div>
13 '''
14 from pyquery import PyQuery as pq
15 doc = pq(html)
16 li = doc('.item-0.active')  # 选取满足条件的节点,选取到的是第三个li节点
17 print(li)
18 li.removeClass('active')    # 删除class属性值active
19 print(li)
20 li.addClass('active')       # 添加class属性值active
21 print(li)
22 
23 输出结果如下所示:
24 <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
25 <li class="item-0"><a href="link3.html"><span class="bold">third item</span></a></li>
26 <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>

从上面代码的输出可知,根据class属性获取到节点后,使用removeClass()方法和addClass()方法可以动态改变节点的class属性。

3.6.2     attr 、text 和html
除了操作class属性外,也可用attr()方法对属性进行操作。还可用text()和html()方法改变节点内部的内容。
示例如下:

 1 html = '''
 2 <ul class="list">
 3      <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 4  </ul>
 5 '''
 6 from pyquery import PyQuery as pq
 7 doc = pq(html)
 8 li = doc('.item-0.active')
 9 print(li)
10 li.attr('name', 'link')     # 参数依次是属性名和属性值
11 print(li)
12 li.text('changed item')     # 修改节点内部的文本
13 print(li)
14 li.html('<span>changed item</span>')    # 修改节点内部的html文本
15 print(li)
16 
17 输出结果如下所示:
18 <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
19 <li class="item-0 active" name="link"><a href="link3.html"><span class="bold">third item</span></a></li>
20 <li class="item-0 active" name="link">changed item</li>
21 <li class="item-0 active" name="link"><span>changed item</span></li>

在上面代码中,首先选中li节点,接着调用attr()方法来修改属性,这个方法的第一个参数是属性名,第二个参数是属性值。接着,调用text()和html()方法来改变节点内部的内容。三次操作后,分别输出当前的li节点。从输出可以看出,调用attr()方法后,li节点多了一个原来没有的属性name,其值为link。接着调用text()方法,传入文本后,li节点内部的文本全被改为传入的字符串文本了。最后调用html()方法传入HTML文本后,li节点内部变为传入的HTML文本。

小结:如果attr()方法只传入第一个参数的属性名,是获取这个属性值;如果传入第二个参数,可以用来修改属性值。text()和html()方法如果不传参数,则是获取节点内纯文本和HTML文本;如果传人参数,则进行赋值。

3.6.3     remove()方法
remove()方法是删除,有时会为信息的提取带来非常大的便利。例如下面这段HTML文本:

 1 html = '''
 2 <div class="wrap">
 3     Hello, World
 4     <p>This is a paragraph.</p>
 5  </div>
 6 '''
 7 from pyquery import PyQuery as pq
 8 doc = pq(html)
 9 wrap = doc('.wrap')     # 获取满足条件的节点
10 print(wrap.text())      # 获取所有节点的文本,输出如下所示:
11 Hello, World
12 This is a paragraph.

上面的代码输出同时获取了满足条件的所有文本,如果不要p节点内的文本,这时就要用到remove()方法。先选中p节点,再调用remove()方法将其移除。这时wrap内部就只剩下Hello, World这个文本,此时再利用text()方法提取即可。
wrap.find('p').remove()      # 找到wrap内部的p节点并删除
print(wrap.text())               # 获取wrap内部删除p节点后的文本
输出是:Hello, World

此外,还有很多的节点操作方法,如append(),empty(),prepend()等方法,用法与jQuery的用法完全一致。
详细用法参考官方文档:http://pyquery.readthedocs.io/en/latest/api.html。

3.7      伪类选择器
css选择器的强大,还有一个重要原因,就是它支持多种多样的伪类选择器,例如选择第一个节点、最后一个节点、奇偶数节点、包含某一文本的节点等。示例如下:

 1 html = '''
 2 <div class="wrap">
 3     <div id="container">
 4         <ul class="list">
 5              <li class="item-0">first item</li>
 6              <li class="item-1"><a href="link2.html">second item</a></li>
 7              <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
 8              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
 9              <li class="item-0"><a href="link5.html">fifth item</a></li>
10          </ul>
11      </div>
12  </div>
13 '''
14 from pyquery import PyQuery as pq
15 doc = pq(html)
16 li = doc('li:first-child')  # 选择第一个li节点
17 print(li)
18 li = doc('li:last-child')   # 选择最后一个li节点
19 print(li)
20 li = doc('li:nth-child(2)') # 选择第二个li节点
21 print(li)
22 li = doc('li:gt(2)')        # 选择第三个li节点之后的li节点
23 print(li)
24 li = doc('li:nth-child(2n)')    # 选择偶数位置的li节点,注意索引从1开始
25 print(li)
26 li = doc('li:contains(second)') # 包含second文本的li节点
27 print(li)
28 
29 输出如下所示:
30 <li class="item-0">first item</li>
31 
32 <li class="item-0"><a href="link5.html">fifth item</a></li>
33 
34 <li class="item-1"><a href="link2.html">second item</a></li>
35 
36 <li class="item-1 active"><a href="link4.html">fourth item</a></li>
37              <li class="item-0"><a href="link5.html">fifth item</a></li>
38 
39 <li class="item-1"><a href="link2.html">second item</a></li>
40              <li class="item-1 active"><a href="link4.html">fourth item</a></li>
41 
42 <li class="item-1"><a href="link2.html">second item</a></li>

上面使用了CSS3的伪类选择器,依次选择了第一个li节点、最后一个li节点、第二个li节点、第三个li之后的li节
点、偶数位置的li节点、包含second文本的li节点。

CSS选择器的更多用法参数:http://www.w3school.com.cn/css/index.asp。
pyquery官方文档:http://pyquery.readthedocs.io。

posted @ 2019-03-08 16:03  远方那一抹云  阅读(1017)  评论(0编辑  收藏  举报