Python基础+刷廖雪峰教程笔记(七)——Pillow、requests、chardet、psutil、virtualenv、图形界面、TCP、UDP、SMTP、POP3

由于在深度学习的路上,发现自己两年前学习的python有些遗忘,在面向对象这一块尤其不熟悉,故刷一遍廖雪峰老师的官方教程,梳理一下遗漏的知识点。

参考网址:https://www.liaoxuefeng.com/wiki/1016959663602400

1.Pillow

PIL:Python Imaging Library,已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。

Pillow,支持最新Python 3.x,又加入了许多新特性,因此,我们可以直接安装使用Pillow。

demo1:

from PIL import Image, ImageFilter

# 打开一个jpg图像文件,注意是当前路径:
im = Image.open('C:\\Users\\DENGFY\\Desktop\\cat.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('C:\\Users\\DENGFY\\Desktop\\thumbnail.jpg', 'jpeg')

# 应用模糊滤镜:
im2 = im.filter(ImageFilter.BLUR)
im2.save('C:\\Users\\DENGFY\\Desktop\\blur.jpg', 'jpeg')

out1:
【缩小后】
在这里插入图片描述
【模糊后】
在这里插入图片描述
PIL的ImageDraw提供了一系列绘图方法,让我们可以直接绘图。比如要生成随机字母验证码图片:

demo2:

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('arial.ttf', 36)
# 创建Draw对象:
draw = ImageDraw.Draw(image)
# 填充每个像素:
for x in range(width):
    for y in range(height):
        draw.point((x, y), fill=rndColor())
# 输出文字:
for t in range(4):
    draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2())
# 模糊:
image = image.filter(ImageFilter.BLUR)
image.save('C:\\Users\\DENGFY\\Desktop\\code.jpg', 'jpeg')

out2:
在这里插入图片描述
2.requests

Python内置的urllib模块,用于访问网络资源。但是,它用起来比较麻烦,而且,缺少很多实用的高级功能。

更好的方案是使用requests。它是一个Python第三方库,处理URL资源特别方便。

demo3:

import requests
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
url = 'https://www.douban.com/'
r = requests.get(url, headers=headers) #网站基本有安全验证
print(r.status_code)
#print(r.text),打印网站内容

r1 = requests.get('https://www.douban.com/search', params={'q': 'python', 'cat': '1001'})#带参数的URL,传入一个dict作为params参数
print(r1.url) # 实际请求的URL

r1.encoding #requests自动检测编码,可以使用encoding属性查看

#print(r.content)  无论响应是文本还是二进制内容,我们都可以用content属性获得bytes对象:

#要发送POST请求,只需要把get()方法变成post(),然后传入data参数作为POST请求的数据:
r2 = 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'}
r3 = requests.post(url, json=params) # 内部自动序列化为JSON

'''
#类似的,上传文件需要更复杂的编码格式,但是requests把它简化成files参数:
upload_files = {'file': open('report.xls', 'rb')}
r = requests.post(url, files=upload_files)
'''

#获取响应头
print(r.headers)
print(r.headers['Content-Type'])

#要在请求中传入Cookie,只需准备一个dict传入cookies参数
cs = {'token': '12345', 'status': 'working'}
r = requests.get(url, cookies=cs)

#最后,要指定超时,传入以秒为单位的timeout参数:
r = requests.get(url, timeout=2.5) # 2.5秒后超时

out3:

