【Python语言初识(六)】

一、网络编程入门

1.1、TCP/IP模型

实现网络通信的基础是网络通信协议,这些协议通常是由互联网工程任务组 (IETF)制定的。所谓“协议”就是通信计算机双方必须共同遵从的一组约定,例如怎样建立连接、怎样互相识别等,网络协议的三要素是:语法、语义和时序。构成我们今天使用的Internet的基础的是TCP/IP协议族,所谓协议族就是一系列的协议及其构成的通信模型,我们通常也把这套东西称为TCP/IP模型。与国际标准化组织发布的OSI/RM这个七层模型不同,TCP/IP是一个四层模型,也就是说,该模型将我们使用的网络从逻辑上分解为四个层次,自底向上依次是:网络接口层、网络层、传输层和应用层,如下图所示。

IP通常被翻译为网际协议,它服务于网络层,主要实现了寻址和路由的功能。接入网络的每一台主机都需要有自己的IP地址,IP地址就是主机在计算机网络上的身份标识。当然由于IPv4地址的匮乏,我们平常在家里、办公室以及其他可以接入网络的公共区域上网时获得的IP地址并不是全球唯一的IP地址,而是一个局域网(LAN)中的内部IP地址,通过网络地址转换(NAT)服务我们也可以实现对网络的访问。计算机网络上有大量的被我们称为“路由器”的网络中继设备,它们会存储转发我们发送到网络上的数据分组,让从源头发出的数据最终能够找到传送到目的地通路,这项功能就是所谓的路由。

TCP全称传输控制协议,它是基于IP提供的寻址和路由服务而建立起来的负责实现端到端可靠传输的协议,之所以将TCP称为可靠的传输协议是因为TCP向调用者承诺了三件事情:

  1. 数据不传丢不传错(利用握手、校验和重传机制可以实现)。
  2. 流量控制(通过滑动窗口匹配数据发送者和接收者之间的传输速度)。
  3. 拥塞控制(通过RTT时间以及对滑动窗口的控制缓解网络拥堵)。

1.2、网络应用模式

  1. C/S模式和B/S模式。这里的C指的是Client(客户端),通常是一个需要安装到某个宿主操作系统上的应用程序;而B指的是Browser(浏览器),它几乎是所有图形化操作系统都默认安装了的一个应用软件;通过C或B都可以实现对S(服务器)的访问。关于二者的比较和讨论在网络上有一大堆的文章,在此我们就不再浪费笔墨了。
  2. 去中心化的网络应用模式。不管是B/S还是C/S都需要服务器的存在,服务器就是整个应用模式的中心,而去中心化的网络应用通常没有固定的服务器或者固定的客户端,所有应用的使用者既可以作为资源的提供者也可以作为资源的访问者。

1.3、HTTP(超文本传输协议) 

HTTP是超文本传输协议(Hyper-Text Transfer Proctol)的简称,维基百科上对HTTP的解释是:超文本传输协议是一种用于分布式、协作式和超媒体信息系统的应用层协议,它是万维网数据通信的基础,设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法,通过HTTP或者HTTPS(超文本传输安全协议)请求的资源由URI(统一资源标识符)来标识。 

HTTP 的基本工作原理是客户端(通常是 web 浏览器)向服务器发送请求,服务器接收到请求后,返回相应的资源。这些资源可以是网页、图像、音频文件、视频等。

HTTP 使用了客户端-服务器模型,其中客户端发送请求,服务器返回响应。也就是说,请求是由接受方——通常是 Web 浏览器——发起的。完整网页文档通常由文本、布局描述、图片、视频、脚本等资源构成。客户端与服务端之间通过交换一个个独立的消息(而非数据流)进行通信。由客户端发出的消息被称作请求(request),由服务端发出的应答消息被称作响应(response)。

