作者: 孔扎根 

简介: 工作13余载,现任高级爬虫工程师,在工作中积累了丰富的数据库、ETL及python开发方面的经验。

座佑铭:美的东西都喜欢, 好的东西都想要, 美好的东西是我追求的目标

  Python是一种跨平台的计算机编程语言。是一种面向对象的动态类型语言,Python的设计哲学是“优雅”、“明确”、“简单”,随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。  

  本篇文章产生的背景是源于公司的一个项目"数字大屏",从公司的服务器下载图片后, 对图像进行处理, 本篇记录了其中的一个小知识点, 在极端网络波动下的图片智能下载处理,我们知道网络稳定顺畅时一切下载都很顺利, 程序结束的很完美,你开心我也开心^_^哈哈。但是图片往往都比较大,当偶尔网络极其慢的时候下载一张图片可能需要很久的时间,而此时业务还要求你不能等太长时间, 怎么办呢? 此时我们可能就希望你不要再下载了, 中断吧……, 我再想想其它法…^_^.

  别瞎叨叨了, 说正事...  

  在使用python3下载图片时, 常用的方法有urlretrieve和requests两种, 但是不管哪种方法在网速极慢的情况下, 都会出现图片下载卡住现象。那如何解决呢? 小编总结了几种常用的图片下载方法及中断下载的方法, 记录了一段寻找最佳方法的心路历程。

方法一、socket: 设置默认超时

  正常网速时下载情况是什么样子呢?

#!/usr/bin/env python3
# -*- coding:utf- -*-
# __author__:kzg import datetime
import os
from urllib.request import urlretrieve

# 函数运行计时器
def count_time(func):
def int_time(*args, **kwargs):
start_time = datetime.datetime.now() # 程序开始时间
func(*args, **kwargs)
over_time = datetime.datetime.now() # 程序结束时间
total_time = (over_time-start_time).total_seconds()
print("download picture used time %s seconds" % total_time)
return int_time

# 下载回调函数, 计算下载进度
def callbackinfo(down, block, size):
'''
回调函数:
down:已经下载的数据块
block:数据块的大小
size:远程文件的大小
'''
per = 100.0 * (down * block) / size
if per > :
per =
print('%.2f%%' % per)

# 图片下载
@count_time
def downpic(url, filename):
try:
print("Start Down %s" % url)
if os.path.exists(filename):
os.remove(filename)
urlretrieve(url, filename, callbackinfo)
except socket.timeout as ex:
print(ex) url = "http://p1.meituan.net/wedding/d4c375416ab70b8b0bc4247875c929b0434138.jpg%40800w_600h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20"
filename = r'C:\blueprint-face-detection\scripts\mv.jpg' downpic(url, filename) 结果:
Start Down http://p1.meituan.net/wedding/d4c375416ab70b8b0bc4247875c929b0434138.jpg%40800w_600h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20
0.00%
7.01%
14.02%
21.03%
28.04%
35.05%
42.06%
49.07%
56.08%
63.09%
70.10%
77.11%
84.12%
91.13%
98.14%
100.00%
download picture used time 0.165558 seconds

结论: 网速较快时下载完全没问题, 仅用零点几秒。

  如果把图片下载速度降低, 那下载完图片的整体时间会拉长, 如果网速特别低,那下载完图片的时候会无限延长。

   那试试在限制网速的情况下, 下载图片会出现什么现象呢, 如结论所说的那样吗?

2、使用charles限制网速, 设置为下载1k/s, 上传10k/s

  1、设置一下charles吧

  

  

  2、设置socket的默认超时时间为10秒


#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __author__:kzg import datetime
import os
import socket
from urllib.request import urlretrieve # 函数运行计时器
def count_time(func):
def int_time(*args, **kwargs):
start_time = datetime.datetime.now() # 程序开始时间
func(*args, **kwargs)
over_time = datetime.datetime.now() # 程序结束时间
total_time = (over_time-start_time).total_seconds()
print("download picture used time %s seconds" % total_time)
return int_time # 下载回调函数, 计算下载进度
def callbackinfo(down, block, size):
'''
回调函数:
down:已经下载的数据块
block:数据块的大小
size:远程文件的大小
'''
per = 100.0 * (down * block) / size
if per > 100:
per = 100
print('%.2f%%' % per) # 图片下载
@count_time
def downpic(url, filename):
try:
print("Start Down %s" % url)
if os.path.exists(filename):
os.remove(filename)
urlretrieve(url, filename, callbackinfo)
except socket.timeout as ex:
print(ex) url = "http://p1.meituan.net/wedding/d4c375416ab70b8b0bc4247875c929b0434138.jpg%40800w_600h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20"
filename = r'C:\blueprint-face-detection\scripts\mv.jpg' # 设置10秒超时
socket.setdefaulttimeout(10)
downpic(url, filename)