200
https://www.douban.com/search?q=python&cat=1001
{'Date': 'Mon, 12 Jul 2021 07:07:16 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=30', 'Vary': 'Accept-Encoding, Accept-Encoding', 'X-Xss-Protection': '1; mode=block', 'X-Douban-Mobileapp': '0', 'Expires': 'Sun, 1 Jan 2006 01:00:00 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'must-revalidate, no-cache, private', 'Set-Cookie': 'll="118331"; path=/; domain=.douban.com; expires=Tue, 12-Jul-2022 07:07:16 GMT, bid=mSqxb78ak4Y; Expires=Tue, 12-Jul-22 07:07:16 GMT; Domain=.douban.com; Path=/', 'X-DAE-App': 'sns', 'X-DAE-Instance': 'home', 'X-DAE-Mountpoint': 'True', 'X-DOUBAN-NEWBID': 'mSqxb78ak4Y', 'Server': 'dae', 'X-Frame-Options': 'SAMEORIGIN', 'Strict-Transport-Security': 'max-age=15552000;', 'Content-Encoding': 'gzip'}
text/html; charset=utf-8

在读取文件时,注意务必使用’rb’即二进制模式读取,这样获取的bytes长度才是文件的长度。

把post()方法替换为put(),delete()等,就可以以PUT或DELETE方式请求资源。

除了能轻松获取响应内容外,requests对获取HTTP响应的其他信息也非常简单。

3.chardet

chardet这个第三方库用来检测编码,简单易用.

demo4:

import chardet
print(chardet.detect(b'Hello, world!')) #confidence字段,表示检测的概率是1.0(即100%)

#检测GBK编码的中文
data = '离离原上草,一岁一枯荣'.encode('gbk')
print(chardet.detect(data)) #检测的编码是GB2312,注意到GBK是GB2312的超集,两者是同一种编码,检测正确的概率是74%,language字段指出的语言是'Chinese'

#对UTF-8编码进行检测
data = '离离原上草,一岁一枯荣'.encode('utf-8')
print(chardet.detect(data))

#对日文进行检测
data = '最新の主要ニュース'.encode('euc-jp')
print(chardet.detect(data))

out4:

{'encoding': 'ascii', 'confidence': 1.0, 'language': ''}
{'encoding': 'GB2312', 'confidence': 0.7407407407407407, 'language': 'Chinese'}
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}
{'encoding': 'EUC-JP', 'confidence': 0.99, 'language': 'Japanese'}

4.psutil

用Python来编写脚本简化日常的运维工作是Python的一个重要用途。

在Python中获取系统信息的一个好办法是使用psutil这个第三方模块。顾名思义,psutil = process and system utilities,它不仅可以通过一两行代码实现系统监控,还可以跨平台使用,支持Linux/UNIX/OSX/Windows等,是系统管理员和运维小伙伴不可或缺的必备模块。

demo5:

import psutil

#获取CPU信息
print(psutil.cpu_count()) # CPU逻辑数量
print(psutil.cpu_count(logical=False)) # CPU物理核心, 8是8核非超线程

#统计CPU的用户/系统/空闲时间
print(psutil.cpu_times())

#实现类似top命令的CPU使用率,每秒刷新一次,累计10次
for x in range(10):
    print(psutil.cpu_percent(interval=1, percpu=True))

#获取内存信息
#使用psutil获取物理内存和交换内存信息,分别使用
print(psutil.virtual_memory())
print(psutil.swap_memory())

#获取磁盘信息
#可以通过psutil获取磁盘分区、磁盘使用率和磁盘IO信息
print(psutil.disk_partitions())# 磁盘分区信息
print(psutil.disk_usage('/')) # 磁盘使用情况
print(psutil.disk_io_counters()) # 磁盘IO

#获取网络信息
#psutil可以获取网络接口和网络连接信息:
print(psutil.net_io_counters()) # 获取网络读写字节/包的个数
print(psutil.net_if_addrs()) # 获取网络接口信息
print(psutil.net_if_stats()) # 获取网络接口状态
print(psutil.net_connections())  #获取当前网络连接信息

#获取进程信息
#通过psutil可以获取到所有进程的详细信息:

print(psutil.pids()) # 所有进程ID
p = psutil.Process(1444) # 获取指定进程ID=1444,其实就是当前Python交互环境
print(p.name()) # 进程名称
print(p.exe()) # 进程exe路径

