python爬虫主流解析库的使用方法——XPath、BuautifulSoup、pyquery

文章目录

  • 前言
    • XPath的使用
      • XPath常用匹配规则
    • Beautiful Soup的使用
      • 节点选择器
        • 选择元素
        • 提取信息
          • 1.获取名称
          • 2.获取属性
          • 3.获取内容
        • 嵌套选择
        • 关联选择
            • 1.子节点和子孙节点
          • 2.父节点和祖先节点
          • 3.兄弟节点
          • 4.提取元素
      • 方法选择器
        • find_all()
          • name
          • attrs
          • text
        • find()
      • CSS选择器
        • 嵌套选择
        • 获取属性
        • 获取文本
    • pyquery的使用
      • 基本的初始化
        • 字符串初始化
        • url初始化
        • 文件初始化
      • 基本CSS选择器
      • 查找节点
        • 子节点
        • 父节点
        • 兄弟节点
      • 遍历
      • 获取信息
        • 获取属性
        • 获取文本
  • 后记

前言

前面学习到了使用正则表达式来实现一个基本的爬虫进行数据的爬取,但是这个正则表达式使用起来还是比较的繁琐,毕竟要写符号啊啥的都太多了,一不留神就容易搞错了,然后导致匹配失败,然后怼着那一坨找半天也不知道到底哪错了,就很烦!由于我们在使用爬虫的时候大多数都是用来爬取网页源代码中的信息,而对于一个HTML网页来说,他其中的逻辑结构还是比较明确的,每一个标签,每一个属性都有其自有的层次关系,我们就可以通过这种关系来获取到我们想要的文本或者属性信息。基于这样一种思想,我们python中提供了功能强大的解析库给我们使用,这些解析库包括lxml、BeautifulSoup、pyquery等,有了他们,我们提取HTML文本信息的时候就方便多了。

XPath的使用

XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。

虽说是用来搜索XML语言的,但是他同样适用于HTML文档的搜索。

XPath常用匹配规则

python爬虫主流解析库的使用方法——XPath、BuautifulSoup、pyquery_第1张图片
图源菜鸟教程
这里我们写一个XPath规则://title[@lang=‘eng’]
他代表选择所有名称为title,同时属性lang的值为eng的标签。
由于XPath很多的节点匹配跟HTML语言里面的标签之间的关系有关,所以大家还是应该先学习一下HTML的知识,然后理解起来就非常的简单了。

Beautiful Soup的使用

Beautiful Soup跟XPath比较的类似,同样是借助网页的结构和属性等特性来解析网页。

节点选择器

选择元素

注:此处包括后文所说的节点指的就是HTML中的标签
直接调用节点的名称就可以选择节点元素,然后调用string属性就可以得到节点内的文本信息了,这种选择的方式非常快,如果节点之间的层次清晰就非常适合用这种方式来解析。

from bs4 import BeautifulSoup
html = '''




BeautifulSoup的使用


    

我的第一个标题。

我的第一个段落。

'''
soup = BeautifulSoup(html, 'lxml') print(soup.title) print(soup.title.string)

运行结果:
< title>BeautifulSoup的使用< /title>
BeautifulSoup的使用
使用对象.节点的形式就能输出整个节点,然后.string就能输出该节点的文本信息。当有多个节点时,只会选择到第一个匹配的节点,后面的其他相同节点都会忽略。

提取信息

上面演示了使用调用string来获取文本的值,那么该如何获取节点属性的值?如何获取节点名?

1.获取名称

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

print(soup.title.name)

运行结果:
title

2.获取属性

每个节点可能有多个属性,比如class、id等,选择这个节点之后,可以调用attrs获取所有属性。

from bs4 import BeautifulSoup
html = '''




BeautifulSoup的使用


    

我的第一个标题。

我的第一个段落。

'''
soup = BeautifulSoup(html, 'lxml') print(soup.h1.attrs) print(soup.h1.attrs['name'])

运行结果:
{‘class’: [‘title’], ‘name’: ‘dromouse’}
dromouse
我们可以看到,attrs的返回结果是字典形式,他把选择的所有属性和属性值组成一个字典,然后如果我们想要获取字典中的某一个属性值的话就可以通过中括号加属性名就可以。当某个属性有较多的属性值时,返回的属性值就是一个列表,单个属性值就是字符串。

3.获取内容

前面其实已经提到过,使用string获取节点元素的文本内容。

嵌套选择

当一个节点中包含另外一个节点时,我们就可以嵌套的通过.来选择节点。来看一个例子,还是上面的HTML文本。

