## 目标网站分析 我们的目标网站为英雄联盟国服官网: 我们的目标数据为上方网站中所有英雄的皮肤图片.  看看这把一刀999的大刀, 这就是我们想要. 在进行具体分析之前, 先简单说个现状: 若爬虫要成功爬取图片, 那么爬虫程序需要根据**图片的URL**(简单来说就是图片的网址), 构造并发送HTTP请求, 再从响应中获取图片数据, 来写入到文件中(即保存下载的数据为具体的文件). ### 先分析下单一直接目标数据 所谓单一直接目标数据, 其实就是先对某一张图片进行分析. 这样再对多张图片进行分析. 我先选择本人最强英雄来分析皮肤图片吧. 该URL为卡特琳娜的详情页, 该页面我们想要下载的图片有11张  先打开开发者模式(在网页上, 鼠标右键选择开发者工具), 这里作者使用的是chrome, 只要是浏览器都会有开发者工具, 但是这里推荐大家使用chrome. 这里在开发者工具出现后, 如下图点击元素检查按钮, 然后再将鼠标移动到图片上, 在移动的过程中, 你会发现页面有点异样, 出现的淡蓝色选项是正常的  将鼠标移动到图片上并点击后, 开发者工具中会直接显示你刚才所点中的位置所对应的已渲染后的代码.  Elements中是浏览器渲染好的代码, 这里通过刚才的点击, 直接帮我们定位到了图片在代码中的位置. 通过肉眼观察, 我们发现图片所对应的代码是一个img标签, 该类标签用来包含页面上的图片. 显然该img标签中的src属性包含的数据就是图片的URL了, 但是这个URL不是完整的, 可以将鼠标放在URL上面, 它会显示该URL实际的完整URL.  右下角就是完整的URL, 我们就照葫芦画瓢, 把这个残缺的URL拼凑好, 然后在浏览器中访问下看看, 能不能直接访问到图片.! 显然可以访问, 同样的操作放在其他的卡特琳娜的皮肤上也是可以获得到相应的图片URL的. 图片URL我们找到了, 意味着可以通过python访问下载了. 但是这些URL是我们手动找到的, 我们不可能手动去获取上百个英雄的URL, 太麻烦. 为了交给程序做, 我们要检查下这些URL是动态加载出来的, 还是静态的. 这里说到了动静态的问题, 给大家简单说明下: - 动态: 代码由浏览器执行, 加载之后所生成的数据 - 静态: 代码中直接包含了原始数据, 不是由浏览器执行加载出来. 为什么要检查数据的动静态? 因为我们今天写的程序没法像浏览器一样加载生成数据, 所以只能获取页面上的静态数据. 若Elements中的URL, 我们拿到网页源代码中搜索没有直接搜索到, 那么就确定数据是动态的.  在页面上鼠标右键就能看到查看网页源代码的选项了. 点击进入就能来到如上的页面, 但是通过复制Elements中的URL进行搜索, 发现页面源代码中没有找到对应的URL匹配项, 说明URL是动态数据. ### 对动态数据进行分析 首先我们要明确一个点, 网页上呈现的数据不管是动态的还是静态的, 这些数据都要给到浏览器才能呈现在眼前, 所以若我们要去找数据, 哪怕很麻烦, 也还是能找到的. 动态数据一般有两种方式生成: - 浏览器通过发送异步请求获取数据, 再由网页的js代码加载生成. - 若是异步请求, 那么抓到异步请求再模拟发送请求获取数据即可. - 网页的js代码从其他文件或自身所在的文件中将相关数据加载生成. - 若是js加载其他文件或本文件数据, 那么就要多分析js代码, 这样对js的知识有所要求. 说完动态数据, 再回到当前目标网站, 我们的目标是动态的, 通过分析目标数据是根据js代码加载其他文件来生成相关数据的. 这里只要够细心分析, 我们会发现英雄详情页的源代码中包含了这样的一段js代码:  显然可以直接感受到, 这段代码就是用来生成页面的皮肤图片的. 通过for循环的方式, 将一个英雄的多张图片的URL拼凑出来, 并生成在页面上, 有了URL, 浏览器就会去请求这些URL来呈现皮肤. 这段代码中关键的对象是`var skin =LOLherojs.champion[heroid].data.skins,` skin包含了英雄的相关数据, 而该对象是根据LOLherojs来获取的. 可以发现在赋值时, 有明显的层级关系: LOLherojs->champion[heroid]->data->skins 我们拿着层级关系中的名称尝试的在页面源代码中进行匹配. 看看匹配到的选项有没有有价值的信息. 通过搜索: champion, heroid发现了特殊意义的js文件和代码 详情页中的js代码:  这段代码中有去加载其他的js文件, 发现heroid作为拼凑URL的对象, 而这个heroid是怎么来的呢?通过代码不难发现, 详情页的URL中 包含了id=Katarina, 而这里heroid其实就是这个id值. 也就是说http://lol.qq.com/biz/hero/Katarina.js中极有可能包含了英雄的相关数据. 进入网址发现该js文件中确实包含了英雄的所有皮肤的名称以及id值, 而这个id值其实也就是图片URL中的末尾数据. 若: `//ossweb-img.qq.com/images/lol/web201310/skin/big55008.jpg` 这里面的big55005.jpg中的数值与上面的js文件中的id值是对应的.  你若有去查看其他英雄的图片URL, 你会发现不同英雄的图片URL 只有`//ossweb-img.qq.com/images/lol/web201310/skin/big{}.jpg`花括号部分的数据是不同的. 也就意味着只要能拿到那个js文件的id值, 就能拿到英雄的图片URL. 除此之外, 搜索champion发现了另一个js文件:  该js文件中包含了所有影响的id值, 这意味着只要拿到该js文件中的数据, 我们就不用去资料库页面匹配每一个英雄的详情页然后再去获取英雄的图片了, 可以直接从这个js文件中获取英雄的id, 然后再用前一个js文件获取英雄的图片数据, 然后就可以下载图片了. 总结: 到目前为止, 我们分析出了如下几点信息. - 英雄详情页中的图片URL是动态生成, 并且是从其他文件中获取的数据由js代码加载的. - 从详情页中获取到了包含所有英雄Id数据的js文件 - 从详情页中获取到了包含单个英雄所有皮肤的图片Id数据的js文件 那么既然一个英雄所有皮肤的图片URL应该具有的数据, 已经所有英雄的heroid也知道了, 那么就可以着手写代码了. ## 代码编写 代码部分需要使用第三方模块: **requests** ### heroid获取 目的: 获取所有英雄的heroid, 用来拼凑获取每一个英雄的皮肤js文件URL. ```python # 0. 构建headers头部信息, 让python发送的请求伪装成浏览器发送的请求. headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" " (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36", } # 1. 该URL包含了所有影响的heroid. champion_js_url = "https://lol.qq.com/biz/hero/champion.js" # 2. 发送请求 resp = requests.get(url=champion_js_url, headers=headers) # 3. 通过正则匹配js文件中的json数据. hero_key_json = re.findall(r'champion=(.*?);', resp.text, re.S)[0] # 4. 将json数据装换为python字典. hero_key_dict = json.loads(hero_key_json) # 5. 通过for循环获取data键中的所有项. for hero_data in hero_key_dict["data"].items(): # hero_data中包含了heroid ``` ### 获取英雄名称和皮肤信息 目的: 根据hero_data, 对每一个英雄皮肤js文件进行请求并获取数据. ```python # 1. 皮肤js文件URL. hero_js_url = "http://lol.qq.com/biz/hero/{}.js" # 2. 获取heroid并发送请求. resp = requests.get(url=hero_js_url.format(hero_data[0]), headers=headers) # 3. 生成正则表达式. regular_expression = "{}=(.*?);".format(hero_data[0]) # 4. 将正则匹配的数据json转换为python字典. result_dict = json.loads(re.findall(regular_expression, resp.text, re.S)[0]) # 5. 获取皮肤信息, 由于皮肤不止一个, 所以这里获取的是列表, 列表中的每个字典包含了皮肤信息. skins_list = result_dict["data"]["skins"] # 6. 获取英雄的默认名称. hero_name = hero_data[1]["name"] + '_' + hero_data[1]["title"] ``` 根据英雄的默认名称在当前目录下创建保存图片的目录. ```python os.makedirs(r"./images/{}".format(hero_name), exist_ok=True) ``` ### 下载图片 目的: 根据皮肤信息中包含的id值, 拼凑图片URL来发送请求, 获取数据并写入到文件中. ```python # 1. 图片的URL. skin_pic_url = "https://ossweb-img.qq.com/images/lol/web201310/skin/big{}.jpg" # 2. 根据皮肤信息中的id来构造URL, 并发送请求. resp = requests.get(url=skin_pic_url.format(skin_info["id"]), headers=headers) # 3. 创建文件并写入数据. with open(r"./images/{}/{}.jpg".format(hero_name, skin_info["name"]), "wb") as f: f.write(resp.content) print("[+] {} 下载完成!".format(skin_info["name"])) ``` 这样我们的图片就下载下来了, 但是记得加上请求延迟. ```python time.sleep(round(random(), 2)) ``` 也可以对相应代码部分加上多线程: ```python from multiprocessing.pool import ThreadPool ``` 结果如下:  有问题欢迎留言评论.有建议或者意见欢迎斧正 不胜感激 有兴趣一起学习爬虫的小伙伴们记得加群: 392521592,会有许多的资源可以供大家学习分享,欢迎大家前来一起学习进步!