解析库的使用——学习笔记

一、XPath

全称:XML Path Language,即XML路径语言。

1、XPath概念

XPath提供了非常简洁明了的路径选择表达式。还提供了100个内键函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。

2、XPath常用规则

XPath常用规则

表达式 描述
nodename 选取此节点的所有子节点
/ 从当前节点选取直接子节点
// 从当前节点选择子孙节点
. 选取当前节点
选取当前节点的父节点
@ 选取属性

举例:

//title[@lang='eng']

选择所有名称为title,同时属性lang的值为eng的节点。

3、准备工作

下载lxml库

pip install lxml

4、实例引入

from lxml import etree

text = '''

'''

# 使用HTML类初始化,构建一个XPath解析对象
html = etree.HTML(text)
# 输出修正后的html代码
result = etree.tostring(html)
print(result.decode('utf-8'))

首先导入lxml库的etree模块,然后声明一个HTML文本,调用etree的HTML类进行初始化,这里就成功构建了一个XPath解析对象。

注意:声明的文本中最后一个li节点是没有闭合的,但是etree模块自动修正了HTML文本。

调用tostring()方法,即可输出修正后的HTML,但是结果是bytes类型。这里利用decode()方法将其转换为str类型。

# 结果
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div>
</body></html>

看到结果发现,li节点标签被补全了,而且还自动添加了body、html节点。

另外,还可直接读取文本文件进行解析:

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
# 输出修正后的html代码
result = etree.tostring(html)
print(result.decode('utf-8'))

test.html文件中的代码就是上一个案例声明的代码。

这次输出的结果略有不同,多了一个DOCTYPE的声明,不过对解析无影响。

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

5、所有节点(*)

我们一般会用//开头的XPath规则来选取所有符合要求的节点。

这里以前面的HTML文本为例,如要要选取出所有节点,可以这样实现:

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)

运行结果如下:

[<Element html at 0x234ab053648>, <Element body at 0x234ab053748>, <Element div at 0x234ab053788>, <Element ul at 0x234ab0537c8>, <Element li at 0x234ab053808>, <Element a at 0x234ab053888>, <Element li at 0x234ab0538c8>, <Element a at 0x234ab053908>, <Element li at 0x234ab053948>, <Element a at 0x234ab053848>, <Element li at 0x234ab053988>, <Element a at 0x234ab0539c8>, <Element li at 0x234ab053a08>, <Element a at 0x234ab053a48>]

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

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

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li')
print(result)

这里选取所有li节点,可以使用//加上节点名即可。

[<Element li at 0x2e3ff7d0748>, <Element li at 0x2e3ff7d0788>, <Element li at 0x2e3ff7d07c8>, <Element li at 0x2e3ff7d0808>, <Element li at 0x2e3ff7d0848>]

提取结果是一个列表形式。其中每个元素都是一个Element对象。如果想要取出其中一个对象,可以直接用中括号加索引,例[0]

6、子节点

可以通过/或//查找子节点或子孙节点。

/ 是查找子节点

// 是查找子孙节点

选择 li 节点中的所有直接 a 子节点:

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)

要获取 ul 节点下的所有子孙 a 节点:

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul//a')
print(result)

如果这里用了//ul/a,就无法获取任何结果了。因为/用于获取直接子节点,而ul节点下没有a节点。

7、父节点

(1)、通过 …

通过 … 来查找父节点。

首先选中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']
'''

(2)、通过 parent::

通过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']
'''

8、属性匹配(@)

使用@符号进行属性过滤

选取class为item-0的 li 节点:

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
print(result)

'''
# 结果:
[, ]
'''

我们通过加入[@class=“item-0”],限制了节点的class属性为item-0。

9、文本获取 (text())

用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节点内部文本,就有两种方式:一是先选取a节点在获取文本,二是使用//

第一种:先选取a节点在获取文本

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)

'''
结果:
['first item', 'fifth item']
'''

第二种:使用//

from lxml import etree


html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
print(result)

'''
结果:
['first item', 'fifth item', '\r\n']
'''

根据结果发现,返回的是3个结果。这里是选取子孙节点的文本,其中前两个是li的子节点中a节点内部的文本,最后一个是li节点内部的文本。