out5:

16
8
#这里输出很长,已隐去
svchost.exe
C:\Windows\System32\svchost.exe

5.virtualenv

virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。

参考这里

6.图形界面

Tkinter

编写的Python代码会调用内置的Tkinter,Tkinter封装了访问Tk的接口;

Tk是一个图形库,支持多个操作系统,使用Tcl语言开发;

Tk会调用操作系统提供的本地GUI接口,完成最终的GUI。

所以,我们的代码只需要调用Tkinter提供的接口就可以了。

demo6:

#第一步是导入Tkinter包的所有内容
from tkinter import *
import tkinter.messagebox as messagebox

#第二步是从Frame派生一个Application类,这是所有Widget的父容器
class Application(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
        self.nameInput = Entry(self)
        self.nameInput.pack()
        self.alertButton = Button(self, text='Hello', command=self.hello)
        self.alertButton.pack()

    def hello(self):
        name = self.nameInput.get() or 'world'
        messagebox.showinfo('Message', 'Hello, %s' % name)

#第三步,实例化Application,并启动消息循环
app = Application()
# 设置窗口标题:
app.master.title('Hello World')
# 主消息循环:
app.mainloop()

out6:
在这里插入图片描述
在这里插入图片描述

在GUI中,每个Button、Label、输入框等,都是一个Widget。Frame则是可以容纳其他Widget的Widget,所有的Widget组合起来就是一棵树。

pack()方法把Widget加入到父容器中,并实现布局。pack()是最简单的布局,grid()可以实现更复杂的布局。

在createWidgets()方法中,我们创建一个Label和一个Button,当Button被点击时,触发self.quit()使程序退出。

GUI程序的主线程负责监听来自操作系统的消息,并依次处理每一条消息。因此,如果消息处理非常耗时,就需要在新线程中处理。

当用户点击按钮时,触发hello(),通过self.nameInput.get()获得用户输入的文本后,使用tkMessageBox.showinfo()可以弹出消息对话框。

7.TCP编程

Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。

客户端

大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。

举个例子,当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。

客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。

demo7:

# 导入socket库:
import socket

# 创建一个socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。SOCK_STREAM指定使用面向流的TCP协议
# 建立连接:
s.connect(('www.sina.com.cn', 80))#新浪提供网页服务的服务器必须把端口号固定在80端口,因为80端口是Web服务的标准端口,参数是一个tuple

# 发送数据,向新浪服务器发送请求,要求返回首页的内容
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')

# 接收数据:
#接收数据时,调用recv(max)方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。
buffer = []
while True:
    # 每次最多接收1k字节:
    d = s.recv(1024)
    if d:
        buffer.append(d)
    else:
        break
data = b''.join(buffer)

# 关闭连接:
s.close()

#接收到的数据包括HTTP头和网页本身,我们只需要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的数据写入文件:
with open('C:\\Users\\DENGFY\\Desktop\\sina.html', 'wb') as f:
    f.write(html)

out7:

HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Mon, 12 Jul 2021 08:30:27 GMT
Content-Type: text/html
Content-Length: 138
Connection: close
Location: https://www.sina.com.cn/
X-Via-CDN: f=edge,s=cmcc.chongqing.union.171.nb.sinaedge.com,c=183.222.32.25;

现在,只需要在浏览器中打开这个sina.html文件,就可以看到新浪的首页了。

服务器

和客户端编程相比,服务器编程就要复杂一些。

服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。

我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello再发回去。

服务器程序:

# 导入socket库:
import socket
import time, threading

#首先,创建一个基于IPv4和TCP协议的Socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

''''
然后,我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,
还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。
端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用9999这个端口号。请注意,小于1024的端口号必须要有管理员权限才能绑定:
'''

# 监听端口:
s.bind(('127.0.0.1', 9999))

#紧接着,调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量:
s.listen(5)
print('Waiting for connection...')


#每个连接都必须创建新线程(或进程)来处理,否则,单线程在处理连接的过程中,无法接受其他客户端的连接:
def tcplink(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Welcome!')
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
    sock.close()
    print('Connection from %s:%s closed.' % addr)

#利用accept()进行接收新连接并进行处理

#接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接:
while True:
    # 接受一个新连接:
    sock, addr = s.accept()
    # 创建新线程来处理TCP连接:
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()



客户端程序:

#连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。如果客户端发送了exit字符串,就直接关闭连接。
#要测试这个服务器程序,我们还需要编写一个客户端程序:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
    # 发送数据:
    s.send(data)
    print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

运行结果:
在这里插入图片描述
同一个端口,被一个Socket绑定了以后,就不能被别的Socket绑定了。

8.UDP编程

TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

我们来看看如何通过UDP协议传输数据。和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口:

服务器程序:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind(('127.0.0.1', 9999))

#创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。
#绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据
print('Bind UDP on 9999...')
while True:
    # 接收数据:
    data, addr = s.recvfrom(1024)
    print('Received from %s:%s.' % addr)
    s.sendto(b'Hello, %s!' % data, addr)

#recvfrom()方法返回数据和客户端的地址与端口,
#这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端

客户端程序:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Michael', b'Tracy', b'Sarah']:
    # 发送数据:
    s.sendto(data, ('127.0.0.1', 9999))
    # 接收数据:
    print(s.recv(1024).decode('utf-8'))
s.close()

在这里插入图片描述
9.SMTP发送邮件

SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。

Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。

发送纯文本

首先,我们来构造一个最简单的纯文本邮件:

demo8:

import smtplib
import threading
from email.mime.text import MIMEText

#注意到构造MIMEText对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入'plain'表示纯文本,
#最终的MIME就是'text/plain',最后一定要用utf-8编码保证多语言兼容性。
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

# 输入Email地址和口令:
from_addr = input('From: ')
password = input('Password: ')#这里的密码是邮箱的授权码
# 输入收件人地址:
to_addr = input('To: ')
# 输入SMTP服务器地址:
smtp_server = input('SMTP server: ')


server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

我们用set_debuglevel(1)就可以打印出和SMTP服务器交互的所有信息。SMTP协议就是简单的文本命令和响应。login()方法用来登录SMTP服务器,sendmail()方法就是发邮件,由于可以一次发给多个人,所以传入一个list,邮件正文是一个str,as_string()把MIMEText对象变成str。

邮件主题、如何显示发件人、收件人等信息并不是通过SMTP协议发给MTA,而是包含在发给MTA的文本中的,所以,我们必须把From、To和Subject添加到MIMEText中,才是一封完整的邮件:

demo9:

import smtplib
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr

def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))


