完整的爬虫:

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

示例爬虫:

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

目的:

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

示例内容:

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

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


代码结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
'''
类注释
'''
class spider():
#抓取页面内容(行注释)
def __fetch_content(self):
'''
方法注释
'''
pass
#数据抽取
def __analysis(self, htmls):
pass
#数据精炼
def __refine(self, pairs):
pass
#排序
def __sort(self, pairs):
pass
#排序的算子
def __seed(self, pairs):
pass
#数据展现
def __show(self, pairs):
pass
#函数入口
def go(self):
调用
s = spider()
s.go()
```
书写代码:
抓取页面:
url = 'http://www.huya.com/g/lol'
分析原网页:
<li class="game-live-item" gid="1">
    <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"}'>
        <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="最强赵信折翼的直播">
                <div class="item-mask"></div>
        <i class="btn-link__hover_i"></i>
        <em class="tag tag-blue">蓝光</em>    </a>
    <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>
    <span class="txt">
        <span class="avatar fl">
            <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="最强赵信折翼">
            <i class="nick" title="最强赵信折翼">最强赵信折翼</i>
        </span>
                <span class="num"><i class="num-icon"></i><i class="js-num">1.5万</i></span>
    </span>
</li>
1
找到含目标数据的最小公约子集:
<span class="txt">
    <span class="avatar fl">
        <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="最强赵信折翼">
        <i class="nick" title="最强赵信折翼">最强赵信折翼</i>
    </span>
            <span class="num"><i class="num-icon"></i><i class="js-num">1.5万</i></span>
</span>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
目标数据:
最强赵信折翼,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)
完整的爬虫代码:

from urllib import request
import re

class spider():

url = 'http://www.huya.com/g/lol'
root_pattern = '<span class="txt">([\s\S]*?)\r\n</li>' #父级目录匹配
# 使用概括字符集 [\d] [\w]  [\s]  [.]
#注意:要加问号,声明是非贪婪的匹配
name_pattern = '<i class="nick" title="([\s\S]*?)">'
num_pattern = '<i class="js-num">([\s\S]*?)</i>'

def __fetch_content(self):  #加__的函数:私有方法
    tmp = request.urlopen(spider.url)
    htmls = tmp.read() #此处的结果是字节码:bytes
    # bytes->str
    htmls = str(htmls,encoding='utf-8')
    a = 1#如果不添加这一句,htmls赋值发生在最后一句
    #那么断点停止时会得不到htmls的值,这时要人为多余的添加一条语句并将断点放到这里即可
    #print(htmls)不推荐打印,会出现解码问题
    return htmls

def __sort(self, pairs):
    #def sorted(iterable, cmp=None, key=None, reverse=False)
    #注意要指定key值
    return sorted(pairs,key=self.__seed,reverse=True)

def __show(self, pairs):
    #for循环中需要拿到序号,直接使用range形式的for循环
    for rank in range(0, len(pairs)):
        print('第',rank,'名:',pairs[rank]['name'],':',pairs[rank]['number'])

def __seed(self, pairs):
    tmp = pairs['number'].replace('万','')
    if('万' in pairs['number']):
        tmp = float(tmp) * 10000
    return int(tmp)

def __refine(self, pairs):
    f = lambda p: { 
        'name':p['name'][0].strip(),
        'number':p['number'][0]
         }
    return map(f, pairs)

def __analysis(self, htmls):
    root_htm = re.findall(spider.root_pattern,htmls)
    pairs = []
    for item in root_htm:
        name = re.findall(spider.name_pattern,item)
        num = re.findall(spider.num_pattern,item)
        pair = {'name':name,'number':num}
        pairs.append(pair)
    return pairs

#设置入口函数,这是一个主方法,里面都是平级函数,推荐这种写法 
def go(self):   
    htmls = self.__fetch_content()      #抓取页面内容
    pairs = self.__analysis(htmls)      #抽取所需数据
    pairs = list(self.__refine(pairs))  #数据精炼
    pairs = self.__sort(pairs)          #数据排序
    self.__show(pairs)                  #数据显示,或后续处理(入库等)

#实例化并调用入口函数
s = spider()
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
    整个流程的核心:    
        爬取的原始数据如何处理,精炼
        处理的结果如何存储,分析