10 实战:原生爬虫项目之二


目录

  • 案例代码调整说明
  • 前奏:爬取目的与分析网页结构
  • 编写爬虫的整体思路
  • 正式编写

    • 模拟HTTP请求,发送至服务器
    • 调试工具
    • 调试变量HTML,字节码变str型
    • 正则分析HTML
    • 精炼数据
    • 业务处理:sorted排序
  • 写函数的技巧分享

(六)业务处理:sorted排序

以后,就与爬虫没什么关系了,主要是跟业务紧密挂钩。

能够进行排序、比较大小的数据,只能是观看人数。
1.目的

  • 把人数从字典中取出来;
  • 将取出来的数字字符串文本,转换成数字的数据类型。
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(),  
            'number':anchor['number'][0]
            }   
    
        return map(l,anchors)  

    def __sorted(self,anchors):           #排序函数
        anchors = sorted(anchors,key = self.__sort_seed)     #系统内置函数sorted,提供排序功能 
             #anchors:表示要进行排序的字典
             # key可以接受一个函数
        return anchors

    def __sort_seed(self,anchor):    #定义种子函数,以指定字典中哪个元素来比较大小
            #接受一个参数anchor,它是anchors中的一个单元字典
        return anchor['number']       #上面sorted函数,指定的比较对象是观看人数

    def __show(self,anchors):
        for anchor in anchors:
            print(anchor['name']+'------'+anchor['number'])

    def go(self):           
        htmls = self.__fetch_contents()
        anchors = self.__analysis(htmls)     
        anchors = list(self.__refine(anchors))   
        anchors = self.__sorted(anchors)     #将排序函数放入主方法,集中调用
        self.__show(anchors)

spider = Spider()       
spider.go() 

-->
小战------100.2万      #结果虽有,但升序排列,方向反了
黑店百地------116.2万
浪D------117.8万
陆雪琪------121.7万
骚男------136.2万
英雄联盟赛事------158.5万
岁月-搁浅------16.5万
Ning------181.5万
kRYST4L------198.2万
二兮------21.0万
凉艾------21.3万
瓜皮诺【919】------21.3万
PM-姚夜残------22.0万
曙望丶十三------22.2万

2.更换排序顺序

...
    def __sorted(self,anchors):
        anchors = sorted(anchors,key = self.__sort_seed,reverse = True)    
                                      #将reverse改为True
            
        return anchors

    def __sort_seed(self,anchor):  
        return anchor['number']      

-->
盛世-叽歪鬼------99.8万          #虽成了降序
你的声优倩【招收主播】------99.1万
小绒尾------9.5万               #但仍不对。原因是:sorted里面,返回的是str,不是数字
曙望-臭臭没吃饱------85.3万      #因为,如果是str,比较的只是一个一个数字,非数值比较
陆雪琪------84.8万
黑店百地------82.6万
曙望-汉堡------82.2万
Zz1tai姿态------74.8万
盛世-纪小鹿【526】------68.6万
温稚------54.8万

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:   
            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(),  
            'number':anchor['number'][0]
            }   
    
        return map(l,anchors)  

    def __sort(self,anchors):
        anchors = sorted(anchors,key = self.__sort_seed,reverse = True)    
        return anchors

    def __sort_seed(self,anchor):           #⭐注意!下面是\d,不是d\,否则会报错!
        r = re.findall('\d*',anchor['number'])     #将数字提取出来了。但此时的r,仍是str文本
        number = float(r[0])      #将str的数字,转换成数字类型。此时不能是int(),因为会涉及小数
        if '万' in anchor['number']:
            number *= 10000           #如果有单位万,就要乘以10000
        return number       

    def __show(self,anchors):
        for anchor in anchors:
            print(anchor['name']+'------'+anchor['number'])

    def go(self):           
        htmls = self.__fetch_contents()
        anchors = self.__analysis(htmls)     
        anchors = list(self.__refine(anchors))   
        anchors = self.__sort(anchors)    #⭐注意!这里不是sorted(),而是sort()。前者与内置函数同名,会引发错误!
        self.__show(anchors)

spider = Spider()       
spider.go() 

-->
TheShy------383.1万
Zz1tai姿态------273.1万
V1ncent丶文森特------262.0万
kRYST4L------234.0万
雨初歇-北枫------182.3万
黑店百地------161.9万
浪D------138.9万
小战------137.2万
骚男------129.2万
...

sort排序,是个开放函数,让别人来决定到底用哪个内容进行排序。

七月老师:
搜索引擎解决问题,是最高效的。有人觉得,花钱买课不提问,是吃亏。其实,你提问了,才是吃亏。因为你错过了一次自我学习、自我成长的机会。

上面的不足,在于没有主播的排名序号12345。

4.增加排名序号