print(soup.head.title.string)

运行结果:
BeautifulSoup的使用
我们既可以直接找到title这个节点,也可以嵌套的找到head节点里面的title节点。

关联选择

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

1.子节点和子孙节点

选取节点元素之后,如果想要直接获取他的直接子节点,可以调用contents属性,他会以一个列表的形式统一返回所有的东西,既包括文本也包括节点。需要注意的是,列表中的每个元素都是该节点的直接子节点。

print(soup.head.contents)

运行结果:
[’\n’, < meta charset=“utf-8”/>, ‘\n’, < title>BeautifulSoup的使用< /title>, ‘\n’]
同样,我们也可以调用children属性得到相应的结果。

print(soup.head.children)
for i, child in enumerate(soup.head.children):
    print(i, child)

运行结果:
< list_iterator object at 0x0000025D5E3C9C88>
0

1 < meta charset=“utf-8”/>
2

3 < itle>BeautifulSoup的使用< /title>
4

还是上面的HTML文本,这里调用了children属性来选择,返回结果是生成器类型,接下来我们用for循环输出相应的内容。
如果想要输出所有子孙节点的话,可以调用descendants属性,用法和children一样。

2.父节点和祖先节点

如果想要获取某个节点元素的父节点,可以调用parent属性。

print(soup.title.parent)

运行结果:
< head>
< meta charset=“utf-8”/>
< title>BeautifulSoup的使用< /title>
< /head>
这里返回了title节点的父节点head节点的所有信息,当然这里输出的是直接父节点,如果想再向外寻找父节点的祖先节点的话可以调用parents属性。

print(type(soup.title.parents))
print(list(enumerate(soup.title.parents)))

运行结果:

<class 'generator'>
[(0, <head>
<meta charset="utf-8"/>
<title>BeautifulSoup的使用</title>
</head>), (1, <html>
<head>
<meta charset="utf-8"/>
<title>BeautifulSoup的使用</title>
</head>
<body>
<h1 class="title" name="dromouse">我的第一个标题。</h1>
<p>我的第一个段落。</p>
</body>
</html>), (2, <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>BeautifulSoup的使用</title>
</head>
<body>
<h1 class="title" name="dromouse">我的第一个标题。</h1>
<p>我的第一个段落。</p>
</body>
</html>
)]

返回结果是生成器类型,这跟上面的descendants返回子孙节点的属性的生成类型是一样的,后面生成了一个列表,列表元素就是title节点的祖先节点。

3.兄弟节点

可以使用next_sibling和previous_sibling分别获取节点的下一个和上一个兄弟元素,next_siblings和previous_siblings则分别返回后面和前面的兄弟节点。
具体我就不举例了,用法和上面的都是一样的。

4.提取元素

前面讲解了关联元素节点的选择方法,如果想要获取他们的一些信息,比如文本、属性等。方法如下:如果返回结果是单个节点,那么我们可以直接调用string、attrs等属性获得其文本和属性;如果返回结果是多个节点的生成器,则可以转为列表后取出某个元素,然后再调用string、attrs等属性获取其对应节点的文本和属性。

方法选择器

前面所讲的选择方法都是通过属性来选择的,这种方法的好处就是比较快,但是在遇到一些比较复杂的选择的话,他就显得比较繁琐了,我们先要找到节点,然后调用他获取属性的方法,很麻烦。还好BeautifulSoup提供了find_all()和find()方法,只要传入对应的参数,就可以方便的查询了。

find_all()

查询所有符合条件的元素,给这个函数传入一些属性或文本,就可以得到符合条件的元素。
他的API如下:
find_all(name, attrs, tecursive, text ,**kwargs)

name

使用节点名来查询元素

from bs4 import BeautifulSoup
html = '''




BeautifulSoup的使用


    

我的第一个标题。

我的第一个段落。

我的第二个段落。

我的第三个段落。

'''
soup = BeautifulSoup(html, 'lxml') for i in soup.find_all(name='body'): print(i.find_all(name='p'))

我们这里稍微变更了一下这个HTML文本。
运行结果:

[<p>我的第一个段落。</p>, <p>我的第二个段落。</p>, <p>我的第三个段落。</p>]

返回结果是列表类型,然后便利每个i,获取他的文本:

for i in soup.find_all(name='body'):
    print(i.find_all(name='p'))
    for pi in i.find_all(name='p'):
        print(pi.string)

返回结果:

[<p>我的第一个段落。</p>, <p>我的第二个段落。</p>, <p>我的第三个段落。</p>]
我的第一个段落。
我的第二个段落。
我的第三个段落。
attrs

