Python爬虫实战——LOL英雄皮肤图片(多线程爬虫,带详细注释,皮肤全部爬完)

前言

        LOL的皮肤的爬取是我当年刚学习爬虫时就想爬的一个网站,课程设计做的也是这个,不过由于当时技术水平有限,爬取的数据很潦草,所以现在我有时间了就又重新去看看这个网站是怎么爬更好了。

目标

1.是获取到皮肤数据(我选择保存的皮肤格式是png)

2.是要获取到皮肤的名字,来为皮肤命名

 示例如下:

 网站分析

        在网上我也看了一些别人爬的教程,不过他们爬到的数据会不全,有很多的皮肤会爬取不到,所以我就自己下手了。

1.打开LOL的官网,滑到最下面,随便点一个英雄

 我直接就选择第一个英雄安妮

 打开界面,按F12之后,F5刷新,在网站反回来的网络请求中我找到了这一个json文件请求文件,这个文件一看就很可疑,点击预览之后果然,所需要的数据都放在了这一个json文件里面。

 

可以看到在里面有我们需要的英雄的名字,进过我对这些图片链接的一个个访问,我也找到了我们需要的图片链接

1.1补充说明

在这里插一句,网上的一些获取图片的方式是通过组合图片的链接

 这样的链接是可以访问,前面的1是英雄的id,最后的0是英雄的第几个皮肤,修改这两个参数可以获取到图片数据

 但是有个问题,这个方法只对很老的皮肤生效,应该是自从某一时间点之后,腾讯修改了数据存放方式

例如:

       安妮有16个皮肤,当我们把数字改成16时,组合成的链接并不能访问

 2.数据链接

观察链接:

https://game.gtimg.cn/images/lol/act/img/js/hero/1.js?ts=2800033

?之后的都可以不用看,属于一些参数。链接后面的1就很可疑

 当把1换成2之后,很明显的看出来是又数据返回的,不过当我尝试其他的数字时,会出现404(尝试数字为130)

 明明LOL有163位英雄,为什么输个130会找不到链接????(我爬的时候是163个英雄吼)

经过观察我发现这个数字其实对应的是英雄的id或者说官方给的编号

 可以看到最新出的英雄和上一个英雄之前的id差了很多,这个问题在早期是没有存在的

 3.获取英雄的id

要拿到英雄的id,进入英雄详情页是拿不到的,所以我们需要到详情页的上一级去看看。

 点击英雄列表,刷新之后可以找到这一个名叫hero_list的json文件,里面就有我们需要的英雄id

4.思路整理

1.我们先访问hero_list文件的链接获取到英雄的id

url = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'

2.然后访问id对应的json文件,获取到皮肤图片和图片的名字

代码编写

所需库:

import os
import requests
import time
from concurrent.futures import ThreadPoolExecutor

设置爬虫的头文件:

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54'
}

1.根据上面的思路分析我们第一步是拿到英雄的id,因为请求的返回是一个JSON文件,然后我们要的数据是放在列表里面,所以我们就不拿文本直接拿JSON文件,然后拿取存放数据的列表,循环列表把我们需要的数据拿出来,存储为一个新的列表。

# 获取LOL英雄的id
def ID():
    # 创建一个列表用于存放英雄的id
    id = []
    # 访问存放有英雄id的接口
    burl = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'
    breq = requests.get(burl).json()  # 拿接口的JSON文件可以更快的帮我们定位到想要的数据
    for i in breq['hero']:
        id.append(i['heroId'])  # 把获取到的id放入列表
    return id

2.根据英雄id访问存放有皮肤名称和皮肤链接的JSON链接,来获取到我们所需要的数据。要是选择返回的是文本,则需要对返回来的数据进行转码。

# 获取LOL英雄的名字作为文件的命名
def name_spider(id, choose):
    names = []  # 存放英雄皮肤名字的列表
    surl = []  # 存放英雄皮肤链接的列表
    uname = 'https://game.gtimg.cn/images/lol/act/img/js/hero/' + str(id) + '.js'
    # 访问的链接,拿到每个英雄的详细信息的JSON文件
    ureq = requests.get(uname, headers=header).json()
    # 对访问到的JSON文件进行处理,获取到我们想要的数据
    for k in ureq['skins']:
        if k['chromas'] == '0':  # 当chromas标签 = 0时会是皮肤的原画( = 1是皮肤的炫彩,不过我们不需要这个)
            names.append(k['name'])
            surl.append(k['mainImg'])
    # 爬取图片和保存函数(全写在一起就太多了点不好看,就分开写了)
    photo_spider(names, surl, choose)  # 爬到的皮肤名称和图片的链接作为参数传进函数

3.访问获取到的皮肤链接,然后把图片下载下来,因为我们要爬的图片,所以我们进行request访问的时候我们拿的返回是二进制流返回。

# 爬取具体图片函数,choose=0会把图片分门别类,放到对应的英雄的文件夹里面,其他数字就直接把图片存入指定文件夹
def photo_spider(names, surl, choose=1, path=r'G:\\工作\\工作文件\\代码学习\\LOL皮肤'):
    # 判断选择的数据存储方式
    if choose == 0:
        # 下载的图片保存到的文件夹
        os.makedirs(path + r'\\' + str(names[0]))  # 在指定文件夹下创建个英雄皮肤的文件夹
        os.chdir(path + r'\\' + str(names[0]))  # 切换当前工作路径为指定路径
    else:
        os.chdir(path)  # 切换当前工作路径为指定路径
    # 对图片链接进行访问
    for i in range(len(surl)):
        photoreq = requests.get(surl[i]).content  # 因为是图片,所以这里我们拿二进制流返回
        # 保存到文件夹,按照皮肤的名字命名,保存的格式为png格式
        with open(names[i].replace('/', '') + '.png', 'wb') as f:  # 因为某些皮肤的名字中有"/"(例:K/DA这个系列的皮肤),会影响文件的保存,所以要替换掉
            f.write(photoreq)

 这里我提供了两中保存的格式,一种是全部图片放在一个文件夹里面,一种是根据英雄名字创建文件夹,然后把这个英雄对应的皮肤放进文件夹里面

