Python中的urllib模块(第4节)


1、urllib模块简介

Python内置封装了很多常见的网络协议的模块,因此Python成为了一个强大的网络编程工具。除了前面教程中介绍的Socket模块,在Python标准库中还有很多网络设计相关的模块,其中,urllib模块用于打开和读取URL、发送请求以及解析响应,它被广泛用于网络请求和数据获取,允许你以编程方式从网络获取数据,如网页内容、文件等。

urllib模块包括4个子模块,每个子模块的功能各不相同:

urllib.request模块:最基本的HTTP请求模块,可以用来发送HTTP请求,并接收服务端的响应数据。这个过程就像在浏览器地址栏输入URL地址,然后按Enter键一样。

urllib.error模块:异常处理模块,如果出现请求错误,我们可以捕获这些异常,然后根据实际情况,或者进行重试,或者直接忽略,或进行其他操作。

urllib.parse模块:是一个工具模块,提供了许多URL处理方法,比如拆分、解析、合并等。

urllib.robotparser:主要用来识别网站的robots.txt文件,然后判断哪些网站可以抓取,哪些网站不可以抓取。robots.txt是一个文本文件,它是搜索引擎访问网站时要查看的第一个文件,是用于规定搜索引擎对网站内容抓取的范围。当一个搜索蜘蛛访问一个站点时,它会首先检查该站点根目录下是否存在robots.txt,如果存在,则会按照文件中的内容来确定抓取的范围。

在网络通信中,浏览器与服务器之间进行请求响应时,最常见的两种HTTP请求是GET请求POST请求。GET请求用于从服务器获取资源,而POST请求用于向服务器提交数据。

2、GET请求

在前面的教程中,我们已经简单介绍了HTTP协议,它是超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议。通常,浏览器向服务器提交HTTP请求时,服务器收集到请求后会向浏览器返回响应(响应包含请求的状态信息以及被请求的内容)。

GET请求是HTTP协议的一种请求方式,用于从Web服务器获取数据。它是一种简单的方式,可以通过浏览器的地址栏发送请求。当您在浏览器的地址栏中键入URL并按下回车键时,浏览器就会发送一个GET请求,然后服务器会将请求的响应返回给浏览器。

GET请求的数据传输是通过URL传递的,请求的参数直接附加在URL的后面,以查询字符串的形式传递给服务器,因此数据是明文传输的,可以在URL中直接看到。例如:

https://example.com/api/users?id=1

比如,我们打开任意一个浏览器用百度搜索,在百度搜索框中搜索“Python”,浏览器的URL地址栏里就会出现有效的URL为:https://www.baidu.com/s?wd=Python&rsv_spt=1,其中参数“wd”后面出现的字符串“Python”就是要通过GET请求的数据,并且以“?”号开始,“wd”和“rsv_spt”代表参数,用“&”符号将多个参数连接在一起。

urllib模块最基本的一个应用就是向服务端发送HTTP请求,然后接收服务端返回的响应数据。这个功能只需要通过urlopen()函数就可以搞定。例如,下面的代码向百度发送HTTP的GET请求,然后输出服务端的响应结果。例如:

动手练一练:

from urllib import request

def fetch_baidu():
    response = request.urlopen("http://www.baidu.com")
    content = response.read()
    print('响应状态码为:', response.status)
    print('响应状态码为:', response.getcode())
    print('响应头所有信息为:', response.getheaders())
    print('响应头指定信息为:', response.getheader('Server'))

    response.close()
    return content.decode("utf-8")

def save_page(content):
    with open("D:\\baidu.txt", "w", encoding="utf8") as f:
        f.write(content)

def main():
    content = fetch_baidu()
    save_page(content)

if __name__ == "__main__":
    main()

执行以上代码,输出结果为:

