[关闭]
@EVA001 2017-11-03T11:27:51.000000Z 字数 5796 阅读 357

Python的原生爬虫案例

Python


完整的爬虫:

反扒机制,自动登录,代理IP等等

示例爬虫:

简单的数据抓取,简单的数据处理

目的:

不使用爬虫框架完成数据爬取
巩固知识、合理编程、内部原理

示例内容:

内容:
    爬取直播网站
确定工作:
    确定爬取数据:某个分类下各主播人气的数据
    确定实现结果:将人气进行排序
准备:
    分析网站结构
    寻找包含爬取信息的页面
    F12检查网页,定位信息(主播姓名,人气数据)
原理:
    对html文件进行文本分析并从中提取信息
使用技术
    正则表达式
具体步骤:
    模拟HTTP请求,向服务器发送请求,获取到服务器返回的HTML
    用正则表达式处理网页文本,过滤出有用数据
    找到相关常量标签,作为正则的定位边界
    定位标签:
        尽量选择具有唯一标识的标识的标签
        尽量选择与目标数据相近的标签
        尽量选择将所有目标数据都包含的标签(闭合的标签),比如包含姓名+人气的标签
        上述即尽量选父标签,不选兄弟标签,为了易于构造正则提取内容
注意:
    构造正则不是难点,难点是应对反爬虫的措施

整体书写规范
    每行代码不要过长
    推荐书写一个入口程序
    推荐在入口中平行的调用逻辑代码
    每个方法中代码尽量少
    注意块注释和行级注释的书写格式


代码结构:
  1. '''
  2. 类注释
  3. '''
  4. class spider():
  5. #抓取页面内容(行注释)
  6. def __fetch_content(self):
  7. '''
  8. 方法注释
  9. '''
  10. pass
  11. #数据抽取
  12. def __analysis(self, htmls):
  13. pass
  14. #数据精炼
  15. def __refine(self, pairs):
  16. pass
  17. #排序
  18. def __sort(self, pairs):
  19. pass
  20. #排序的算子
  21. def __seed(self, pairs):
  22. pass
  23. #数据展现
  24. def __show(self, pairs):
  25. pass
  26. #函数入口
  27. def go(self):
  28. 调用
  29. s = spider()
  30. s.go()