HTTP 的请求-响应模型通常由以下几个步骤组成:

  • 建立连接:客户端与服务器之间建立连接。在传统的 HTTP 中,这是基于 TCP/IP 协议的。最近的 HTTP/2 和 HTTP/3 则使用了更先进的传输层协议,例如基于 TCP 的二进制协议(HTTP/2)或基于 UDP 的 QUIC 协议(HTTP/3)。

  • 发送请求:客户端向服务器发送请求,请求中包含要访问的资源的 URL、请求方法(GET、POST、PUT、DELETE 等)、请求头(例如,Accept、User-Agent)以及可选的请求体(对于 POST 或 PUT 请求)。

  • 处理请求:服务器接收到请求后,根据请求中的信息找到相应的资源,执行相应的处理操作。这可能涉及从数据库中检索数据、生成动态内容或者简单地返回静态文件。

  • 发送响应:服务器将处理后的结果封装在响应中,并将其发送回客户端。响应包含状态码(用于指示请求的成功或失败)、响应头(例如,Content-Type、Content-Length)以及可选的响应体(例如,HTML 页面、图像数据)。

  • 关闭连接:在完成请求-响应周期后,客户端和服务器之间的连接可以被关闭,除非使用了持久连接(如 HTTP/1.1 中的 keep-alive)。

URL(统一资源定位符)是因特网中的唯一资源的地址。它是浏览器用于检索已发布资源(例如 HTML 页面、CSS 文档、图像等)的关键机制之一。URL 由不同的部分组成,其中一些是必须的,而另一些是可选的。最重要的部分以在下面的 URL 上高亮(详细信息在下面的各节中提供):备注:你可以将 URL 视为普通的邮寄地址:方案(scheme)表示你想要使用的邮政服务,域名(domain name)就像是城市或城镇,端口(port)就像邮政编码;路径(path)表示你的邮件应该送到的建筑物;参数(parameter)表示额外的信息,例如建筑物中公寓的编号;最后,锚点(anchor)表示邮件的实际收信人。

URL 的第一部分是方案(scheme),它表示浏览器必须使用的协议来请求资源(协议是计算机网络中交换或传输数据的一组方法)。通常对于网站,协议是 HTTPS 或 HTTP(它的非安全版本)。访问网页需要这两者之一,但浏览器还知道如何处理其他方案,比如 mailto:(打开邮件客户端)。

接下来是权威(authority),它与方案之间用字符模式 :// 分隔。如果存在,权威会包括(例如 www.example.com)和端口80),由冒号分隔:

  • 域指示被请求的 Web 服务器。通常这是一个域名,但也可以使用 IP 地址(但这很少见,因为它不太方便)。
  • 端口指示用于访问 Web 服务器上资源的技术“门户”。如果 Web 服务器使用 HTTP 协议的标准端口(HTTP 为 80,HTTPS 为 443)来授予对其资源的访问权限,则通常会省略端口。否则,端口是强制的。

    备注:方案和权威之间的分隔符是 ://。冒号将方案与 URL 的下一部分分隔开,而 // 表示 URL 的下一部分是权威。

    不使用权威的 URL 示例之一是邮件客户端(mailto:foobar)。它包含方案,而不使用权威的部分。因此,冒号后没有跟着两个斜杠,并且只充当方案和邮件地址之间的分隔符。

/path/to/myfile.html 是 Web 服务器上资源的路径。在 Web 的早期阶段,像这样的路径表示 Web 服务器上的物理文件位置。如今,它主要是由没有任何物理现实的 Web 服务器处理的抽象。

?key1=value1&key2=value2 是提供给 Web 服务器的额外参数。这些参数是用 & 符号分隔的键/值对列表。在返回资源之前,Web 服务器可以使用这些参数来执行额外的操作。每个 Web 服务器都有自己关于参数的规则,唯一可靠的方式来知道特定 Web 服务器是否处理参数是通过询问 Web 服务器所有者。

#SomewhereInTheDocument 是资源本身的另一部分的锚点。锚点表示资源中的一种“书签”,给浏览器显示位于该“加书签”位置的内容的方向。例如,在 HTML 文档上,浏览器将滚动到定义锚点的位置;在视频或音频文档上,浏览器将尝试转到锚代表的时间。值得注意的是,# 后面的部分(也称为片段标识符)不会随请求被发送到服务器。

1.4、JSON格式 

JSONJavaScript Object Notation)是一种轻量级的数据交换语言,该语言以易于让人阅读的文字(纯文本)为基础,用来传输由属性值或者序列性的值组成的数据对象。尽管JSON是最初只是Javascript中一种创建对象的字面量语法,但它在当下更是一种独立于语言的数据格式,很多编程语言都支持JSON格式数据的生成和解析,Python内置的json模块也提供了这方面的功能。由于JSON是纯文本,它和XML一样都适用于异构系统之间的数据交换,而相较于XML,JSON显得更加的轻便和优雅。下面是表达同样信息的XML和JSON,而JSON的优势是相当直观的。