from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
msg['From'] = _format_addr('元爸 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

发送HTML邮件

如果我们要发送HTML邮件,而不是普通的纯文本文件怎么办?方法很简单,在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html就可以了:

msg = MIMEText('<html><body><h1>Hello</h1>' +
    '<p>send by <a href="http://www.python.org">Python</a>...</p>' +
    '</body></html>', 'html', 'utf-8')

发送附件

如果Email中要加上附件怎么办?带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可:

demo10:

from email import encoders
from email.header import Header
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import parseaddr, formataddr

import smtplib


def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))


from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')

# 邮件对象:
msg = MIMEMultipart()
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

# 邮件正文是MIMEText:
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('C:\\Users\\DENGFY\\Desktop\\cat.jpg', 'rb') as f:
    # 设置附件的MIME和文件名,这里是png类型:
    mime = MIMEBase('image', 'jpg', filename='cat.jpg')
    # 加上必要的头信息:
    mime.add_header('Content-Disposition', 'attachment', filename='test.png')
    mime.add_header('Content-ID', '<0>')
    mime.add_header('X-Attachment-Id', '0')
    # 把附件的内容读进来:
    mime.set_payload(f.read())
    # 用Base64编码:
    encoders.encode_base64(mime)
    # 添加到MIMEMultipart:
    msg.attach(mime)

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