结果:
Start Down http://p1.meituan.net/wedding/d4c375416ab70b8b0bc4247875c929b0434138.jpg%40800w_600h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20
timed out
download picture used time 10.013625 seconds

  结论: 正如期望的那样, 10秒种后socket超时报出错误,urlretrieve下载过程被迫中断,进而捕获错误信息打印出来。

又重试了3次, 每次都好使,  好高兴 ^_^

那再猜想一下, 如果把网速提高一些呢, 应该更没问题了!!!, 下载速度设置为10k/s, 上传10k/s 。

看图:

运行一下:

  完啦, 擦擦擦, 这是什么情况, 网速提高了, 10秒钟早过了, 按说下载应该结束才对, 这都过半分钟了咋还在运行呢?

经过多次尝试发现下载速度超过4k/s时, socket设置defaulttime好像就不起作用了, 图片下载不能被中断, 由此可见想用此方法在任何情况下完全做到强制中断图片下载是不行的。

那咱办呀,要不要在一颗树上吊死呀,那就个换个方法试试呗。

方法二、线程

  一说到线程, 想到最多的可能就是父子线程了,主线程结束子线程也跟着结束,听着感觉很完美, 这个咱也会呀,啥不说了, 上代码看看效果吧。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __author__:kzg import threading
import os, datetime, time
from urllib.request import urlretrieve # 回调函数
def callbackinfo(down, block, size):
'''
回调函数:
down:已经下载的数据块
block:数据块的大小
size:远程文件的大小
'''
per = 100.0 * (down * block) / size
if per > 100:
per = 100
print('%.2f%%' % per) # 计时器
def count_time(func):
def int_time(*args, **kwargs):
start_time = datetime.datetime.now() # 程序开始时间
func(*args, **kwargs)
over_time = datetime.datetime.now() # 程序结束时间
total_time = (over_time-start_time).total_seconds()
print("download picture used time %s seconds" % total_time)
return int_time # 图片下载函数
@count_time
def downpic(url):
print("Start Down %s" % url)
if os.path.exists(filename):
os.remove(filename)
urlretrieve(url, filename, callbackinfo) # 子线程
def mainFunc(funcname, args):
t = threading.Thread(target=funcname, args=(args,))
t.start()
t.join(10) # 子线程阻塞10秒 url = "http://p1.meituan.net/wedding/d4c375416ab70b8b0bc4247875c929b0434138.jpg%40800w_600h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20"
filename = r'C:\blueprint-face-detection\scripts\mv.jpg' # 主线程
m = threading.Thread(target=mainFunc, args=(downpic, url))
m.setDaemon(True)
m.start()
m.join() # 10秒后取消子线程阻塞, 主线程不阻塞立即运行后面程序
print("m is alive? ", m.is_alive())
time.sleep(10)
os.remove(filename)

将下载速度分别限制在1k/st和10k/s,结果如下

结论: 主线程已经结束, 但子线程并未结束, 图片下载在持续中, 直到整个程序结束后, 下载被中断。所以通过父线程结束子线程也结束的方法来强制图片中断下载是不可行的。

这下可惨啦, 没办法实现了吗? 于是乎在网上找呀找, 终于看到有人也遇到了这类问题, 怎么解决呢?  。。。。 强制中断线程, 听起来很霸气,那咱也跟着试试吧, 上代码。