响应状态码为 200
响应状态码为 200
响应头所有信息为 [('Bdpagetype', '1'), ('Bdqid', '0xb90b484e002c04ba'), ('Content-Length', '402457'), ('Content-Type', 'text/html; charset=utf-8'), ('Date', 'Sat, 22 Jun 2024 04:50:20 GMT'), ('Server', 'BWS/1.1'), ('Set-Cookie', 'BIDUPSID=725279B30F37CD574767E9BEE1C1D2C6; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2549488636; path=/; domain=.baidu.com'), ('Set-Cookie', 'PSTM=1717231880; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'H_PS_PSSID=60425_66338_607319_60936; path=/; expires=Sun, 22-Jun-25 04:50:20 GMT; domain=.baidu.com'), ('Set-Cookie', 'BDSVRTM=3; path=/'), ('Set-Cookie', 'BD_HOME=1; path=/'), ('Set-Cookie', 'BAIDUID=769279B30F37CD57476279B30F1C1D2C6:FG=1; Path=/; Domain=baidu.com; Max-Age=3162800'), ('Set-Cookie', 'BAIDUID_BFESS=725135B30F37CD574767E9BEE1C1D2C6:FG=1; Path=/; Domain=baidu.com; Max-Age=3162800; Secure; SameSite=None'), ('Traceid', '1319031820260454492214633830621625019130'), ('Vary', 'Accept-Encoding'), ('X-Ua-Compatible', 'IE=Edge,chrome=1'), ('X-Xss-Protection', '1;mode=block'), ('Connection', 'close')]
响应头指定信息为 BWS/1.1

上面的例子中,我们使用urllib.request模块的urlopen()方法,可以完成最基本的简单网页的GET请求抓取。其中,response对象是http.client.HTTPResponse类型,利用read()方法可以读取整个网页的源代码,最后把网页的源代码成功保存到D盘的baidu.txt文件中。status属性和getcode()方法都可以获得返回结果的状态码,如200代表请求成功,404代表网页未找到等。getheaders()方法用于查看响应头所有信息。getheader()中传入参数,用于查看响应头的指定信息。我们通过调用getheader()方法并传递一个参数Server获取了响应头中的Server值,包含服务器的信息、名称、版本号等。

3、POST请求

GET方法POST方法是两种最基本的HTTP请求方法,前面已经介绍了GET方法是用于从Web服务器请求数据,而POST方法则用于向Web服务器提交数据。POST请求是HTPP协议中一种常用的请求方法,它的使用场景是客户端向服务器提交数据,比如登录、注册新用户、添加数据等场景。在使用POST方法时,浏览器将数据打包并发送到Web服务器。Web服务器收到数据后,可以根据数据执行相应的操作,并向浏览器发送响应。

urllib.request模块同样提供了发送POST请求方法。urllib.request.urlopen()函数接受一个URL字符串和一个包含表单数据的字典作为参数,注意该参数必须通过encode()转换成bytes字节类型传入,最后返回一个响应对象。例如:

动手练一练:

import json
import pprint
from urllib import request, parse

# 将dict字典类型转化为str字符串
post_data = parse.urlencode({"Pyhint" : "test"})
# 传入URL和数据,发送请求并获取响应 
response = request.urlopen("http://httpbin.org/post", data=post_data.encode("utf-8"))
#调用read()方法可以获取最原始的信息
content = response.read()
# json.loads()函数将JSON字符串转换成字典
content = json.loads(content)
# pprint()函数采用分行打印输出
pprint.pprint(content)

执行以上代码,输出结果为:

{'args': {},
'data': '',
'files': {},
'form': {'Pyhint': 'test'},
'headers': {'Accept-Encoding': 'identity',
'Content-Length': '11',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'httpbin.org',
'User-Agent': 'Python-urllib/3.8',
'X-Amzn-Trace-Id': 'Root=1-67895c9e-4795c9eec795c9e0b4efeaa4efe5'},
'json': None,
'origin': '170.56.138.155',
'url': 'http://httpbin.org/post'}

上面的例子中,我们传递了一个字典内容{"Pyhint" : "test"},需要用urllib.parse模块里的urlencode()方法来将参数字典转化为字符串,然后通过encode()函数转码成bytes(字节流)类型。

在这里我们发送请求的站点是httpbin.org,它可以提供HTTP请求测试,本次我们请求的URL为:http://httpbin.org/post,这个链接可以用来测试POST请求,它可以输出Request的一些信息,其中就包含我们传递的data参数。

request.urlopen()的功能有限,如果我们想为请求头部添加更多的设置信息,如“User-Agent”向要访问的网站提供用户所使用的浏览器类型及版本、操作系统及版本、浏览器内核等信息的标识,那么用request.urlopen()是无法实现的。

所以,在此基础上,我们可以使用request.Request()函数构造Request实例,它的功能与request.urlopen()类似,可以打开网页,但比request.urlopen()的功能更强大,它可以为请求头部自定义信息,无限扩展功能。例如:

动手练一练:

import json
import pprint
from urllib import request, parse

# 将dict字典类型转化为str字符串
post_data = parse.urlencode({"Pyhint" : "test"})
# requset.Request()构造了一个Request实例,其中可以包含大量header内容
response = request.Request("http://httpbin.org/post")
#添加一个User-Agent的Header信息
response.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
# 通过encode()函数将str转化为服务器接收的bytes类型
response.data = post_data.encode("utf-8")
#最后用urlopen()打开该实例即可
with request.urlopen(response) as s:
   content = s.read()
   content = json.loads(content)
   pprint.pprint(content)

执行以上代码,输出结果为:

{'args': {},
'data': '',
'files': {},
'form': {'Pyhint': 'test'},
'headers': {'Accept-Encoding': 'identity',
'Content-Length': '11',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'httpbin.org',
'User-Agent': 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS '
'X) AppleWebKit/536.26 (KHTML, like Gecko) '
'Version/8.0 Mobile/10A5376e Safari/8536.25',
'X-Amzn-Trace-Id': 'Root=1-66795c9e-4795c9eec795c9e0b4efeaa4efe5'},
'json': None,
'origin': '180.36.156.148',
'url': 'http://httpbin.org/post'}

上面的例子中,通过使用request.Request()函数为HTTP请求自定义User-Agent头部信息,并且可以无限扩展功能,模拟浏览器访问服务器,不仅如此,对于一些爬虫应用中的身份隐藏等操作,也只需在headers中设置即可。

4、利用urllib模块模拟POST请求

我们首先进入百度翻译首页https://fanyi.baidu.com,输入英文单词“Girl”,就会自动翻译成中文“女孩”。这里我们可以利用request.urlopen()函数模拟浏览器访问服务器的POST请求方法,在输出的Request信息中,就可以看到翻译结果。例如:

动手练一练:

import json
import pprint
from urllib import request, parse

# 将dict字典类型转化为str字符串
post_data = parse.urlencode({"kw" : "Girl"})
#构造request对象
response = request.urlopen("https://fanyi.baidu.com/sug", data=post_data.encode("utf-8"))
#调用read()方法可以获取最原始的信息
content = response.read()
# json.loads()函数将一个JSON字符串转换成了字典
content = json.loads(content)
# pprint()函数采用分行打印输出
pprint.pprint(content)

执行以上代码,输出结果为:

{'data': [{'k': 'girl', 'v': 'n. 女孩; 姑娘,未婚女子; 女职员,女演员; (男人的)女朋友'},
{'k': 'GIRL',
'v': 'abbr. Generalised Information Retrieval Language <'},
{'k': 'girls', 'v': 'n. 女孩; 女儿( girl的名词复数 ); 女工; (男人的)女朋友'},
{'k': 'GIRLS',
'v': 'abbr. Generalized Information Retrieval and Listin'},
{'k': 'girly', 'v': 'adj. <非正>(杂志或图片)以表现裸体女子为特色的'}],
'errno': 0,
'logid': 2133138084}

5、GET请求和POST请求的区别

GET请求POST请求的主要区别在于数据的传输方式和安全性:

GET请求:用于从服务器获取数据,一般用于获取资源或查询数据,比如获取网页内容、图片等。GET请求通过URL参数传递数据,数据以明文形式出现在URL中,并以键值对的形式传递给服务器,因此不适合传输敏感信息。例如:

GET /api/users?id=1 HTTP/1.1
Host: example.com

POST请求:通常用于向服务器提交数据,比如用户登录、提交表单等,通过请求体传递数据,并通过请求头中的Content-Type指定数据格式,数据以密文形式传输,因此更安全,适合传输敏感信息。例如:

POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json

{"username": "user", "password": 123456}

总之,GET请求适合用于获取资源,而POST请求适合用于提交数据。在实际的程序开发中,我们需要根据具体的需求选择合适的请求方式。