发送图片

如果要把一个图片嵌入到邮件正文中怎么做?直接在HTML邮件中链接图片地址行不行?答案是,大部分邮件服务商都会自动屏蔽带有外链的图片,因为不知道这些链接是否指向恶意网站。

要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML中通过引用src="cid:0"就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。

把上面代码加入MIMEMultipart的MIMEText从plain改为html,然后在适当的位置引用图片:

demo11:

from email import encoders
from email.header import Header
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import parseaddr, formataddr

import smtplib


def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))


from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')

# 邮件对象:
msg = MIMEMultipart()
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

# 邮件正文是MIMEText:,,,修改了该段
msg.attach(MIMEText('<html><body><h1>Hello</h1>' +
    '<p><img src="cid:0"></p>' +
    '</body></html>', 'html', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('C:\\Users\\DENGFY\\Desktop\\cat.jpg', 'rb') as f:
    # 设置附件的MIME和文件名,这里是png类型:
    mime = MIMEBase('image', 'jpg', filename='cat.jpg')
    # 加上必要的头信息:
    mime.add_header('Content-Disposition', 'attachment', filename='test.png')
    mime.add_header('Content-ID', '<0>')
    mime.add_header('X-Attachment-Id', '0')
    # 把附件的内容读进来:
    mime.set_payload(f.read())
    # 用Base64编码:
    encoders.encode_base64(mime)
    # 添加到MIMEMultipart:
    msg.attach(mime)

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

同时支持HTML和Plain格式

如果我们发送HTML邮件,收件人通过浏览器或者Outlook之类的软件是可以正常浏览邮件内容的,但是,如果收件人使用的设备太古老,查看不了HTML邮件怎么办?

办法是在发送HTML的同时再附加一个纯文本,如果收件人无法查看HTML格式的邮件,就可以自动降级查看纯文本邮件。

利用MIMEMultipart就可以组合一个HTML和Plain,要注意指定subtype是alternative:

msg = MIMEMultipart('alternative')
msg['From'] = ...
msg['To'] = ...
msg['Subject'] = ...

msg.attach(MIMEText('hello', 'plain', 'utf-8'))
msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))
# 正常发送msg对象...

加密SMTP

使用标准的25端口连接SMTP服务器时,使用的是明文传输,发送邮件的整个过程可能会被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件。

某些邮件服务商,例如Gmail,提供的SMTP服务必须要加密传输。我们来看看如何通过Gmail提供的安全SMTP发送邮件。

必须知道,Gmail的SMTP端口是587,因此,修改代码如下:

smtp_server = 'smtp.gmail.com'
smtp_port = 587
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
# 剩下的代码和前面的一模一样:
server.set_debuglevel(1)
...

只需要在创建SMTP对象后,立刻调用starttls()方法,就创建了安全连接。后面的代码和前面的发送邮件代码完全一样。

10.POP3收取邮件

收取邮件就是编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或者手机上。收取邮件最常用的协议是POP协议,目前版本号是3,俗称POP3。

Python内置一个poplib模块,实现了POP3协议,可以直接用来收邮件。

注意到POP3协议收取的不是一个已经可以阅读的邮件本身,而是邮件的原始文本,这和SMTP协议很像,SMTP发送的也是经过编码后的一大段文本。

要把POP3收取的文本变成可以阅读的邮件,还需要用email模块提供的各种类来解析原始文本,变成可阅读的邮件对象。

所以,收取邮件分两步:

第一步:用poplib把邮件的原始文本下载到本地;

第二部:用email解析原始文本,还原为邮件对象。

通过POP3下载邮件

demo12:

import poplib
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr

# 输入邮件地址, 口令和POP3服务器地址:
email = input('Email: ')
password = input('Password: ')
pop3_server = input('POP3 server: ')

