Pillow
PIL:Python Imaging Library,
已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。
PIL
仅支持到Python 2.7,在Python 3.x版本,其被替换为Pillow
。
这里使用的Anaconda,内置了Pillow
.
操作图像
最常见的图像缩放操作,只需三四行代码:
#尽管变成了pillow,但是导入的时候依然是导入PIL
from PIL import Image
# 打开一个jpg图像文件,注意是当前路径:
im = Image.open('test.jpg')
# 获得图像尺寸:
w, h = im.size
print('Original image size: %sx%s' % (w, h))
# 缩放到50%:
im.thumbnail((w//2, h//2))
print('Resize image to: %sx%s' % (w//2, h//2))
# 把缩放后的图像用jpeg格式保存:
im.save('thumbnail.jpg', 'jpeg')
其他功能如切片、旋转、滤镜、输出文字、调色板等一应俱全。
比如,模糊效果也只需几行代码:
from PIL import Image, ImageFilter
im = Image.open(r'e:\web\tt\3.jpg')
w, h = im.size
print('Original image size is: %s %s' % (w,h))
im.thumbnail((w//2,h//2))
print('Resize image to: %sx%s' % (w//2, h//2))
# 应用模糊滤镜:
im2 = im.filter(ImageFilter.BLUR)
im.save('thumbnail.jpg', 'jpeg')
效果:
PIL的ImageDraw
提供了一系列绘图方法,让我们可以直接绘图。比如要生成字母验证码图片:
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random
# 随机字母:
def rndChar():
return chr(random.randint(65, 90))
# 随机颜色1:
def rndColor():
return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))
# 随机颜色2:
def rndColor2():
return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
# 240 x 60:
width = 60 * 4
height = 60
image = Image.new('RGB', (width, height), (255, 255, 255))
# 创建Font对象:
font = ImageFont.truetype('C:/Windows/Fonts/Arial.ttf', 36)#用绝对路径
# 创建Draw对象:
draw = ImageDraw.Draw(image)
# 填充每个像素:
for x in range(width):
for y in range(height):
draw.point((x, y), fill=rndColor())
# 输出文字:
# #在新建的对象 上坐标(60 * t + 10, 10)处开始画出文本
for t in range(4):
draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2())
# 模糊:
image = image.filter(ImageFilter.BLUR)
image.save('code.jpg', 'jpeg')
'如果要生成字母加数字的验证码'
def rndchar_num():
if random.randint(1,1000) % 2 ==0:
return str(random.randint(0,9))
else:
return chr(random.randint(65,90))
要详细了解PIL的强大功能,请请参考Pillow官方文档:https://pillow.readthedocs.org/
requests
我们已经讲解了Python内置的urllib
模块,用于访问网络资源。但是,它用起来比较麻烦,而且,缺少很多实用的高级功能。
更好的方案是使用requests
。它是一个Python第三方库,处理URL资源特别方便。
如果不使用anaconda,就需要在命令行安装:
$ pip install requests
使用requests
要通过GET
访问一个页面,只需要几行代码:
import requests
r = requests.get('https://www.baidu.com/')
print(r.status_code)
print(r.text)
#结果:
200
<!DOCTYPE html>
<!--STATUS OK--><html> <head>......
对于带参数的URL
,传入一个dict
作为params
参数:
import requests
r = requests.get('https://www.douban.com/search', params={'q': 'python', 'cat': '1001'})
print(r.url)
#结果:
https://www.douban.com/search?q=python&cat=1001
requests
自动检测编码,可以使用encoding
属性查看:
>>> r.encoding
'utf-8'
无论响应是文本还是二进制内容,我们都可以用content
属性获得bytes
对象:
>>> r.content
b'<!DOCTYPE html>\n<html>\n<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n...'
requests
的方便之处还在于,对于特定类型的响应,例如JSON
,可以直接获取:
>>> r = requests.get('https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20%3D%202151330&format=json')
>>> r.json()
{'query': {'count': 1, 'created': '2017-11-17T07:14:12Z', .
需要传入HTTP Header
时,我们传入一个dict
作为headers
参数:
>>> r = requests.get('https://www.douban.com/', headers={'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'})
>>> r.text
'<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n <title>豆瓣(手机版)</title>...'
要发送POST
请求,只需要把get()
方法变成post()
,然后传入data参数
作为POST
请求的数据:
import requests
r = requests.post('https://accounts.douban.com/login', data={'form_email': 'abc@example.com', 'form_password': '123456'})
requests
默认使用application/x-www-form-urlencoded
对POST
数据编码。如果要传递JSON
数据,可以直接传入json
参数:
params = {'key': 'value'}
r = requests.post(url, json=params) # 内部自动序列化为JSON
类似的,上传文件需要更复杂的编码格式,但是requests
把它简化成files
参数:
>>> upload_files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=upload_files)
在读取文件时,注意务必使用’rb’即二进制模式读取,这样获取的bytes长度才是文件的长度。
把post()
方法替换为put(),delete()
等,就可以以PUT
或DELETE
方式请求资源。
除了能轻松获取响应内容外,requests
对获取HTTP响应的其他信息也非常简单
。例如,获取响应头:
>>> r.headers
{Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Content-Encoding': 'gzip', ...}
>>> r.headers['Content-Type']
'text/html; charset=utf-8'
requests
对Cookie
做了特殊处理,使得我们不必解析Cookie
就可以轻松获取指定的Cookie:
>>> r.cookies['ts']
'example_cookie_12345'
要在请求中传入Cookie
,只需准备一个dict传入cookies
参数:
>>> cs = {'token': '12345', 'status': 'working'}
>>> r = requests.get(url, cookies=cs)
最后,要指定超时,传入以秒为单位的timeout
参数:
r = requests.get(url, timeout=2.5) # 2.5秒后超时
'廖雪峰大大教程上一个大佬写的:有时间看一下:1个进程分析图片地址; 2个进程8线程下载图片; 1个进程4线程解决第一次下载失败的图片; 主进程的主线程实现程序监听,判断是否终止系统,'
import requests,json,os,threading,time,logging
from bs4 import BeautifulSoup
from multiprocessing import Process,Queue
from multiprocessing.queues import Empty
from urllib.request import urlretrieve
logging.basicConfig(level=logging.ERROR,filename='failed_img.log')
# 抓取地址
answers_url='https://www.zhihu.com/api/v4/questions/29815334/answers?data[*].author.follower_count%2Cbadge[*].topics=&data[*].mark_infos[*].url=&include=data[*].is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cis_labeled&limit=5&offset=0&platform=desktop&sort_by=default'
# 待下载图片队列
img_queue = Queue()
# 下载图片失败队列
bad_queue = Queue()
# 请求头
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0',
'Host':'www.zhihu.com'
}
'''
请求知乎回答数据
:param answers_url: 知乎问题的第一个回答请求地址
:param imgq: 待下载图片队列
:param count: 递归结束标识,用于判断是否最后一页
:return:
'''
def getR(answers_url, imgq, count=0):
while True:
try:
r = requests.get(answers_url, headers=headers)
except BaseException:
continue
else:
rj = str(r.json()).replace("True", "\"true\"").replace("False", "\"false\"")
rj = json.loads(json.dumps(eval(rj)))
for x in rj['data']:
content = x['content']
soup = BeautifulSoup(content, 'lxml')
# 查找img标签并且class属性等于origin_image zh-lightbox-thumb的元素
for img in soup.find_all('img', attrs={'class': 'origin_image zh-lightbox-thumb'}):
# 保存图片到img_queue队列
imgq.put(img['data-original'])
print('保存%s到img_queue' % os.path.split(img['data-original'])[1])
if rj['paging']['is_end'] != 'true':
answers_url = rj['paging']['next']
else:
print('request_pro 执行完毕')
# 最后一页
break
'''
下载进程:分别创建四个线程,用于下载图片
:param imgq: 待下载图片队列
:param badq: 下载图片失败队列
:param pname: 进程名称
:return:
'''
def download_pro(imgq,badq,pname):
# 这里设置name可用于区分是哪个进程的哪个线程
t1 = threading.Thread(target=download,args=(imgq,badq),name='%s - 下载线程1' % pname)
t2 = threading.Thread(target=download,args=(imgq,badq),name='%s - 下载线程2' % pname)
t3 = threading.Thread(target=download,args=(imgq,badq),name='%s - 下载线程3' % pname)
t4 = threading.Thread(target=download,args=(imgq,badq),name='%s - 下载线程4' % pname)
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()
'''
下载图片函数
:param imgq: 待下载图片队列
:param badq: 下载图片失败队列
:return:
'''
def download(imgq,badq):
thread_name = threading.current_thread().name
while True:
try:
# 设置timeout=10,10秒后队列都拿取不到数据,那么数据已经下载完毕
c = imgq.get(True, timeout=10)
name = os.path.split(c)[1]
except Empty:
print('%s 执行完毕' % thread_name)
# 退出循环
break
else:
imgcontent = requests.get(c)
''' 图片请求失败,将图片地址放入bad_queue,等待bad_pro处理'''
if imgcontent.status_code != 200:
print('%s 下载%s失败' % (thread_name,name))
badq.put(c)
continue
with open(r'F:\img\%s' % name, 'wb') as f:
f.write(imgcontent.content)
print('%s 下载%s成功' % (threading.current_thread().name, name))
'''
创建四个线程用于尝试bad_queue队列中的图片,
:param badp: bad_queue
:return:
'''
def again_pro(badq):
b1 = threading.Thread(target=again_download, args=(badq,),name='重下线程1')
b2 = threading.Thread(target=again_download, args=(badq,),name='重下线程2')
b3 = threading.Thread(target=again_download, args=(badq,),name='重下线程3')
b4 = threading.Thread(target=again_download, args=(badq,),name='重下线程4')
b1.start()
b2.start()
b3.start()
b4.start()
'''
尝试重复下载bad_queue队列中的图片
:param badq: bad_queue
:return:
'''
def again_download(badq):
while True:
c = badq.get(True)
name = os.path.split(c)[1]
# 尝试重复下载5次,如果5次均下载失败,记录到错误日志中
for x in range(5):
try:
# 个人感觉使用urlretrieve下载的成功率要高些,哈哈^_^
urlretrieve(c,r'F:\img\%s'%name)
except BaseException as e:
if x==4:
logging.error('%s 重复下载失败,error:[%s]'% (c,repr(e)))
# 下载失败; 再次尝试
continue
else:
# 下载成功;跳出循环
print('%s 在第%s次下载%s成功' % (threading.current_thread().name,(x+1),name))
break
'''
监听器,用于关闭程序,10秒刷新一次
:param imgq: img_queue
:param badq: bad_queue
:return:
'''
def monitor(imgq,badq):
while True:
time.sleep(10)
if imgq.empty() and badq.empty():
print('所有任务执行完毕,40秒后系统关闭')
count = 39
while count > 0:
time.sleep(1)
print('距离系统关闭还剩:%s秒'%count)
count-=1
break
if __name__=='__main__':
# 请求数据进程
request_pro = Process(target=getR, args=(answers_url, img_queue))
''' 下载图片进程[first]和[second] '''
download_pro_first = Process(target=download_pro, args=(img_queue,bad_queue,'download process first'))
download_pro_second = Process(target=download_pro, args=(img_queue,bad_queue,'download process second'))
# 处理[first]和[second]进程下载失败的图片
bad_pro = Process(target=again_pro,args=(bad_queue,))
bad_pro.daemon = True
# req_pro进程启动
request_pro.start()
print('3秒之后开始下载 >>>>>>>>>>>>>>>')
time.sleep(3)
# first 和 second 进程启动
download_pro_first.start()
download_pro_second.start()
# 重复下载进程启动
bad_pro.start()
# 监听线程:判断系统是否退出
request_pro.join()
print('监听线程启动 >>>>>>>>>>>>>>>')
sys_close_t = threading.Thread(target=monitor,args=(img_queue,bad_queue,))
sys_close_t.start()