书写代码:
    抓取页面:
        url = 'http://www.huya.com/g/lol'
    分析原网页:
  1. <li class="game-live-item" gid="1">
  2. <a href="http://www.huya.com/yanmie" class="video-info new-clickstat" target="_blank" report='{"eid":"click/position","position":"lol/0/1/5","game_id":"1","ayyuid":"380335691"}'>
  3. <img class="pic" data-original="//screenshot.msstatic.com/yysnapshot/1711a622dc4d670fe32de2018f78a2d030fcde37cfe8?imageview/4/0/w/338/h/190/blur/1" src="//a.msstatic.com/huya/main/assets/img/default/338x190.jpg" onerror="this.onerror=null; this.src='//a.msstatic.com/huya/main/assets/img/default/338x190.jpg';" alt="最强赵信折翼的直播" title="最强赵信折翼的直播">
  4. <div class="item-mask"></div>
  5. <i class="btn-link__hover_i"></i>
  6. <em class="tag tag-blue">蓝光</em> </a>
  7. <a href="http://www.huya.com/yanmie" class="title new-clickstat" report='{"eid":"click/position","position":"lol/0/1/5","game_id":"1","ayyuid":"380335691"}' title="可以用赵信上王者第一的男人" target="_blank">可以用赵信上王者第一的男人</a>
  8. <span class="txt">
  9. <span class="avatar fl">
  10. <img data-original="//huyaimg.msstatic.com/avatar/1081/63/c83bdc0701b64646c86065e273fd05_180_135.jpg" src="//a.msstatic.com/huya/main/assets/img/default/84x84.jpg" onerror="this.onerror=null; this.src='//a.msstatic.com/huya/main/assets/img/default/84x84.jpg';" alt="最强赵信折翼" title="最强赵信折翼">
  11. <i class="nick" title="最强赵信折翼">最强赵信折翼</i>
  12. </span>
  13. <span class="num"><i class="num-icon"></i><i class="js-num">1.5万</i></span>
  14. </span>
  15. </li>
    找到含目标数据的最小公约子集:
  1. <span class="txt">
  2. <span class="avatar fl">
  3. <img data-original="//huyaimg.msstatic.com/avatar/1081/63/c83bdc0701b64646c86065e273fd05_180_135.jpg" src="//a.msstatic.com/huya/main/assets/img/default/84x84.jpg" onerror="this.onerror=null; this.src='//a.msstatic.com/huya/main/assets/img/default/84x84.jpg';" alt="最强赵信折翼" title="最强赵信折翼">
  4. <i class="nick" title="最强赵信折翼">最强赵信折翼</i>
  5. </span>
  6. <span class="num"><i class="num-icon"></i><i class="js-num">1.5万</i></span>
  7. </span>
    目标数据:
        最强赵信折翼,1.5万
    构造正则表达式:
        选出上面的最小公约子集:    
            root_pattern = '<span class="txt">([\s\S]*?)\r\n</li>' 
            注意:
                要加问号,声明是非贪婪的匹配

        然后选出,姓名:    
            name_pattern = '<i class="nick" title="([\s\S]*?)">'
        然后选出,人气:
            num_pattern = '<i class="js-num">([\s\S]*?)</i>'

        注意:
            上述正则的边界并不一定是完整的html标签,因为使用正则即对字符进行匹配,所以可以随意拆分。

    几个重要功能:

        获取页面内容:
            from urllib import request
            tmp = request.urlopen(spider.url)
            htmls = tmp.read() 
            注意:此处的结果是字节码:bytes
            必须要进行转化:bytes->str
            htmls = str(htmls,encoding='utf-8')

            注意:不推荐打印出页面内容,会出现解码问题,可以加断点调试

        循环:
            此处的循环需要获取到下标,而直接for i in list,获取不到下标
            此时应该使用for index in range(0,len(list))这种形式
            代码:
                for rank in range(0, len(pairs)):
                    print('第',rank,'名:',pairs[rank]['name'],':',pairs[rank]['number'])

        排序:
            此处使用内置函数sorted(iterable, cmp=None, key=None, reverse=False)
            注意:
                key = 函数名,此函数应该返回排序的比较值
                cmp = 函数名,此函数可以重写排序规则
                reverse=False,从小到大正序排列
            代码:
                sorted(pairs,key = seed,reverse = True)
                def seed(self, pairs):
                    tmp = pairs['number'].replace('万','')
                    if('万' in pairs['number']):
                        tmp = float(tmp) * 10000
                    return int(tmp)


    完整的爬虫代码:
  1. from urllib import request
  2. import re
  3. class spider():
  4. url = 'http://www.huya.com/g/lol'
  5. root_pattern = '<span class="txt">([\s\S]*?)\r\n</li>' #父级目录匹配
  6. # 使用概括字符集 [\d] [\w] [\s] [.]
  7. #注意:要加问号,声明是非贪婪的匹配
  8. name_pattern = '<i class="nick" title="([\s\S]*?)">'
  9. num_pattern = '<i class="js-num">([\s\S]*?)</i>'
  10. def __fetch_content(self): #加__的函数:私有方法
  11. tmp = request.urlopen(spider.url)
  12. htmls = tmp.read() #此处的结果是字节码:bytes
  13. # bytes->str
  14. htmls = str(htmls,encoding='utf-8')
  15. a = 1#如果不添加这一句,htmls赋值发生在最后一句
  16. #那么断点停止时会得不到htmls的值,这时要人为多余的添加一条语句并将断点放到这里即可
  17. #print(htmls)不推荐打印,会出现解码问题
  18. return htmls
  19. def __sort(self, pairs):
  20. #def sorted(iterable, cmp=None, key=None, reverse=False)
  21. #注意要指定key值
  22. return sorted(pairs,key=self.__seed,reverse=True)
  23. def __show(self, pairs):
  24. #for循环中需要拿到序号,直接使用range形式的for循环
  25. for rank in range(0, len(pairs)):
  26. print('第',rank,'名:',pairs[rank]['name'],':',pairs[rank]['number'])
  27. def __seed(self, pairs):
  28. tmp = pairs['number'].replace('万','')
  29. if('万' in pairs['number']):
  30. tmp = float(tmp) * 10000
  31. return int(tmp)
  32. def __refine(self, pairs):
  33. f = lambda p: {
  34. 'name':p['name'][0].strip(),
  35. 'number':p['number'][0]
  36. }
  37. return map(f, pairs)
  38. def __analysis(self, htmls):
  39. root_htm = re.findall(spider.root_pattern,htmls)
  40. pairs = []
  41. for item in root_htm:
  42. name = re.findall(spider.name_pattern,item)
  43. num = re.findall(spider.num_pattern,item)
  44. pair = {'name':name,'number':num}
  45. pairs.append(pair)
  46. return pairs
  47. #设置入口函数,这是一个主方法,里面都是平级函数,推荐这种写法
  48. def go(self):
  49. htmls = self.__fetch_content() #抓取页面内容
  50. pairs = self.__analysis(htmls) #抽取所需数据
  51. pairs = list(self.__refine(pairs)) #数据精炼
  52. pairs = self.__sort(pairs) #数据排序
  53. self.__show(pairs) #数据显示,或后续处理(入库等)
  54. #实例化并调用入口函数
  55. s = spider()
  56. s.go()
    注意事项:
        如果需要调试,不推荐站桩print,推荐使用断点调试
        调试方法:
            启动应用程序 F5
            单步执行F10
            跳到下一个断点 F5
            调到函数内部 F11

        例如在 html = tmp.read() 处打断点
        在当前断点处,悬停鼠标会显示变量值,也可以在vscode左侧的甲壳虫选项中查看变量的值


    缺陷:
        虽然通过类进行了封装,但是其实最基础的封装
        但是,复用性差,抵御需求变化的能力太差,违反开闭原则

    进阶:
        可以使用更加面向对象的设计来完成功能
        借助构造函数__init__来对类进行带参数的实例化:
        代码:
            class Spider():
                '''
                This is a class
                '''
                url = ''
                root_pattern = ''
                name_pattern = ''
                num_pattern = ''

                def __init__(self,url,root_pattern,name_pattern,num_pattern): 
                    Spider.url = url
                    Spider.root_pattern = root_pattern
                    Spider.name_pattern = name_pattern
                    Spider.num_pattern = num_pattern    

            s = Spider(
                'http://www.huya.com/g/4',
                '<span class="txt">([\s\S]*?)\r\n</li>',
                '<i class="nick" title="([\s\S]*?)">',
                '<i class="js-num">([\s\S]*?)</i>'
            )
            s.go()  
        类封装的意义:
            这样封装可以完成一个主播人气排序的爬虫类,参数有四个:
                爬取的直播网站;
                爬取的名称人气的父元素的正则
                爬取名称的正则
                爬取人气的正则

    展望:
        爬虫模块或框架:
            BeautifulSoup模块
            Scrapy框架(多线程、分布式、较臃肿,看需求谨慎使用)
        反反爬虫技术:
            频繁爬取会使IP被封,需要使用定时器!切记!!
            寻找代理IP库,应对封IP
        整个流程的核心:    
            爬取的原始数据如何处理,精炼
            处理的结果如何存储,分析
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注