XML的例子:

<?xml version="1.0" encoding="UTF-8"?>
<message>
	<from>Alice</from>
	<to>Bob</to>
	<content>Will you marry me?</content>
</message>

JSON的例子:

{
    "from": "Alice",
    "to": "Bob",
    "content": "Will you marry me?"
}

1.5、requests库 

requests是一个基于HTTP协议来使用网络的第三库,其官方网站有这样的一句介绍它的话:“Requests是唯一的一个非转基因的Python HTTP库,人类可以安全享用。”简单的说,使用requests库可以非常方便的使用HTTP,避免安全缺陷、冗余代码以及“重复发明轮子”(行业黑话,通常用在软件工程领域表示重新创造一个已有的或是早已被优化過的基本方法)。前面的文章中我们已经使用过这个库,下面我们还是通过requests来实现一个访问网络数据接口并从中获取美女图片下载链接然后下载美女图片到本地的例子程序,程序中使用了天行数据提供的网络API。

我们可以先通过pip安装requests及其依赖库。

pip install requests

如果使用PyCharm作为开发工具,可以直接在代码中书写import requests,然后通过代码修复功能来自动下载安装requests。

from time import time
from threading import Thread

import requests

# 继承Thread类创建自定义的线程类
class Download_Handler(Thread):
    def __init__(self, url):
        super().__init__()
        self_url = url

    def run(self):
        filename = self.url[self.url.rfind('/') + 1:]
        resp = requests.get(self.url)
        with open('/User/Hao/' + filename, 'wb') as f:
            f.write(resp.content)

def main():
    # 通过requests模块的get函数获取网络资源
    resp = requests.get('https://apis.tianapi.com/topnews/index?key=276d4391c55b8b74a52d79525bb77f99')
    # 将服务器返回的JSON格式的数据解析为字典
    data_model = resp.json()
    for mm_dict in data_model['result']['list']:
        url = mm_dict['picUrl']
        # 通过多线程方式实现图片下载
        Download_Handler(url).start()

if __name__ == '__main__':
    main()

1.6、TCP套接字 

套接字就是一套用C语言写成的应用程序开发库,主要用于实现进程间通信和网络编程,在网络应用开发中被广泛使用。在Python中也可以基于套接字来使用传输层提供的传输服务,并基于此开发自己的网络应用。实际开发中使用的套接字可以分为三类:流套接字(TCP套接字)、数据报套接字和原始套接字。

Socket(套接字)可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的API(应用程序编程接口),也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 Socket中,该 Socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的 Socket中,使对方能够接收到这段信息。 Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。Socket是面向客户/服务器模型而设计的。通信的一方扮演客户机的角色,另一方扮演服务器的角色。服务器在运行中一直监听套接字指定的传输层端口,并等待着客户机的连接请求。当服务器端收到客户机发来的连接请求以后,服务器会接受客户机的连接请求,双方建立连接后,就可进行数据的传递。

套接字包括 IP 地址和端口号两个部分。通过网络通信的每对进程需要使用一对套接字。不同的进程之间的通信所使用的套接字是不一样的,套接字可以用来区分不同的进程之间的数据传输。套接字主要有目标IP、传输层使用的传输协议、传输层使用的端口号这3个重要参数。

套接字Socket=(IP地址:端口号),套接字的表示方法是点分十进制的lP地址后面写上端口号,中间用冒号或逗号隔开。每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。例如:如果IP地址是210.37.145.1,而端口号是23,那么得到套接字就是(210.37.145.1:23)。

所谓TCP套接字就是使用TCP协议提供的传输服务来实现网络通信的编程接口。在Python中可以通过创建socket对象并指定type属性为SOCK_STREAM来使用TCP套接字。由于一台主机可能拥有多个IP地址,而且很有可能会配置多个不同的服务,所以作为服务器端的程序,需要在创建套接字对象后将其绑定到指定的IP地址和端口上。这里的端口并不是物理设备而是对IP地址的扩展,用于区分不同的服务,例如我们通常将HTTP服务跟80端口绑定,而MySQL数据库服务默认绑定在3306端口,这样当服务器收到用户请求时就可以根据端口号来确定到底用户请求的是HTTP服务器还是数据库服务器提供的服务。端口的取值范围是0~65535,而1024以下的端口我们通常称之为“著名端口”(留给像FTP、HTTP、SMTP等“著名服务”使用的端口,有的地方也称之为“周知端口”),自定义的服务通常不使用这些端口,除非自定义的是HTTP或FTP这样的著名服务。

