H原子 发表于 2021-6-11 18:38:50

多线程爬取全部王者高清壁纸

本帖最后由 H原子 于 2021-6-11 18:43 编辑

时间:2021.06.08
作者:H原子
分析:
待爬取根网页:https://pvp.qq.com/web201605/wallpaper.shtml###
1)通过浏览器检查元素和网页源代码进行对比,猜测此网页使用了Ajax与web服务器通信,
或者使用了js对页面进行了渲染。

2)通过查找浏览器向目标服务器发出的所有请求,确定是使用了Ajax,
请求包名为:
workList_inc.cgi?activityId=2735&sVerifyCode=ABCD&…267733&iActId=2735&iModuleId=2735&_=1623155585880

URL地址为:
https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi?activityId=2735&sVerifyCode=ABCD&sDataType=JSON&iListNum=20&totalpage=0
&page=24&iOrder=0&iSortNumClose=1&jsoncallback=jQuery17102744849148361894_1623155545802
&iAMSActivityId=51991&_everyRead=true&iTypeId=2&iFlowId=267733&iActId=2735&iModuleId=2735&_=1623155585880


**注意**
      (1)page参数:page=0表示返回第一页所需的信息,以此类推(创作这篇文章时共有25页)
      (2)jsoncallback参数:指定返回的json格式(实际就是利用该参数的值将json对象括起来),使用时删去该参数
      
3)通过分析响应信息得到:每张壁纸对应有8张图片地址,第1张为展示图片,后7张为不同分辨率的图片
由于响应对内容加密了得到如下URL:
"http%3A%2F%2Fshp%2Eqpic%2Ecn%2Fishow%2F2735122519%2F1545737998%5F%2D888937974%5F13439%5FsProdImgNo%5F6%2Ejpg%2F200"
通过urllib.parse.unquote()解码得:   
'http://shp.qpic.cn/ishow/2735122519/1545737998_-888937974_13439_sProdImgNo_6.jpg/200'

**注意**
      (1)这里将解码后的URL输入浏览器并不能得到预期的高分辨率图片,需要将末尾数字200改为0
      (2)还可以通过 requests.utlis.unquote():解码
                                        requests.utils.quote():编码

4)读取json格式响应内容,获取图片下载地址,下载保存。

结果展示:
总计有489个主题,每个主题有8张图片,应有4912张,实际有4911,一张下载失败


源代码获取:
import requests
import json
from urllib import parse
from urllib import request
import os
import threading
from queue import Queue


#生产者:通过page_queue队列获取页面对应Ajax响应地址,提取所有页面数据,获取壁纸主题名并创建相应文件夹,并将所有的图片下载地址和保存路径加入到imgurl_queue队列中
class Producer(threading.Thread):
    def __init__(self,page_queue,imgurl_queue,*args,**kwargs):
      self.page_queue = page_queue
      self.imgurl_queue = imgurl_queue
      super().__init__()
      
    def run(self):
      headers = {
                'user-agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'
            }
      while not self.page_queue.empty():
            try:
                response = requests.get(self.page_queue.get(),headers = headers)
            except:
                print('获取页面数据失败')
            jsondata = json.loads(response.content)
            datas = jsondata['List']
            for data in datas:
                theme_name = parse.unquote(data['sProdName']).replace(':','').strip()#有些主题名含有英文‘:’或者空格,这类名字不能命名文件夹
                theme_name = os.path.join('images',theme_name)
                if not os.path.exists(theme_name):#当该主题文件夹未创建时创建(不加这个判断会报出'文件已存在的错误')
                  os.mkdir(theme_name)
                for x in range(1,9):
                  imgdict = {}
                  imgdict['imgurl'] = parse.unquote(data['sProdImgNo_%d'%x])[:-3]+'0'#利用切片将200修改为0
                  imgdict['imgpath'] = os.path.join(theme_name,'%d.jpg'%x)#图片命名为:x.jpg的格式
                  self.imgurl_queue.put(imgdict)

#消费者:通过imgurl_queue队列获取图片下载地址和保存路径,将图片下载至对应路径
class Consumer(threading.Thread):
    def __init__(self,imgurl_queue,*args,**kwargs):
      self.imgurl_queue = imgurl_queue
      super().__init__()
      
    def run(self):
      while True:
            try:
                img_obj = self.imgurl_queue.get(timeout=10)#队列为空阻塞时,等待10s,超时将触发异常,此时下载完成
                imgurl = img_obj.get('imgurl')
                imgpath = img_obj.get('imgpath')
                try:
                  request.urlretrieve(imgurl,imgpath)#传递url和下载路径即可完成下载
                except:
                  print('%s图片下载失败' % imgpath)
            except:
                print('所有图片已下载完毕...')
                break

def main():
    if not os.path.exists('images'):#将所有下载的壁纸都保存在images文件夹下
      os.mkdir('images')
   
    Ajax_url = 'https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi?activityId=2735&sVerifyCode=ABCD&sDataType=JSON&iListNum=20&totalpage=0&page={}&iOrder=0&iSortNumClose=1&iAMSActivityId=51991&_everyRead=true&iTypeId=2&iFlowId=267733&iActId=2735&iModuleId=2735&_=1623155585880'
    page_queue = Queue(30)#目前总共有25页,大小为30足够了
    imgurl_queue = Queue(1000)
   
    #将所有页面对应的Ajax响应地址加入到page_queue队列中
    for i in range(25):
      page_queue.put(Ajax_url.format(i))
      
    #创建生产者线程   
    for x in range(3):
      tp = Producer(page_queue,imgurl_queue,name='生产者%d号'%x)
      tp.start()

    #创建消费者线程
    for x in range(5):
      tc = Consumer(imgurl_queue,name='消费者%d号'%x)
      tc.start()
      

   
if __name__=='__main__':
    main()

Minecraft程序猿 发表于 2021-6-11 23:56:26

贴图是个好习惯

我不是第一个 发表于 2021-6-12 09:28:29

贴图是个好习惯,没图没诱惑

H原子 发表于 2021-6-12 19:00:44

阿这…,有道理

Sungne_jer 发表于 2021-6-12 21:45:48

虽然我看不懂,但是我知道这一定很厉害!

wujiale 发表于 2021-6-17 21:55:39

分析出来才是最难的吧

elitever 发表于 2021-6-17 22:48:06

支持一下,希望楼主做的更好,加油!

hornwong 发表于 2021-6-18 12:26:05

{:5_95:}

qite1314 发表于 2021-6-18 15:41:37

很酷很酷,可是看不懂源码。。。。{:5_102:}

染上灬网络 发表于 2021-6-18 21:01:58

亲测,可以下载,如果有显示进度情况就好了。。。

刚打开还以为没有反应。。

H原子 发表于 2021-6-21 11:44:05

努力学习中,图片和进度条下次一定,感谢大家前来捧场
页: [1]
查看完整版本: 多线程爬取全部王者高清壁纸