22 Python网络编程
Python是很强大的网络编程工具。Python有很多针对常见网络协议的库,这些库可以使我们集中精力在程序的逻辑处理上,而不是停留在网络实现的细节中。使用Python很容易写出处理各种协议格式的代码,Python在处理字节流的各种模式方面很擅长。
网络编程初识
自从互联网诞生以来,基本上所有程序都是网络程序,很少有单机版程序了。
计算机网络把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程在程序中实现了两台计算机的通信。
举个例子,当你使用浏览器访问淘宝网时,你的计算机和淘宝的某台服务器通过互联网连接起来了,淘宝的服务器就会把网页内容作为数据通过互联网传输到你的电脑上。
由于你的电脑上可能不止浏览器,还有微信、办公软件、邮件客户端等,不同程序连接的计算机也会不同,因此网络通信是两台计算机的两个进程之间的通信。比如,浏览器进程和淘宝服务器上某个Web服务进程通信,而微信进程是和腾讯服务器上某个进程通信。
网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程就是在Python程序的进程内连接别的服务器进程的通信端口进行通信。
TCP/IP简介
大家对互联网应该很熟悉,计算机网络的出现比互联网要早很多。
为了联网,计算机必须规定通信协议。早期的计算机网络都是由各厂商自己规定一套协议,如IBM和Microsoft都有各自的网络协议,互不兼容。这就好比一群人有的说英语,有的说中文,有的说德语,但都只懂一种语言,因此只有说同一种语言的人可以交流,说不同语言的人就不行了。
为了把全世界所有不同类型的计算机都连接起来,必须规定一套全球通用协议。为了实现互联网这个目标,大家共同制定了互联网协议簇(Internet Protocol Suite),作为通用协议标准。Internet是由inter和net两个单词组合起来的,原意是连接“网络”的网络,有了Internet,只要支持这个协议,任何私有网络都可以连入互联网。
互联网协议包含上百种协议标准,由于最重要的两个协议是TCP和IP协议,因此大家把互联网协议简称为TCP/IP协议。
通信时双方必须知道对方的标识,好比发邮件必须知道对方的邮件地址。互联网上计算机的唯一标识就是IP地址,如192.168.12.27。如果一台计算机同时接入两个或更多网络(如路由器),就会有两个或多个IP地址,所以IP地址对应的实际是计算机的网络接口,通常是网卡。
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此路由器负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途经多个路由,但不保证能到达,也不保证按顺序到达。IP地址实际上是一个32位整数(IPv4),以字符串表示的IP地址实际上是把32位整数按8位分组后的数字表示(如192.168.0.1),目的是便于阅读。
IPv6地址实际上是128位整数,是目前使用的IPv4的升级版,以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334。
TCP协议建立在IP协议之上。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后对每个IP包编号,确保对方按顺序收到,如果包丢掉了就自动重发。
许多常用的更高级的协议都是建立在TCP协议基础上的,如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个IP包除了包含要传输的数据外,还包含源IP地址和目标IP地址、源端口和目标端口。
端口有什么作用?两台计算机通信时,只发IP地址是不够的,因为同一台计算机运行着多个网络程序。一个IP包来了之后交给浏览器还是微信,需要端口号进行区分。每个网络程序都向操作系统申请唯一的端口号,这样两个进程在两台计算机之间建立网络连接就需要各自的IP地址和端口号。
一个进程也可能同时与多台计算机建立连接,因此它会申请很多端口。
网络设计模块
前面我们了解了TCP/IP协议、IP地址和端口的基本概念,下面我们开始了解网络编程。
标准库中有很多网络设计模块,除了明确处理网络事务的模块外,还有很多模块是与网络相关的。接下来我们讨论其中几个模块。
1 Socket简介
网络编程中有一个基本组件——套接字(socket)。
套接字为特定网络协议(如TCP/IP、ICMP/IP、UDP/IP等)套件对上的网络应用程序提供者提供当前可移植标准的对象。套接字允许程序接收数据并进行连接,如发送和接收数据。为了建立通信通道,网络通信的每个端点拥有一个套接字对象极为重要。
套接字为BSD UNIX系统核心的一部分,而且被许多类似UNIX的操作系统(包括Linux)所采纳。许多非BSD UNIX系统(如MS-DOS、Windows、OS/2,Mac OS及大部分主机环境)都以库形式提供对套接字的支持。
3种最流行的套接字类型是stream、datagram和raw。stream和datagram套接字可以直接与TCP协议进行接口,而raw套接字与IP协议进行接口。套接字并不限于TCP/IP。
套接字主要是两个程序之间的“信息通道”。程序(通过网络连接)可能分布在不同的计算机上,通过套接字相互发送信息。在Python中,大多数网络都隐藏了socket模块的基本细节,并且不直接和套接字交互。
2 socket模块
套接字模块是一个非常简单的基于对象的接口,提供对低层BSD套接字样式网络的访问。使用该模块可以实现客户机和服务器套接字。要在Python中建立具有TCP和流套接字的简单服务器需要使用socket模块。利用该模块包含的函数和类定义可生成通过网络通信的程序。一般来说,建立服务器连接需要6个步骤。
步骤一:创建socket对象
在Python中,我们用socket()函数创建套接字,语法格式如下:
socket.socket([family[, type[, protocol]]])
- family:可以是AF_UNIX(UNIX域,用于同一台机器上的进程间通信),也可以是AF_INET(对于IPV4协议的TCP和UDP)或AF_INET6(对于IPV6)。
- type:套接字类型可以根据面向连接和非连接分为SOCK_STREAM(流套接字)或SOCK_DGRAM(数据报文套接字)。
- protocol:一般不填,默认为0。
family参数指定调用者期待返回的套接口地址结构的类型。family的值包括3种:AF_INET、AF_INET6和AF_UNSPEC。
如果指定AF_INET,函数就不能返回任何IPV6相关的地址信息。
如果仅指定AF_INET6,就不能返回任何IPV4地址信息。
AF_UNSPEC意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。
如果某个主机既有AAAA记录(IPV6)地址,又有A记录(IPV4)地址,那么AAAA记录将作为sockaddr_in6结构返回,而A记录作为sockaddr_in结构返回。
AF_INET6用于IPV6系统,AF_INET和PF_INET用于IPV4系统。
AF表示ADDRESS FAMILY地址族。
PF表示PROTOCOL FAMILY协议族。
在Windows中,AF_INET与PF_INET完全一样;在UNIX/Linux系统中,不同版本的AF_INET与PF_INET有微小差别。
步骤二:将socket绑定(指派)到指定地址上,socket.bind(address)。
address必须是一个双元素元组((host,port)),参数为主机名或ip地址+端口号。如果端口号正在被使用或保留、主机名或IP地址错误,就会引发socke.error异常。
步骤三:绑定后必须准备好套接字,以便接受连接请求。请求方式如下:
socket.listen(backlog)
backlog用于指定最多连接数,至少为1。接到连接请求后,这些请求必须排队,如果队列已满,就拒绝请求。
步骤四:服务器套接字通过socket的accept方法等待客户请求一个连接。请求方式如下:
connection,address=socket.accept()
调用accept方法时,socket会进入等待(或阻塞)状态。客户请求连接时,accept方法建立连接并返回服务器。accept方法返回一个含有两个元素的元组,如(connection, address)。第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的互联网地址。
步骤五:处理阶段,服务器和客户通过send和recv方法通信(传输数据)。
服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接收信息。调用recv时,必须指定一个整数控制本次调用所接收的最大数据量。recv方法在接收数据时会进入blocket状态,最后返回一个字符串,用于表示收到的数据。如果发送的量超过recv允许的量,数据就会被截断。多余的数据将缓冲于接收端。以后调用recv时,多余的数据会从缓冲区删除。
步骤六:传输结束,服务器调用socket的close方法以关闭连接。建立一个简单的客户连接需要4个步骤。
(1)创建一个socket以连接服务器socket=socket.socket(family,type)。
(2)使用socket的connect方法连接服务器socket.connect((host,port))。
(3)客户和服务器通过send和recv方法通信。
(4)结束后,客户通过调用socket的close方法关闭连接。
3 socket对象(内建)方法
socket提供了表1所示的服务器端套接字函数。
1 服务器端套接字函数 socket提供了表2所示的客户端套接字函数。 2 客户端套接字函数 socket提供了表3所示的公共用途套接字函数。 3 公共用途套接字函数TCP编程
Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,并且指定协议类型。大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的是客户端,被动响应连接的是服务器。
1 客户端
当我们在浏览器中访问某个网站时,自己的计算机就是客户端,浏览器会主动向所访问网站的服务器发起连接。如果一切顺利,所访问网站的服务器接受了我们的连接,一个TCP连接就建立起来了,接着就可以发送网页内容了。
例如,要创建一个基于TCP连接的Socket(以连接本地为例),可以这样做:
# 导入socket 库
import socket
# 创建一个socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
# 设置端口号
port = 9999
# 连接服务,指定主机和端口
s.connect((host, port))
创建Socket时,AF_INET指定使用IPv4协议。如果要用更先进的IPv6,就指定为AF_INET6。SOCK_STREAM指定使用面向流的TCP协议,这样一个Socket对象就创建成功了,但是还没有建立连接。
如果客户端要主动发起TCP连接,就必须知道服务器的IP地址和端口号。比如百度的IP地址可以用域名www.baidu.com自动转换到IP地址,但是怎么知道百度服务器的端口号呢?
作为服务器,提供服务时端口号必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80端口。80端口是Web服务的标准端口。其他服务都有对应的标准端口号,如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务端口,端口号大于1024的可以任意使用。
例如,连接百度服务器的代码如下:
s.connect(('www.baidu.com', 80))
建立TCP连接后,我们可以向百度服务器发送请求,要求返回首页的内容:
# 发送数据:
s.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
TCP连接创建的是双向通道,双方可以同时给对方发数据。谁先发、谁后发,怎么协调,要根据具体协议决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。
发送的文本格式必须符合HTTP标准。如果格式没问题,接下来就可以接收百度服务器返回的数据了:
# 接收数据
buffer = []
while True:
# 每次最多接收1k 字节
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
接收数据时,调用recv(max)方法一次最多接收指定的字节数,因此会在while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。
接收完数据后,调用close()方法关闭Socket,一次完整的网络通信就结束了:
# 关闭连接
s.close()
接收到的数据包括HTTP头和网页,我们只需要把HTTP头和网页分离一下,输出HTTP头,将网页内容保存到文件:
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的数据写入文件
with open('baidu.html', 'wb') as f:
f.write(html)
接下来,只需要打开baidu.html文件,就可以进入百度首页了。
下面是以上功能的完整代码。
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import socket
def socket_client():
# 创建 socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取主机名
host = 'www.baidu.com'
# 设置端口号
port = 80
# 连接服务,指定主机和端口
s.connect((host, port))
#发送数据
s.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
# 接收数据
buffer = []
while True:
# 每次最多接收1k 字节
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的数据写入文件
with open('baidu.html', 'wb') as f:
f.write(html)
s.close()
def main():
socket_client()
if __name__ == '__main__':
main()
2 服务器
和客户端编程相比,服务器编程更复杂一些。
服务器编程首先要绑定一个端口,监听来自其他客户端的连接。如果某个客户端发起连接了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。
服务器会打开固定端口(如80)监听,每发起一个客户端连接,就创建该Socket连接。由于服务器有大量来自客户端的连接,因此要能够区分一个Socket连接是和哪个客户端绑定的。确定唯一的Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口。
服务器还需要同时响应多个客户端请求,每个连接都需要一个新进程或新线程处理,否则服务器一次只能服务一个客户端。
下面编写一个简单的服务器程序,用于接收客户端连接,把客户端发过来的字符串加上Hello再发回去。
首先创建一个基于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的端口号必须有管理员权限才能绑定,操作如下:
# 获取本地主机名
host = socket.gethostname()
# 设置端口号
port = 9999
# 监听端口
s.bind((host, port))
接着调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量:
# 设置最大连接数,超过后排队
s.listen(5)
接下来,服务器程序通过一个永久循环接受来自客户端的连接,accept()会等待并返回一个客户端连接,操作如下:
while True:
# 接受一个新连接
sock, addr = s.accept()
# 创建新线程处理TCP连接
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
每个连接都必须创建新线程(或进程)来处理,否则单线程在处理连接的过程中无法接受其他客户端连接,操作如下:
def tcp_link(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send('欢迎学习Python网络编程!'.encode('utf-8'))
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)
连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。如果客户端发送了exit字符串,就直接关闭连接。
要使用这个服务器程序,我们还需要一个客户端程序,代码如下:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
# 设置端口号
port = 9999
# 建立连接
s.connect((host, port))
# 接收欢迎消息
print(s.recv(1024).decode('utf-8'))
for data in ['小萌', '小智', '小强']:
# 发送数据
s.send(data.encode('utf-8'))
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
注意,客户端程序运行完毕就退出了,而服务器程序会永远运行下去,必须按Ctrl+C退出程序。
下面是以上功能的完整代码。
服务端代码实现:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import socket
import threading
import time
def socket_server():
# 创建 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
port = 9999
# 绑定端口
server_socket.bind((host, port))
# 设置最大连接数,超过后排队
server_socket.listen(5)
while True:
# 接受一个新连接
sock, addr = server_socket.accept()
# 创建新线程处理TCP连接
t = threading.Thread(target=tcp_link, args=(sock, addr))
t.start()
def tcp_link(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send('欢迎学习Python网络编程!'.encode('utf-8'))
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)
def main():
socket_server()
if __name__ == '__main__':
main()
客户端代码实现:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import socket
def socket_client():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
port = 9999
# 建立连接
s.connect((host, port))
# 接收欢迎消息
print(s.recv(1024).decode('utf-8'))
for data in ['小萌', '小智', '小强']:
# 发送数据
s.send(data.encode('utf-8'))
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
def main():
socket_client()
if __name__ == '__main__':
main()
服务器和客户端执行结果如图1和2所示。
服务器输出信息 2 客户端输出信息
UDP编程
TCP用于建立可靠连接,并且通信双方可以以流的形式发送数据。相对于TCP, UDP面向无连接的协议。
使用UDP协议时不需要建立连接,只需要知道对方的IP地址和端口号就可以直接发数据包。但是发送的数据包是否能到达就不知道了。
虽然用UDP传输数据不可靠,但是优点是速度快。对于不要求可靠到达的数据可以使用UDP协议。
下面来看如何通过UDP协议传输数据。和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口,操作如下:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
host = socket.gethostname()
port = 9999
# 绑定端口
s.bind((host, port))
创建Socket时,SOCK_DGRAM指定了Socket的类型是UDP。绑定端口和TCP一样,不过不需要调用listen()方法,而是直接接收来自任何客户端的数据,操作如下:
while True:
# 接收数据
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'Hello, %s!' % data, addr)
recvfrom()
方法返回数据和客户端的地址与端口。这样,服务器收到数据后,直接调用sendto()
就可以把数据用UDP发给客户端。
客户端使用UDP时,首先仍然是创建基于UDP的Socket,然后不需要调用connect()
,直接通过sendto()
给服务器发数据,操作如下:
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()
下面是以上功能的完整代码。
服务器端:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import socket
def socket_udp_server():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
host = socket.gethostname()
port = 9999
# 绑定端口
s.bind((host, port))
while True:
# 接收数据
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'hello, %s,welcome!' % data, addr)
def main():
socket_udp_server()
if __name__ == "__main__":
main()
客户端:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import socket
def socket_udp_client():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['小萌', '小智']:
host = socket.gethostname()
port = 9999
# 发送数据
s.sendto(data.encode('utf-8'), (host, port))
# 接收数据
print(s.recv(1024).decode('utf-8'))
s.close()
def main():
socket_udp_client()
if __name__ == '__main__':
main()
服务器和客户端执行结果如图3和4所示。
3 服务器输出信息
4 客户端输出信息
urllib模块
在Python中,能使用的各种网络工作库中,功能最强大的是urllib。urllib能够通过网络访问文件,就像这些文件在我们电脑上一样。通过一个简单的函数调用,几乎可以把任何URL指向的事物用做程序输入。
urllib提供了一系列用于操作URL的功能,其中最常用的请求是GET和POST。下面简单介绍一下在Python中使用GET和POST请求。
1 GET请求
urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定页面,然后返回HTTP响应,示例如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
from urllib import request
def get_request():
with request.urlopen('http://www.baidu.com') as f:
data = f.read()
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', data.decode('utf-8'))
def main():
get_request()
if __name__ == "__main__":
main()
运行程序,得到HTTP响应的头和JSON数据,代码如下:
Status: 200 OK
Content-Type: text/html
Content-Length: 6234
Data: <!DOCTYPE html><html><head><meta
http-equiv="content-type"content="text/html;charset=utf-8"/><meta
http-equiv="X-UA-Compatible"content="IE=Edge"/><meta content="never"name="referrer"/><title>百度一下,你
就知道
如果想模拟浏览器发送GET请求,就需要使用Request对象,通过往Request对象添加HTTP头可以把请求伪装成浏览器。
2 POST请求
如果要以POST发送一个请求,就要把参数data以bytes形式传入。
模拟一个微博登录,先读取登录的邮箱和口令,然后按照weibo.cn登录页的格式以username=xxx&password=xxx的编码传入,代码实现如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
from urllib import request, parse
def login_post():
print('Login to weibo.cn...')
email = input('Email: ')
passwd = input('Password: ')
login_data = parse.urlencode([
('username', email),
('password', passwd),
('entry', 'mweibo'),
('client_id', ''),
('savestate', '1'),
('ec', ''),
('pagerefer',
'https://passport.weibo.cn/signin/welcome?'
'entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
])
req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent',
'Mozilla/6.0 AppleWebKit/536.26 '
'(KHTML, like Gecko) Version/8.0'
' Safari/8536.25')
req.add_header('Referer',
'https://passport.weibo.cn/signin/login?'
'entry=mweibo&res=wel&wm=3349'
'&r=http%3A%2F%2Fm.weibo.cn%2F')
with request.urlopen(req, data=login_data.encode('utf-8')) as f:
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', f.read().decode('utf-8'))
def main():
login_post()
if __name__ == "__main__":
main()
执行该程序,并输入对应的email和password,若账户存在,则得到如下结果:
Status: 200 OK
Server: nginx/1.2.0
Date: Sat, 22 Oct 2016 08:38:37 GMT
......
Set-Cookie: SSOLoginState=1477125517; path=/; domain=weibo.cn
......
Data: {"retcode":20000000,"msg":"","data":{...,"uid":"3538172252"}}
若登录失败,则得到如下输出结果:
Status: 200 OK
Server: nginx/1.2.0
Date: Sat, 22 Oct 2016 08:46:04 GMT
......
Data:
{"retcode":50011015,"msg":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef","data":{"username":"test@ali
yun.com","errline":634}}
urllib提供的功能是利用程序执行各种HTTP请求。如果要模拟浏览器完成特定功能,就要把请求伪装成浏览器。伪装的方法是先监控浏览器发出的请求,再根据浏览器的请求头进行伪装,User-Agent头就是用来标识浏览器的。