下面的代码实现了一个提供时间日期的服务器。

from socket import socket, SOCK_STREAM, AF_INET
from datetime import datetime

def main():
    # 1.创建套接字对象并指定使用哪种传输服务
    # family=AF_INET - IPv4地址
    # family=AF_INET6 - IPv6地址
    # type=SOCK_STREAM - TCP套接字
    # type=SOCK_DGRAM - UDP套接字
    # type=SOCK_RAW - 原始套接字
    sever = socket(family=AF_INET, type=SOCK_STREAM)    # 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
    # 2.绑定IP地址和端口(端口用于区分不同的服务)
    # 同一时间在同一个端口上只能绑定一个服务否则报错
    sever.bind(('192.168.137.1', 6789))
    # 3.开启监听 - 监听客户端连接到服务器
    # 参数512可以理解为连接队列的大小
    sever.listen(512)
    print('服务器启动开始监听...')
    while True:
       # 4.通过循环接收客户端的连接并作出相应的处理(提供服务)
        # accept方法是一个阻塞方法如果没有客户端连接到服务器代码不会向下执行
        # accept方法返回一个元组其中的第一个元素是客户端对象
        # 第二个元素是连接到服务器的客户端的地址(由IP和端口两部分构成)
        client, addr = sever.accept()
        print(str(addr) + '连接到了服务器')
        # 5.发送数据
        client.send(str(datetime.now()).encode('utf-8'))
        # 6.断开链接
        client.close()

# telnet 192.168.137.1 6789
# telnet就是查看某个端口是否可访问。

# 客户端初始化一个Socket,然后连接服务器(connect),
# 如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,
# 服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

运行服务器程序后我们可以通过Windows系统的telnet来访问该服务器,结果如下图所示。 

telnet 192.168.1.2 6789

假如出现这种情况的报错,我们先Win+R,在框框里输入control

打开这个控制面板。

在程序里面找到上图画红框的地方点开。

在这里就可以找到Telnet客户端,并打勾启用它。

如果想要查看自己想使用的端口号是否被占用,netstat -ano这个命令可以帮助你。

当然我们也可以通过Python的程序来实现TCP客户端的功能,相较于实现服务器程序,实现客户端程序就简单多了,代码如下所示。 

from socket import socket


def main():
    # 1.创建套接字对象默认使用IPv4和TCP协议
    client = socket()
    # 2.连接到服务器(需要指定IP地址和端口)
    client.connect(('192.168.1.2', 6789))
    # 3.从服务器接收数据
    print(client.recv(1024).decode('utf-8'))
    client.close()


if __name__ == '__main__':
    main()

需要注意的是,上面的服务器并没有使用多线程或者异步I/O的处理方式,这也就意味着当服务器与一个客户端处于通信状态时,其他的客户端只能排队等待。很显然,这样的服务器并不能满足我们的需求,我们需要的服务器是能够同时接纳和处理多个用户请求的。下面我们来设计一个使用多线程技术处理多个用户请求的服务器,该服务器会向连接到服务器的客户端发送一张图片。

服务器端代码:

# 服务器端
from socket import socket, SOCK_STREAM, AF_INET
from base64 import b64encode
from json import dumps
from threading import Thread

def main():
    # 自定义线程类
    class FileTransferHandler(Thread):
        def __init__(self,cclient):
            super().__init__()
            self.cclient = cclient
        
        def run(self):
            my_dict = {}
            my_dict['filename'] = 'guido.jpg'
            # JSON是纯文本不能携带二进制数据
            # 所以图片的二进制数据要处理成base64编码
            my_dict['filename'] = data
            # 通过dumps函数将字典处理成JSON字符串
            json_str = dumps(my_dict)
            # 发送JSON字符串
            self.cclient.send(json_str.encode('utf-8'))
            self.cclient.close()
    
    # 创建套接字对象并指定使用哪种传输服务
    sever = socket()
    # 绑定IP地址和端口(区分不同的服务)
    sever.bind(('192.168.137.1', 5566))
    # 开启监听 - 监听客户端连接到服务器
    sever.listen(512)
    print('服务器开始监听')
    with open('guido.jpg','rb') as f:
        # 将二进制数据处理成base64在解码成字符串
        data = b64encode(f.read()).decode('utf-8')
    while True:
        client, addr = sever.accept()
        # 启动一个线程来处理客户端的请求
        FileTransferHandler(client).start()

