前言:在网络无处不在的今天,大数据的分析与提取也逐渐发展了起来。现在我们使用Python来做一个原生爬虫的小案例——爬取斗鱼直播平台分区下推荐视频播放量的排名,通过这个案例可以回顾前面所学的正则表达式等知识。

原生爬虫步骤:

1.模拟请求

2.用标签定位并提取数据(重点是正则表达式的编写)

3.数据精炼

4.业务处理

一. 模拟请求

  • 建立Spider类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from urllib import request


    class Spider:
    url = "https://www.douyu.com/g_LOL"

    def __fetch_content(self):
    r = request.urlopen(Spider.url)
    htmls = r.read()
    # 此处断点调试

    def go(self):
    self.__fetch_content()


    spider = Spider()
    spider.go()

    调试结果

    可以看出,需要对htmls进行转码:

    1
    htmls = str(htmls,encoding='utf-8')

    再次调试后报错:

    网上查阅资料后发现,是因为:

    以"b’\x1f\x8b\x08"开头的 ,说明它是gzip压缩过的数据,这也是报错的原因,所以我们需要对我们接收的字节码进行一个解码操作。

    修改代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from urllib import request
    from io import BytesIO
    import gzip


    class Spider:
    # 需要爬虫的URL
    url = "https://www.douyu.com/g_LOL"

    def __fetch_content(self):
    r = request.urlopen(Spider.url)
    # 获取HTML字节码
    htmls = r.read()
    buff = BytesIO(htmls)
    f = gzip.GzipFile(fileobj=buff)
    result = f.read().decode("utf-8") # 解码

    def go(self):
    self.__fetch_content()


    spider = Spider()
    spider.go()

    调试结果:

    p3

    可以看到,已经爬取到了网页的HTML数据

  • 上面部分可能会遇到的错误与解决方法

    urllib.error.URLError: urlopen error [SSL: CERTIFICATE_VERIFY_FAILED]

    1
    2
    3
    4
    # 全局取消证书验证

    import ssl
    ssl._create_default_https_context = ssl._create_unverified_context

二. 定位数据与提取、业务处理

  • 第一步:观察游览器内HTML数据,尽可能选择闭合标签

    如上图所示,我定位到了包含了主播名字、播放量的数据的div标签

  • 第二步:编写正则表达式,抽取div标签

    1
    2
    root_pattern = '<div class="DyListCover-info">[\s\S]*?</div>'
    # 使用re.findall方法

    匹配取得一个列表后,打印其中一个元素验证是否获取成功

    可以看到,我们成功获取了主播"芜湖大司马"的一个视频播放量"224.9万",但还需要精确提取

  • 第三步:在div标签中抽取数据

    1
    2
    # 在正则表达式中加入组()标签,即可取出中间内容
    root_pattern = '<div class="DyListCover-info">([\s\S]*?)</div>'

    写名字、播放量的正则表达式(根据有规律的标签定位)

    1
    2
    name_pattern = '<use xlink:href="#icon-user_c95acf8"></use></svg>([\s\S]*?)</h2>'
    number_pattern = '</use></svg>([\s\S]*?)</span><h2 class="DyListCover-user is-template">'

    然后调用函数进行匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def __analysis(self, htmls):
    root_html = re.findall(Spider.root_pattern, htmls)
    anchors = []
    for html in root_html:
    name = re.findall(Spider.name_pattern, html)
    number = re.findall(Spider.number_pattern, html)
    if name != []:
    anchor = {"name": name, "number": number}
    anchors.append(anchor)
    return anchors

    匹配并打印anchors结果如下

    可以看到,我们已经成功抓取出这部分数据了

  • 第四步:数据精炼

    1
    2
    3
    4
    5
    6
    def __refine(self, anchors):
    l = lambda anchor: {
    "name": anchor["name"][0].strip(), # strip()为python内置函数
    "number": anchor["number"][0], # 用于去除字符串前后空格
    }
    return map(l, anchors)
  • 第五步:业务处理(排序)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def __sort(self, anchors):
    anchors = sorted(
    anchors, key=self.__sort_seed, reverse=True
    ) # 第二个参数为key,指定比较大小的元素,第三个参数reverse可调整顺序或倒序排序
    return anchors


    def __sort_seed(self, anchor):
    r = re.findall("\d*", anchor["number"])
    number = float(r[0])

    if "万" in anchor["number"]: # 将万转换为10000
    number *= 10000
    return number
  • 第六步:打印数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def __show(self, anchors):
    # for anchor in anchors:
    # print(anchor['name'] + "-----" + anchor['number'])
    for rank in range(0, len(anchors)):
    print(
    "rank"
    + str(rank + 1)
    + ": "
    + anchors[rank]["name"]
    + " "
    + anchors[rank]["number"]
    )

    结果:

三. 总结

以上就是简单的使用Python原生爬虫的一个小案例,虽然成功爬取,但是代码复用性较差(比如只能爬取斗鱼某个区的视频播放量,如果更换其他直播平台要重写正则表达式),可以考虑使用框架(如Scrapy)。

除此之外,由于网站的不断更新迭代,前面所写的正则表达式可能失效。

下面是总体代码,更新时间:2022-10

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from urllib import request
from io import BytesIO
import gzip
import re


class Spider():
# 需要爬虫的URL
url = "https://www.douyu.com/g_LOL"
root_pattern = '<div class="DyListCover-info">([\s\S]*?)</div>'
name_pattern = '(?<=<div class="DyListCover-userName is-template">).*'
number_pattern = '(?<=</use></svg>).*?(?=</span><h2 class="DyListCover-user is-template">)'
title_pattern = '(?<=title=").*?(?=">)'

def __fetch_content(self):
r = request.urlopen(Spider.url)
# 获取HTML字节码
htmls = r.read()
buff = BytesIO(htmls)
f = gzip.GzipFile(fileobj=buff)
result = f.read().decode('utf-8') # 解码
return result

def __analysis(self, htmls):
root_html = re.findall(Spider.root_pattern, htmls)
anchors = []
for i in range(0, len(root_html), 2):
title = re.findall(Spider.title_pattern, root_html[i])
name = re.findall(Spider.name_pattern, root_html[i + 1])
number = re.findall(Spider.number_pattern, root_html[i + 1])
if name != []:
anchor = {'name': name, 'number': number, 'title': title}
anchors.append(anchor)
return anchors

def __refine(self, anchors):
# 使用lambda将每个字段的list类型转成string类型
l = lambda field: {
'name': field['name'][0].strip(), # strip()为python内置函数
'number': field['number'][0], # 用于去除字符串前后空格
'title': field['title'][0]
}
return map(l, anchors)

def __sort(self, maps):
anchors = sorted(maps, key=self.__sort_seed, reverse=True) # 第二个参数为key,指定比较大小的元素,第三个参数reverse可调整顺序或倒序排序
return anchors

def __sort_seed(self, ele):
r = re.findall('\d*', ele['number'])
number = float(r[0])

if '万' in ele['number']: # 将万转换为10000
number *= 10000

return number

def __show(self, anchors):
for rank in range(0, len(anchors)):
print('rank' + str(rank + 1) + ': ' + anchors[rank]['name'] + ' ' + anchors[rank]['number'] + ' ' +
anchors[rank]['title'])

def go(self):
htmls = self.__fetch_content()
anchors = self.__analysis(htmls)
maps = self.__refine(anchors)
sort_maps = self.__sort(maps)
self.__show(sort_maps)


spider = Spider()
spider.go()