目录
- 案例代码调整说明
- 前奏:爬取目的与分析网页结构
- 编写爬虫的整体思路
正式编写
- 模拟HTTP请求,发送至服务器
- 调试工具
- 调试变量HTML,字节码变str型
- 正则分析HTML
- 精炼数据
- 业务处理:sorted排序
- 写函数的技巧分享
一、案例代码调整说明
1.原来的课程的熊猫TV网站不能爬取了,所以会换个新网站虎牙。其中。原理和方法都是一样的。
2.看完了,理解了,关键还是要会应用。
因为,再好的课程,也是带你入门,最重要的是自己以后能够灵活运用、解决问题。
3.本章的爬虫,非完整爬虫,只是功能简易的小案例,且没有使用任何框架。
因为完整的爬虫是很复杂的,复杂之处并不在于写法复杂,而是因为有多种多样的辅助功能,例如反爬机制、自动登录、代理IP。
尽管爬虫有简单复杂之分,但其本质都是:对HTML文件进行文本的分析,提取想要的信息。
4.该实战项目的目的有三个:
- 巩固以前所学的知识
- 合理的编码规范
- 熟悉爬虫原理
以下是原课程的知识体系。
二、前奏:爬取目的与分析网页结构
1.爬取目的
虎牙网站中,某一款游戏下各个主播的人气排行。
2.分析网页结构
(1)在将网页中想要的数据,抓取到自己的程序中之前,必须了解下网页的显示原理?
之所以网页显示排版规整的图片文字,是因为服务器给了一段HTML格式的信息。
谷歌浏览器,快捷键F12,查看网页的HTML信息。
(2)如何理解左右两边的关系?
(3)如何快速找到做左边的“观看人数”在HTML中的对应信息?
点击右边的左上角的箭头,鼠标悬停左边的“观看人数”,即可快速找到。
(4)如何提取信息?
系统内置的字符串函数,力不从心。所以用正则表达式。
七月老师:
依靠爬虫,建立的产品有:搜索引擎、今日头条等等。
三、编写爬虫的整体思路
1.爬虫前奏
(1)明确目的
虎牙网站中,某一款游戏下各个主播的人气排行
(2)找到数据对应的网页
(3)分析网页结构,找到数据所在的标签位置
2.正式编码
模拟HTTP请求,向服务器发送这个请求,获取到服务器返回给我们的HTML。
然后,用正则表达式,提取我们的数据。
最后,处理数据。
四、正式编写
(一)模拟HTTP请求,发送至服务器
如何模拟HTTP请求向服务器发送这个请求,获取到服务器返回给我们的HTML?
from urllib import request #从内置模块urllib中,导入对象request
class Spider():
url = 'https://www.huya.com/g/lol'
def __fetch_contents(self):
r = request.urlopen(Spider.url) #通过对象request,调用下面方法urlopen(),来接受网址
htmls = r.read() #通过read()函数,读取类变量
def go(self): #主方法、入口方法
self.__fetch_contents() #将拿回内容函数放入主方法,集中调用
spider = Spider() #类的实例化
spider.go() #通过实例对象spider,调用实例方法
(二)调试工具
1.调试重要性
代码出现bug,但又找不到问题在什么地方。此时,第一件应该做的事情:断点调试。
这是每个开发者必备的技能,应足够重视。
对于复杂项目,print()不好用。因为print只能看到一个结果,项目多了就要加很多的print,而且,过程中的变量是无法看到的。
七月老师:
学习一门课,可能没那么重要。但是,通过学习掌握调试,你会逐渐培养起自己独立思考、解决问题的能力。后者终生受益。
2.调试的正确姿势
之前在下方的命令行中的python c8.py
,只是运行代码,不是调试。
调试快捷键:
F5 :启动调式/断点之间调试
F10 :单步调试 #一次只执行一行
F11 :进入函数内部
断点调试最重要的意义:鼠标于变量上悬停,可以查看变量状态。
小技巧:
调试的黄色进度箭头,如果跳出了目标变量的作用域,就无法查看其状态。
所以,可以加个a = 1
并标上断点,即可加以解决。
(三)调试变量HTML,字节码变str型
通过上述讲到的调试技巧,悬停查看变量可知:htmls = r.read()
得到的是字节码的HTML。那么,如何将其转换成字符串类型的HTML文本呢?
用str()
内置函数
from urllib import request
class Spider():
url = 'https://www.huya.com/g/lol'
def __fetch_contents(self):
r = request.urlopen(Spider.url)
htmls = r.read() #字节码形式的HTML
htmls = str(htmls,encoding = 'utf-8') #指定字符串的编码格式
def go(self): #主方法、入口方法
self.__fetch_contents()
spider = Spider()
spider.go()
(四)正则分析HTML
1.定义正则分析函数__analysis()
用于对HTML文本的正则分析,通过规则匹配,找到想要的所有主播姓名和观看人数数据。
from urllib import request
class Spider():
url = 'https://www.huya.com/g/lol'
def __fetch_contents(self):
r = request.urlopen(Spider.url)
htmls = r.read()
htmls = str(htmls,encoding = 'utf-8')
def __analysis(self,htmls): #定义正则分析函数
pass
def go(self):
htmls = self.__fetch_contents()
self.__analysis(htmls) #将正则分析函数放入主方法,集中调用
spider = Spider()
spider.go()
2.HTML定位标签
这是爬虫里面最重要的知识点。
寻找一个标签、标识符,以定位到要抓取的信息。
(1)寻找原因
常量有定位的边界作用,可以缩小寻找范围
(2)寻找原则
1.唯一性
2.最接近目标数据的标签
不能离得太远
3.闭合性
首位平级闭合,有利于提取中间内容
(3)多数据的寻找
不推荐对nickname
和number
分别分析,分别找定位标签。因为两个数据是一一对应的,即使后期排序也是绑定一起。
应该将其当作一组数据,视为一个整体,在组外统一找个定位标签。
最好是父级的标签。
(4)标签层级关系
由上述原则和特点,可找标签如下:
本网站编写较规范,如上所述,三级文档,提取2次。
如果有些网站编写复杂、不规范,有可能是提取3~4次。
但,提取的原理是一样的。
本质就是个数据精细化的过程。
上面的导图,是其中一个主播的标签关系。实际上,某一款游戏下面,有很多的主播。即:
3.编写正则表达式
这是爬虫里面最重要的知识点。
(1)编写一级的匹配规则
from urllib import request
class Spider():
url = 'https://www.huya.com/g/lol'
list_pattern = '<li class="game-live-item" data-gid="1" data-lp="[\d]*">([\s\S]*?)</li>'
#一级的匹配规则
#其中,这里的[\d]*是指代原HTML中一串数字
def __fetch_contents(self):
r = request.urlopen(Spider.url)
htmls = r.read()
htmls = str(htmls,encoding = 'utf-8')
def __analysis(self,htmls):
pass
def go(self):
htmls = self.__fetch_contents()
self.__analysis(htmls)
spider = Spider()
spider.go()
其中:
(1)单双引号的特性
最外面的单引号''
,表示python中的字符串
里面的双引号""
,表示要匹配内容的字符串,必须与原HTML完全一样
所以,上述的引号,不能调换位置。
(2)正则表达式中规则的写法:
[] #表示里面的元字符是或关系 #见第7章:找出中间值是c或f的单词,'a[cf]c'
[\s\S] #通过互补的概括字符集,表示囊括所有数据类型一个元素
[\s\S]* # 星号*,表示重复前面的一个字符,0次或无限多次
[\s\S]*? #关闭贪婪模式,遇到第一个</li>即停止
([\s\S]*?) #小括号(),表示用组的概念提取中间内容,将两边的定位标签去掉
(2)一次匹配
将上述一级的匹配规则,传送到正则分析函数中,进行一次匹配。
from urllib import request
import re
class Spider():
url = 'https://www.huya.com/g/lol'
list_pattern = '<li class="game-live-item" data-gid="1" data-lp="[\d]*">([\s\S]*?)</li>'
# 正则的匹配规则太长了,所以以变量形式,传参到下面re.foundall函数中
def __fetch_contents(self):
r = request.urlopen(Spider.url)
htmls = r.read()
htmls = str(htmls,encoding = 'utf-8')
return htmls
def __analysis(self,htmls):
list_html = re.findall(Spider.list_pattern,htmls) #一级的正则寻找
#上面的一级匹配规则,以变量的形式,传了进来
print(list_html[0]) #通过下标索引,查看其中一个变量的状态
a = 1 #此处多写一行,用以调试查看上一行的结果
def go(self):
htmls = self.__fetch_contents()
self.__analysis(htmls)
spider = Spider()
spider.go()
-->
<a href="https://www.huya.com/gushouyu" class="video-info clickstat" target="_blank" data-eid="" data-edesc=""><img class="pic" src="//a.msstatic.com/huya/main/assets/img/default/338x190.jpg" data-original="//live-cover.msstatic.com/huyalive/1703883402-1703883402-7318123487787220992-3407890260-10057-A-0-1/20220430165511.jpg?x-oss-process=style/w338_h190&streamName=1703883402-1703883402-7318123487787220992-3407890260-10057-A-0-1&interval=10" data-default-img="338x190" alt="爱拍-古手羽的直播" title="爱拍-古手羽的直播"/><em class="tag tag-recommend">大神推荐</em><div class="item-mask"></div><i class="btn-link__hover_i"></i><p class="tag-right"><em class="tag-blue">蓝光<!-- -->8M</em></p></a><a href="https://www.huya.com/gushouyu" class="title" target="_blank" title="重回前十:打字骂人送六十万!发飙把把C">
重回前十:打字骂人送六十万!发飙把把C</a><span class="txt"><span class="avatar fl"><img data-original="https://huyaimg.msstatic.com/avatar/1087/0e/e70f70894f22e202bb5bd7f64235ad_180_135.jpg?1605070684" src="//a.msstatic.com/huya/main/assets/img/default/84x84.jpg" data-default-img="84x84" alt="爱拍-古手羽" title="爱拍-古手羽"/><i class="nick" title="爱拍-古手羽">爱拍-古手羽</i></span><span class="num"><i class="num-icon"></i><i class="js-num">220.5万</i></span></span>
#结果确实包含名字与观看人数两个目标数据
接下来,就是根据匹配的结果,继续做正则二次匹配。
(3)二次匹配
编写二级的匹配规则,编写二级的正则寻找
from urllib import request
import re
class Spider():
url = 'https://www.huya.com/g/lol' #虎牙网站
list_pattern = '<li class="game-live-item" data-gid="1" data-lp="[\d]*">([\s\S]*?)</li>'
name_pattern = '<i class="nick" title="[\s\S]*?">([\s\S]*?)</i>'
number_pattern = '<i class="js-num">([\s\S]*?)</i>'
#二级的匹配规则的写法,与一级几乎相同
def __fetch_contents(self):
r = request.urlopen(Spider.url)
htmls = r.read()
htmls = str(htmls,encoding = 'utf-8')
return htmls
def __analysis(self,htmls):
list_html = re.findall(Spider.list_pattern,htmls)
anchors = [] #二级的正则寻找
for html in list_html: #用for in 循环,遍历总列表
name = re.findall(Spider.name_pattern,html) #这里的html是大列表中的一个小字典
number = re.findall(spider.number_pattern,html)
anchor = {'name':name,'number':number} #将name和number拼成一个字典,叫anchor
anchors.append(anchor) #在列表中,添加元素的方法,是用.append()内置函数
print(anchors[0])
a = 1 #调试查看上一行结果
def go(self):
htmls = self.__fetch_contents()
self.__analysis(htmls)
spider = Spider()
spider.go()
-->
{'name': ['爱拍-古手羽'], 'number': ['209.3万']}
以上是正则分析全过程,获取到了主播名字与观看人数。
(五)精炼数据
爬虫中比较重要的环节。
目的:
- 将目标数据中,前面的空格、换行符等去掉
- 字典中的value是列表形式,为方便比较,将其转换成单一的字符串类型
from urllib import request
import re
class Spider():
url = 'https://www.huya.com/g/lol'
list_pattern = '<li class="game-live-item" data-gid="1" data-lp="[\d]*">([\s\S]*?)</li>'
name_pattern = '<i class="nick" title="[\s\S]*?">([\s\S]*?)</i>'
number_pattern = '<i class="js-num">([\s\S]*?)</i>'
def __fetch_contents(self):
r = request.urlopen(Spider.url)
htmls = r.read()
htmls = str(htmls,encoding = 'utf-8')
return htmls
def __analysis(self,htmls):
list_html = re.findall(Spider.list_pattern,htmls)
anchors = []
for html in list_html:
name = re.findall(Spider.name_pattern,html)
number = re.findall(spider.number_pattern,html)
anchor = {'name':name,'number':number}
anchors.append(anchor)
return anchors
def __refine(self,anchors): #精炼数据
l = lambda anchor:{
'name':anchor['name'][0].strip(), #用内置函数strip来去除字符串内前后的换行符、空格
'number':anchor['number'][0]
}
return map(l,anchors) #用map进行lambda表达式的调用 #这里的结果是个map对象
def go(self):
htmls = self.__fetch_contents()
anchors = self.__analysis(htmls)
anchors = list(self.__refine(anchors)) #将精炼数据函数放入主方法,集中调用
#用list()函数,将map对象,转换成列表形式
#传入该函数里面的参数,是上一级函数的结果
print(anchors[0:2])
spider = Spider()
spider.go()
-->
[{'name': '爱拍-古手羽', 'number': '229.4万'}, {'name': '神超', 'number': '215.8万'}]
其中,重点分析下lambda表达式:
anchor['name'] #通过键访问值,拿到的是列表
anchor['name'][0] #列表中只有一个元素,通过下标索引[0]即可拿到,并是str
anchor['name'][0].strip()
对比:
正则分析HTML结构 | 精炼数据 | |
---|---|---|
主要是分析HTML结构 | 已获得数据,但不规范,进一步规范下格式 | |
比较粗糙 | 比较精炼 | |
结果 | {'name': ['n\ 爱拍-古手羽'], 'number': ['209.3万']} | {'name': '爱拍-古手羽', 'number': '229.4万'} |
Comments | NOTHING