客户端代码:

# 客户端
from socket import socket
from json import loads
from base64 import b64decode

def main():
    client = socket()
    client.connect(('196.168.137.1', 5566))
    # 定义一个保存二进制数据的对象
    in_data = bytes()
    # 由于不知道服务器发送的数据有多大每次接收1024字节
    data = client.recv(1024)
    while data:
        in_data += data
        data = client.recv(1024)

    # 将收到的二进制数据解码成JSON字符串并转换成字典
    # loads函数的作用就是将JSON字符串转成字典对象
    my_dict = loads(in_data.decode('utf-8'))
    filename = my_dict['filename']
    filedata = my_dict['filedata'].encode('utf-8')
    with open('/User/Hao/' + filename, 'wb') as f:
        # 将base64格式的数据解码成二进制数据并写入文件
        f.write(b64decode(filedata))
    print('图片已保存.')

在这个案例中,我们使用了JSON作为数据传输的格式(通过JSON格式对传输的数据进行了序列化和反序列化的操作),但是JSON并不能携带二进制数据,因此对图片的二进制数据进行了Base64编码的处理。

Base64是一种用64个字符表示所有二进制数据的编码方式,通过将二进制数据每6位一组的方式重新组织,刚好可以使用0~9的数字、大小写字母以及“+”和“/”总共64个字符表示从000000111111的64种状态。

具体转换步骤:

  • 第一步,将待转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。
  • 第二步,将上面的24个二进制位每6个一组,共分为4组。
  • 第三步,在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
  • 第四步,根据Base64编码对照表(见下图)获得对应的值。
  1. 0 A  17 R   34 i   51 z
  2. 1 B  18 S   35 j   52 0
  3. 2 C  19 T   36 k   53 1
  4. 3 D  20 U   37 l   54 2
  5. 4 E  21 V   38 m   55 3
  6. 5 F  22 W   39 n   56 4
  7. 6 G  23 X   40 o   57 5
  8. 7 H  24 Y   41 p   58 6
  9. 8 I  25 Z   42 q   59 7
  10. 9 J  26 a   43 r   60 8
  11. 10 K  27 b   44 s   61 9
  12. 11 L  28 c   45 t   62 +
  13. 12 M  29 d   46 u   63 /
  14. 13 N  30 e   47 v
  15. 14 O  31 f   48 w   
  16. 15 P  32 g   49 x
  17. 16 Q  33 h   50 y

从上面的步骤我们发现:

  • Base64字符表中的字符原本用6个bit就可以表示,现在前面添加2个0,变为8个bit,会造成一定的浪费。因此,Base64编码之后的文本,要比原文大约三分之一。
  • 为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个二进制位,每6个bit位一组,恰好能够分为4组。

第一步:“M”、“a”、"n"对应的ASCII码值分别为77,97,110,对应的二进制值是01001101、01100001、01101110。如图第二三行所示,由此组成一个24位的二进制字符串。
第二步:如图红色框,将24位每6位二进制位一组分成四组。
第三步:在上面每一组前面补两个0,扩展成32个二进制位,此时变为四个字节:00010011、00010110、00000101、00101110。分别对应的值(Base64编码索引)为:19、22、5、46。
第四步:用上面的值在Base64编码表中进行查找,分别对应:T、W、F、u。因此“Man”Base64编码之后就变为:TWFu。

上面是按照三个字节来举例说明的,如果字节数不足三个,那么该如何处理?

两个字节:两个字节共16个二进制位,依旧按照规则进行分组。此时总共16个二进制位,每6个一组,则第三组缺少2位,用0补齐,得到三个Base64编码,第四组完全没有数据则用“=”补上。因此,上图中“BC”转换之后为“QKM=”;
一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个二进制位,每6个一组,则第二组缺少4位,用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用“=”补上。因此,上图中“A”转换之后为“QQ==”。

1.7、UDP套接字