# 连接到POP3服务器:
server = poplib.POP3(pop3_server)
# 可以打开或关闭调试信息:
server.set_debuglevel(1)
# 可选:打印POP3服务器的欢迎文字:
print(server.getwelcome().decode('utf-8'))

# 身份认证:
server.user(email)
server.pass_(password)

# stat()返回邮件数量和占用空间:
print('Messages: %s. Size: %s' % server.stat())
# list()返回所有邮件的编号:
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]
print(mails)

# 获取最新一封邮件, 注意索引号从1开始:
index = len(mails)
resp, lines, octets = server.retr(index)

# lines存储了邮件的原始文本的每一行,
# 可以获得整个邮件的原始文本:
msg_content = b'\r\n'.join(lines).decode('utf-8')
# 稍后解析出邮件:
msg = Parser().parsestr(msg_content)

# 可以根据邮件索引号直接从服务器删除邮件:
# server.dele(index)
# 关闭连接:
server.quit()

out12:

Email: 617633113@qq.com
Password: xxxxxx
POP3 server: pop.qq.com
+OK XMail POP3 Server v1.0 Service Ready(XMail v1.0)
*cmd* 'USER 617633113@qq.com'
*cmd* 'PASS xxxxxx'
*cmd* 'STAT'
*stat* [b'+OK', b'5', b'34882']
Messages: 5. Size: 34882
*cmd* 'LIST'
[b'1 9671', b'2 4546', b'3 8529', b'4 8529', b'5 3607']
*cmd* 'RETR 5'
*cmd* 'QUIT'

解析邮件

解析邮件的过程和上一节构造邮件正好相反。

demo13:

import poplib
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr


# 邮件的Subject或者Email中包含的名字都是经过编码后的str,要正常显示
# 就必须decode
def decode_str(s):
    # 在不转换字符集的情况下解码消息头值,返回一个list
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value


# 文本邮件的内容也是str,还需要检测编码,
# 否则,非UTF-8编码的邮件都无法正常显示:

def guess_charset(msg):
    print('msg::%s' % msg)
    # 得到字符集
    charset = msg.get_charset()
    print('charset::%s' % charset)
    if charset is None:
        # lower:所有大写字符为小写
        content_type = msg.get('Content-Type', '').lower()
        print('content_type::%s' % content_type)
        # find:检测字符串中是否包含子字符串
        # 返回charset=头字符的位置
        pos = content_type.find('charset=')
        print('pos::', pos)
        if pos >= 0:
            # strip:移除字符串头尾指定的字符(默认为空格)
            charset = content_type[pos + 8:].strip()
    print('charset::%s' % charset)
    return charset


# indent用于缩进显示:
def print_info(msg, indent=0):
    # 初始分析
    if indent == 0:
        # 遍历获取 发件人,收件人,主题
        for header in ['From', 'To', 'Subject']:
            # 获得对应的内容
            value = msg.get(header, '')
            # 有内容
            if value:
                # 如果是主题
                if header == 'Subject':
                    # 解码主题
                    value = decode_str(value)
                else:
                    # parseaddr:解析字符串中的email地址
                    hdr, addr = parseaddr(value)
                    # 解码主题
                    name = decode_str(hdr)
                    # 合成内容
                    value = u'%s <%s>' % (name, addr)
            print('%s%s:%s' % (' ' * indent, header, value))
    # 如果消息由多个部分组成,则返回True。
    if msg.is_multipart():
        # 返回list,包含所有的子对象
        parts = msg.get_payload()
        # enumerate将其组成一个索引序列,利用它可以同时获得索引和值
        for n, part in enumerate(parts):
            # 打印消息模块编号
            print('%s part %s' % (' ' * indent, n))
            # 打印分割符号
            print('%s--------------------' % (' ' * indent))
            # 递归打印
            print_info(part, indent + 1)
    else:
        # 递归结束条件,打印最基本模块
        # 返回消息的内容类型。
        content_type = msg.get_content_type()
        # 如果是text类型或者是html类型
        if content_type == 'text/plain' or content_type == 'text/html':
            # 返回list,包含所有的子对象,开启解码
            content = msg.get_payload(decode=True)
            # 猜测字符集
            charset = guess_charset(msg)
            # 字符集不为空
            if charset:
                # 解密
                content = content.decode(charset)
            # 打印内容
            print('%s Text: %s' % (' ' * indent, content + '...'))
        else:
            print('%s Attachment: %s' % (' ' * indent, content_type))


