解析库
解析库的意思是对我们需要爬取的数据进行操作,从而获得想要的数据。
解析库有很多种,我们可以根据自己的习惯进行选取。
主要分为以下四种:
- 正则表达式
- xpath
- Beautiful Soup
- pyquery
一、正则表达式
首先讲解正则表达式的使用方法.
1) 元字符
元字符 | 匹配内容 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配所有普通字符(数字、字母或下划线) |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
\b | 匹配一个单词的结尾 |
^ | 匹配字符串的开始位置 |
$ | 匹配字符串的结尾位置 |
\W | 匹配非字母或数字或下划线 |
\D | 匹配非数字 |
\S | 匹配非空白符 |
a|b | 匹配字符 a 或字符 b |
() | 正则表达式分组所用符号,匹配括号内的表达式,表示一个组。 |
[...] | 匹配字符组中的字符 |
[^...] | 匹配除了字符组中字符的所有字符 |
2) 量词
量词 | 用法说明 |
---|---|
* | 重复零次或者更多次 |
+ | 重复一次或者更多次 |
? | 重复0次或者一次 |
重复n次 | |
重复n次或者更多次 | |
重复n到m次 |
3) 字符组
有时也会出现各种字符组成的字符组,这在正则表达式中使用[]
正则 | 待匹配字符 | 匹配结果 | 说明 |
---|---|---|---|
[0123456789] | 8 | True | 在一个字符组里枚举所有字符,字符组里的任意一个字符 和"待匹配字符"相同都视为可以匹配。 |
[0123456789] | a | False | 由于字符组中没有 "a" 字符,所以不能匹配。 |
[0-9] | 7 | True | 也可以用-表示范围,[0-9] 就和 [0123456789] 是一个意思。 |
[a-z] | s | True | 同样的如果要匹配所有的小写字母,直接用 [a-z] 就可以表示。 |
[A-Z] | B | True | [A-Z] 就表示所有的大写字母。 |
[0-9a-fA-F] | e | True | 可以匹配数字,大小写形式的 a~f,用来验证十六进制字符。 |
1、match方法
该方法用于指定文本模式和待匹配的字符串。第一个参数表示文本模式,第二个参数表示待匹配的字符串。
如果匹配成功,就可以调用group方法获取匹配成功的字符串。
import re
m = re.match('hello','hello')
if m is not None:
print(m.group()) #使用group方法
print(m.__class__.__name__) #输出m的类名
m = re.match('hello','hello world') #只要模式从字符串起始位置开始,也可以匹配成功
if m is not None:
print(m.group())
print(m)
hello
Match
hello
<re.Match object; span=(0, 5), match='hello'>
2、search方法
搜索是正则表达式的另一类常用的应用场景。也就是从一段文本找到一个或多个与文本模式相匹配的字符串。
import re
m = re.match('python','I love python')
print(m)
m = re.search('python','I love python')
print(m)
s = '.ind'
m = re.match(s,'bind')
if m is not None:
print(m.group())
print(m)
m = re.match(s,'bin')
print(m)
None
<re.Match object; span=(7, 13), match='python'>
bind
<re.Match object; span=(0, 4), match='bind'>
None
3、分组
如果一个模式字符串中有一对圆括号括起来的部分,那么这部分就会作为一组,可以通过group方法的参数获取指定的组匹配的字符串。在我们匹配一些需要抓取的信息时,往往会在一次模式匹配时,就获取很多想要的信息,那么此时,我们就需要将想要获取的信息用括号括起来,就可以形成一个列表,这个列表里面的内容就都是我们想要的内容。
import re
m = re.match('(\d{3})-(\d{4})-([a-z]{2})','123-4567-xy')
if m is not None:
print(m.group())
print(m.group(1))
print(m.group(2))
print(m.group(3))
print(m.groups())
print("========================")
123-4567-xy
123
4567
xy
('123', '4567', 'xy')
=======================
4、findall和finditer方法
findall函数用于查询字符串中某个正则表达式模式全部的非重复出现情况,finditer方法与findall方法相似,只不过使用finditer方法需要进行迭代,这样更节省内存。
import re
s = '12-a-abc54-a-xyz---78-A-ytr'
#r是规避转义的字符
result = re.findall(r'\d\d-a-[a-z]{3}',s)
#匹配出了两个与该模式字符串相匹配的字符串
print(result)
['12-a-abc', '54-a-xyz']
5、sub和subn方法
sub函数与subn函数用于实现搜索和替换功能。这两个函数功能几乎完全一样,只不过sub函数返回替换后的结果,subn函数返回一个元组,元组的第一个元素是替换后的结果,第二个元素是替换的总数。
import re
#使用sub函数,第一个参数是模式字符串,第二个参数是要替换的字符串,第三个参数是被替换的字符串
result = re.sub('Mike','Bill','Mike is my son,I like Mike')
print(result)
result = re.subn('Mike','Bill','Mike is my son,I like Mike')
print(result)
#运行结果
Bill is my son,I like Bill
#元组
('Bill is my son,I like Bill', 2)
6、split方法
split函数用于根据正则表达式分隔字符串,split函数的第一个参数是模式字符串,第二个参数是带分隔的字符串。
import re
result = re.split(';','Bill;Mike;John')
print(result)
#运行结果
['Bill', 'Mike', 'John']
实战案例:抓取猫眼电影TOP100榜单(http://www.maoyan.com/)
import requests
#导入正则表达式
import re
#导入会话,维持状态
from requests import Session
session = Session()
class CatEyeMovie:
def __init__(self):
"""
个人爬取网站的一些经验:
一开始我只使用了UA最为代理,后来发现爬取一段时间后,就不能在进行爬取了。
然后我就导入了Host和Referer两个请求头作为参数,然后又爬取一段时间后又不能
爬取了,然后我有采用了cookie值,并导入了session维持会话,后来保持稳定了,但
我也不能完全保证后面不会出现问题,还有一个需要大家注意的点,就是我们最好将UA
经常换一下,否则网站很容易判断你就是一个爬虫。
"""
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
'Host':'www.maoyan.com',
'Referer':'https: // www.maoyan.com / board / 6?timeStamp = 1705477546939 & channelId = 40011 & index = 1 & signKey = 53785d6920acbc1fad61a808070e6544 & sVersion = 1 & webdriver = false',
'Cookie':'__mta = 208959021.1705475089718.1705477531026.1705477547761.23;uuid_n_v = v1;uuid = AEE07180B50611EE8FD01FDE53D00FC1F4728774816242469EDC18C661A68E3E;_csrf = bd11e59bd2541a5db1defecd0b98453acc792bc7b43e8c627d9f291922d330a5;Hm_lvt_703e94591e87be68cc8da0da7cbd0be2 = 1705475088;_lx_utm = utm_source % 3DBaidu % 26utm_medium % 3Dorganic;_lxsdk_cuid = 18d163ca06e88 - 09e6b05cd12e1e - 26001951 - 1bcab9 - 18d163ca06fc8;WEBDFPID = z7vx1480u69z54z2y8wxxw25u87w9wy681w834vxv2w9795839872754 - 2020837336064 - 1705477335287GSOGGSIfd79fef3d01d5e9aadc18ccd4d0c95079584;token = AgFrJIp1VufADHtEUTi0mp6KitQUBV_Ejob2I8Ux0OAvh6hc4y2_WhnCuFWgooIo_KncuV_jDf4ttQAAAABjHQAAZVmX7tI2iKUXgdLslHWY_dwRCQzD5i7IK_Ayog8 - 7acQiKH6CO0VVeeAwm4LSd1d;uid = 4274654873;uid.sig = vNE7mg6CHWQbb4imGsjuWSza8rs;lxsdk = AEE07180B50611EE8FD01FDE53D00FC1F4728774816242469EDC18C661A68E3E;__mta = 208959021.1705475089718.1705477312976.1705477544324.6;Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2 = 1705477548;_lxsdk_s = 18d163ca06f - 4af - c1e - 68b % 7C % 7C62'
}
def get_html(self):
#通过分析网站的URL,我们可以得出网站地址的规律
for i in range(0,101,10):
url = f'https://www.maoyan.com/board/4?timeStamp=1705475173402&channelId=40011&index=10&signKey=31588cad6150fc9509dd3f73c509fdac&sVersion=1&webdriver=false&offset={i}'
response = session.get(url,headers=self.headers)
#作判断,如果不是200,表示爬取不了
if response.status_code == 200:
html = response.text
#print(html)
self.re_html(html)
def re_html(self,html):
#使用正则表达式获取电影名和主演
re_act = '<p\sclass="name">.*?>(.*?)</a></p>\n.*?<p\s.*?>\n(.*?)\n.*?</p>'
#通过正则表达式获取图片链接
re_img_src = '<img\sdata-src="(.*?)".*?/>'
lists = re.findall(re_act,html,re.S)
img_lists = re.findall(re_img_src,html,re.S)
for list,img_list in zip(lists,img_lists):
title = list[0]
#使用strip函数去掉字符串两头的空格
lead_act = list[1].strip()
print(title)
print(lead_act)
print(img_list)
print("==================")
if __name__ == '__main__':
cat = CatEyeMovie()
cat.get_html()
我不是药神
主演:徐峥,周一围,王传君
https://p0.pipi.cn/mmdb/d2dad59253751bd236338fa5bd5a27c710413.jpg?imageView2/1/w/160/h/220
==================
肖申克的救赎
主演:蒂姆·罗宾斯,摩根·弗里曼,鲍勃·冈顿
https://p0.pipi.cn/mmdb/fb7386020fa51b0fafcf3e2e3a0bbe694d17d.jpg?imageView2/1/w/160/h/220
==================
海上钢琴师
主演:蒂姆·罗斯,比尔·努恩 ,克兰伦斯·威廉姆斯三世
https://p0.pipi.cn/mmdb/d2dad592c7e7e1d2365bf1b63cd25951b722b.jpg?imageView2/1/w/160/h/220
==================
绿皮书
主演:维果·莫腾森,马赫沙拉·阿里,琳达·卡德里尼
https://p0.pipi.cn/mmdb/d2dad59253751b230f21f0818a5bfd4d8679c.jpg?imageView2/1/w/160/h/220
==================
霸王别姬
主演:张国荣,张丰毅,巩俐
https://p0.pipi.cn/mmdb/fb7386beddd338537c8ea3bb80d25a9078b13.jpg?imageView2/1/w/160/h/220
==================
美丽人生
主演:罗伯托·贝尼尼,朱斯蒂诺·杜拉诺,赛尔乔·比尼·布斯特里克
https://p0.pipi.cn/mmdb/d2dad592c7e7e1d2367a3507befaed31a5903.jpg?imageView2/1/w/160/h/220
==================
这个杀手不太冷
主演:让·雷诺,加里·奥德曼,娜塔莉·波特曼
https://p0.pipi.cn/mmdb/d2dad592c7e7e13ba3ddd25677b4d70fc45fa.jpg?imageView2/1/w/160/h/220
==================
小偷家族
主演:中川雅也,安藤樱,松冈茉优
https://p0.pipi.cn/mmdb/d2dad5925372ffd7c387a9d01bddad81625c3.jpg?imageView2/1/w/160/h/220
==================
星际穿越
主演:马修·麦康纳,安妮·海瑟薇,杰西卡·查斯坦
https://p0.pipi.cn/mmdb/d2dad592b125bfc9fd300b8a46169f8008efb.jpg?imageView2/1/w/160/h/220
==================
怦然心动
主演:玛德琳·卡罗尔,卡兰·麦克奥利菲,艾丹·奎因
https://p0.pipi.cn/mmdb/d2dad592b122ff8d3387a93ccab6036f616c1.jpg?imageView2/1/w/160/h/220
==================
盗梦空间
主演:莱昂纳多·迪卡普里奥,渡边谦,约瑟夫·高登-莱维特
https://p0.pipi.cn/mmdb/fb7386510fa2ffd7c3e19bcb2e6ea4dc44460.jpg?imageView2/1/w/160/h/220
==================
泰坦尼克号
主演:莱昂纳多·迪卡普里奥,凯特·温丝莱特,比利·赞恩
https://p0.pipi.cn/mmdb/fb73869af2a51b339e537c802afb40db7a7fe.jpg?imageView2/1/w/160/h/220
==================
哪吒之魔童降世
主演:吕艳婷,囧森瑟夫,瀚墨
https://p0.pipi.cn/mmdb/d2dad592537923f0ee07acada3ac59b9f3ffb.jpg?imageView2/1/w/160/h/220
==================
二、lxml和xpath
1、lxml基础
lxml是python的一个解析库,用于解析HTML和XML,支持xpath解析方式。由于lxml底层是使用C语言编写的,所以解析效率非常高。但是lxml插件需要安转,在这里,我就不讲如何安装,大家可以用自己的方法去安装。
2、操作xml
lxml可以读取XML文件,也可以使用字符串形式的XML文档。读取XML文件时,需要使用parse函数,该函数需要传入XML文件名。如果要解析字符串形式的XML文档,需要使用fromstring函数,该函数的参数就是XML字符串。
<products>
<product id = "0001">
<name>手机</name>
<price>2000</price>
</product>
<product id = "0002">
<name>电脑</name>
<price>8350</price>
</product>
</products>
from lxml import etree
tree = etree.parse('product.xml')
print(type(tree))
#将tree重新转换为字符串形式的XML文档
print(str(etree.tostring(tree,encoding="utf-8"),'utf-8'))
#获得根结点对象
root = tree.getroot()
#输出根结点的名称
print(root.tag)
#获得根结点的所有子节点
children = root.getchildren()
for child in children:
#获取子节点的ID属性值
print(child.get('id'))
#获取子节点的文本内容
print(child[0].text)
print(child[1].text)
#重写XML文档
root = etree.fromstring(
'''
<product>
<product1 name="iphone"/>
<product2 name="iPad"/>
</product>
'''
)
#注意:从这里root的值就开始改变了,不在是XML文件里的值,而是自己写的
print(root.tag)
children = root.getchildren()
for child in children:
print(child.get('name'))
<class 'lxml.etree._ElementTree'>
<products>
<product id="0001">
<name>手机</name>
<price>2000</price>
</product>
<product id="0002">
<name>电脑</name>
<price>8350</price>
</product>
</products>
products
0001
手机
2000
0002
电脑
8350
product
iphone
iPad
3、操作HTML
使用lxml库解析HTML与解析XML类似,在这里,我通过代码能够让读者更加容易理解。
from lxml import etree
#创建lxml.etree.HTMLPareser对象
parser = etree.HTMLParser()
print(type(parser))
#读取并解析html文件
tree = etree.parse('product.html',parser)
#获取根结点
root = tree.getroot()
#将html文档转换为可读格式
result = etree.tostring(root,encoding='utf-8',pretty_print=True,method="html")
print(result,'utf-8')
#获取根结点名称
print(root.tag)
print(root.get('lang'))
#在这里相当于列表,head是列表里的第一个值,body是列表的第二个值
print(root[0][0].get('charset'))
print(root[0][1].text)
print(root[1][0].text)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>这是一个HTML文件</title>
</head>
<body>
<h1>解析库</h1>
</body>
</html>
<class 'lxml.etree.HTMLParser'>
b'<html lang="en">\r\n<head>\r\n <meta charset="UTF-8">\r\n <title>\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaaHTML\xe6\x96\x87\xe4\xbb\xb6</title>\r\n</head>\r\n<body>\r\n <h1>\xe8\xa7\xa3\xe6\x9e\x90\xe5\xba\x93</h1>\r\n</body>\r\n</html>\n' utf-8
html
en
UTF-8
这是一个HTML文件
解析库
4、Xpath
1) 基本语法使用
Xpath 使用路径表达式在文档中选取节点,下表列出了常用的表达式规则:
表达式 | 描述 |
---|---|
node_name | 选取此节点的所有子节点。 |
/ | 绝对路径匹配,从根节点选取。 |
// | 相对路径匹配,从所有节点中查找当前选择的节点,包括子节点和后代节点,其第一个 / 表示根节点。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性值,通过属性值选取数据。常用元素属性有 @id 、@name、@type、@class、@tittle、@href。 |
2) xpath通配符
Xpath 表达式的通配符可以用来选取未知的节点元素,基本语法如下:
通配符 | 描述说明 |
---|---|
* | 匹配任意元素节点 |
@* | 匹配任意属性节点 |
node() | 匹配任意类型的节点 |
xpath其它知识点
1、可以使用“|”,表示多路径匹配,有时获取相同的位置格式,解析语法并不是完全一样的。
2、xpath还有很多内建函数,在这里我说一个经常用的就是“text属性”,后面进行实战案例时,会给大家详细介绍。
想要了解更多关于 Xpath 的知识可访问官方网站:https://www.w3.org/TR/xpath/
实战案例:抓取起点中文网小说信息()
#导入Excel表格
import xlwt
import requests
#导入随机函数,可以使用随机UA代理池
import random
from lxml import etree
#这里面是一些UA代理,可以随机切换UA,避免浏览器发现爬虫程序
from UA_info import ua_list
class StartSpider:
#定义表头
header = ['书名', '作者', '类型', '完成度', '介绍']
#创建workbook对象
book = xlwt.Workbook(encoding='utf-8')
#添加一个名为novels的sheet
sheet = book.add_sheet('novels')
#为Excel表单添加表头
for h in range(len(header)):
sheet.write(0, h, header[h])
def __init__(self):
#计数器
self.i = 1
self.headers = {
'User-Agent': random.choice(ua_list),
}
def get_html(self):
for i in range(1,6):
url = f'https://www.qidian.com/all/page{i}/'
response = requests.get(url,headers=self.headers)
if response.status_code == 200:
html = response.text
self.xml_html(html)
def xml_html(self,html):
xml = etree.HTML(html)
#书名的解析语法
title_list = xml.xpath('//*[@id="book-img-text"]/ul/li/div[2]/h2/a/text()')
#作者名的解析语法
author_list = xml.xpath('//*[@id="book-img-text"]/ul/li/div[2]/p[1]/a[1]/text()')
#小说类型的解析语法
type_list1 = xml.xpath('//ul[@class="all-img-list cf"]//p/a[2]/text()')
type_list2 = xml.xpath('//ul[@class="all-img-list cf"]//p/a[3]/text()')
#小说完成度解析语法
complete_list = xml.xpath('///ul[@class="all-img-list cf"]//p[@class="author"]/span/text()')
#小说简介
introduce_list = xml.xpath('//ul[@class="all-img-list cf"]//p[@class="intro"]/text()')
for title,author,type1,type2,complete,introduce in zip(title_list,author_list,type_list1,type_list2,complete_list,introduce_list):
type = type1+'·'+type2
print(title)
print(author)
print("========================")
self.save_data(title,author,type,complete,introduce)
#定义保存数据的函数
def save_data(self,title,author,type,complete,introduce):
self.sheet.write(self.i,0,title)
self.sheet.write(self.i,1,author)
self.sheet.write(self.i,2,type)
self.sheet.write(self.i,3,complete)
self.sheet.write(self.i,4,introduce)
self.i += 1
if __name__ == '__main__':
start = StartSpider()
start.get_html()
#将内存中的Excel数据保存为该文件
StartSpider.book.save('novels.xls')
灵境行者
卖报小郎君
========================
赤心巡天
情何以甚
========================
宿命之环
爱潜水的乌贼
========================
都重生了谁谈恋爱啊
错哪儿了
========================
深海余烬
远瞳
========================
谁让他修仙的!
最白的乌鸦
========================
这游戏也太真实了
晨星LL
========================
万古神帝
飞天鱼
========================
逼我重生是吧
幼儿园一把手
========================
玄鉴仙族
季越人
========================
以上是写入Excel表格中的数据!
三、Beautiful Soup库
由于Beautiful Soup提供了多种选择器,所以使用起来不仅比XPATH方便,而且更灵活。
注意:在使用Beautiful Soup时,需要指定解析器,一般使用lxml解析器,这个解析器非常好用
1、节点选择器
节点选择器直接通过节点的名称选取节点,然后再用string属性就可以得到节点内的文本
from bs4 import BeautifulSoup
html = '''
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Beautiful Soup演示</title>
</head>
<body>
<div>
<ul>
<li class="item1" value1="1234" value2="hello world">
<a href="http://www.baidu.com">baidu.com</a>
</li>
<li class="item2">
<a href="https://www.jd.com">京东商城</a>
</li>
<li class="item3">
<a href="https://www.taobao.com">淘宝</a>
</li>
</ul>
</div>
</body>
</html>
'''
#创建BeautifulSoup对象,并通过BeautifulSoup类的第二个参数指定lxml解析器
soup = BeautifulSoup(html,'lxml')
#获取title节点的名称
print(soup.title.name)
#获取第一个li节点所有属性名和属性值
print(soup.li.attrs)
#获取第一个li节点value2属性的值
print(soup.li.attrs["value2"])
#获取第一个li节点value1属性的值
print(soup.li['value1'])
#获取第一个a节点的href属性的值
print(soup.a['href'])
#获取第一个a标签的文本内容
print(soup.a.string)
title
{'class': ['item1'], 'value1': '1234', 'value2': 'hello world'}
hello world
1234
http://www.baidu.com
baidu.com
在使用节点选择器时,并不是一次能够将需要的节点都取出来,大部分的时候,我们需要分多步来实现。
一下内容就不用代码展示了,后面在讲实战案例时,会用到,大家可以仔细了解。
- 获取直接子节点:通过contents属性或children属性,可以获取当前节点的直接子节点。其中contents属性返回一个列表,children属性返回迭代器,是一个可迭代对象,该对象需要使用for 循环遍历。
- 获取所有的子孙节点:需要使用descendants属性,该属性返回一个产生器,需要使用for循环进行迭代才可以输出产生器的值。
- 选择父节点:需要使用parent属性,如果要选取某个节点的所有父节点,需要使用parents属性。
- 选择兄弟节点:需要通过next_sibling属性获得当前节点的下一个兄弟节点,通过previous_sibling属性获得当前节点的上一个兄弟节点。
2、方法选择器
1、find_all方法
find_all方法用于根据节点名、属性、文本内容等选择所有符合要求的节点。
def find_all(self,name=None,attrs={},recursive=True,text=None,limit=None,**kwargs)
- name参数:用于指定节点名,find_all方法会选取所有节点名与name参数值相同的节点。
- attrs参数:attrs其实一般是用来限制name参数的,当name参数带有属性时,我们可以根据属性限制选取的节点。
- text参数:通过text参数可以搜索匹配的文本节点
2、find方法
find方法与find_all方法有以下几点不同:
- find方法用于查询满足条件的第一个节点
- 两种方法的返回值不一样
3、CSS选择器
基本用法:常见的CSS选择器有如下几个
- .classname:选取样式名为classname的节点,也就是class属性值是classname的节点(注意:必须在属性名前面带上“.”,这个是class的唯一标识)
-
idname:选取id属性值为idname的节点(注意:“#”是ID的唯一标识)
- nodename:选取节点名为nodename的节点
实战案例:抓取酷狗网站TOP500的歌单(https://www.kugou.com/)
import requests
import random
import time
from bs4 import BeautifulSoup
from UA_info import ua_list
class KuGouSpider(object):
def __init__(self):
self.headers = {
'User-Agent':random.choice(ua_list),
}
def get_html(self):
#这个URL需要分析一下,其实也很好得到,稍微观察一下就会发现只有一个值在变
for i in range(1,20):
url = f'https://www.kugou.com/yy/rank/home/{i}-8888.html?from=rank'
response = requests.get(url, headers=self.headers)
#print(response.status_code)
if response.status_code == 200:
html = response.text
self.bs4_html_parse(html)
def bs4_html_parse(self,html):
soup = BeautifulSoup(html,'lxml')
#抓取歌曲的排名,根据CSS选择器爬取
ranks = soup.select('span.pc_temp_num')
#爬取歌手和歌曲名
titles = soup.select('div.pc_temp_songlist>ul>li>a')
#爬取歌曲时间
times = soup.select('span.pc_temp_time')
#存入字典打印
for rank,title,time in zip(ranks,titles,times):
data = {
'rank':rank.get_text().strip(),
'singer':title.get_text().split('-')[0].strip(),
'song':title.get_text().split('-')[1].strip(),
'time':time.get_text().strip()
}
print(data)
if __name__ == '__main__':
kugou = KuGouSpider()
kugou.get_html()
{'rank': '1', 'singer': '偷心', 'song': '张学友', 'time': '4:21'}
{'rank': '2', 'singer': '离别开出花 (弹唱版)', 'song': '就是南方凯', 'time': '2:52'}
{'rank': '3', 'singer': '你总要学会往前走', 'song': '任夏', 'time': '3:07'}
{'rank': '4', 'singer': '我还想她', 'song': '林俊杰', 'time': '4:07'}
{'rank': '5', 'singer': '我期待的不是雪 (而是有你的冬天)', 'song': '张妙格', 'time': '2:46'}
{'rank': '6', 'singer': '句号', 'song': 'G.E.M. 邓紫棋', 'time': '3:55'}
{'rank': '7', 'singer': '安和桥', 'song': '宇西', 'time': '4:08'}
{'rank': '8', 'singer': '是你 (Live)', 'song': '张靓颖、王赫野', 'time': '3:54'}
{'rank': '9', 'singer': '姑娘别哭泣 (弹唱版)', 'song': '柯柯柯啊', 'time': '2:57'}
{'rank': '10', 'singer': '我想念', 'song': '汪苏泷', 'time': '3:44'}
{'rank': '11', 'singer': '夏夜最后的烟火', 'song': '颜人中', 'time': '4:39'}
{'rank': '12', 'singer': '你总要学会往前走 (男版)', 'song': '小灿', 'time': '3:21'}
{'rank': '13', 'singer': 'Back Seat', 'song': 'JYJ', 'time': '3:10'}
{'rank': '14', 'singer': '有形的翅膀', 'song': '张韶涵', 'time': '3:36'}
{'rank': '15', 'singer': '西楼儿女', 'song': '岳云鹏', 'time': '4:20'}
{'rank': '16', 'singer': '瞬', 'song': '郑润泽', 'time': '4:44'}
{'rank': '17', 'singer': 'All My People', 'song': 'Alexandra Stan、Manilla Maniacs', 'time': '3:19'}
四、pyquery库
1、pyquery的基本用法
pyquery包中有一个PyQuery类,使用pyquery要先导入该类,然后创建该类的实例。可以通过三种方式将HTML文档传入该类的对象。
- 字符串
- URL
- 文件
PyQuery对象有很多API可以操作HTML文档,最简单的是直接获取某个节点。
doc = pq(html)
for a in doc('a'):
print(a.get('href'),a.text)
doc = pq(url='https://www.jd.com')
print(doc('title'))
#运行结果
http://www.baidu.com baidu.com
https://www.jd.com 京东商城
https://www.taobao.com 淘宝
https://www.microsoft.com 微软
https://www.google.com 谷歌
<title>京东(JD.COM)-正品低价、品质保障、配送及时、轻松购物!</title>
2、CSS选择器
from pyquery import PyQuery as pq
html = '''
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Beautiful Soup演示</title>
</head>
<body>
<div id="panel">
<ul class="list1">
<li class="item1" value1="1234" value2="hello world">
<a href="http://www.baidu.com">baidu.com</a>
</li>
<li class="item2">
<a href="https://www.jd.com">京东商城</a>
</li>
<li class="item3">
<a href="https://www.taobao.com">淘宝</a>
</li>
</ul>
<ul class="list2">
<li class="item4">
<a href="https://www.microsoft.com">微软</a>
</li>
<li class="item5">
<a href="https://www.google.com">谷歌</a>
</li>
</ul>
</div>
</body>
</html>
'''
doc = pq(html)
#提取id属性值为panel,并且在该节点中所有属性值为list1的所有节点
result = doc('#panel .list1')
print(type(result))
#可以发现result任然是PyQuery对象
print(result)
print(result('.item1'))
print(result('a')[1].get('href'),result('a')[1].text)
#运行结果
<class 'pyquery.pyquery.PyQuery'>
<ul class="list1">
<li class="item1" value1="1234" value2="hello world">
<a href="http://www.baidu.com">baidu.com</a>
</li>
<li class="item2">
<a href="https://www.jd.com">京东商城</a>
</li>
<li class="item3">
<a href="https://www.taobao.com">淘宝</a>
</li>
</ul>
<li class="item1" value1="1234" value2="hello world">
<a href="http://www.baidu.com">baidu.com</a>
</li>
https://www.jd.com 京东商城
3、查找节点
- 查找子节点:使用find方法,还可以使用children方法,该方法需将CSS选择器作为参数传入
- 查找父节点:通过parent方法
- 查找兄弟节点:使用sibling方法
- 获取节点信息:获取节点信息分为5类信息
-
- 节点名称:使用tag属性获取
- 节点属性:使用get方法获取
- 节点文本:使用text方法获取
- 整个节点的HTML代码:使用etree的tostring函数
- 节点内部的HTML代码:使用html方法
4、修改节点
由于修改节点方法太多,我在这里就只讲一些比较常用的方法。
①:添加和移除节点的样式
doc = pq(html)
li = doc('.item2')
li.add_class('myitem')
print(li)
li.remove_class('item2')
print(li)
②:修改节点属性和文本内容
使用attr方法可以向结点添加新的属性,如果添加的属性存在,则会修改原来的属性值。attr方法需要传入两个参数,第一个参数是属性名,第二个参数是属性值。
使用removeAttr方法可以删除节点属性。
使用text方法和html方法修改节点文本内容
doc = pq(html)
li = doc('.item2')
li.attr('class','list')
print(li)
li.remove_attr('class')
print(li)
li.text('我不是淘宝')
print(li)
#运行结果
<li class="list">
<a href="https://www.jd.com">京东商城</a>
</li>
<li>
<a href="https://www.jd.com">京东商城</a>
</li>
<li>我不是淘宝</li>
③:删除节点
使用remove方法可以删除节点,该方法接收一个字符串类型的参数,用于指定要删除的节点名。
5、伪类选择器
伪类选择器很强大,在这里告诉大家一个事情,就是在我们使用浏览器时,打开开发者环境,我们用右键点击想要获取的内容时,然后点击copy,然后在选择copy select就可以获取解析语法,一般比较复杂的解析语法就是使用伪类选择器的。
实战案例:抓取当当图书排行榜(https://www.dangdang.com/)
from requests import Session
from pyquery import PyQuery as pq
import random
from UA_info import ua_list
import time
session = Session()
class DangBookSpider(object):
def __init__(self):
self.headers = {
'User-Agent':random.choice(ua_list),
'Host':'search.dangdang.com',
'Referer':'https://login.dangdang.com/',
}
def get_html(self):
for i in range(1,10):
url = f'https://search.dangdang.com/?key=python&act=input&sort_type=sort_sale_amt_desc&page_index={i}'
response = session.get(url, headers=self.headers)
if response.status_code == 200:
html = response.text
#在这里我使用了一个变量来接收生成器
book_infos = self.pq_html_parse(html)
#使用for循环迭代
for book_info in book_infos:
print(book_info)
time.sleep(1)
def pq_html_parse(self,html):
doc = pq(html)
"""
在这里不建议大家直接获得想要的数据
因为我尝试过了,基本上都查找不到
建议大家一步一步查找
"""
ul = doc('.bigimg')
li_list = ul('li')
for li in li_list.items():
#获取图书名称
a = li('a:first-child')
title = a[0].get('title')
#抓取价格
span = li('.search_now_price')
price = span[0].text[1:]
#获取作者名
p = li('.search_book_author')
author = p('a:first-child').attr('title')
#可以起到一个挂起的作用,相当于return,但是比return更好用
yield {
'title':title,
'price':price,
'author':author
}
if __name__ == '__main__':
dang = DangBookSpider()
dang.get_html()
{'title': ' Python编程三剑客第3版:Python编程从入门到实践第3版+快速上手第2版+极客项目编程(当当套装共3册)', 'price': '21.60', 'author': None}
{'title': ' 零基础学Python(Python3.10全彩版)编程入门 项目实践 同步视频', 'price': '23.10', 'author': None}
{'title': ' Python编程从入门到实践 第3版', 'price': '69.80', 'author': '[美]埃里克・马瑟斯(Eric Matthes)'}
{'title': ' 用Python编程和实践!数学教科书 机器学习 chatgpt聊天机器人 深度学习中必要的基础知识 人工智能 数学基础书', 'price': '34.90', 'author': None}
{'title': ' 大话Python机器学习实战手册 chatgpt聊天机器人 python深度学习人工智能丛书 机器学习方法 python', 'price': '34.90', 'author': None}
{'title': ' 一步到位!Python数据科学与人工智能应用实战(NumPy、Pandas、Matplotlib、Scikit-lear', 'price': '29.50', 'author': None}
{'title': ' 少儿编程', 'price': '34.90', 'author': None}
{'title': ' 矩阵力量:线性代数全彩图解+微课+Python编程', 'price': '129.00', 'author': '姜伟生'}
{'title': ' 零基础学Python(Python3.10全彩版)编程入门 项目实践 同步视频', 'price': '23.10', 'author': '明日科技(Mingri Soft)'}
{'title': ' AI时代系列丛书AI时代程序员+产品经理+项目经理+架构师+代Python量化交易实战', 'price': '43.50', 'author': None}
{'title': ' Python编程从零基础到项目实战PYTHON(微课视频版) python编程 从入门到实践 python学习手册 py', 'price': '28.40', 'author': None}
{'title': ' Excel机器学习', 'price': '29.90', 'author': '[美] 周红'}