传输层除了有可靠的传输协议TCP之外,还有一种非常轻便的传输协议叫做用户数据报协议,简称UDP。TCP和UDP都是提供端到端传输服务的协议,二者的差别就如同打电话和发短信的区别,后者不对传输的可靠性和可达性做出任何承诺从而避免了TCP中握手和重传的开销,所以在强调性能和而不是数据完整性的场景中(例如传输网络音视频数据),UDP可能是更好的选择。可能大家会注意到一个现象,就是在观看网络视频时,有时会出现卡顿,有时会出现花屏,这无非就是部分数据传丢或传错造成的。在Python中也可以使用UDP套接字来创建网络应用。 

1.8、网络应用开发

1.8.1、发送电子邮件 

在即时通信软件如此发达的今天,电子邮件仍然是互联网上使用最为广泛的应用之一,公司向应聘者发出录用通知、网站向用户发送一个激活账号的链接、银行向客户推广它们的理财产品等几乎都是通过电子邮件来完成的,而这些任务应该都是由程序自动完成的。

就像我们可以用HTTP(超文本传输协议)来访问一个网站一样,发送邮件要使用SMTP(简单邮件传输协议),SMTP也是一个建立在TCP(传输控制协议)提供的可靠数据传输服务的基础上的应用级协议,它规定了邮件的发送者如何跟发送邮件的服务器进行通信的细节,而Python中的smtplib模块将这些操作简化成了几个简单的函数。

下面的代码演示了如何在Python发送邮件。

from smtplib import SMTP
from email.header import Header    # 创建符合 MIME 要求的标头,其中可包含不同字符集的字符串。
from email.mime.text import MIMEText

"""
MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的标准,用来表示文档、文件或字节流的性质和格式。
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
"""

def main():
    # 请自行修改下面的邮件发送者和接收者
    sender = 'abcdefg@126.com'
    receivers = ['uvwxyz@qq.com', 'uvwxyz@126.com']
    message = MIMEText('用Python发送邮件的示例代码.', 'plain', 'utf-8')
    message['From'] = Header('王大锤', 'utf-8')
    message['To'] = Header('刘子翊', 'utf-8')
    message['Subject'] = Header('示例代码实验邮件', 'utf-8')
    smtper = SMTP('smtp.126.com')   # 通过第三方登陆
    # 请自行修改下面的登录口令
    smtper.login(sender, 'secretpass')  # 发给QQ邮箱需要生成授权码作为密码
    smtper.sendmail(sender, receivers, message.as_string())
    print('邮件发送完成!')

如果要发送带有附件的邮件,那么可以按照下面的方式进行操作。

# 带有附件的邮件
from smtplib import SMTP
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

import urllib

def main():
    # 创建一个带附件的邮件消息对象
    message = MIMEMultipart()

    # 创建文本内容
    text_content = MIMEText('附件中有本月数据,请查收', 'plain', 'utf-8')
    message['Subject'] = Header('本月数据', 'utf-8')
    # 将文本内容添加到邮件消息对象中
    message.attach(text_content)

    # 读取文件并将文件作为附件添加到邮件消息对象中
    with open('/User/Hao/Desktop/hello.txt','rb') as f:
        txt = MIMEText(f.read(), 'base64', 'utf-8')
        txt['Content-Type'] = 'text/plain'    # 表示文本文件的默认值
        txt['Content-Disposition'] = 'attachment; filename=hello.txt'
        message.attach(txt)
    # 读取文件并将文件作为附件添加到邮件消息对象中
    with open('/Users/Hao/Desktop/汇总数据.xlsx', 'rb') as f:
        xls = MIMEText(f.read(), 'base64', 'utf-8')
        xls['Content-Type'] = 'application/vnd.ms-excel'
        xls['Content-Disposition'] = 'attachment; filename=month-data.xlsx'
        message.attach(xls)

    # 创建SMTP对象
    smtper = SMTP('smtp.126.com')     # SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于
    # 由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
    # 开启安全连接
    # smtper.starttls()
    sender = 'abcdefg@126.com'
    receivers = ['uvwxyz@qq.com']
    # 登录到SMTP服务器
    # 请注意此处不是使用密码而是邮件客户端授权码进行登录
    smtper.login(sender, 'secretpass')
    # 发送邮件
    smtper.sendmail(sender, receivers, message.as_string())
    # 与邮件服务器断开连接
    smtper.quit()
    print('发送完成!')
1.8.2、发送短信