# # 输入邮件地址,口令和POP3服务器地址
email = input('Email:')
password = input('Password:')
pop3_server = input('POP 3 server:')

# 连接到POP3服务器
server = poplib.POP3_SSL(pop3_server)
# 可以打开或者关闭调试信息:
server.set_debuglevel(1)
# 可选:打印POP3服务器的欢迎文字
print(server.getwelcome().decode('utf-8'))

# 身份认证:
server.user(email)
server.pass_(password)

# stat()返回邮件数量和占用空间:
print('Message:%s.Size:%s' % server.stat())
# list()返回所有邮件的编号:
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]
print(mails)

# 获取最新一封邮件,注意索引号从1开始:
index = len(mails)
resp, lines, octets = server.retr(index)

# lines存储了邮件的原始文本的每一行,
# 可以获得整个邮件的原始文本:
msg_content = b'\r\n'.join(lines).decode('utf-8')
# 稍后解析出邮件:
msg = Parser().parsestr(msg_content)

print_info(msg)
# 可以根据邮件索引号直接从服务器删除邮件:
# server.dele(index)
# 关闭连接:
server.quit()

原教程网址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
刚刚学习Python, 边看廖神的教程边想着搞个离线版,用requests+beautifulsoup抓的。仅供学习用 Python教程 Python简介 安装Python Python解释器 第一个Python程序 使用文本编辑器 Python代码运行助手 输入和输出 Python基础 数据类型和变量 字符串和编码 使用list和tuple 条件判断 循环 使用dict和set 函数 调用函数 定义函数 函数的参数 递归函数 高级特性 切片 迭代 列表生成式 生成器 迭代器 函数式编程 高阶函数 map/reduce filter sorted 返回函数 匿名函数 装饰器 偏函数 模块 使用模块 安装第三方模块 单元测试 文档测试 IO编程 文件读写 StringIO和BytesIO 操作文件和目录 序列化 进程和线程 多进程 多线程 ThreadLocal 进程 vs. 线程 分布式进程 正则表达式 常用内建模块 datetime collections base64 struct hashlib hmac itertools contextlib urllib XML HTMLParser 常用第三方模块 Pillow requests chardet psutil virtualenv 图形界面 网络编程 TCP/IP简介 TCP编程 UDP编程 电子邮件 SMTP发送邮件 POP3收取邮件 访问数据库 使用SQLite 使用MySQL 使用SQLAlchemy Web开发 HTTP协议简介 HTML简介 WSGI接口 使用Web框架 使用模板 异步IO 协程 asyncio async/await aiohttp 实战 file:///E|/Personal/PythonCreep/liaoxuefeng/0_index.html[2018-6-7 下午 08:03:16] Day 1 - 搭建开发环境 Day 2 - 编写Web App骨架 Day 3 - 编写ORM Day 4 - 编写Model Day 5 - 编写Web框架 Day 6 - 编写配置文件 Day 7 - 编写MVC Day 8 - 构建前端 Day 9 - 编写API Day 10 - 用户注册和登录 Day 11 - 编写日志创建页 Day 12 - 编写日志列表页 Day 13 - 提升开发效率 Day 14 - 完成Web App Day 15 - 部署Web App Day 16 - 编写移动App FAQ 期末总结

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值