#!/usr/bin/env python3
# -*- coding:utf- -*-
# __author__:kzg import threading
import os, datetime, time
import ctypes
import inspect
from urllib.request import urlretrieve # 回调函数
def callbackinfo(down, block, size):
'''
回调函数:
down:已经下载的数据块
block:数据块的大小
size:远程文件的大小
'''
per = 100.0 * (down * block) / size
if per > :
per =
print('%.2f%%' % per) # 计时器
def count_time(func):
def int_time(*args, **kwargs):
start_time = datetime.datetime.now() # 程序开始时间
func(*args, **kwargs)
over_time = datetime.datetime.now() # 程序结束时间
total_time = (over_time-start_time).total_seconds()
print("download picture used time %s seconds" % total_time)
return int_time # 图片下载函数
@count_time
def downpic(url):
print("Start Down %s" % url)
if os.path.exists(filename):
os.remove(filename)
urlretrieve(url, filename, callbackinfo) def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == :
raise ValueError("invalid thread id")
elif res != :
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed") def stop_thread(thread):
_async_raise(thread.ident, SystemExit) url = "http://p1.meituan.net/wedding/d4c375416ab70b8b0bc4247875c929b0434138.jpg%40800w_600h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20"
filename = r'C:\blueprint-face-detection\scripts\mv.jpg' # 开启线程下载图片
t = threading.Thread(target=downpic, args=(url,))
t.start()
t.join() # 强制结束线程
if t.is_alive():
stop_thread(t) time.sleep()
print("thread is alive?", t.is_alive())
os.remove(filename)

结论:

  下载速度设置为1k/s, 8k/s, 10k/s, 15k/s时, 结果如下,跟预期结果一致(重试几次结果一致)

  下载速度设置为3k/s,12k/s时结果如下(重试几次结果一致)

  似乎看出点什么了吧, 为什么同一个程序, 不同网速下执行结果不一致呢?因实现网速我们是无法控制的,所以此方法大部分情况下都是可以的, 但也会出现例外。

  python下载图片时, 每次下载8k, 下载完就会写入一次文件。

方法三、 request

  timeout 仅对连接过程有效,与响应体的下载无关。 timeout 并不是整个下载响应的时间限制,而是如果服务器在 timeout 秒内没有应答,将会引发一个异常(更精确地说,是在 timeout 秒内没有从基础套接字上接收到任何字节的数据)。

  换句话说: 如果在timeout时间内没有返回任何字节数据, 则request会报错退出, 如果在timeout时间内收到了任何字节的数据, 则request会一直下载, 直到响应体被下载完。

  1、正常网速下的现象

# -*- coding: utf-8 -*-
import requests
import datetime url = "http://p1.meituan.net/wedding/d4c375416ab70b8b0bc4247875c929b0434138.jpg%40800w_600h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20"
filename = r'C:\blueprint-face-detection\scripts\meinv.jpg' # 计时器
def count_time(func):
def int_time(*args, **kwargs):
start_time = datetime.datetime.now() # 程序开始时间
func(*args, **kwargs)
over_time = datetime.datetime.now() # 程序结束时间
total_time = (over_time-start_time).total_seconds()
print("download picture used time %s seconds" % total_time)
return int_time @count_time
def downpic(url):
ret = requests.get(url)
print("req status is: ", ret.status_code)
with open(filename, 'wb') as fp:
print("start write picture..")
fp.write(ret.content) downpic(url) 结果:

req status is: 200
start write picture..
download picture used time 0.26226 seconds

  结论: 正常网速下, request请求成功了, 美女0.3秒被成功请出。

  1、将网速限制在100k/s,结果如何呢 ?

结论: 下载速度为100k/s时, request也成功了, 美女经过15.6秒被成功请出来。

  2. 将网速限制在50k/s,结果如何呢?

结论: 下载速度为50k/s时, request也成功了, 美女经过40.9秒被成功请出来。 设置timeout=3, 是否会生效呢?

感觉要绝望了,咋整呀? 没有一种方法可以及时中断下载吗?经过不断的尝试终于找到了一个可行的方法。

  函数中断 !!! 如果超过设定时间则正在下载的函数被中断, 试一下吧。

# #!/usr/bin/python3
# # -*- coding: utf- -*-
import requests
import datetime
from func_timeout import func_set_timeout, FunctionTimedOut url = "http://pic1.win4000.com/wallpaper/5/58b3b56d0a0a7.jpg" @func_set_timeout()
def downpic(url):
ret = requests.get(url)
print("Start Down %s" % url)
if ret.status_code == :
with open('meinv.jpg', 'wb') as fp:
fp.write(ret.content)
print("downLoad pic ok %s" % url) print(datetime.datetime.now())
downpic(url)
print(datetime.datetime.now())

