实用利器:手把手教你写Python百度图片多模式爬虫 支持缩略图和高清大图批量下载
前言
上一篇《十分钟玩转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分钟左右,我觉得还可以啦,很好用
结