10、属性获取 (@)

用@就可以获取属性的值

获取所有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', 'link3.html', 'link4.html', 'link5.html']
'''

通过@href获取节点的href属性。

和属性匹配不同,属性匹配是中括号加属性名和值来限定某个属性:[@class=“item-0”]

属性获取的@href指的是获取节点的某个属性。

11、属性多值匹配

通过contains()方法,第一个参数传入属性,第二个参数传入属性值。

text = '''
  • first item
  • '''

    这里的HTML文本中li节点的class属性有两个值分别是li和li-first

    使用contains()方法,匹配class值为li和li-first的li节点:

    from lxml import etree
    
    text = '''
    
  • first item
  • '''
    html = etree.HTML(text) result = html.xpath('//li[contains(@class, "li li-first")]') print(result) ''' 结果: [] '''

    12、多属性匹配

    使用运算符连接

    根据多个属性来确定一个节点,可以使用and符号连接

    from lxml import etree
    
    text = '''
    
  • first item
  • '''
    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
    or
    mod 计算除法的余数
    | 计算两个节点集
    + 加法
    - 减法
    * 乘法
    div 除法
    = 等于
    != 不等于
    < 小于
    <= 小于或等于
    > 大于
    >= 大于或等于

    13、按序选择

    在选择的时候某些属性可能同时匹配了多个节点,但是只想要其中的某个节点。

    • [index] 中括号中传入下标,从1开

      result = html.xpath('//li[1]/a/text()')
      
    • [last()] 最后一个

      result = html.xpath('//li[last()]/a/text()')
      
    • [position()❤️] 位置小于3的

      result = html.xpath('//li[position()<3]/a/text()')
      
    • [last()-2 ] 倒数第三个,last()是最后一个,所以last()-2就是倒数第三个

      result = html.xpath('//li[last()-2]/a/text()')
      

    14、节点轴选择

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

    • ancestor轴,获取祖先节点

      result = html.xpath('//li[1]/ancestor::*')
      # 获取所有祖先节点
      
      result = html.xpath('//li[1]/ancestor::div')
      # 获取祖先节点中的所有div节点
      
    • attribute轴,获取所有属性值

      result = html.xpath('//li[1]/attribute::*')
      
    • child轴,获取所有直接子节点

      result = html.xpath('//li[1]/child::a[@href="link1.html"]')
      
    • descendat轴,获取所有子孙节点

      result = html.xpath('//li[1]/descendant::span')
      # 限制必须是span节点
      
    • following轴,获取当前节点之后的所有节点

      result = html.xpath('//li[1]/following::*[2]')
      # 有加了索引选择,所以只获取2个后续节点
      
    • flolowing-sibling轴,获取当前节点之后的所有同级节点

      result = html.xpath('//li[1]/follwing-sibling')
      

    二、Beautiful Soup

    1、简介

    Beautiful Soup 就是Python的一个HTML或XML的解析库

    2、准备工作

    pip install lxml
    pip install beautifulsoup4
    

    3、解析器

    Beautiful soup实际上是依赖解解析器的。

    它除了支持Python标准库中的HTML解析器外,还支持一些第三方解析器(比如:lxml)。

    Beautiful soup支持的解析器

    解析器 使用方法 优势 劣势
    Python标准库 BeautifulSoup(markup, “html.parser”) Python的内置标准库、执行速度适中,文档容错能力强。 Python2.7.3及3.2.2之前的版本文档容错能力差。
    lxml HTML解析器 BeautifulSoup(markup, “lxml”) 速度快、文档容错能力强 需要安装C语言库
    lxml XML解析器 BeautifulSoup(markup, “xml”) 速度快,唯一支持XML的解析器 需要安装C语言库
    html5lib BeautifulSoup(markup, “html5lib”) 最好的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 速度慢、不依赖外部扩展

    4、基本用法

    html = '''
    The Dormouse's story
    
    

    The Dormouse's story

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

    ...

    '''
    # 从bs4中导入BeautifulSoup from bs4 import BeautifulSoup # 生成一个BeautifulSoup对象,参数1:html文本,参数2:解析器 soup = BeautifulSoup(html, 'lxml') # prettify()方法是可以把要解析的字符串格式化 print(soup.prettify()) print(soup.title.string) ''' 结果: The Dormouse's story

    The Dormouse's story

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

    ...

    The Dormouse's story '''

    ​ 首先是声明一个变量html,它是一个HTML字符串。注意,并不是一个完整的HTML字符串,因为body和html节点都没有闭合。

    ​ 接着将声明的html作为第一个参数传个BeautifulSoup对象,改对象的第二个参数为解析器的类型(这里使用lxml),此时就完成了BeautifulSoup对象的初始化,并赋值给soup变量。

    ​ 接下来,就可以调用soup()各个方法和属性解析这串HTML代码了。

    ​ 首先,使用prettify()方法。这个方法可以把要解析的字符串以标准的缩进格式输出。需要注意的是:不标准的HTML字符串是由BeautifulSoup自动修正的,prettify()方法只是按照缩进标准格式化了代码。

    ​ 然后调用了soup.title.string,这实际上是输出HTML中title节点的文本内容。所以soup.title可以选出HTML中的title节点,再调用string属性就可以得到里面的文本了。

    5、节点选择器

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

    (1)选择元素

    html = '''
    The Dormouse's story
    
    

    The Dormouse's story

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

    ...

    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.title) print(type(soup.title)) print(soup.title.string) print(soup.head) print(soup.p) ''' 结果: The Dormouse's story The Dormouse's story The Dormouse's story

    The Dormouse's story

    '''

    首先打印输出的是title节点的选择结果,输出的结果是节点加上节点里面的内容。

    接下来输出它的类型,是bs4.element.Tag类型,经过选择器后,选择结构都是这种Tag类型。Tag具有一些属性,比如string,调用该属性可以得到节点内的文本

    接下来又选择了head节点,输出的结果是节点加上节点里面的内容。

    又选择了p节点,发现结果是第一个p节点的内容,后面的几个p节点并没有选到。也就是说,当有多个节点的时候,这种方式只会选择到第一个匹配的节点,其他的节点都会被忽略。

    (2)提取信息

    上面调用string属性来获取文本的值,那么如何获取节点属性的值?

    <1>获取名称

    可以利用name属性获取节点的名称。

    print(soup.title.name)
    
    '''
    结果:
    title
    '''
    

    <2>获取属性

    调用attr获取节点的所有属性

    print(soup.p.attrs)
    print(soup.p.attrs['name'])
    
    '''
    结果:
    {'class': ['title'], 'name': 'dormouse'}
    dormouse
    '''
    

    attrs返回结果是字典类型。它把选择的节点的所有属性和属性值组合成一个字典,

    接下来想要获取name属性,就相当于从字典中获取某个键值。

    其实还有更简单的获取方式:

    print(soup.p['name'])
    print(soup.p['class'])
    
    
    '''
    结果:
    dormouse
    ['title']
    '''
    

    <3>获取内容

    使用string属性获取节点元素包含的文本内容

    比如:获取第一个p节点的文本

    html = '''
    The Dormouse's story
    
    

    The Dormouse's story

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

    ...

    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.p.string) ''' 结果: The Dormouse's story # 注意,这里选择到的p节点是第一个p节点,所以获取的文本也是第一个p节点里面的文本。 '''

    (3)嵌套选择

    从上面得知,每一个返回的结果都是bs4.element.Tag类型,它同样可以继续调用节点进行下一步的选择。

    html = '''
    The Dormouse's story
    
    '''
    
    
    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(html, 'lxml')
    print(soup.head.title)
    print(type(soup.head.title))
    print(soup.head.title.string)
    
    '''
    结果:
    The Dormouse's story
    
    The Dormouse's story
    '''
    

    (4)关联选择器

    需要先选取一个节点元素,然后依照这节点元素为基准再选择它的子节点、父节点、兄弟节点等。

    子节点:contents属性 children属性 子孙节点:descendants属性

    <1>子节点和子孙节点

    选取节点元素后,如果想要获取它的直接子节点,可以调用contents属性。

    html = '''
    
    
    The Dormouse's story
    
    
    

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

    ...

    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.p.contents) ''' 运行结果: ['\n Once upon a time there were three little sisters; and their names were\n ', Elsie , '\n', Lacie, '\nand\n', Tillie, ';\nand they lived at the bottom of a well.\n'] '''

    返回的结果是列表形式。列表中的每个元素都是p节点的直接子节点。比如第一个a节点里面包含一层span节点,这相当于子孙节点了,但是返回结果并没有单独把span节点选出来。所以说contents属性得到的结果是直接子节点的列表。

    获取直接子节点调用children属性 有与contents相同的结果:

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(html, 'lxml')
    print(soup.p.children)
    for i, child in enumerate(soup.p.children):
        print(i, child)
    
     # enumerate()函数,可以将一个可遍历对象组合为一个索引序列,同时列出下标和数据在元组中。  
        
     
    '''
    结果:
    
    0 
        Once upon a time there were three little sisters; and their names were
        
    1 
    Elsie
    
    2 
    
    3 Lacie
    4 
    and
    
    5 Tillie
    6 ;
    and they lived at the bottom of a well.
    '''
    

    这里调用children属性,返回一个生成器,使用for循环输出相应内容。

    获取子孙节点使用descendants属性:

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(html, 'lxml')
    print(soup.p.descendants)
    for i, child in enumerate(soup.p.descendants):
        print(i, child)
    
        
    '''
    结果:
    
    0 
        Once upon a time there were three little sisters; and their names were
        
    1 
    Elsie
    
    2 
    
    3 Elsie
    4 Elsie
    5 
    
    6 
    
    7 Lacie
    8 Lacie
    9 
    and
    
    10 Tillie
    11 Tillie
    12 ;
    and they lived at the bottom of a well.
    '''
    

    返回的结果还是一个生成器,遍历输出可以看到,这输出结果就包含了span节点。

    descendants会递归查询所有子节点,得到所有的子孙节点。

    <2>父节点和祖先节点

    父节点:parent, 祖先节点:parents

    获取某个节点元素的父节点,可以调用parent属性

    html = '''
    
    
    The Dormouse's story
    
    
    

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

    ...

    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.a.parent) ''' 结果:

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

    '''

    我们选择的是第一个a节点的父节点元素。很明显,它的父节点是p节点,输出结果便是p节点及其内部的全部内容。

    获取某个节点的祖先节点,使用parents属性:

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(html, 'lxml')
    print(soup.a.parents)
    print(type(soup.a.parents))
    print(list(enumerate(soup.a.parents)))
    

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

    <3> 兄弟节点

    next_sibling:下一个兄弟节点,返回元素

    previous_sibling:上一个兄弟节点,返回元素

    next_siblings:所有后面的兄弟节点,返回生成器类型

    previous_siblings:所有前面的兄弟节点,返回生成器类型

    html = '''
    
    
    The Dormouse's story
    
    
    

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

    ...

    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print('next_sibling>>>', soup.a.next_sibling) print('previous_sibling>>>', soup.a.previous_sibling) print('next_siblings>>>', list(enumerate(soup.a.next_siblings))) print('previous_siblings>>>', list(enumerate(soup.a.previous_siblings)))

    <4>提取信息

    和上面节点选择器的提取信息,使用的方法一样

    获取节点名称:name

    获取节点属性:attrs

    获取节点文本:string

    6、方法选择器

    find_all()

    查询所有符合条件的元素,返回所有匹配的元素

    find_all(name, attrs, recursive, text, **kwargs)

    (1) name

    可以根据节点名来查询元素:

    html = '''
    

    Hello

    • Foo
    • Bar
    • Jay
    • Foo
    • Bar
    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.find_all(name='ul')) print(type(soup.find_all(name='ul')[0])) ''' 结果: [
    • Foo
    • Bar
    • Jay
    ,
    • Foo
    • Bar
    ] '''

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

    因为都是Tag类型,所以依然可以进行嵌套查询。还是同样的文本,这里查询出所有ul节点或,在继续查询其内部的li节点:

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    for ul in soup.find_all(name='ul'):
        print(ul.find_all(name='li'))
        
        
    '''
    结果:
    [
  • Foo
  • ,
  • Bar
  • ,
  • Jay
  • ] [
  • Foo
  • ,
  • Bar
  • ] '''

    返回的结果是列表类型,列表中的每个元素依然还是Tag类型。

    接下来,遍历每个li,获取它的文本:

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    for ul in soup.find_all(name='ul'):
        print(ul.find_all(name='li'))
        for li in ul.find_all(name='li'):
            print(li.string)
    
    '''
    结果:
    [
  • Foo
  • ,
  • Bar
  • ,
  • Jay
  • ] Foo Bar Jay [
  • Foo
  • ,
  • Bar
  • ] Foo Bar '''

    (2) attrs

    还可以根据,节点属性来查询:

    html = '''
    

    Hello

    • Foo
    • Bar
    • Jay
    • Foo
    • Bar
    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.find_all(attrs={ 'id': 'list-1'})) print(soup.find_all(attrs={ 'name': 'elements'})) ''' 结果: [
    • Foo
    • Bar
    • Jay
    ] [
    • Foo
    • Bar
    • Jay
    ] '''

    这里查询的时候传入的是attrs参数,参数的类型是字典类型。比如,要查询id为list-1的节点,可以传入attrs={‘id’ : ‘list-1’}的查询条件。得到的结果是列表类型,包含的内容就是符合id为list-1的所有节点。

    对于一些常用的属性,比如id、class等,可以不同attrs来传递。比如要查询id为list-1的节点,可以直接传入id这个参数。还是上面的文本:

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    print(soup.find_all(id='list-1'))
    print(soup.find_all(class_='element'))
    
    
    '''
    结果:
    [
    • Foo
    • Bar
    • Jay
    ] [
  • Foo
  • ,
  • Bar
  • ,
  • Jay
  • ,
  • Foo
  • ,
  • Bar
  • ] '''

    这里直接传入id=‘list-1’,就可以查询id为list-1的节点元素。

    而对于class来说,由于class在Python是关键字,所以后面需要加上一个下划线,即class_=‘element’,返回的结果依然还是Tag组成的列表。

    (3) text

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

    html = '''
    
    '''
    
    import re
    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(html, 'lxml')
    print(soup.find_all(text=re.compile('link')))
    
    
    '''
    结果:
    ['Hello, this is a link', 'Hello, this is a link, too']
    '''
    

    find()

    find()方法,返回的是单个元素,也就是第一个匹配的元素。

    html = '''
    

    Hello

    • Foo
    • Bar
    • Jay
    • Foo
    • Bar
    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.find(name='ul')) print(type(soup.find(name='ul'))) print(soup.find(class_='list')) ''' 结果:
    • Foo
    • Bar
    • Jay
    • Foo
    • Bar
    • Jay
    '''

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

    还有其他的查询方法,用法和find_all()、find()用法一样,只不过是查询范围不一样:

    find_parents() 和 find_parent():前者返回所有祖先节点,后者返回直接父节点。

    find_next_siblings() 和 find_next_sibling():前者返回后面的所有兄弟节点,后者返回后面第一个兄弟节点。

    find_previous_siblings() 和 find_previous_sibling():前者返回前面所有兄弟节点,后者返回上一个兄弟节点。

    find_all_next() 和

    () :前者返回节点后所有符合条件的节点,后者返回节点后第一个符合条件的节点。

    find_all_previous() 和 find_previous():前者返回节点前所有符合条件的节点,后者返回节点前第一个符合条件的节点。

    7、CSS选择器

    使用css选择器时,只需要调用select()方法,传入相应的css选择器即可。

    html = '''
    

    Hello

    • Foo
    • Bar
    • Jay
    • Foo
    • Bar
    '''
    from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.select('.panel .panel-heading')) print(soup.select('ul li')) print(soup.select('#list2 .element')) print(type(soup.select('ul')[0])) ''' 结果: [

    Hello

    ] [
  • Foo
  • ,
  • Bar
  • ,
  • Jay
  • ,
  • Foo
  • ,
  • Bar
  • ] [
  • Foo
  • ,
  • Bar
  • ] '''

    用了3次css选择器,返回的结果均是符合css选择器节点组成的列表。

    例如select(‘ul li’)则是返回所有ul节点下面的所有li节点,结果便是所有li节点组成的列表。

    最后一句打印输出了列表中元素的类型。可以看出,类型依然是Tag

    嵌套选择

    select()方法同样支持嵌套选择。

    例如:先选择所有ul节点,在遍历每个ul节点,选择器li节点:

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    for ul in soup.select('ul'):
        print(ul.select('li'))
        
    '''
    结果:
    [
  • Foo
  • ,
  • Bar
  • ,
  • Jay
  • ] [
  • Foo
  • ,
  • Bar
  • ] '''

    获取属性

    我们知道节点类型是Tag类型,也可以用前面讲的attrs方法

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    for ul in soup.select('ul'):
        print(ul.attrs)
        print(ul['id'])
        print(ul.attrs['id'])
        
    '''
    结果:
    {'class': ['list'], 'id': 'list-1', 'name': 'elements'}
    list-1
    list-1
    {'class': ['list', 'list-small'], 'id': 'list2'}
    list2
    list2
    '''
    

    获取文本

    两个方法:string、get_text()

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    for li in soup.select('li'):
        print('Get Text:', li.get_text())
        print('String:', li.string)
        
    '''
    结果:
    String: Foo
    Get Text: Bar
    String: Bar
    Get Text: Jay
    String: Jay
    Get Text: Foo
    String: Foo
    Get Text: Bar
    String: Bar
    '''
    

    总结:

    推荐使用lxml解析器,必要时使用html.parser

    节点选择筛选功能弱,但是速度快

    建议使用find()或者find_all()查询匹配单个结果或者多个结果

    如果对CSS选择器熟悉的话,可以使用select()方法选择。

    三、pyquery

    1、准备工作

    安装pyquery库

    2、初始化

    需要传入一个HTML文本来初始化一个PyQuery对象。初始化方式有很多种,比如直接传入字符串、传入URL、传入文件名

    字符串初始化

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    print(doc('li'))
    
    
    '''
    结果:
    
  • first item
  • second item
  • third item
  • fourth item
  • fifth item
  • '''

    首先引入PyQuery对象,然后声明一个长HTML字符串,并将其当做参数传递给PyQuery类,这样就完成了初始化。接下来,将给这个实例传入li节点,这就就可以选择li节点。

    URL初始化

    初始化的参数还可以传入网页的URL

    from pyquery import PyQuery
    
    doc = PyQuery(url="https://www.baidu.com")
    print(doc('title'))
    

    PyQuery对象首先会请求这个URL,然后用得到的HTML内容完成初始化,就相当于用网页的源代码以字符串的形式传递给PyQuery类来初始化。

    文件初始化

    初始化的参数可以传递本地文件名,将参数指定为filename即可:

    from pyquery import PyQuery
    
    # test.html内的代码就是字符串初始化中html变量对应的代码
    doc = PyQuery(filename="test.html")
    print(doc('li'))
    
    '''
    结果:
    
  • first item
  • second item
  • third item
  • fourth item
  • fifth item
  • '''

    3、基本CSS选择器

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    # 选取id为container节点下面的clas为list节点下面的li节点
    print(doc('#container .list li'))
    
    
    '''
    # 结果:
    
  • first item
  • second item
  • third item
  • fourth item
  • fifth item
  • '''

    初始化PyQuery对象之后,传入一个CSS选择器’#container .list li’,它的意思是选取id为container节点,然后在选取其内部class为list的节点内部的li节点。然后打印输出。

    根据结果发现,它的类型是pyquery.pyquery.PyQuery——PyQuery类型。

    4、查找节点

    子节点

    需要使用find()方法,传入参数是CSS选择器

    子孙节点:find()

    子节点:children()

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    # 找到class为list的节点
    items = doc.find('.list')
    print(type(items))
    print(items)
    print('-'*10, '分界线', '-'*10)
    
    # 从class为list的节点内部找到li节点
    print(type(items.find('li')))
    print(items.find('li'))
    
    
    '''
    结果:
    
    
    
    ---------- 分界线 ----------
    
    
  • first item
  • second item
  • third item
  • fourth item
  • fifth item
  • '''

    首先,选取class为list的节点,然后调用find()方法,传入CSS选择器,选取内部的li节点。

    find()方法,会将符合条件的所有节点全部选择出来。

    find()的查找范围是节点的所以子孙节点

    如果想查找直接子节点可使用children()方法

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    items = doc.find('.list')
    lis = items.children()
    print(type(lis))
    print(lis)
    
    '''
    结果:
    
    
  • first item
  • second item
  • third item
  • fourth item
  • fifth item
  • '''

    如果要筛选所有子节点中符合条件的节点,比如筛选出子节点中class为active的节点,可以向children()方法传入CSS选择器.active:

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    items = doc.find('.list')
    lis = items.children('.active')
    print(type(lis))
    print(lis)
    
    '''
    结果:
    
    
  • third item
  • fourth item
  • '''

    父节点

    使用parent()方法来获取某节点的父节点。

    直接父节点:parent()

    祖先节点:parents()

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    items = doc('.list')
    container = items.parent()
    print(type(container))
    print(container)
    
    
    '''
    结果:
    
    
    '''
    

    首先用.list选取class为list的节点,然后调用parent()方法得到其父节点。其类型依旧是PyQuery类型。

    如果要查询祖先节点,使用parents()方法:

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    items = doc('.list')
    container = items.parents()
    print(type(container))
    print(len(container))
    print(container)
    
    
    '''
    结果:
    
    2
    
    '''
    

    输出结果有两个:一个是class为wrap的节点,一个是id为container的节点。也就是说parents()方法会返回一个节点的所有祖先节点。一层层返回。

    如果想获取某个祖先节点,可以向parents()里传入CSS选择器:

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    items = doc('.list')
    # 只想要class为wrap的祖先节点
    parent = items.parents('.wrap')
    print(parent)
    
    '''
    结果:
    
    '''
    

    这样输出的结果就只保留了一个,保留了class为wrap的节点。

    兄弟节点

    siblings()

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    items = doc('.list .item-0.active')
    # .item-0.active中间没有空格,证明这个是在形容一个节点
    print(items.siblings())
    
    
    '''
    结果:
    
  • second item
  • first item
  • fourth item
  • fifth item
  • '''

    选择了class为list的节点内的class为item-0和active的节点。然后打印出这个节点的兄弟节点。

    根据结果发现,打印出了符合条件节点的上下所有兄弟节点。

    如果想筛选出某个节点可以向siblings()方法中传入CSS选择器:

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    items = doc('.list .item-0.active')
    print(items.siblings('.active'))
    
    '''
    结果:
    
  • fourth item
  • '''

    这里筛选了class为active的节点。

    5、遍历

    pyquery的选择结果可能是多个节点,也可能是单个节点。类型是PyQuery类型,并没有返回Beautiful Soup那样的列表。

    对于单个节点来说,可以直接打印输出,也可以直接转换成字符串。

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    li = doc('.item-0.active')
    print(li)
    print(str(li))
    
    
    '''
    结果:
    
  • third item
  • third item
  • '''

    对于多个节点的结果,我们就需要遍历来获取了。

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    lis = doc('li').items()
    for li in lis:
        print(type(li))
        print(li)
        
    '''
    结果:
    
    
  • first item
  • second item
  • third item
  • fourth item
  • fifth item
  • '''

    调用items()方法会得到一个生成器,然后遍历这个生成器就可以得到每一个li节点。它的类型是PyQuery类型。

    每个li可以继续调用前面说的方法,比如继续查询子节点、寻找某个祖先节点。

    6、获取信息

    获取属性

    抓取到某个节点是PyQuery类型的后,可以调用attr()方法来获取属性。

    attr(‘属性名称’)

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    a = doc('.item-0.active a')
    print(type(a))
    print(a.attr('href'))
    
    
    '''
    结果:
    
    link3.html
    '''
    

    首先选择class为item-0和active的节点中的a节点。这个a节点的类型是PyQuery类型的。然后调用attr()方法,在这里方法中传入属性名称。

    如果选中的是多个元素,就需要先遍历对每一个元素调用attr()方法:

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    a = doc('a')
    for i in a.items():
        print(i.attr('href'))
        
    '''
    结果:
    link2.html
    link3.html
    link4.html
    link5.html
    '''
    

    多个元素,需要使用items()方法将其转换为生成器然后遍历这个生成器中的每个元素,这时候每个元素都是PyQuery类型的,在对其使用attr()方法,提取属性。

    获取文本

    text():忽略标签内的HTML部分,返回纯文本。

    html():返回标签内的HTML部分和纯文本部分。

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    a = doc('.item-0.active a')
    print(a)
    print(a.text())
    
    
    '''
    结果:
    third item
    third item
    '''
    

    选中class为item-0和active节点下的a节点后,对其使用text()方法提取文本,该方法会忽略其内部所有HTML代码。只返回纯文本部分。

    如果想要返回内部的HTML代码部分,需要使用html()方法:

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    a = doc('.item-0.active a')
    print(a)
    print(a.html())
    
    '''
    结果:
    third item
    third item
    '''
    

    如果选中的结果是多个节点,那么使用html()和text()会返回什么内容?

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    li = doc('li')
    print(li.html())
    print(li.text())
    print(type(li.text()))
    
    
    '''
    结果:
    second item
    second item third item fourth item fifth item
    
    '''
    

    在多元素的情况下:

    html()方法:返回的是第一个节点中的HTML文本

    text()方法:返回所有节点中的纯文本部分,并且用空格隔开,返回类型是str字符串类型。

    如果想获取多元素的内部HTML文本需要对其使用遍历,然后在对其使用html()方法提取文本。

    7、节点操作

    addClass 和 removeClass

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    li = doc('.item-0.active')
    print(li)
    li.removeClass('active')
    print(li)
    li.addClass('active')
    print(li)
    
    
    '''
    结果:
    
  • third item
  • third item
  • third item
  • '''

    首先选中一个li节点,然后调用removeClass()方法删除active这个class,然后又调用addClass()方法,将active添加回来。

    attr、text和html

    attr()方法对属性进行操作。

    text()和html()方法来改变节点内部的内容。

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    li = doc('.item-0.active')
    print(li)
    li.attr('name', 'link')
    print(li)
    li.text('changed item')
    print(li)
    li.html('changed item')
    print(li)
    
    
    '''
    结果:
    
  • third item
  • third item
  • changed item
  • changed item
  • '''

    首先查找到class为item-0和active的节点。

    使用attr()方法,添加一个name属性,值是link。

    再使用text()方法,传入文本后,将其节点内的文本更改为changed item。

    再使用html()方法,传入HTML文本后,将其接节点内的文本改为传入的HTML文本。

    attr()方法:传入第一个值是获取属性,传入第二个参数,可以用来修改属性值。

    text() 和 html():如果不传参数是获取纯文本和HTML文本。如果传入参数则是修改文本内容。

    remove()

    remove()方法就是移除。

    从文本中提取出Hello, World这个字符串,而不要p节点内部的字符串。

    html = '''
    
    Hello, World

    This is a paragraph.

    '''
    from pyquery import PyQuery doc = PyQuery(html) wrap = doc('.wrap') print(wrap.text()) ''' 结果: Hello, World This is a paragraph. '''

    结果还是包含了p节点的内容。这时候使用remove()就可以解决:

    from pyquery import PyQuery
    
    doc = PyQuery(html)
    # 找到class为wrap的节点,将其赋值给wrap
    wrap = doc('.wrap')
    # 从wrap中找到p节点,并删除掉
    wrap.find('p').remove()
    print(wrap.text())
    
    '''
    结果:
    Hello, World
    '''
    

    首先找到p节点,使用remove()方法删除,此时只剩下Hello, World这句话了,在调用text()方法,从wrap中提取出来。

    8、伪类选择器

    html = '''
    
    '''
    
    from pyquery import PyQuery
    
    doc = PyQuery(html)
    # 第一个li节点
    li = doc('li:first-child')
    print(li)
    
    # 最后一个li节点
    li = doc('li:last-child')
    print(li)
    
    # 第2个li节点
    li = doc('li:nth-child(2)')
    print(li)
    
    # 第三个之后的li节点
    li = doc('li:gt(2)')
    print(li)
    
    # 偶数位置的li节点
    li = doc('li:nth-child(2n)')
    print(li)
    
    # 文本包含second的li节点
    li = doc('li:contains(second)')
    print(li)
    

    你可能感兴趣的:(解析库的使用——学习笔记)