Python爬虫 解析库的使用


已写章节

第一章 网络爬虫入门
第二章 基本库的使用
第三章 解析库的使用
第四章 数据存储
第五章 动态网页的抓取


第三章 解析库的使用



3.1BeautifulSoup



BeautifulSoup也被称作为”美味汤“,BeautifulSoup提供了一些简单的、Python式的函数来处理导航、搜索、修改和分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据。BeautifulSoup已成为和lxml、html6lib一样出色的Python解释器,为用户灵活的提供不同的解析策略或强劲的速度。

作者提示:当你学习了Xpath后,你会发现BeautifulSoup是非常不好用的,并且学了Xpath之后,基本不用BeautifulSoup了。但是,我想说的是,学习BeautifulSoup会加强你对标签树、子标签、父标签、后代标签以及标签的结构的理解,而这些都是学习Xpath的基础,这也是为什么很多人将BeautifulSoup作为自己学习Python网络爬虫的第一个解析库。

Beautiful Soup库是解析、遍历、维护“标签树"的功能库。

BeautifulSoup文档



3.1.1 BeautifulSoup的安装


在cmd命令行中输入以下命令来使用pip安装BeautifulSoup库:

pip install beautifulsoup4


Beautiful Soup库,也叫beautifulsoup4或bs4,约定导入方式如下:

from bs4 import BeautifulSoup   # 或: import bs4

在Python文件中使用上面的代码导入BeautifulSoup库,如果没有报错,说明你已经成功安装了BeautifulSoup。


3.1.2 BeautifulSoup库的理解


BeautifulSoup是将HTML代码当做一个便签树来处理,BeautifulSoup对应一个HTML/XML文档的全部内容 。其中的每一个标签的结构如下:

在这里插入图片描述

基本元素说明
Tag标签,最基本的信息组织单元,分别用<>和</>标明开头和结尾
Name标签名,<p>...</p>的名字是’p’,格式:<tag>.name
Attributes标签属性,字典形式组织,格式:<tag>.attrs
NavigableString标签内的非属性字符串,<>...</>中的字符串,格式:<tag>.string
Comment标签内字符串的注释部分,一种特殊的comment类型

3.1.3 解析器


Beautiful Soup在解析时需要依赖解析器:

soup = BeautifuSoup('<html>data</html>','html.parser')
解析器使用方法条件
bs4的HTML解析器BeautifulSoup(mk,‘html.parser’)安装bs4库
lxml的解析器BeautifulSoup(mk,‘lxml’)pip install lxml
lxml的XML解析器BeautifulSoup(mk,‘xml’)pip install lxml
html5lib的解析器BeautifulSoup(mk,'html5lib‘)pip install html5lib

3.1.4 BeautifulSoup的基本使用


在上面介绍了Tag(标签),它是最基本的信息组织单元,分别用<>和</>标明开头和结尾,下面介绍BeautifulSoup的基本使用。


3.1.4.1 使用soup.<tag>来获取指定的标签
from bs4 import BeautifulSoup

# 使用soup.<tagname>来获取指定的标签
text = '''
<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
 <body>
  <p class="title">
   <b>
    The Dormouse's story
   </b>
  </p>
  <p class="story">
   Once upon a time there were three little sisters; and their names were
   <a class="sister" href="http://example.com/elsie" id="link1">
    Elsie
   </a>
   ,
   <a class="sister" href="http://example.com/lacie" id="link2">
    Lacie
   </a>
   and
   <a class="sister" href="http://example.com/tillie" id="link3">
    Tillie
   </a>
   ;
and they lived at the bottom of a well.
  </p>
  <p class="story">
   ...
  </p>
  <f>
   <!--This is a comment-->
  </f>
  <c>
   This is not a comment
  </c>
 </body>
</html>
'''

soup = BeautifulSoup(text, "html.parser")
print(soup.a)

通过soup.a来获取HTML中的a标签,如果结果有多个,取第一个。下面是运行结果:

<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

注意:任何存在于HTML语法中的标签都可以使用soup.<tag>访问获得,当HTML中存在多个相同<tag>标签时,soup.<tag>返回第一个。


3.1.4.2 获取标签的属性
from bs4 import BeautifulSoup

# BeautifulSoup中获取标签的属性
text = '''
<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
 <body>
  <p class="title">
   <b>
    The Dormouse's story
   </b>
  </p>
  <p class="story">
   Once upon a time there were three little sisters; and their names were
   <a class="sister" href="http://example.com/elsie" id="link1">
    Elsie
   </a>
   ,
   <a class="sister" href="http://example.com/lacie" id="link2">
    Lacie
   </a>
   and
   <a class="sister" href="http://example.com/tillie" id="link3">
    Tillie
   </a>
   ;
and they lived at the bottom of a well.
  </p>
  <p class="story">
   ...
  </p>
  <f>
   <!--This is a comment-->
  </f>
  <c>
   This is not a comment
  </c>
 </body>
</html>
'''

soup = BeautifulSoup(text, "html.parser")

print("a标签的名字是:", soup.a.name)

print(type(soup.a.attrs))
print("a标签的属性为:", soup.a.attrs)

print("a标签的class属性是", soup.a.attrs['class'])

print(type(soup.a.string))
print("a标签中的非属性字符串为:", soup.a.string)

print(type(soup.f.string))
print("f标签中的注释为:", soup.f.string)

运行结果:

a标签的名字是: a
<class 'dict'>
a标签的属性为: {'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'}
a标签的class属性是 ['sister']
<class 'bs4.element.NavigableString'>
a标签中的非属性字符串为: Elsie
<class 'bs4.element.Comment'>
f标签中的注释为: This is a comment


3.1.4.3 标签树的遍历

标签树的下行遍历

属性说明
.contents子节点的列表,将<tag>的所有儿子节点存入列表
.children子节点的迭代类型,与.contents类似,用于循环遍历儿子节点
.descendants子孙节点的迭代类型,包含所有子孙节点,用于循环遍历
from bs4 import BeautifulSoup

# 标签树的下行遍历
text = '''
<html><head><title>The Dormouse's story</title></head> 
<body>
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
<a><p class="story">...</p>aaa</a>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')

print(len(soup.body.contents))
print(soup.body.contents)

print("-"*50)
for i, child in enumerate(soup.body.children):
    print(i, child)

print("-"*50)
for i, descendant in enumerate(soup.body.descendants):
    print(i, descendant)

运行结果:

9
['\n', <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, '\n', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, '\n', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, '\n', <a><p class="story">...</p>aaa</a>, '\n']
--------------------------------------------------
0 

1 <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2 

3 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
4 

5 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
6 

7 <a><p class="story">...</p>aaa</a>
8 

--------------------------------------------------
0 

1 <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2 Elsie
3 

4 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
5 Lacie
6 

7 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
8 Tillie
9 

10 <a><p class="story">...</p>aaa</a>
11 <p class="story">...</p>
12 ...
13 aaa
14 

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


标签的上行遍历

属性说明
.parent节点的父亲标签
.parents节点的先辈标签的迭代类型,用于循环遍历先辈节点
from bs4 import BeautifulSoup

# 标签树的上行遍历
text = '''
<html><head><title>The Dormouse's story</title></head> 
<body>
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
<a><p class="story">...</p>aaa</a>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')

print(soup.p.parent)

print("-"*50)
for i, parent in enumerate(soup.p.parents):
    print(i, parent)

运行结果:

<a><p class="story">...</p>aaa</a>
--------------------------------------------------
0 <a><p class="story">...</p>aaa</a>
1 <body>
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
<a><p class="story">...</p>aaa</a>
</body>
2 <html><head><title>The Dormouse's story</title></head>
<body>
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
<a><p class="story">...</p>aaa</a>
</body>
</html>
3 
<html><head><title>The Dormouse's story</title></head>
<body>
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
<a><p class="story">...</p>aaa</a>
</body>
</html>

标签的平行遍历

属性说明
.next_sibling返回按照HTML文本顺序的下一个平行节点标签
.previous_sibling返回按照HTML文本顺序的上一个平行节点标签
.next_siblings迭代类型,返回按照HTML文本顺序的后续所有平行节点标签
.privious_siblings迭代类型,返回按照HTML文本顺序的前续所有平行节点标签
from bs4 import BeautifulSoup

# 标签树的平行遍历
text = '''
<html><head><title>The Dormouse's story</title></head> 
<body>
<a></a>
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<p>pp</p>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
<a><p class="story">...</p>aaa</a>
</body>
</html>
'''

text = text.replace('\n', '')  # 去除换行符的干扰
soup = BeautifulSoup(text, 'html.parser')

print(soup.p.next_sibling)
print("-"*50)

print(soup.p.previous_sibling)
print("-"*50)

for i, previous_sibling in enumerate(soup.p.previous_siblings):
    print(i, previous_sibling)
print("-"*50)

for i, next_sibling in enumerate(soup.p.next_siblings):
    print(i, next_sibling)

运行结果如下:

<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
--------------------------------------------------
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
--------------------------------------------------
0 <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1 <a></a>
--------------------------------------------------
0 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
1 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
2 <a><p class="story">...</p>aaa</a>


3.1.4.4 使用BeautifulSoup提供的方法来选择标签

find_all()方法

查询所有符合条件的元素,给它传入一些属性或文本,就可以得到符合条件的元素。

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


  • name参数:筛选出所有符合标签名条件的标签,可以是一个标签名,也可以是一个Python字典或列表封装的若干个标签名
from bs4 import BeautifulSoup

# soup.find_all()方法中筛选指定标签名的标签
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<p class="title"></p>
<a href="http://example.com" class="sister">Elsie</a>
<a href="http://example.com" class="sister">Tillie</a>
<p class="story">...</p>
</body>
</html>
'''

text = text.replace('\n', '')
soup = BeautifulSoup(text, 'html.parser')

print(soup.find_all('a'))  # 找出所有的a标签

print(soup.find_all('p'))  # 找出所有的p标签

print(soup.find_all(['p', 'title']))  # 找出所有的a标签和title标签

运行结果:

[<a class="sister" href="http://example.com">Elsie</a>, <a class="sister" href="http://example.com">Tillie</a>]
[<p class="title"></p>, <p class="story">...</p>]
[<title>The Dormouse's story</title>, <p class="title"></p>, <p class="story">...</p>]

  • attrs参数:根据标签的属性来筛选满足条件的标签,用字典封装一个标签的若干个属性和对应的属性值
from bs4 import BeautifulSoup

# soup.find_all()中根据标签属性来筛选标签
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<p class="title"></p>
<a href="link1" class="b">Elsie</a>
<a href="link1" class="a">Tillie</a>
<a href="link1" class="c">df</a>
<p class="story">...</p>
</body>
</html>
'''

text = text.replace('\n', '')
soup = BeautifulSoup(text, 'html.parser')

print(soup.find_all('a', {'href': 'link1'}))  # 找出所有的href属性为link1的a标签
print('-'*50)

print(soup.find_all('a', {'href': 'link1', 'class': 'a'}))  # 找出所有的href属性为link1且class属性为a的p标签
print('-'*50)

print(soup.find_all('a', {'class': ['a', 'b']})) # 找出说有的class属性为a或b的a标签
print('-'*50)

print(soup.find_all(['p', 'title']))  # 找出所有的p标签和title标签

运行结果:

[<a class="b" href="link1">Elsie</a>, <a class="a" href="link1">Tillie</a>, <a class="c" href="link1">df</a>]
--------------------------------------------------
[<a class="a" href="link1">Tillie</a>]
--------------------------------------------------
[<a class="b" href="link1">Elsie</a>, <a class="a" href="link1">Tillie</a>]
--------------------------------------------------
[<title>The Dormouse's story</title>, <p class="title"></p>, <p class="story">...</p>]

  • recursive参数:布尔变量,默认为True。它决定了要抓取HTML文档标签结构里的多少层信息。如果设置为True,find_all()方法就会根据你的要求去查找标签参数的所有子标签,以及子标签的子标签。如果设置为False,find_all()方法就只查找文档的一级标签。recursive一般不需要设置,除非你真正了解自己需要哪些信息,而且抓取的速度非常重要,那时你就可以设置递归参数。

  • text参数:用标签的文本内容去匹配
from bs4 import BeautifulSoup
import re

# soup.find_all()中使用文本来筛选标签
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<p class="title"></p>
<a href="link1" class="b">Elsie</a>
<a href="link1" class="a">Tillie</a>
<a href="link1" class="c">df</a>
<p class="story">...</p>
<b>dfd</b>
</body>
</html>
'''

text = text.replace('\n', '')
soup = BeautifulSoup(text, 'html.parser')

print(soup.find_all(text=re.compile('.*df.*')))  # 找出所有标签文本中包含df的文本,返回结果为字符串列表

运行结果:

['df', 'dfd']

  • limit参数:结果数目限制参数,它限制了查找结果标签的数目,它只适用于find_all()方法,find()方法其实等价于find_all()方法的limit为1的时候。
from bs4 import BeautifulSoup

# soup.find_all()中限制结果的个数
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b">Elsie</a>
<a href="link1" class="a">Tillie</a>
<a href="link1" class="c">df</a>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')
print(soup.find_all('a', limit=2))

运行结果如下:

[<a class="b" href="link1">Elsie</a>, <a class="a" href="link1">Tillie</a>]

  • keyword参数:关键词参数keyword可以让你选择那些具有指定属性的标签,例如:
from bs4 import BeautifulSoup

text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link1" class="a" id='id1'>Tillie</a>
<a href="link1" class="c" id='id2'>df</a>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')
all_label = soup.find_all(id="id1")
print(all_label[0].get_text())

运行结果:

Elsie

使用keyword参数的注意事项:

  • 关键词参数可以使用其他方法替代,例如下面的两行代码功能是一样的:

    bsobj.find_all(id="text")
    bsobj.find_all("", {"id": "text"})
    
  • 在使用keyword偶尔也会出现问题,尤其在用class属性查找标签时,因为class是Python中受保护的关键字,而Python中不能拿关键字作为参数名。不过,BeautifulSoup提供了有点儿臃肿的方案,在class下面添加一个下划线:

    bsobj.find_all(class_="green")
    

    当然,你也可以这样写:

    bsobj.find_all("", {"class"="green"})
    

find()方法

除了find_all()方法,还有find()方法,只不过find()方法返回的是单个的标签,也就是第一个匹配的标签,而find_all()方法返回的是所有匹配的标签组成的列表。

from bs4 import BeautifulSoup

# bs_object.find()方法的使用
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link1" class="a" id='id1'>Tillie</a>
<a href="link1" class="c" id='id2'>df</a>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')
print(soup.find(id="id1"))  # 这里如果使用的是find_all方法,返回的将是一个包含两个标签的列表

运行结果:

<a class="b" href="link1" id="id1">Elsie</a>

可以看到,符合id=id1的标签有两个,但是find()方法只会返回第一个符合条件的标签。


prettify()方法

这个方法可以将html文本补齐并且将其美化:

from bs4 import BeautifulSoup

# bs_object.prettify()方法的使用
text = '''
<html><head>
<title>The Dormouse's
 story</title></head> <body> 
<a href="link1" 
class="b" id='id1'>Elsie</a>
<a href="link1" class="a" id='id1'>
Tillie</a>
<a href=
"link1" class="c" id='id2'>df</a>
</body>

'''

soup = BeautifulSoup(text, 'html.parser')
print(soup.prettify())

运行结果:

<html>
 <head>
  <title>
   The Dormouse's
 story
  </title>
 </head>
 <body>
  <a class="b" href="link1" id="id1">
   Elsie
  </a>
  <a class="a" href="link1" id="id1">
   Tillie
  </a>
  <a class="c" href="link1" id="id2">
   df
  </a>
 </body>
</html>

使用prettify()方法之前的HTML文本有一些标签是缺少结束标签的,而且格式是乱的,从执行的结果来看,prettify()方法将结束标签补齐了,也对HTML文本进行了格式化。


其他查询方法:
  • 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():前者返回节点前的所有符合条件的节点,后者返回节点前的第一个符合条件的节点



3.1.4.6 使用Lambda表达式

BeautifulSoup允许我们将特定的函数当做find_all()方法的参数。唯一的限制条件是这些函数必须把一个标签当做参数且返回结果是布尔类型。BeautifulSoup用这个函数来筛选它遇到的每一个标签,将符合Lambda中的删选条件的标签保留下来。

下面的例子就使用Lamabd表达式来筛选出所有有两个属性的标签:

from bs4 import BeautifulSoup

# BeautifulSoup中Lambda表达式的使用
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" >df</a>
<a link="https"></a>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')
# 使用lambda表达式将标签属性为2的标签筛选出来
print(soup.find_all(lambda tag: len(tag.attrs) == 2))

运行结果:

[<a class="c" href="link3">df</a>]


3.1.4.5 css选择器

BeautifulSoup还提供了另外一种选择器,那就是css选择器。要使用css选择器,只需要调用select()方法,传入相应的css选择器即可。

选择器示例示例说明
elementp选择所有的<p>元素
element,elementdiv,p选择所有<div>元素和<p>元素
element elementdiv p选择<div>元素内的所有<p>元素
.class.intro选择所有class="intro"的元素
#id#firstname选择所有id="firstname"的元素
\*\*选择所有元素
[attribute][target]选择所有带有target属性元素
[attribute=value][target=blank]选择所有target="blank"的元素
[attribute*=value][title*=f]选择所有title属性中包含f单词的元素
[attribute^=value][lang^=en]选择lang属性以en开头的所有元素
[attribute$=value][href$=.jpg]选择所有href属性以.jpg结尾的元素
tag.get_texta.get_text获取a标签中的文本内容
from bs4 import BeautifulSoup

# bs_object.select()方法中使用css选择器
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
<a link="https"></a>
<b link="af"></b>
<b link="bba"></b>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')

print("选取所有的a标签:", soup.select('b'))

print("选取所有class属性为a的a标签", soup.select('a .a'))

print("选取所有class属性为a且id属性为id1的a标签", soup.select('a .a #id1'))

print("选取所有link属性为https的a节点:", soup.select('a[link="https"]'))

print("选取所有link属性中包含f的b节点", soup.select('b[link*="f"]'))

print("选取所有link属性以b开头的b标签", soup.select('b[link^="b"]'))

运行结果:

选取所有的a标签: [<b link="af"></b>, <b link="bba"></b>]
选取所有class属性为a的a标签 []
选取所有class属性为a且id属性为id1的a标签 []
选取所有link属性为https的a节点: [<a link="https"></a>]
选取所有link属性中包含f的b节点 [<b link="af"></b>]
选取所有link属性以b开头的b标签 [<b link="bba"></b>]


嵌套选择
from bs4 import BeautifulSoup

# BeautifulSoup中使用嵌套选择
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
<a link="https"></a>
<b link="af"></b>
<b link="bba"></b>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')
for i in soup.select('html'):
    print(i.select('head'))
    print(i.select('title'))

运行结果:

[<head><title>The Dormouse's story</title></head>]
[<title>The Dormouse's story</title>]


获取属性
from bs4 import BeautifulSoup

#BeautifulSoup支持使用css选择器获取标签
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<b link="af"></b>
<b link="bba"></b>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')
for i in soup.select('b'):
   print(i['link'])
   print(i.attrs['link'])
af
af
bba
bba


获取文本
from bs4 import BeautifulSoup

#获取标签的文本内容
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<b link="af">def</b>
<b link="bba">oo</b>
</body>
</html>
'''

soup = BeautifulSoup(text, 'html.parser')
for i in soup.select('b'):
   print(i.string)
   print(i.get_text())

运行结果:

def
def
oo
oo


3.1.5 使用BeautifulSoup爬取qq音乐的首发音乐

from bs4 import BeautifulSoup
import requests
from fake_useragent import UserAgent

# 使用BeautifulSoup爬取qq音乐的首发音乐
url = 'https://y.qq.com/'
response = requests.get(url=url, headers={'user-Agent': UserAgent().chrome})
soup = BeautifulSoup(response.text, 'html.parser')

divs_1 = soup.find('div', {'id': 'new_album_box'})
names = divs_1.select('span.playlist__title_txt > a')
authors = divs_1.select('div.playlist__author > a')
for name, author in zip(names, authors):
    print(name.text, author.text, sep="  ")

运行结果:

倒带  刘珂
不再年少  大志
英雄如你  洛天依
愿你可以  王琪玮
爱我的时候  单依纯
鹞  周兴哲
红火中国年  小5
除夕  郭秀枫
一起过年  唐诗
蒙着眼睛走  大志


3.2 Xpath(重点)


3.2.1 Xpath介绍


Xpath,全称是XML Path Language,即XML路径语言,它是一门在XML文档中查找信息的语言。它最初是用来搜索XML文档的,但是同样适用于HTML文档的搜索。

W3C的Xpath教程

菜鸟教程的Xpath教程

Xpath helper插件

Xpath官方文档



3.2.2 Xpath的常用规则


选取节点

Xpath使用路径表达式在XML文档中选取节点

表达式描述实例实例含义
nodename选取此节点的所有子节点a选取a节点的所有子节点
/从根节点开始选取/a选取根节点a
//从任意节点开始选取//a选取所有的a节点,不管它在文档中的位置
.选取当前节点//a[@class="class1" and id="id1"]选取所有的class属性为class1且id属性为id1的节点
..选取当前节点的父节点//a[@class="class1"]//..选取所有class属性为class1的a节点的父节点
@选取属性//a[@class="class1"]选取所有class属性为class1的节点
text()选取节点中的文本//a[class="class1"]/text()选取所有class属性为class1的a节点的文本内容
*匹配任何节点/a/*选取a节点下的所有子节点

谓语

谓语用来查找特定的节点,谓语被嵌在方括号中

路径表达式结果
/a/b[1]选取a节点下的第一个b节点
/a/b[last()]选取a节点下的最后一个b节点
/a/b[last()-1]选取a节点下的倒数第二个b节点
/a/b[position()<3]选取a节点下的前2个b节点
/a[@class]选取所有的有class属性的a节点
/a[@*]选取所有有属性的a节点
/a[@class="class1"]选取所有class属性为class1的a节点
/a/b[@price>20]选取a节点下所有price属性的值大于20的b节点
/a[contains(@class, “a”)]选取所有class属性中包含a单词的节点
/a[@class=“abc” and @id=“abc”]选取所有class属性为abc且id属性为abc的a节点

Xpath的语法推荐大家在Chrome中使用Xpath helper插件来练习,练习熟练之后就可以在Python中使用了!

Xpath helper插件:
链接: https://pan.baidu.com/s/1gOa8vewuIUhQ0GTefjrNsQ
提取码: sp4m



3.2.3 Xpath的使用


3.2.3.1 安装lxml

在Python中使用Xpath要安装lxml库:

pip install lxml

3.2.3.2 使用Xpath

使用Xpath解析HTML文本

from lxml import etree

# 使用etree.HTML()方法解析HTML文本
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
<a link="https"></a>
<b link="af"></b>
<b link="bba"></b>
</body>
</html>
'''

html = etree.HTML(text, etree.HTMLParser())
result = html.xpath('/html/head/title/text()')
print(result)

运行结果:

["The Dormouse's story"]

上面的Xpath解析式/html/head/title/text()选取html标签下的head标签下的title标签的文本,再将结果打印出来。

在Python爬虫中使用Xpath的关键就是根据要提取的内容写成对应的Xpath表达式,只要将Xpath表达式写好,替换上面的代码中的Xpath表达式就可以提取到想要的内容了,所有,大家学习Xpath的重点在:会根据需要提取的信息写出对应的Xpath表达式。


使用Xpath解析HTML文档

from lxml import etree

# 使用etree.parse()方法解析HTNL文档
html = etree.parse('htmlText.html', etree.HTMLParser())
result = html.xpath('/html/head/title/text()')
print(result)

使用Xpath解析HTML文档使用的是etree.parse()方法,
htmlTest.html文档中的内容:

<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
<a link="https"></a>
<b link="af"></b>
<b link="bba"></b>
</body>
</html>

运行结果:

["The Dormouse's story"]


3.2.3.3 xpath使用技巧
  • 可以使用concat()方法将两个xpath表达式的结果合并

    爬取链家网中二手房的价格使用的xpath表达式:

    concat(//div[@class=“price”]/span[1]/text(),//div[@class=“price”]/span[2]/span/text())

  • 可以使用last()方法从筛选出的标签列表的末尾开始提取标签:

    爬取链家网中二手房的供暖情况使用的xpath表达式:

    //div[@class=“base”]/div[@class=“content”]/ul/li[last()-1]/text()

  • contains()的使用

//div[contains(@name,’Tom’)] 查找name属性中包含字符串Tom的所有div标签



3.2.4 使用Xpath爬取穷游网的中国热门城市

import requests
from fake_useragent import UserAgent
import parsel
import csv

def getdata(url):
    headers = {
        "user-Agent": UserAgent().chrome
    }
    response = requests.get(url=url, headers=headers)
    response.encoding = response.apparent_encoding

    selector = parsel.Selector(response.text)  # 把字符串类型转化为对象
    lis = selector.xpath('//ul[@class="plcCitylist"]/li')

    for li in lis:
        city_names = li.xpath('./h3/a/text()').get()
        city_names = city_names.rstrip()
        number_people = li.xpath('./p[2]/text()').get()
        place_hot = li.xpath('./p[@class="pois"]/a/text()').getall()
        place_hot = [place.strip() for place in place_hot]
        place_hot = '、'.join(place_hot)
        place_url = li.xpath('./p[@class="pics"]/a/@href').get()
        img_url = li.xpath('./p[@class="pics"]/a/img/@src').get()
        print(city_names, number_people, place_url, img_url, place_hot, sep='|')

        with open('qiongyouData.csv', mode='a', encoding='utf-8', newline='') as file_object:
            csv_write = csv.writer(file_object)
            csv_write.writerow([city_names, number_people, place_url, img_url, place_hot])

def main():
    for i in range(1, 172):
        url = "https://place.qyer.com/china/citylist-0-0-{}/".format(str(i))
        getdata(url)

if __name__ == '__main__':
    main()

代码运行后将在控制台打印爬取到的数据,并将数据写入qingyouData.csv文件,由于没有使用多线程,所以爬取所有的数据花费时间较长。



3.3 pyquery(了解)


虽然 xpath 与 Beautiful Soup 已经很强大了,不过语法还是过于啰嗦,pyquery提供了更加简洁优雅的语法,你可以像写jquery一般提取数据。

如果你使用过jQuary,那么你可能更适合使用pyquery解析库。

pyquery官方文档

3.3.1 pyquery的安装


和之前一样,在cmd控制台中输入以下代码来使用pip安装pyquery库:

pip install pyquery

3.3.2 初始化

像BeautifulSoup一样,初始化pyquery的时候,也需要传入一个HTML文本来初始化一个pyQuery对象。它的初始化的方式有很多,比如直接传入字符串,传入URL,传入文件名等等。


  • 字符串的初始化
from pyquery import PyQuery as pq

# 将字符串初始化为pyquery对象
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
<a link="https"></a>
<b link="af"></b>
<b link="bba"></b>
</body>
</html>
'''

doc = pq(text)
print(doc('b'))

运行结果:

<b link="af"/>
<b link="bba"/>

  • URL初始化

初始化的参数可以是一个URL,此时只需要指定参数为url即可,但是,一般情况下都不会传入一个URL来初始化,因为通常都会出现编码方式出错导致出现乱码。

from pyquery import PyQuery as pq

# 将URL链接初始化为pyquery对象
doc = pq(url='https://www.baidu.com', encoding='utf-8')
print(doc('title'))

运行结果:

<title>百度一下,你就知道</title>

  • 文件初始化

要使用文件来初始化pyquery,只需要指定filename即可:

from pyquery import PyQuery as pq

# 将文件初始化为pyquery对象
doc = pq(filename='pyqueryTest.html')
print(doc('a'))

下面是pyqueryTest.html中的内容:

<html><head><title>The Dormouse's story</title></head> <body>
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
<a link="https"></a>
<b link="af"></b>
<b link="bba"></b>
</body>
</html>

下面是运行结果:

<a href="link1" class="b" id="id1">Elsie</a>
<a href="link2" class="a" id="id1">Tillie</a>
<a href="link3" class="c" id="id2">df</a>
<a link="https"/>


3.3.3 pyquery的使用


pyquery支持css选择器:
from pyquery import PyQuery as pq

# pyquery中使用css选择器
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
</body>
</html>
'''
html = pq(text)
print(html('a.b#id1'))

首先传入text将pyquery初始化,让后传入’a.b#id1’这个选择器,这个选择器的功能是选择所有class属性为b且id属性为id1的a标签。

运行结果如下:

<a href="link1" class="b" id="id1">Elsie</a>

下面是一些常用的查询函数,这些函数和jQuery中函数的用法完全相同:

方法介绍
PyQuery.find('css')在当前节点的所有子孙节点中查找所有满足css选择器的节点,返回的是PyQuery对象。
PyQuery.children('css')和find()的用法一样,只不过它的查找范围是子节点。
PyQuery.parent()获取当前节点的父节点,返回的是PyQuery对象。
PyQuery.parents('css')获取当前节点的祖先节点,返回的是PyQuery对象。
PyQuery.siblings('css')获取当前节点的兄弟节点,返回的是PyQuery对象。
PyQuery.items()得到当前节点的生成器,用于遍历PyQuery对象。
PyQuery.attr()获取当前节点的属性,当PyQuery中包含多个节点时,attr()只会返回第一个节点的属性。
PyQuery.text()获取文本,但PyQuery中包含多个节点时,它将返回一个将所有节点的文本合并成的字符串。
PyQuery.html()获取标签中的HTML文本,如果PyQuery中包含多个节点,它将返回第一个节点中的HTML文本。
PyQuery.addClass()为节点添加一个class
PyQuery.removeClass()移除节点的一个class
PyQuery.attr(' ', ' ')更改节点的属性内容
PyQuery.text(' ')修改节点内容
PyQuery.html(' ')修改节点内的HTML文本
PyQuery.remove()移除节点

下面使用实例向大家介绍这些方法的使用过程:

find()

from pyquery import PyQuery as pq

# pq_object.find()方法的使用
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<li>
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
</li>
</body>
</html>
'''
pq_object = pq(text)  # 初始化一个pyquery对象
bod = pq_object('body')  # 在pyquery对象中查找body标签
print(type(bod))

a = bod.find('a.b#id1')  # 在body标签的子孙节点中查找class属性为a,id属性为id1的a标签
print(type(a))
print(a.text())

运行结果:

<class 'pyquery.pyquery.PyQuery'>
<class 'pyquery.pyquery.PyQuery'>
Elsie

children()

from pyquery import PyQuery as pq

# pq_object.children()方法的使用
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<li>
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
</li>
</body>
</html>
'''

pq_object = pq(text)
bod = pq_object('body')
print(bod.children('a'))

运行代码发现没有打印任何内容,因为children()方法是在body标签的子标签中查找a标签,而body标签的子标签只有li标签,要提取所有的a标签,需要将代码改一下:

from pyquery import PyQuery as pq

# pq_object.children()方法的使用
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<li>
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
</li>
</body>
</html>
'''

pq_object = pq(text)
bod = pq_object('li')
print(bod.children('a'))

运行结果:

<a href="link1" class="b" id="id1">Elsie</a>
<a href="link2" class="a" id="id1">Tillie</a>
<a href="link3" class="c" id="id2">df</a>

parent()

from pyquery import PyQuery as pq

# pq_object.parent()方法的使用
text = '''
<body> 
<li>
<a class="b" id='id1'>Elsie</a>
</li>
</body>
'''

pq_object = pq(text)
a = pq_object('a.b#id1')
print(a.parent())
print('-'*50)
print(a.parents())
<li>
<a class="b" id="id1">Elsie</a>
</li>

--------------------------------------------------
<body> 
<li>
<a class="b" id="id1">Elsie</a>
</li>
</body><li>
<a class="b" id="id1">Elsie</a>
</li>

siblings()

from pyquery import PyQuery as pq

# pq.object.siblings()方法的使用
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
</body>
</html>
'''

pq_object = pq(text)
a = pq_object('a.a#id1')
print(a.siblings())

运行结果:

<a href="link1" class="b" id="id1">Elsie</a>
<a href="link3" class="c" id="id2">df</a>

items()

from pyquery import PyQuery as pq

# pq_object.items()方法的使用
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie</a>
<a href="link2" class="a" id='id1'>Tillie</a>
<a href="link3" class="c" id='id2'>df</a>
</body>
</html>
'''

pq_object = pq(text)
a = pq_object('a')
for i in a.items():
    if i.attr('href') == 'link1':
        print(i)

运行结果:

<a href="link1" class="b" id="id1">Elsie</a>

html()

from pyquery import PyQuery as pq

# pq_object.html()方法的使用
text = '''
<head>
<title>The Dormouse's story</title>
</head> 
'''

pq_object = pq(text)
head = pq_object('head')
print(head.html())

运行结果:

<title>The Dormouse's story</title>

节点操作

from pyquery import PyQuery as pq

# pyquery对节点的操作
text = '''
<html><head><title>The Dormouse's story</title></head> <body> 
<a href="link1" class="b" id='id1'>Elsie<b>text</b></a>
</body>
</html>
'''

pq_object = pq(text)

a = pq_object('a.b#id1')
a.add_class('new_class')
print("将a节点增加class属性'new_class的结果为:", a)

a.remove_class('b')
print("将a节点移除class属性b的结果为:", a)

a.attr('href', 'new_link')
print("将a节点中的href属性更改为'new_link'后的结果为:", a)

a.text('new_text')
print("将a节点的text更改为'new_text'后的结果为:", a)

a.html('<b>b_new_text</b>')
print("将a节点的HTML更改为'<b>b_new_text</b>'后的结果为:", a)

doc = pq_object('html')
a.remove()
print("将a节点删除后的结果为:", doc)

运行结果:

将a节点增加class属性'new_class的结果为: <a href="link1" class="b new_class" id="id1">Elsie<b>text</b></a>

将a节点移除class属性b的结果为: <a href="link1" class="new_class" id="id1">Elsie<b>text</b></a>

将a节点中的href属性更改为'new_link'后的结果为: <a href="new_link" class="new_class" id="id1">Elsie<b>text</b></a>

将a节点的text更改为'new_text'后的结果为: <a href="new_link" class="new_class" id="id1">new_text</a>

将a节点的HTML更改为'<b>b_new_text</b>'后的结果为: <a href="new_link" class="new_class" id="id1"><b>b_new_text</b></a>

将a节点删除后的结果为: <html><head><title>The Dormouse's story</title></head> <body> 
 
</body>
</html>

好了,上面就介绍完了Python网络爬虫中常用的四个解析库BeautifulSoup、Xpath(重点)、pyquery、re(重点),其中re在第二章 基本库的使用 中介绍了,要将这些解析库学好,大量的练习是必不可少的,希望大家能多多实践,将学的知识用起来。


最后,感谢你的阅读。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值