运行一下程序, 正常网速时下载一张图片的时间是多少 !

可以看出大概8秒时间可以下载完一张图片, 那我将时间设置为5秒, 那图片下载会被中断,肯定下不完。

正如预料的那样, 5秒钟后图片下载被中断了, 经过多次检查, 图片并没有继续下载, 证明图片下载被中断成功。

哈……

  

python下载图片超时的调查的更多相关文章

  1. Python下载图片并保存本地的两种方式

    一:使用Python中的urllib类中的urlretrieve()函数,直接从网上下载资源到本地,具体代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ...

  2. python下载图片(2)

    #-*- coding: UTF-8 -*- import urllib2, re,datetime,time, os,sys from PIL import Image, ImageDraw, Im ...

  3. python 下载图片的方法

    a='http://wx1.sinaimg.cn/mw600/006HOayNgy1fqjdi2nxohj32pw3o8x6s.jpg'  #图片下载地址   ( 这里改成 文件txt地址)w='/U ...

  4. 使用python下载图片(福利)

    刚学python 没多久, 代码处处是漏洞,也希望各位大佬理解一下 爬出来的图片... 使用的 是 https://www.tianapi.com/  接口下的 美女图片... (需要自己注册一个账号 ...

  5. Python下载图片小程序

    欢迎大侠们指正批评 思路: 1.引入相关的python文件(import re  import urllib) 2.读取对应网页的html文件(使用 urllib) def getHtml(url): ...

  6. Python 下载图片的几种方法

    import osos.makedirs('./image/', exist_ok=True)IMAGE_URL = "http://image.nationalgeographic.com ...

  7. python下载图片(3)

    # -*- coding: utf-8 -*-"""some function by metaphy,2007-04-03,copyleftversion 0.2&quo ...

  8. python下载图片

    import re import  urllib.request   def getHtml(url): page = urllib.request.urlopen(url) html = page. ...

  9. python 下载图片

    import requests from PIL import Image from io import BytesIO url = 'http://image2.buslive.cn/shp/upl ...

随机推荐

  1. Python之TensorFlow的(案例)验证码识别-6

    一.这里的案例相对比较简单,主要就是通过学习验证码的识别来认识深度学习中我们一般在工作中,需要处理的东西会存在哪些东西. 二.因为我没有数据集,没有关系,这里自己写了一个数据集,来做测试,为了方便我把 ...

  2. python3--说简单也不简单的排序算法

    在刚开始接触算法时,我们可能一脸懵,不知从何处下手,尤其是现在使用的语言五花八门,各种语言的实现又不尽相同,所以,在这种情况下,千万不能迷失了自己,掌握了算法的原理,就像解数学公式一样,定理给你了,仔 ...

  3. Java字节流文件复制

    1.字节流 在 Java 中,文件的复制使用字节输入流和字节输出流实现,java.io 包有 InputStream 和 OutputStream 这两个顶层抽象类规范了读写文件所需的核心 API. ...

  4. vue初级使用

    一.Vue是什么? Vue(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的渐进式框架.采用自底向上增量开发的设计.Vue.js 的目标是通过尽可能简单的 API 实现响应 ...

  5. mysql 安装与基本管理

    目录 mysql 安装与基本管理 MySQL介绍 下载安装 登录设置密码 破解密码 统一字符编码 mysql参数配置 mysql 常用指令 mysql 安装与基本管理 MySQL介绍 MySQL是一个 ...

  6. JS 中Json常用操作

    转自: https://www.jianshu.com/p/6501b0f3124f 直接定义json var json = {"name": "小明", &q ...

  7. iptables详细介绍

    iptables简介 netfilter/iptables(简称为iptables)组成Linux平台下的包过滤防火墙,与大多数的Linux软件一样,这个包过滤防火墙是免费的,它可以代替昂贵的商业防火 ...

  8. git 放弃merge 回到上一次commit

    用git reset --hard  放弃正在合并中merge,返回上一次的commit

  9. pip 和pip3的区别

    前言装完python3后发现库里面既有pip也有pip3,不知道它们的区别,因此特意去了解了一下. 解释先搜索了一下看到了如下的解释, 安装了python3之后,库里面既会有pip3也会有pip 1. ...

  10. HTML&CSS基础-外边框

    HTML&CSS基础-外边框  作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.HTML <!DOCTYPE html> <html> <h ...