除了根据节点名查询,我们也可以传入一些属性来查询:
还是上面的HTML文本。

print(soup.find_all(class_='l1'))

运行结果:
注:这里的class属性为了跟python内置的class标识符加以区分,加了一个下划线,其他无差别。

[<p class="l1">我的第一个段落。</p>]
text

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

print(soup.find_all(text=re.compile("一")))

返回结果:
[‘我的第一个标题。’, ‘我的第一个段落。’]
这里返回了所有正则表达式能够匹配的节点文本组成的列表。

find()

find()跟find_all()方法的用法基本上相同,只不过find()方法返回的是单个元素,也就是第一个成功匹配的元素。

CSS选择器

要使用css对HTML页面中的元素实现一对一,一对多或者多对一的控制,这就需要用到CSS选择器。
HTML页面中的元素就是通过CSS选择器进行控制的。

具体CSS选择器的内容大家可以看这个:https://www.w3school.com.cn/cssref/css_selectors.asp
使用CSS选择器时只需要调用select()方法,传入相应的CSS选择器即可。如果你熟悉web开发的话,应该知道CSS选择器在哪里找到。

我们可以在浏览器的开发者工具中方便的找到某个节点属性的CSS选择器。

嵌套选择

select()方法同样支持嵌套选择,还是上面的HTML文本,这里我们先选择body节点,然后遍历选择其中的p节点,代码如下:

for i in soup.select('body'):
    print(i.select('p'))

运行结果:

[<p class="l1">我的第一个段落。</p>, <p class="l2">我的第二个段落。</p>, <p class="l3">我的第三个段落。</p>]

这里就正常的输出了body节点下所有p节点组成的列表。

获取属性

节点类型时Tag类型,所有获取属性还可以用原来的方法。

for i in soup.select('p'):
    print(i['class'])
    print(i.attrs['class'])

运行结果:
[‘l1’]
[‘l1’]
[‘l2’]
[‘l2’]
[‘l3’]
[‘l3’]
不管是直接传入中括号加属性名还是通过attrs获取属性值都可以。

获取文本

除了前面讲的string属性,我们还可以使用get_text()方法。

for i in soup.select('p'):
    print(i.get_text())

运行结果:
我的第一个段落。
我的第二个段落。
我的第三个段落。

pyquery的使用

基本的初始化

字符串初始化

还是使用上面的HTML文本

from pyquery import PyQuery as pq
doc = pq(html)
print(doc('p'))

运行结果:

<p class="l1">我的第一个段落。</p>
    <p class="l2">我的第二个段落。</p>
    <p class="l3">我的第三个段落。</p>

这里我们首先导入pyquery模块中的PyQuery对象,取别名为pq,然后声明一个HTML字符串文本,把他当作参数传到该类完成字符串的初始化。然后将初始化的对象传入CSS选择器,在这里我们传入p节点,然后就选择了所有的p节点并打印出来。

url初始化

初始化的参数不仅能够以字符串的形式传递,还可以传入网页的url,此时就能够获取到网页的源代码

doc = pq(url='https://www.csdn.net')
print(doc('title'))

运行结果:

<title>CSDN - 专业开发者社区</title>

这里我们向pq对象中传入的参数就是url,完成初始化,然后在初始化对象中传入CSS选择器title节点,就打印出来了title节点的内容。

文件初始化

pq对象中还可以传入文件,其他操作跟前面的都一样。

基本CSS选择器

这里我们将上面的HTML文本稍作更改,然后使用pyquery中的CSS选择去来运行一个实例。

from pyquery import PyQuery as pq
html = '''




BeautifulSoup的使用


    

    我的第一个标题。

    我的第一个段落。

    我的第二个段落。

    我的第三个段落。

'''
doc = pq(html) print(doc('#container .list p'))

运行结果:

<p class="l1">我的第一个段落。</p>
    <p class="l2">我的第二个段落。</p>
    <p class="l3">我的第三个段落。</p>

这里我们初始化一个PyQuery对象之后,传入了一个CSS选择器#container .list li,他的意思是先选取id为container的节点,然后选取其内部的class为list的节点内部的所有li节点,然后打印输出。

查找节点

子节点

查找子节点的时候需要用到find()方法,此时传入的参数是CSS选择器,还是以上面的HTML文本为例:

doc = pq(html)
items = doc('.list')
print(items)
lis = items.find('p')
print(lis)

运行结果:

<ul class="list">
    <h1 class="title" name="dromouse">我的第一个标题。</h1>
    <p class="l1">我的第一个段落。</p>
    <p class="l2">我的第二个段落。</p>
    <p class="l3">我的第三个段落。</p>
    </ul>
    <p class="l1">我的第一个段落。</p>
    <p class="l2">我的第二个段落。</p>
    <p class="l3">我的第三个段落。</p>

这里我们选取class为list的节点,然后调用了find()方法,传入CSS选择器,选取其中的p节点,最后打印出来。可以看到find()方法会选择所有的子孙节点,如果只想查找子节点的话可以用children()方法,用法跟上面的一样。

父节点

我们可以用parent方法来获取某个节点的父节点。

doc = pq(html)
items = doc('.list')
lis = items.parent()
print(lis)

运行结果:

<div id="container">
    <ul class="list">
    <h1 class="title" name="dromouse">我的第一个标题。</h1>
    <p class="l1">我的第一个段落。</p>
    <p class="l2">我的第二个段落。</p>
    <p class="l3">我的第三个段落。</p>
    </ul>
    </div>

我们首先选取class为list的节点,然后调用parent()方法得到其父节点,这里的父节点为直接父节点,可以看到只输出了div这个节点里面的内容。如果想要获取祖先节点可以使用parents()方法。

兄弟节点

如果想要获取兄弟节点可以使用siblings()方法

doc = pq(html)
items = doc('.list .l1')
lis = items.siblings()
print(lis)

运行结果:

<h1 class="title" name="dromouse">我的第一个标题。</h1>
    <p class="l2">我的第二个段落。</p>
    <p class="l3">我的第三个段落。</p>

这里我们首先选择了class为list的节点内部class为l1的节点,然后他打印出了ul节点内部的除第一个p节点的所有节点,即第一个p节点的兄弟节点。

遍历

pyqyery的选择结果可能是单个节点也可能是多个节点,对于单个节点我们只需要打印输出即可,但对于多个节点来说我们就需要遍历获取了,遍历我们需要调用items()方法。

doc = pq(html)
item = doc('p')
print(type(item))
print(item)
items = doc('p').items()
print(type(items))
for item in items:
    print(item, type(item))

运行结果:

<class 'pyquery.pyquery.PyQuery'>
<p class="l1">我的第一个段落。</p>
    <p class="l2">我的第二个段落。</p>
    <p class="l3">我的第三个段落。</p>
    
<class 'generator'>
<p class="l1">我的第一个段落。</p>
     <class 'pyquery.pyquery.PyQuery'>
<p class="l2">我的第二个段落。</p>
     <class 'pyquery.pyquery.PyQuery'>
<p class="l3">我的第三个段落。</p>
     <class 'pyquery.pyquery.PyQuery'>

我们看到运行结果还是有区别的,首先就是生成的结果的类型是不同的,前面直接打印的结果是PyQuery类型,后面使用items()遍历则是生成器类型,但是每一个单独的节点仍然是Pyquery类型,然后可以调用前面的方法完成一系列操作。

获取信息

获取属性

我们可以调用attr()方法来获取属性,跟前面的BeautifulSoup有一点不同,前面是attrs。

doc = pq(html)
a = doc('.list h1')
print(a.attr('class'))

运行结果:
title
这里我们首先选中class为list的ul节点内部的h1节点,然后调用attr()方法,在这个方法中传入属性名class就得到了属性值title,跟原文本完全吻合。但是需要注意的是,attr()方法只会得到匹配的第一个属性结果,如果有多个属性,并且想要获得所有属性时,我们就需要用到前面的遍历了。

doc = pq(html)
a = doc('p')
print(a.attr('class'))
for item in a.items():
    print(item.attr('class'))

运行结果:
l1
l1
l2
l3
前面直接调用attr()方法只输出了第一个,后面遍历后就输出了所有的节点。

获取文本

我们可以调用text()方法来获取文本内容。

doc = pq(html)
a = doc('p')
print(a.text())

运行结果:

我的第一个段落。 我的第二个段落。 我的第三个段落。

后记

这篇应该是我目前码字最多的文章了,前前后后也是写了好几天,内心异常的燥热,因为中间的组织架构实在是太复杂了,并且有很多的重复内容,不写又怕看不懂,写了代码啥的基本上都一样,但好在还是坚持下来了。中间有什么错误的地方欢迎指正,走过路过的pljj、sqgg点个赞再走呗!

你可能感兴趣的:(Python爬虫,python,爬虫,xpath,BeautifulSoup,pyquery)