前言

      上一篇《十分钟玩转Flex布局》发布之后,我分享给了大一大二学弟之后,他们给我聊天说确实学到东西了,我听了还是很开心的,初心达到了。

      ε=(´ο`*)))唉,我已经好久没更新啦。

     不是我偷懒在家睡觉,而是最近真的太忙了,事情一件接一件的来。其实最近做东西的时候遇见了好多问题,早都想总结分享一下啦,可是真的没有时间,寻找实习岗位、学校里的事、域名备案被退了好几次、小程序客户端和服务端......等等等每天晚上都是撸代码到深夜。昨天晚上微信小程序搞完,服务端部署到服务器上之后,才能有点时间在这写点东西。O(∩_∩)O哈哈~ 啰嗦了

    好了进入正题。前两天,在做微信小程序测试的时候,需要一些小朋友的照片存服务器上,无奈我的图片文件夹都是一些代码截图,我就自己简单写了一个python脚本爬了一些网图就丢服务器上了。昨天晚上做生成用户小程序海报模块的时候如下图,发现用户头像很模糊,最初我以为是canvas的问题,反复在前端代码里面找问题,缩小头像比例,无果!最后到服务器上一看图片大小1.5kb左右。。。。问题就找到了,我爬的是缩略图图片质量很低,再经过拉伸,canvas绘图,图片就模糊了。

  于是乎,今天上午,重新写了一下python代码,支持缩略图和高清大图两种模式图片的批量下载。

  我把整体的流程分析一下,当然啦,也可以直接拿着源代码去奔放!

分析过程

一、分析网页

①在百度图片中敲入关键字搜索(例如:fighting)图片

 首页展现了30张图片,这一看就是瀑布流式布局,随着滚动条下拉,图片往后慢慢叠加。所以肯定是通过ajax发送异步请求向服务器获取数据的。

②分析请求

我们F12后,network选中xhr,随着滚动条的移动,不断地在发送新的异步请求。

③分析请求报文

  

我们得出以下结论:1.get请求 2.关键参数  queryWord word关键词  pn、rn与数量有关

解释:rn是每页请求的个数(每次最多60张,我试过了),pn是总量,第二页就相当于pn=2*rn,实际是页面上的第三页,最开始有30张图片了,最后一个参数是时间戳,其他参数不用管,一并发送即可

我们都知道,get请求时QueryString传值,参数跟url后面,所以我们只需要https://image.baidu.com/search/acjson,之后添后面的参数就行了

④分析response数据

我们还是把数据(对应ajax请求的response里面)放到json.cn中看一下。

很明显,我们需要的数据在data里面,缩略图地址thumbURl,原图地址objURL。

我咋知道的?点进去试试嘛 O(∩_∩)O哈哈~objURL是点不动的【加密的】,这个后面再说

至此,分析完毕,思路清晰,模拟发送get请求的数据,清洗数据,图片下载到本地,over!

代码实现

  准备工作 

 就这四个库,后三个都是内置的

import  requests
import  json
import  time
import  os

①获取图片地址

   代码分析:

  代码:

import  requests
import  json
url="https://image.baidu.com/search/acjson"
request_args={"tn":"resultjson_com","ipn":"rj","ct":"201326592","is":"","fp":"result","queryWord":"fighting","cl":2,"lm":-1,"ie":"utf-8",
			  "oe":"utf-8","adpicid":"","st":-1,"z":"","ic":0,"hd":"","latest":"","copyright":"","word":"fighting","s":"","se":"","tab":"",
			  "width":"","height":"","face":"0","istype":2,"qc":"","nc":1,"fr":"","expermode":"","force":"","pn":30,"rn":30,
			  "gsm":"1e","1585198294921":"",
}
headers={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
    ,"Referer":"https://image.baidu.com"
}
response=requests.get(url=url,headers=headers,params=request_args)
if response.status_code==200:
    data_list=json.loads(response.text)["data"]
    data_list=list(filter(lambda x:x.keys().__len__()>0,data_list))
    for item in data_list:
        print(item["thumbURL"])

 效果:

下面我们只需存盘就行了,但是我们现在得到的是缩略图的地址,我们还有原图地址没有获取呢

②获取原图真实地址

 我用了“真实”,可能都已经猜到了,要用一些手段才能得到想到的地址。

我们打印objURL发现,是一段看不懂的字符串,我最初以为是base64但是验证了一下不是,于是另辟蹊径,去看看前端有没有相应的js去处理的。

于是乎,我扒拉了半天,过程省略,终于找到了与编码有关的JS

   

我去!又是看不懂的东西!实际这为了优化前端性能,对js进行了压缩。

我用vscode格式化了一下,只放有用的部分看一下吧

var u = function (e) {
            var r = a.getSearchConf(),
                n = new Date,
                t = n.getTime();
            if (void 0 == r.fmq) e.fmq.value = t + "_R";
            else if (r.fmq.indexOf("m") > -1 && -1 == r.fmq.indexOf("_m") && -1 == r.fmq.indexOf("_R")) {
                var f = r.fmq;
                e.fmq.value = t + "_" + f + "_R"
            } else e.fmq.value = t + "_R";
            return e.fm.value = void 0 == r.fr || "" == r.fr ? "detail" : r.fr, !0
        },
        c = {
            w: "a",
            k: "b",
            v: "c",
            1: "d",
            j: "e",
            u: "f",
            2: "g",
            i: "h",
            t: "i",
            3: "j",
            h: "k",
            s: "l",
            4: "m",
            g: "n",
            5: "o",
            r: "p",
            q: "q",
            6: "r",
            f: "s",
            p: "t",
            7: "u",
            e: "v",
            o: "w",
            8: "1",
            d: "2",
            n: "3",
            9: "4",
            c: "5",
            m: "6",
            0: "7",
            b: "8",
            l: "9",
            a: "0",
            _z2C$q: ":",
            "_z&e3B": ".",
            AzdH3F: "/"
        },
        s = /([a-w\d])/g,
        m = /(_z2C\$q|_z&e3B|AzdH3F)/g;
    a.uncompile = function (e) {
        var r = e.replace(m, function (e, r) {
            return c[r]
        });
        return r.replace(s, function (e, r) {
            return c[r]
        })
    }

那一串k/v形式的对象就是编码规则,只要知道编码格式就可以进行译码了

定义译码函数:

def encode_objurl(objurl):
    code_dic= {
			"w": "a",
			"k": "b",
			"v": "c",
			"1": "d",
			"j": "e",
			"u": "f",
			"2": "g",
			"i": "h",
			"t": "i",
			"3": "j",
			"h": "k",
			"s": "l",
			"4": "m",
			"g": "n",
			"5": "o",
			"r": "p",
			"q": "q",
			"6": "r",
			"f": "s",
			"p": "t",
			"7": "u",
			"e": "v",
			"o": "w",
			"8": "1",
			"d": "2",
			"n": "3",
			"9": "4",
			"c": "5",
			"m": "6",
			"0": "7",
			"b": "8",
			"l": "9",
			"a": "0",
		}
    objurl=objurl.replace("_z2C$q",":").replace("_z&e3B",".").replace("AzdH3F","/")
    res=""
    for c in objurl:
        if c in code_dic.keys():
            res+=code_dic[c]
        else:
            res+=c
    return res

效果:

我们调用函数之后,把刚刚加密的url地址进行了解密得到了最终的原图地址,现在我们只剩最后一步了,保存图片到本地。

③保存图片到本地

ext_name=imgsrc[imgsrc.rindex("."):].split("?")[0]#扩展名
                path="./"+keyword+"_imgs/"
                if not os.path.exists(path):
                    os.mkdir(path)
                filename=path+keyword+str(index)+ext_name
                try:
                    with open(filename,"wb") as wstream:
                        headers = {
                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
                        }
                        try:
                            imgresponse = requests.get(url=imgsrc, headers=headers,timeout=4)
                            wstream.write(imgresponse.content)
                            print(filename)
                            index += 1
                        except Exception:
                            print("网络迷失了方向")
                except Exception:
                    print("出错啦,写入失败")

说明:

这段代码是从写好的完整代码中粘贴出来的,只是写入图片部分的代码。imgsrc是得到的路径,index是一个标识

将网络图片保存到本地,同样用到requests但是要注意要用response.content进行文件读写操作。同时要进行相应的异常处理,因为我们获取原图时是解密之后再请求的,可能会有解密错误的情况,所以要设置请求超时的时间,并做异常处理。

好了至此,所有主要功能都实现完了,之后再加上相应的逻辑处理就可以了。

完整代码

Github地址:https://github.com/Young-coder-wrz/PythonScript/tree/master 

图片保存地址默认为当前路径下的一个文件夹,可以更改path参数,自行设置

import  requests
import  json
import  time
import  os
def encode_objurl(objurl):
    code_dic= {
			"w": "a",
			"k": "b",
			"v": "c",
			"1": "d",
			"j": "e",
			"u": "f",
			"2": "g",
			"i": "h",
			"t": "i",
			"3": "j",
			"h": "k",
			"s": "l",
			"4": "m",
			"g": "n",
			"5": "o",
			"r": "p",
			"q": "q",
			"6": "r",
			"f": "s",
			"p": "t",
			"7": "u",
			"e": "v",
			"o": "w",
			"8": "1",
			"d": "2",
			"n": "3",
			"9": "4",
			"c": "5",
			"m": "6",
			"0": "7",
			"b": "8",
			"l": "9",
			"a": "0",
		}
    objurl=objurl.replace("_z2C$q",":").replace("_z&e3B",".").replace("AzdH3F","/")
    res=""
    for c in objurl:
        if c in code_dic.keys():
            res+=code_dic[c]
        else:
            res+=c
    return res

def DownloadImg(keyword,mode,numbers):
    if mode.lower() not in ["small","big"]:
        print("模式输入错误!")
        return
    if int(numbers)>2000:
        print("太多了,扛不住!")
        return
    url = "https://image.baidu.com/search/acjson"
    pages=numbers//60 if numbers%60==0 else numbers//60+1
    pindex=1
    index = 1
    for pindex in range(1,pages+1):
        request_args = {
            "tn": "resultjson_com",
            "ipn": "rj",
            "ct": "201326592",
            "is": "",
            "fp": "result",
            "queryWord": keyword,
            "cl": 2,
            "lm": -1,
            "ie": "utf-8",
            "oe": "utf-8",
            "adpicid": "",
            "st": -1,
            "z": "",
            "ic": 0,
            "hd": "",
            "latest": "",
            "copyright": "",
            "word": keyword,
            "s": "",
            "se": "",
            "tab": "",
            "width": "",
            "height": "",
            "face": "0",
            "istype": 2,
            "qc": "",
            "nc": 1,
            "fr": "",
            "expermode": "",
            "force": "",
            "pn": pindex * 60,
            "rn": 60,
            "gsm": "1e",
            "1585198294921": "",
        }
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
            , "Referer": "https://image.baidu.com"
        }
        response=requests.get(url=url,headers=headers,params=request_args)
        if response.status_code==200:
            data_list=list(filter(lambda x:x.keys().__len__()>0,json.loads(response.text,encoding="utf-8")["data"]))
            for item in data_list:
                imgsrc=item["thumbURL"] if mode.lower()=="small" else encode_objurl(item["objURL"])
                ext_name=imgsrc[imgsrc.rindex("."):].split("?")[0]#扩展名
                path="./"+keyword+mode.lower()+"_imgs/"
                if not os.path.exists(path):
                    os.mkdir(path)
                filename=path+keyword+str(index)+ext_name
                try:
                    with open(filename,"wb") as wstream:
                        headers = {
                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
                        }
                        try:
                            imgresponse = requests.get(url=imgsrc, headers=headers,timeout=4)
                            wstream.write(imgresponse.content)
                            print(filename)
                            index += 1
                        except Exception:
                            print("网络迷失了方向")
                except Exception:
                    print("出错啦,写入失败")
            print("保存了" + str(pindex) + "页," + str(index-1) + "张图片")
            time.sleep(2)
        else:
            print("网络迷失了方向")
            return

if __name__ == '__main__':
    #author:睿吉吉
    #date:2020年3月26日
    #version:1.0.0
    print("**********************Image download script**********************")
    keword=input("请输入要下载的图片关键字:")
    mode=input("请输入模式:small or big? [small 缩略图(速度杠杠滴)  big高清大图(略慢,质量杠杠滴)]")
    try:
        numbers=int(input("请输入图片下载数量:"))
        print("任务创建成功->关键字:"+keword+"   数量:"+str(numbers))
        DownloadImg(keword,mode,numbers)
        print("下载完毕,over!")
    except Exception:
        print("不要乱输入,不让你下了,拜拜┏(^0^)┛")
        exit(0)

 效果演示

缩略图:

高清原图:

缩略图比较小,也不用解密下载的比较快,300张大概20~30秒。高清原图文件本来就大,还要进行一次解密,300张大概2分钟左右,我觉得还可以啦,很好用

        好啦,跟大家一步步解析了 ,从网页到请求头到代码实现,应该很清楚了吧,大多是基本操作叭^_^

        我也看了同类文章,大多都是直接解析了原网页一次取30张图片,并没有好好去分析啊,30张那能行,来还不多来点,不能客气啊!哈哈

        大家可以跟着写一写试试或者直接拿去用都行,开心就好,我很认真去写我的每一篇博客,为了让看到我的博客的人能够收获到东西,觉得好用不妨点个赞、点个关注谢谢啦