4.开启多线程爬取,为了不让线程混乱,所以我们还要引入一个线程池

来点多线程笑话:

你想象中的多线程:

 实际的多线程:

不加线程池就会这样,不过我们爬取的是图片,如果爬到一样的,后面爬的会把前面爬的覆盖,但是这样会让我们效率降低,那你这个多线程开和没开有什么区别是吧

多线程我放在了主函数里面:

if __name__ == '__main__':
    # 程序开始时间
    time_start = time.time()
    choose = input("输入需要存储的类型(0为把皮肤分类放到各个英雄的文件夹下):")
    # 调用函数爬取英雄的id
    id = ID()
    # 创建线程池,开启10个进程
    pool = ThreadPoolExecutor(max_workers=10)
    for pic_url in id:
        # 将耗时间的任务放到线程池中交给线程来执行
        pool.submit(name_spider, pic_url, choose)  # 执行的函数,函数所需的参数
    pool.shutdown()  # 让main函数在线程全部结束之后再结束
    # 程序结束时间
    time_end = time.time()
    print('程序执行的时间:' + str(time_end - time_start))

我这边开10个线程爬完全部的图片是耗时35s,如果是开单线程的话用时是350s,刚好快了10倍

总结

 整体来说爬取难度不难,全部爬取下来也不用多少时间,这就是开了多线程的快速,不过我也是只开了10个线程。不过线程也不要开太多,不要给别人的网站造成太大的服务器压力,要是把人家爬崩了,警察叔叔可就要来找你了。

完整代码

# -- coding: utf-8 --
# @Time : 2023/3/10 14:16
# @File : LOL_Photo.py
# @Software: PyCharm

import os
import requests
import time
from concurrent.futures import ThreadPoolExecutor


header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54'
}


# 获取LOL英雄的id
def ID():
    # 创建一个列表用于存放英雄的id
    id = []
    # 访问存放有英雄id的接口
    burl = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'
    breq = requests.get(burl).json()  # 拿接口的JSON文件可以更快的帮我们定位到想要的数据
    for i in breq['hero']:
        id.append(i['heroId'])  # 把获取到的id放入列表
    return id


# 获取LOL英雄的名字作为文件的命名
def name_spider(id, choose):
    names = []  # 存放英雄皮肤名字的列表
    surl = []  # 存放英雄皮肤链接的列表
    uname = 'https://game.gtimg.cn/images/lol/act/img/js/hero/' + str(id) + '.js'
    # 访问的链接,拿到每个英雄的详细信息的JSON文件
    ureq = requests.get(uname, headers=header).json()
    # 对访问到的JSON文件进行处理,获取到我们想要的数据
    for k in ureq['skins']:
        if k['chromas'] == '0':  # 当chromas标签 = 0时会是皮肤的原画( = 1是皮肤的炫彩,不过我们不需要这个)
            names.append(k['name'])
            surl.append(k['mainImg'])
    # 爬取图片和保存函数(全写在一起就太多了点不好看,就分开写了)
    photo_spider(names, surl, choose)  # 爬到的皮肤名称和图片的链接作为参数传进函数


# 爬取具体图片函数,choose=0会把图片分门别类,放到对应的英雄的文件夹里面,其他数字就直接把图片存入指定文件夹
def photo_spider(names, surl, choose=1, path=r'G:\\工作\\工作文件\\代码学习\\LOL皮肤'):
    # 判断选择的数据存储方式
    if choose == 0:
        # 下载的图片保存到的文件夹
        os.makedirs(path + r'\\' + str(names[0]))  # 在指定文件夹下创建个英雄皮肤的文件夹
        os.chdir(path + r'\\' + str(names[0]))  # 切换当前工作路径为指定路径
    else:
        os.chdir(path)  # 切换当前工作路径为指定路径
    # 对图片链接进行访问
    for i in range(len(surl)):
        photoreq = requests.get(surl[i]).content  # 因为是图片,所以这里我们拿二进制流返回
        # 保存到文件夹,按照皮肤的名字命名,保存的格式为png格式
        with open(names[i].replace('/', '') + '.png', 'wb') as f:  # 因为某些皮肤的名字中有"/"(例:K/DA这个系列的皮肤),会影响文件的保存,所以要替换掉
            f.write(photoreq)


if __name__ == '__main__':
    # 程序开始时间
    time_start = time.time()
    choose = input("输入需要存储的类型(0为把皮肤分类放到各个英雄的文件夹下):")
    # 调用函数爬取英雄的id
    id = ID()
    # 创建线程池,开启10个进程
    pool = ThreadPoolExecutor(max_workers=10)
    for pic_url in id:
        # 将耗时间的任务放到线程池中交给线程来执行
        pool.submit(name_spider, pic_url, choose)  # 执行的函数,函数所需的参数
    pool.shutdown()  # 让main函数在线程全部结束之后再结束
    # 程序结束时间
    time_end = time.time()
    print('程序执行的时间:' + str(time_end - time_start))

猜你喜欢

转载自blog.csdn.net/weixin_54243306/article/details/129807022
今日推荐