发送短信也是项目中常见的功能,网站的注册码、验证码、营销信息基本上都是通过短信来发送给用户的。在下面的代码中我们使用了互亿无线短信平台(该平台为注册用户提供了50条免费短信以及常用开发语言发送短信的demo,可以登录该网站并在用户自服务页面中对短信进行配置)提供的API接口实现了发送短信的服务。

import urllib.parse
import http.client
import json


def main():
    host  = "106.ihuyi.com"
    sms_send_uri = "/webservice/sms.php?method=Submit"
    # 下面的参数需要填入自己注册的账号和对应的密码
    params = urllib.parse.urlencode({'account': '你自己的账号', 'password' : '你自己的密码', 'content': '您的验证码是:147258。请不要把验证码泄露给其他人。', 'mobile': '接收者的手机号', 'format':'json' })
    # 将一个字典类型的参数转换成url格式的参数,常用于构建get请求的参数。
    print(params)
    headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'}    # application表明是某种二进制数据
    conn = http.client.HTTPConnection(host, port=80, timeout=30)    # 创建了一个HTTP连接
    conn.request('POST', sms_send_uri, params, headers) # params 接收一个字典或者字符串的查询参数
    # 组合成一个完整url地址
    response = conn.getresponse()
    response_str = response.read()  # 返回的是响应内容的字节串
    jsonstr = response_str.decode('utf-8')
    print(json.loads(jsonstr))
    conn.close()


if __name__ == '__main__':
    main()

二、图像和办公文档处理 

2.1、操作图像

  1. 颜色。如果你有使用颜料画画的经历,那么一定知道混合红、黄、蓝三种颜料可以得到其他的颜色,事实上这三种颜色就是被我们称为美术三原色的东西,它们是不能再分解的基本颜色。在计算机中,我们可以将红、绿、蓝三种色光以不同的比例叠加来组合成其他的颜色,因此这三种颜色就是色光三原色,所以我们通常会将一个颜色表示为一个RGB值或RGBA值(其中的A表示Alpha通道,它决定了透过这个图像的像素,也就是透明度)。

    名称RGBA值名称RGBA值
    White(255, 255, 255, 255)Red(255, 0, 0, 255)
    Green(0, 255, 0, 255)Blue(0, 0, 255, 255)
    Gray(128, 128, 128, 255)Yellow(255, 255, 0, 255)
    Black(0, 0, 0, 255)Purple(128, 0, 128, 255)
  2. 像素。对于一个由数字序列表示的图像来说,最小的单位就是图像上单一颜色的小方格,这些小方块都有一个明确的位置和被分配的色彩数值,而这些一小方格的颜色和位置决定了该图像最终呈现出来的样子,它们是不可分割的单位,我们通常称之为像素(pixel)。每一个图像都包含了一定量的像素,这些像素决定图像在屏幕上所呈现的大小。

2.2、用Pillow操作图像 

Pillow是由从著名的Python图像处理库PIL发展出来的一个分支,通过Pillow可以实现图像压缩和图像处理等各种操作。可以使用下面的命令来安装Pillow。

pip install pillow

Pillow中最为重要的是Image类,读取和处理图像都要通过这个类来完成。

>>> from PIL import Image
>>>
>>> image = Image.open(r'E:\Python_test\network_program\guido.jpg')
>>> image.format, image.size, image.mode
('JPEG', (500, 750), 'RGB')
>>> image.show()
2.2.1、剪裁图像 
>>> image = Image.open('./res/guido.jpg')
>>> rect = 80, 20, 310, 360
>>> image.crop(rect).show()
2.2.2、生成缩略图
>>> image = Image.open('./res/guido.jpg')
>>> size = 128, 128
>>> image.thumbnail(size)
>>> image.show()
2.2.3、缩放和黏贴图像
>>> image1 = Image.open('./res/luohao.png')
>>> image2 = Image.open('./res/guido.jpg')
>>> rect = 80, 20, 310, 360
>>> guido_head = image2.crop(rect)
>>> width, height = guido_head.size
>>> image1.paste(guido_head.resize((int(width / 1.5), int(height / 1.5))), (172, 40))
2.2.4、旋转和翻转
>>> image = Image.open('./res/guido.png')
>>> image.rotate(180).show()
>>> image.transpose(Image.FLIP_LEFT_RIGHT).show()
2.2.5、操作像素
>>> image = Image.open('./res/guido.jpg')
>>> for x in range(80, 310):
...     for y in range(20, 360):
...         image.putpixel((x, y), (128, 128, 128))
... 
>>> image.show()
2.2.6、滤镜效果
>>> from PIL import Image, ImageFilter
>>>
>>> image = Image.open('./res/guido.jpg')
>>> image.filter(ImageFilter.CONTOUR).show()

