python学习多线程下载图片实战
Posted woodwhale
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python学习多线程下载图片实战相关的知识,希望对你有一定的参考价值。
python多线程下载图片(setu)
前言
python的学习其实早在两三个星期前就开始了,奈何我是个急性子,所以学的很浮躁,甚至连md笔记都没写。但是想了想,我学python的目的反正也仅仅是为了ctf的比赛。所以我直接去学习了对ctf有用的部分。例如,requests库、re库、threading库。所以这章的内容也就是我这几周研究的成果了。
那么进入正题。这章是对学习多线程的一个交代。
说来也好笑,激发我学习python的动力之一居然是setu。
由于ctf男♂童交流群有个bot,其中有个功能是——“来份色图”
这就很nb了。虽然我做不到监听QQ消息,制作出QQbot的程度,但是我可以找学长py一个setu接口呀~
所以拿到了api的我,开始了爬取setu的艰苦奋斗之路!
如果是想要最终版本的多线程爬取setu脚本的话,直接下拉到最后即可!
另附setu API
请节制使用!!!
Version 01
这是我的第一个爬取图片脚本。
当时是第一次写python的下载脚本。
刚刚学会用with打开文件的我,迫不及待的开始了第一版本的脚本撰写~
import requests
import time
for i in range(1,300):
print('start')
url = 'https://api.lolicon.app/setu/?r18=2'
res = requests.get(url)
data = res.json()
data = data['data'][0]
setu_url = data['url']
setu_id = data['uid']
with open ('C:/Users/木鲸/OneDrive/setu_url.txt',"a") as file :
file.write(setu_url+'\\n')
try:
res = requests.get(setu_url).content
except:
res = requests.get(setu_url).content
else:
pass
with open('C:/Users/木鲸/OneDrive/setup/'+str(setu_id)+".jpg","wb") as f:
f.write(res)
print('over')
time.sleep(2)
当时写的想法就是,先访问api接口,爬取json的数据中的url和uid。
url用来下载图片,为了避免重复下载同样的,用uid来当作图片的名字。
我是将图片下载到本地的OneDrive中。因为当时用heroku搭建了一个每个月500小时免费的onemanager下载站。这样在下载站上看图片也比较方便。
但是这个版本的脚本可以说是一坨屎,根本没有什么技术可言,就仅仅是爬取了url进行下载。也没有进行错误处理。在当时这个api接口只有每日300次的时候,这个脚本可以说是非常浪费资源了。
Version 02
接下来来到了第二个版本
这个版本相较于01版本,多了一个错误处理。
原理就是,每次连接api接口的时候,将其中的url写入一个txt中。
然后调用处理txt的脚本来下载文件。
而下载失败的url会存入一个err_txt中,在下载完成之后,我只需要打开errtxt进行下载就行。
那么放下面这两个脚本
下载setu_url.txt中的图片内容
import requests
import time
data = []
err = []
err_txt = []
# 打开存着url的txt文件,将url全部读入data中
with open ('C:/Users/木鲸/OneDrive/setu_url.txt','r') as f:
data = f.read().splitlines()
# 开始下载url中的图片,保存在本地的OneDrive中
for i in range(0,len(data)):
print('Start! Ready to count!')
startTime = time.time()
url = data[i]
try:
print('Trying to connect!')
res = requests.get(url).content
except:
err.append(url)
print("error!")
continue
outpath = time.strftime("%Y%m%d%H%M%S", time.localtime())
with open('C:/Users/木鲸/OneDrive/setup/'+(outpath)+".jpg","wb") as file:
file.write(res)
endTime = time.time()
runTime = endTime - startTime
print('over! It downloads ' + str(runTime))
time.sleep(1)
# 将请求失败的数据再次下载
for i in range(0,len(err)):
print('Start! Ready to count!')
startTime = time.time()
url = err[i]
try:
print('Trying to connect!')
res = requests.get(url).content
except:
err_txt.append(url)
print('error! To be a txt!')
continue
outpath = time.strftime("%Y%m%d%H%M%S", time.localtime())
with open('C:/Users/木鲸/OneDrive/setup/'+(outpath)+".jpg","wb") as file:
file.write(res)
endTime = time.time()
runTime = endTime - startTime
print('over! It downloads ' + str(runTime))
time.sleep(1)
# 将最终错误的url写入一个存放错误的txt中
with open ('C:/Users/木鲸/OneDrive/err_txt.txt','a') as f:
for i in range(0,len(err_txt)):
f.write(err_txt[i]+'\\n')
# 结束!
print('All over!')
下载err_txt.txt中的图片内容
import requests
import time
data = []
err = []
with open ('C:/Users/木鲸/OneDrive/err_txt.txt','r') as f:
data = f.read().splitlines()
for i in range(0,len(data)):
print('Start! Ready to count!')
startTime = time.time()
url = data[i]
try:
print('Trying to connect!')
res = requests.get(url).content
except:
err.append(url)
print('error!')
continue
outpath = time.strftime("%Y%m%d%H%M%S", time.localtime())
with open('C:/Users/木鲸/OneDrive/setup/'+outpath+".jpg",'wb') as file:
file.write(res)
endTime = time.time()
runTime = endTime - startTime
print('Over! It downloads ' + str(runTime))
time.sleep(1)
for i in range(0,len(err)):
print('Start! Ready to count!')
startTime = time.time()
url = err[i]
try:
print("Trying to connect error_txt!")
res = requests.get(url).content
except:
print('Error! Try again!')
res = requests.get(url).content
else:
pass
outpath = time.strftime("%Y%m%d%H%M%S", time.localtime())
with open('C:/Users/木鲸/OneDrive/setup/'+outpath+".jpg",'wb') as file:
file.write(res)
endTime = time.time()
runTime = endTime - startTime
print('Over! It downloads ' + str(runTime))
time.sleep(1)
print('All over!')
这个版本虽然解决了下载失败的部分问题,但他的缺点也是显而易见的,速度太慢了。一张图快的时候3秒下载完成,慢的时候需要200秒。这种单线程下载实在是太慢了。
所以我决定这个时候去学习多线程下载!
Version 03
这是第一个多线程版本,也是废了半天时间写出来的。
由于那个时候api接口是有连接限制,例如不能过快访问,不然ip会被ban,而且每天访问的次数只有300次。
所以我写了一个多线程脚本,大致思想就是,进行20次循环,每次循环中访问API 15次,并且开15个线程进行图片下载。等这这一轮的线程结束,进行下一次循环。
这个版本的脚本不仅有记录err的txt,也有多线程的快速,可以说是在API没有无限开放时候的巅峰了。
脚本如下:
import requests
import time
import threading
import re
url = 'https://api.lolicon.app/setu/?r18=2'
headers = {
'user-agent': '这里写你自己的user-agent!'
}
def download(url,pid,way):
try:
res = requests.get(url,headers=headers).content
except:
print('Download error!')
with open ('C:/Users/木鲸/OneDrive/setup/err.txt',"a") as file :
file.write(setu_url+'\\n')
return
with open('C:/Users/木鲸/OneDrive/setup/'+ str(pid)+ way, "wb") as file:
file.write(res)
for count in range(1,21):
print(f'开始第{count}次下载!')
setu_urls = []
setu_pids = []
ways = []
threads = []
for i in range(1,16):
try:
print('Try to connect api!')
res = requests.get(url,headers=headers)
print('Successful connection!')
try:
data = res.json()
data = data['data'][0]
setu_url = data['url']
setu_pid = data['pid']
except:
print('JOSN error!')
continue
way = re.findall('(.png|.jpg)', setu_url,re.S)[0]
ways.append(way)
setu_urls.append(setu_url)
setu_pids.append(setu_pid)
time.sleep(0.5)
except:
print('Connect api error!')
time.sleep(1)
continue
for i in range(len(setu_urls)):
t = threading.Thread(target=download, args=(setu_urls[i],setu_pids[i],ways[i],))
t.start()
threads.append(t)
for t in threads:
t.join()
print(f'{t.name}-执行下载!')
Version 04
这个版本是最终版本。
今天早上看到室友写的一份脚本,发现同样的api接口多了一个num参数,一次访问可以得到最多100份数据。
再上API网站,发现开放限制了。
于是直接写了一份不需要等待,一次爬100张图片的最终版本。
脚本如下:
import requests
import re
import threading
import time
# 获取json数据
def getApiJSON(url,headers):
try:
print('Try to connect api!')
res = requests.get(url,headers=headers)
print('Successful connecting!')
except:
print('Connect error!')
return getApiJSON(url,headers)
JSON_data = res.json()
return JSON_data
# 进行图片下载
def download(url, pid, way):
global errCount
try:
res = requests.get(url).content
except:
print('Download error!')
lock.acquire()
errCount += 1
lock.release()
with open ('C:/Users/木鲸/OneDrive/setup/err.txt',"a") as file :
file.write(setu_url+'\\n')
return
with open('C:/Users/木鲸/OneDrive/setup/'+ pid + way, "wb") as file:
file.write(res)
print('Success!')
allTime = 0
allSuccess = 0
# 每次循环下载100张图
for i in range(2):
print(f'The {i+1} time!')
url = 'https://api.lolicon.app/setu/?num=100&r18=1'
headers = {'user-agent': '这里写你自己的user-agent!'}
lock = threading.Lock()
threads = []
errCount = 0
startTime = time.time()
jsons = getApiJSON(url, headers)
jsons = jsons['data']
for json in jsons:
setu_url = json['url']
setu_pid = str(json['pid'])
way = re.findall('(.png|.jpg)', setu_url, re.S)[0]
t = threading.Thread(target=download, args=(setu_url,setu_pid,way,))
t.start()
threads.append(t)
time.sleep(1)
for t in threads:
t.join()
print(f'{t.name} over!')
endTime = time.time()
print(f'The {i+1} time over! It takes {endTime-startTime}! It has {100-errCount} successes!')
allTime += endTime - startTime
allSuccess += 100 - errCount
time.sleep(1)
print(f"All over! It takes {allTime}! It has {allSuccess} successes!")
最终测试发现。如果每次time.sleep(0.5),会有25%的错误率,速度也是在300s左右。但是晚上运行脚本的时候,无意间改成了time.sleep(1),发现错误率为0,并且速度也是在210s左右。几乎是1s一张图片。可以说在速度和错误率上都是最优的了。
后话
当然,本人也是刚刚学习的python多线程,这个最终版本的脚本也是花了一天时间不断的改出来的,所以可能并不是最完美的。但是在我这里,这个多线程setu爬取脚本已经毕业了。从最初的单线程下载,到后面的记录错误下载,再到后面的多线程爬取,最后的不断改进。
在这个过程中,我发现,枯燥的听网课是很难运用一个知识的。只有在学习的过程中去真正自己动手,这样知识才能学好。
所以在学习的时候,实践、实践、实践!这点很重要!
当然,看看这个文件夹的大小,你就知道这几天我有多努力了(bushi)
从开始有多线程的想法(飞机师傅的指导)
到学习多线程遇到瓶颈
再到最后发现更改睡眠时间的欣喜若狂
其实这样学习才是快乐的~
以上是关于python学习多线程下载图片实战的主要内容,如果未能解决你的问题,请参考以下文章