因为列表已经排序好了,所以主播的序号,其实就是列表元素的序号。

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(),  
            'number':anchor['number'][0]
            }   
    
        return map(l,anchors)  

    def __sort(self,anchors):
        anchors = sorted(anchors,key = self.__sort_seed,reverse = True)    
        return anchors

    def __sort_seed(self,anchor):     
        r = re.findall('\d*',anchor['number'])     
        number = float(r[0])     
        if '万' in anchor['number']:
            number *= 10000          
        return number       
 
    def __show(self,anchors):            #排名函数
        for rank in range(0,len(anchors)):   #用for in range 来获取列表元素的各个序号
            print('rank  '+str(rank + 1)
            +' : '+anchors[rank]['name']   
            +' :     '+anchors[rank]['number'])
           #rank的起始值是0,所以要加上1     #str只能和str相加,不能是int
                                
    def go(self):    #总控方法很好,清晰展现了数据处理的每个关键过程。因为是平级、没有嵌套,所以自己好维护,别人也一目了然    
        htmls = self.__fetch_contents()      #获取页面
        anchors = self.__analysis(htmls)     #正则分析HTML
        anchors = list(self.__refine(anchors))    #精炼数据 
        anchors = self.__sort(anchors)     #排序
        self.__show(anchors)      #规范展示

spider = Spider()       
spider.go() 

-->
rank  1 : TheShy :     398.7万
rank  2 : Zz1tai姿态 :     292.5万
rank  3 : V1ncent丶文森特 :     252.8万
rank  4 : kRYST4L :     233.1万
rank  5 : 雨初歇-北枫 :     181.2万
rank  6 : 黑店百地 :     179.9万
rank  7 : 浪D :     165.4万
rank  8 : 芜湖神 :     141.9万
rank  9 : 骚男 :     141.4万
rank  10 : 小战 :     127.2万
小技巧:在VSCode中,如何在很长的文件里,找到想要的函数呢?
Ctrl + Shift + O,输入show,会快速跳转到该方法。

如果后续有处理,那就不是show方法了,那可能就是编写存取到数据库中的方法。

五、写函数的技巧分享

1.pyhton中的注释写法
(1)块注释

模块注释:多行字符串(str)形式,最高级

'''
This is a module.

'''

from urllib import request

类注释:多行字符串形式

class Spider():
    '''
    this is a class.
    '''
    url = 'https://www.huya.com/g/lol'
     

方法注释:多行字符串形式


def __fetch_content(self):  
        '''
        this is a method.
        '''

(2)单行代码注释

既可以在单行代码的后面,也可以在上面(推荐),并空行。

        
        r = request.urlopen(Spider.url)      #request对象下面,有个urlopen方法:接受抓取网页的url地址    #将类变量url传送到urlopen中来   #实例方法中读取类变量

        htmls = r.read()    #用read方法读取出html    #html是一些字节码bytes


        #request对象下面,有个urlopen方法:接受抓取网页的url地址    #将类变量url传送到urlopen中来   #实例方法中读取类变量
        r = request.urlopen(Spider.url)   

        #用read方法读取出html    #html是一些字节码bytes
        htmls = r.read() 

2.代码格式的规范性

(1)善用空行
有意义的空行,会方便阅读。但勿滥用。

(2)一个函数,建议小巧,勿写太多行
优点:

  • 会确保函数名就是函数功能的最好描述
  • 体积小,越灵活,复用越方便。你如果写太多逻辑,几乎不能复用。

国外建议:8行左右。
七月老师建议:控制在10-20行

3.需求变更
1.虎牙网站属于模板化建站,想爬取虎牙网站下其他的游戏,如王者荣耀,只需要在代码里,改个网站即可。

2.如果不抓取虎牙,可能就要全改。即抵御业务需求变化的能力太差。
因为本项目的爬虫,只是实现了功能,并没有融入面向对象的思维。
大部分人就是这个写法,很糟糕。

七月老师:
课程git上有面向对象版本的爬虫,可下载学习。

4.关于中大型爬虫
(1)爬虫框架
本案例属于小型爬虫,没使用任何的库或框架。
如果,想要写中大型的爬虫,比如分布式、多线程的爬虫,那就可以使用以下库:

  • BeautifulSoup
  • Scrapy

上述两个爬虫框架,在小的、简单的爬虫项目中,没必要用。

七月老师:
个人习惯,能不用就不用,专注于业务处理、解决问题、
不要为了学框架而学框架,非常耗时。

爬虫只是个过渡技术,非终极技术。真正重要、更有意义的是,是精炼数据、与业务结合处理。而不是,为了爬而爬。

当然,除了上述需求,还有爬虫效率、爬虫海量数据的存取等。

(2)IP被封
最有可能遇到的问题是,IP被封。因为你爬取的频率太高了。所以,建议将频率设小一点。
如果被封,那就要寻找这样一个代理IP库。

声明:Jerry's Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 10 实战:原生爬虫项目之二


Follow excellence, and success will chase you.