网络编程是Python比较擅长的领域,Python不但内置了网络编程相关的库,而且与网络编程相关的第三方库也非常丰富,所以使用Python进行网络编程非常方便,Web应用程序、网络爬虫、网络游戏等常见的网络应用都可以使用Python进行开发。本章将介绍Python网络编程基础、内置的urllib库和第三方request库的详细使用方法。
11.1 网络编程基础
在日常生活中,我们使用的浏览器、PC上安装的应用程序、手机APP等都可以称为一个客户端。有了客户端,客户端就要获取资源,获取资源的方式就是通过网络向服务端发送Request请求,服务端接收到客户端的请求后,处理客户端请求,返回Response响应内容。例如,某个网站部署Web Server程序的服务器,就是一个服务端,用于接收和处理网络中不同客户端的请求。服务端相对于普通用户来说比较陌生,也不需要关心,但是对于程序员来说就非常熟悉了。
程序员在进行网络编程时,要按照客户端/服务端模型来开发,为了实现客户端和服务端在网络中能够正常通信,还要遵守HTTP网络传输协议。
- HTTP
HTTP(HyperText Transport Protocol)是超文本传输协议的缩写,在网络中两台计算机之间通信必须遵守HTTP协议。使用浏览器通过“http://www”的方式访问一个网站使用的就是HTTP协议。
- URL
URL(Uniform Resource Locator)是统一资源定位符的缩写,也就是我们熟悉的网址。在浏览器中通过输入URL地址可以访问到一个网站的资源。
- HTTP请求方式
HTTP常用的两种请求方式是GET请求和POST请求。这两种请求的区别,如下图所示。
通过对比我们可以发现,GET请求方式简单方便,但是随着请求发送的数据会暴露在URL中,安全性低,可传输数据量较少;POST请求方式相比GET请求稍微复杂一些,但是传输的数据更加隐蔽,安全性高,可传输数据量大。
- 状态码
当客户端向服务器发送HTTP请求后,服务器会返回给客户端一个Response响应内容,响应内容中会包含状态码,标志这个请求的返回状态。
常见的状态码如下:
- 200:表示请求成功,成功返回请求资源;
- 404:表示请求的资源不存在,例如:URL错误、访问的网页发生变更等;
- 500:服务器错误,有可能是服务器宕机,无法接受客户端请求。
除了列举的这几个常见的状态码,还有其他的一些状态码表示不同的响应状态,可以从网上查看相关资料继续学习,在此不过多介绍。
11.2 urllib库
urllib库是Python内置的HTTP请求库,如果成功安装了Python,则在使用时直接引入即可。urllib库使得HTTP请求变得非常方便,在程序中只需要设置访问的站点URL地址,就可以向站点发送请求,并且能够获取站点服务器返回的响应内容。urllib库内置的模块描述信息如下图所示。
下面通过具体示例分别讲解urllib库内各个模块内置函数的详细使用方法。
11.2.1 urllib.request.urlopen函数
urllib库中reaquest模块内置的urlopen函数用于向目标URL地址发送请求,返回一个HTTPResponse类型对象,通过该对象获取响应内容。
函数常用参数如下:
- url:目标URL访问地址,例如:“http://www.baidu.com”;
- data:默认值是None,表示以GET方式发送请求,如果改为以POST方式发送请求,需要传入data参数值;
- timeout:访问超时时间,有时由于网络问题或者目标站点服务器处理请求时间较长可能会产生超时异常,可以通过设置timeout参数延长超时时间。
- 发送GET请求
例11-1 以GET方式访问百度首页
案例代码如下:
import urllib
import requests
response = urllib.request.urlopen("http://www.baidu.com")
# response.read()获取网页内容,需要使用utf8编码对内容转码才能正常显示
print(response.read().decode("utf-8"))
解析:通过urlopen函数访问百度“http://www.baidu.com”,返回是百度首页的网页源代码。如果把这段源代码放在浏览器中运行,所显示的网页内容与我们在浏览器中输入网址访问百度显示的网页内容是相同的。
- 发送POST请求
例11-2 “http://httpbin.org”是一个用于测试各种HTTP请求的网站,以POST方式访问“http://httpbin.org/post”网站。
案例代码如下:
import urllib
import requests
# 与HTTP请求一起发送的数据
param_dist = {"key":"hello"}
# 调用urlencode函数将字典类型数据转换成字符串
param_str = urllib.parse.urlencode(param_dist)
# 将传输的数据封装成一个bytes对象
param_datas = bytes(param_str,encoding="utf8")
# 在urlopen函数中传入data参数值,表示将发送一个POST请求
response = urllib.request.urlopen("http://httpbin.org/post",data=param_datas)
# 打印网站返回的响应内容
print(response.read())
解析:从运行结果看,urlopen函数成功地向测试网站发送了POST请求,“http://httpbin.org”网站用于各种HTTP请求测试。与POST请求一起发送的数据要在网络间传输,所以在发送POST请求之前,调用bytes( )方法将这些参数封装到一个字节流对象中。bytes方法接收的第一个参数是字符串类型,调用urllib.parse模块中的urlencode函数将字典类型参数转换成字符串;第二个参数encoding用于指定编码格式,在这里表示以utf-8的方式将字符串转换成bytes对象。
网站服务器返回的响应内容中“form”对应的值就是刚才以POST方式向测试网站发送的数据,了解Web前端编程的读者对form表单应该比较熟悉,表明当以POST方式向测试网站发送请求时,会将数据以form表单提交的方式传输给测试网站服务器。
使用urlopen函数发送HTTP请求时,不传入data参数,表示以GET方式发送请求,传入data参数,表示以POST方式发送请求。
- 超时时间
在平常的开发过程中,有时由于网络延迟较高或者服务器处理能力有限等问题,导致客户端向服务器发送请求时间长就会产生超时,我们可以在程序中通过设置timeout参数值避免由于请求超时造成程序崩溃。
例11-3 设置请求超时时间
案例代码如下:
import urllib
import requests
# 设置timeout参数值是0.001秒
response = urllib.request.urlopen("http://httpbin.org/post",timeout=0.001)
# 打印网站返回的响应内容
print(response.read())
解析:设置urlopen函数的timeout参数值为0.001秒,网站服务器返回响应内容是time out请求超时,原因是我们设置的超时时间太短,服务器还没来得及响应。
- 响应状态码与头信息
例11-4 获取响应状态码
案例代码如下:
import urllib
import requests
response = urllib.request.urlopen("http://www.baidu.com")
# 获取状态码
print(response.status)
通过浏览器访问百度,在开发者工具中,查看状态码StatusCode。
例11-5 获取响应头信息
案例代码如下:
import urllib
import requests
# 获取整个响应头信息
print("headers:",response.getheaders())
# 获取头信息中的日期
print("header date:",response.getheader("Date"))
解析:通过getheaders函数可以获取全部的请求头信息,通过getheader函数可以获取头信息中指定的一条信息。
11.2.2 urllib.request.Request类
使用urlopen函数可以向目标URL地址发送基本的请求,但是参数比较简单,无法完成一些复杂的请求操作,比如在请求中添加headers头信息等。对于复杂的请求操作可以使用urllib库内置的Request类构建一个Request对象,给这个对象添加更丰富的属性值,完成复杂的请求操作。
例11-6 在Request对象中添加浏览器相关的头信息,将程序伪装成浏览器发送POST请求
案例代码如下:
解析:在字典类型变量headers中,通过“User-Agent”设置伪装的浏览器是Mac系统的谷歌浏览器,如果不设置User-Agent,那么默认的User-Agent值是“Python-urllib/3.6”。伪装成浏览器的原因是,防止通过程序发起的请求被网站拦截。通过parse模块的urlencode函数和bytes函数将字典类型数据转换成bytes字节流。
创建Request对象时,将已定义好的变量传入Request构造方法中,通过method=“POST”设置请求方式是POST请求。依然是通过urlopen函数接收Request对象发送HTTP请求。通过简单的几行代码,就将程序伪装成浏览器成功地发起了POST请求。
除了在创建Request对象时通过向构造方法中传入headers参数值设置header头信息,还可以调用Request对象的add_header方法,动态地添加header头信息,代码如下所示。
11.2.3 urllib.error异常处理模块
当发起一个HTTP请求时,可能会因为各种原因导致请求出现异常,比如请求的网页不存在或者请求的服务器无法响应等。为了提高程序的稳定性,需要捕获这些异常,还可以根据需要针对捕获到的异常做进一步的处理。
Python内置了异常处理模块urllib.error,在该模块中定义了两个常用的异常,分别是URLError和HTTPError,其中HTTPError是URLError的子类。下面我们分别讲解这两个异常。
- URLError
通过reason属性可以获取产生URLError的原因。
产生URLError的原因有两种:
- 网络异常,失去网络连接;
- 访问的服务器不存在,服务器连接失败。
例11-7 访问一个不存在的URL,捕获URLError异常
案例代码如下:
2. HTTPError
使用urlopen函数发送一个HTTP请求,会返回一个Response响应对象,该对象中包含一个响应状态码,如果urlopen函数不能处理,就会产生一个HTTPError异常,捕获到这个异常后,可以通过HTTPError内置的code属性获取响应状态码。
code属性的说明:
- code:通过该属性可以获取响应的状态码。
- reason:通过该属性可以获取产生HTTPError的原因。
- headers:通过该属性可以获取HTTP请求头信息。
例11-8 访问一个不存在的URL,捕获HTTPError和URLError,并获取异常原因、响应状态码、请求头信息
案例代码如下:
解析:从运行结果看,返回的响应状态码是404,表示访问的页面不存在。HTTPError是URLError的子类,如果两个异常全部捕获,需要先捕获HTTPError,再捕获URLError,否则将无法捕获到HTTPError。
11.3 requests库
requests库是基于urllib开发的HTTP相关操作库,相比直接使用urllib库更加简洁、更加易用。requests库是Python的第三方库,需要单独安装才能使用。
11.3.1 安装requests库
安装requests库步骤如下:
(1)使用pip或pip3命令安装requests库
(2)验证安装是否成功
进入Python交互模式,使用import引入requests库,然后通过requests内置的get方法访问百度,如果返回的状态码是200,则表示成功向百度发送了一次GET请求,同时也验证了requests库安装成功。
import requests
a = requests.get("http://www.lbxcrmyy.com")
print(a)
11.3.2 requests库基本使用方法
在11.2节中我们已经学习了urllib库的基本使用方法,有了urllib库的使用经验,再学习requests库的使用方法将会非常容易。本节将详细介绍如何使用requests库发起HTTP请求及相关操作。
我们知道使用urllib库的urlopen函数发起HTTP请求时,若不传入data参数,默认会采用GET请求方式,若传入data参数,将会采用POST请求方式。在requests库中为了更加清晰、更加方便地发送不同类型的请求,分别提供了发送GET请求的get函数,发送POST请求的post函数。使用这两个函数发送请求成功后,会返回一个requests.models. Response对象,通过Response对象内置的方法可以获取更多我们想要的信息。
- GET请求
使用requests库内置的get函数可以发起一次GET请求,通常在get函数中传入两个参数,第一个必选参数是要访问的URL;第二个可选参数是一个命名参数params,该参数的值会被传输到目标站点服务器。
例11-9 向测试地址“http://httpbin.org/get”发送GET请求
案例代码如下:
import requests
response = requests.get("http://httpbin.org/get")
print(f"response类型:{type(response)}")
print(f"状态码status_code={response.status_code}")
# 获取响应内容
print(response.text)
解析:访问的“http://httpbin.org/get”这个地址用于测试客户端发起的GET请求,当服务器端接收到客户端的GET请求后,会将状态码、响应内容、Cookies等信息返回给客户端,get函数将会根据服务端信息创建一个requests.models.Response对象,通过Response对象的status_code属性获取状态码,通过Response对象的text属性获取服务端返回的响应内容。
Response对象还有更多的属性可以获取到不同的数据,常用的几个属性介绍如下:
- content:获取二进制数据,例如:服务端返回的图片、视频等都是由二进制码组成的。
- url:获取请求的url。
- encoding:获取响应内容的编码格式。
- cookies:获取cookie信息。
- headers:获取响应头信息。
发送GET请求时,如果想传输一些数据,需要在URL后面添加一个问号“?”,在问号后面添加需要传输的数据,数据是以“key-value”键值对的形式组织的,多个数据之间用“&”分隔。
例11-10 向测试地址“http://httpbin.org/get”发送GET请求,同时传输两个参数“key=python”和“page=10”
案例代码如下:
import requests
response = requests.get("http://httpbin.org/get?key=python&page=10")
print(f"请求的url:{response.url}")
print(f"响应内容:{response.text}")
# 获取响应内容
print(response.text)
解析:从运行结果看,客户端成功地发送了一次GET请求,返回的响应内容中args对应的值就是通过GET请求传输到服务端的参数。
上边这个例子在URL中只添加了两个简单的参数,如果要添加更多的参数,就要把所有的参数拼接在URL字符串后面,每添加一个参数就要添加一个“&”分隔符,拼接起来非常麻烦,这样做会让URL的可读性变得很差。有一种更加简洁的方式,就是将待添加的参数以字典的形式存储,然后将参数字典赋值给get函数中的params参数。下面改造例11-10的代码。
import requests
data = {
"key":"python",
"page":10
}
response = requests.get("http://httpbin.org/get",params=data)
print(f"请求的url:{response.url}")
- POST请求
POST请求是除GET请求外另一种常见的请求方式,可以使用requests库内置的post函数发起一次POST请求,通常在post函数中传入两个参数,第一个必选参数是要访问的URL;第二个可选参数是一个命名参数data,该参数的值会被传输到目标站点服务器,但是不会像GET请求那样将参数拼接到URL之后,而是以form表单的形式提交到服务端。POST请求这种以提交form表单的方式传输数据的优势是不会将参数暴露在URL中,传输的数据量更大。
例11-11 向测试地址“http://httpbin.org/post”发送POST请求,同时传输“key=python”“version=3.6”和“page=10”3个参数
案例代码如下:
import requests
data = {
"key":"python",
"version":"3.6",
"page":10
}
response = requests.get("http://httpbin.org/get",data=data)
print(response.text)
解析:从运行结果看,客户端成功地发送了一次POST请求,在请求的URL后面没有拼接提交的参数。返回的响应内容中“form”有对应的字典数据,这就表示服务端成功地接收到了POST请求中表单提交的数据。
- 添加headers头信息
当使用程序模拟浏览器向服务器发送请求时,需要在请求中添加headers头信息。这样的设置常出现在爬虫程序中,爬虫程序用于从目标网站爬取想要的数据,目标网站为了防止爬虫程序频繁地爬取网站数据,通常会将非浏览器发送的请求拦截下来,这时候爬虫程序就需要伪装成浏览器向网站发送请求,爬取有用的数据。
例11-12 编写爬虫程序,在京东商城搜索关键字“手机”,将搜索结果网页爬取下来
案例代码如下:
解析:本例使用requests库实现了一个简单的爬虫程序,实现过程是通过向京东商城搜索地址“https://search.jd.com/Search”发送GET请求,获取“手机”搜索结果网页源代码。由于返回的源代码比较多,在运行结果中省略了很多内容,这里只截取了一小部分源代码来说明问题。为了将爬虫程序伪装成浏览器,在get函数中添加了headers参数,这样京东的服务器接收到这个请求时,就会认为这个请求是浏览器发出的,于是将搜索结果返回给爬虫程序。在spider_jd函数获取搜索结果网页源代码的部分,先通过Response对象的status_code属性获取状态码,判断status_code是否等于200(status_code=200表示请求成功)。请求成功后通过Response对象的encoding属性获取响应内容的编码格式,获取到的编码格式是“ISO-8859-1”,这种编码格式的数据中如果包含中文,打印输出的时候就会出现乱码,为了解决中文乱码问题,可以重新设置enconding编码格式为“urf-8”,最后通过Response对象的text属性获取网页源代码。