2.3、处理Excel电子表格

Python的openpyxl模块让我们可以在Python程序中读取和修改Excel电子表格,由于微软从Office 2007开始使用了新的文件格式,这使得Office Excel和LibreOffice Calc、OpenOffice Calc是完全兼容的,这就意味着openpyxl模块也能处理来自这些软件生成的电子表格。

1)、工作薄(workbook):一个EXCEL文件就称为一个工作薄,一个工作薄中可以包含若干张工作表。

2)、工作表(sheet):工作薄中的每一张表格称为工作表,每张工作表都有一个标签,默认为sheet1\sheet2\sheet3来命名(一个工作薄默认为由3个工作表组成)。
3)、活动表(active sheet):指当前正在操作的工作表。
4)、行(row):工作表中的每一行行首数字(1、2、3、)称为行标题;一张工作表最多有65536行。
5)、列(column):列标题:工作表中每一列列首的字母(A、B、C)称为列标题;一张工作表最多有256列。
6)、单元格(cell):工作表的每一个格称为单元格。

Workbook对象属性:

sheetnames:获取工作簿中的表
active:获取当前活跃的Worksheet
worksheets:以列表的形式返回所有的Worksheet(表格)
read_only:判断是否以read_only模式打开Excel文档
encoding:获取文档的字符集编码
properties:获取文档的元数据,如标题,创建者,创建日期等

Worksheet,Cell对象:

Worksheet:
title:表格的标题
max_row:表格的最大行
min_row:表格的最小行
max_column:表格的最大列
min_column:表格的最小列
rows:按行获取单元格(Cell对象) - 生成器
columns:按列获取单元格(Cell对象) - 生成器
values:按行获取表格的内容(数据) - 生成器

Cell:

row:单元格所在的行
column:单元格坐在的列
value:单元格的值
coordinate:单元格的坐标

import datetime

from openpyxl import Workbook    # 一个Workbook对象代表一个Excel文档
# 对于一个已经存在的Excel文档,可以使用openpyxl模块的load_workbook函数进行读取,
# 该函数包涵多个参数,但只有filename参数为必传参数。filename 是一个文件名,
# 也可以是一个打开的文件对象。

wb = Workbook()
ws = wb.active    # 当创建好工作簿之后会自动生成一个工作表(sheet),名为"Sheet",可以通过active来获取

ws['A1'] = 42
ws.append([1, 2, 3])
ws['A2'] = datetime.datetime.now()

wb.save("sample.xlsx")

2.4、处理Word文档 

利用python-docx模块,Python可以创建和修改Word文档,当然这里的Word文档不仅仅是指通过微软的Office软件创建的扩展名为docx的文档,LibreOffice Writer和OpenOffice Writer都是免费的字处理软件。

from docx import Document
from docx.shared import Inches

document = Document()

document.add_heading('Document Title', 0)

p = document.add_paragraph('A plain paragraph having some ')
p.add_run('bold').bold = True
p.add_run(' and some ')
p.add_run('italic.').italic = True

document.add_heading('Heading, level 1', level=1)
# 引用
document.add_paragraph('Intense quote', style='Intense Quote')

# 圆点列表
document.add_paragraph(
    'first item in unordered list', style='List Bullet'
)
# 序号列表
document.add_paragraph(
    'first item in orderen list', style='List Number'
)

"""
document.add_picture('monty-truth.png', width=Inches(1.25))
"""

records = (
    (3, '101', 'Spam'),
    (7, '422', 'Eggs'),
    (4, '631', 'Spam, spam, eggs, and spam')
)

table = document.add_table(rows=1, cols=3)
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Qty'
hdr_cells[1].text = 'Id'
hdr_cells[2].text = 'Desc'
for qty, id, desc in records:
    row_cells = table.add_row().cells
    row_cells[0].text = str(qty)
    row_cells[1].text = id
    row_cells[2].text = desc

document.add_page_break()

document.save('demo.docx')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱里承欢。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值