zoukankan      html  css  js  c++  java
  • 第四部分 解析库的使用(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)                 # 输出是:[' ']

    在输出结果中,没有获取到文本内容,获取到是一个换行符。在上面代码中,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', ' ']

    输出的结果有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 ['
                Once upon a time there were three little sisters; and their names were
                ',
    25 <a class="sister" href="http://example.com/elsie" id="link1">
    26 <span>Elsie</span>
    27 </a>, '
    ', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, '
                and
                ',
    28 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>,
    29 '
                and they lived at the bottom of a well.
            ']

    从输出可知,结果是列表形式。在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, '
                Hello
                '),
    31                (1, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>),
    32                (2, '
                and
                '),
    33                (3, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>),
    34                (4, '
                and they lived at the bottom of a well.
            ')]
    35 Prev Siblings [(0, '
                Once upon a time there were three little sisters; and their names were
                ')]

    在上面代码中,调用了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=' ')
    输出结果如下所示:
    <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。

  • 相关阅读:
    国王游戏
    选数
    双塔
    线段树
    树状数组及其他特别简单的扩展
    折半搜索
    VUE项目
    git_基本使用
    同源
    axios-使用
  • 原文地址:https://www.cnblogs.com/Micro0623/p/10496376.html
Copyright © 2011-2022 走看看