Urllib库是python自带的发送网络请求库,可以满足日常接口请求接收响应数据任务,在实际场景中发挥出它的价值还需要对这个库做一些了解,才能熟练使用它完成我们的需求,关于urllib库的使用都在这篇文章中进行总结。
通过发送一个get请求,获取响应内容的示例快速掌握urllib库使用
import urllib.request
# 定义访问的url地址
url = 'http://www.baidu.com'
# 模拟浏览器向服务器发送请求,获取响应response
'''
urlopen 返回类型是 http.client.HTTPResponse 类对象
'''
response = urllib.request.urlopen(url)
# 获取响应中页面的源码
content = response.read()
print(content)
运行get请求查看返回结果
b'查看返回的数据发现开头有个b字母,这个代表当前数据是二进制的字节码,是将字符串编译后给机器看的,中文显示格式为\xe5\x85。
这是因为read()函数返回的数据就是一个字节码,需要解码转为字符串后才会显示中文。可以使用decode()函数解码,解码时输入它的字符编码要和返回信息的编码一致中文才不会显示乱码。
如何知道使用什么编码那,在返回数据的请求头都会显示它的编码字符集,例如上面返回结果数据的第一行charset=utf-8 就是他的字符集,使用它来解码。
# 获取响应中页面的源码
'''
read()返回的信息是字节码,通过decode()函数将字节码转为字符串
'''
content = response.read().decode('utf-8')
查看字节码解码为字符串运行结果
<!DOCTYPE html><!--STATUS OK--><html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta content="always" name="referrer"><meta name="theme-color" content="#ffffff"><meta name="description" content="全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。"><link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /><link rel="search" type="application/opensearchdescription+xml" href="/content-search.xml" title="百度搜索" />
请求返回的数据中开头的b已经不在了,说明现在是字符串类型,再看中文已经正确显示。现在使用urllib库完成了get请求。
上面的示例中已经使用read函数获取了响应数据,下面介绍下read函数的其他使用方式。
# read函数默认是按照一个字节一个字节读取数据
content = response.read().decode('utf-8')
print(content)
# 获取指定长度的字节
content = response.read(5)
print(content)
# 结果输出5个字节
b'
# 读取一行数据
content = response.readline()
print(content)
# 读取多行数据
content = response.readlines()
print(content)
# 返回状态码
print(response.getcode())
# 返回url地址
print(response.geturl())
# 获取header头
print(response.getheaders())
如果我们需要将返回数据中的文件、图片、视频等下载到本地,使用urlretrieve()函数可以下载我们需要的内容。
# 下载图片
url_img = 'http://pic3.zhimg.com/v2-a08aad3a5f85058844f4237919449ee4_r.jpg?source=172ae18b'
# url:下载文件路径 filename:保存文件的名称
urllib.request.urlretrieve(url=url_img, filename='cat.jpg')
# 下载视频
url_video = 'https://tv.sohu.com/v/dXMvMTY0NzEzNTc3LzU2NzEwOTQ5LnNodG1s.mp4'
urllib.request.urlretrieve(url=url_video, filename='xz.mp4')
为什么要构建请求对象那?
当我们发送请求爬取数据的时候相对的就会有反爬虫策略防止网站数据被爬取。当我们遇到反爬策略时还想爬取到数据就需要根据反爬策略做出应对的办法,构建请求对象就是一种应对反爬策略的办法。下面通过一个例子感受下通过构建请求对象如何攻破反爬策略。
下面是一个访问百度网站请求,但是这次访问地址协议由http改为了https协议,我们看下会发生什么。
import ssl
import urllib.request
# Mac系统会校验ssl证书,通过全局取消ssl证书验证避免报错
ssl._create_default_https_context = ssl._create_unverified_context
url = 'https://www.baidu.com'
response = urllib.request.urlopen(url)
content = response.read().decode('utf-8')
print(content)
运行示例查看结果
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
查看结果返现访问百度没有返回正确的信息,这个就是百度的反爬策略,下面来详细介绍下这个策略以及如何攻破这个策略拿到数据。
在介绍UA策略前先普及下url的组成,它有6个部分组成。
https://www.baidu.com/s?wd=周杰伦
当我们使用https协议访问百度时,它会检测请求端的UA是否包含正常浏览器发起请求信息,如果不包含则认为是爬虫就会拒绝访问。
那么什么是UA那,我们要怎么构建一个合格的UA突破反爬那,下面介绍将解决这些疑问。
什么是UA
UA是User Agent中文名为用户代码,简称UA。 他是一个特殊的字符串,向服务器提交当前客户端使用的操作系统及版本、CPU类型、浏览器内核及版本号等。
查看当前浏览器的UA
在浏览器上右键打开检查功点击点击网络标签,然后访问一个网站,在标头的最下面就可以看到UA信息。
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8
构建请求头携带UA请求网站
当我们找到了浏览器的UA信息后就可以用它来构建我们的请求对象,让它带着UA信息再次访问网站。
import ssl
import urllib.request
# 全局取消ssl证书验证
ssl._create_default_https_context = ssl._create_unverified_context
url = 'https://www.baidu.com'
# 将UA信息放到headers字典中
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
# 构建请求对象
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
运行示例查看结果
<!DOCTYPE html><!--STATUS OK--><html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta content="always" name="referrer"><meta name="theme-color" content="#ffffff">
<meta name="description" content="全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。">
...
...
在运行结果中可以看到我们攻破了UA反爬策略拿到了百度的数据。
当我们使用urllib库发送网络请求,主要包含两个内容请求数据和响应数据。这两个数据的中文如果希望服务器能正确处理,不出现乱码那么就会与字符编码表打交道,下面就来介绍下如何使用字符编码表处理中文。
下面通过一个例子介绍如何使用编码表处理请求中的中文。
首先使用浏览器打开百度搜下字符编码,点击搜索。在浏览器的地址栏就能看到搜索的内容,将它复制到pycharm编辑器中。

当我们复制到编辑器后发现中文变成了乱码,然后我们将乱码部分改为我们要搜索的中文。
# 复制后的url
https://cn.bing.com/search?q=%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81
# 修改后的url
https://www.baidu.com/s?wd=字符编码
使用修改后的url发送请求代码,看看是否能通过。
url = 'https://cn.bing.com/search?q=字符编码'
# 将UA信息放到headers字典中
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
# 构建请求对象
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
运行示例查看结果
UnicodeEncodeError: 'ascii' codec can't encode characters in position 14-17: ordinal not in range(128)
运行结果提示不能解析characters,它不在ascii 编码表 range(128)范围之内。
上面的例子运行结果抛出的异常中告诉我们中文不在ascii编码表范围之内,这个异常是什么意思那?下面先普及下字符编码表再来揭晓答案。
由于计算机早起是在美国诞生的,因此只有127个字符被编码到计算机中。这个编码就被定义为ASCII编码表,在表中只有大小写字母,数字和一些符号。
随着计算机的普及,只有127个字符的编码表已经不能满足各个国家的需求,因此每个国家都制定了自己语言的编码表,中国制定了GB2312,但是因为各个编码表不通用,因此Unicode应用而生,它把所有语言都统一到一套编码表里,这样就不会再出现乱码了。
UTF,是UnicodeTransformation Format的缩写,意为Unicode转换格式。UTF-8是UNICODE的一种变长字符编码,由Ken Thompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码UNICODE字符。
了解了字符编码表后上面遇到的异常就能解释了,当我们将浏览器中含有中文的url复制到pycharm工具中编程了乱码,这是因为在浏览器中网页使用的是utf-8编码表,而pycharm使用ASCII编码表来解码遇到中文自然就会出现乱码。
当我们将URL乱码改为中文发送请求那么ASCII编码表因为不能在127个编码中对中文解码因此抛出了异常。
现在知道了请求异常的原因后,我们可以通过编码表对中文进行转码,再发送请求解决我们的问题。
将中文转为ASCII编码
urllib库为我们提供了编码的函数,通过urllib.parse.quote()函数将中文编码为ASCII编码字符,下面是将中文转码的例子。
# 通过urllib.parse.quote()函数将中文编码为ASCII编码字符
name = urllib.parse.quote('字符编码')
print(name)
# 运行结果
%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81
# 将输出的结果和复制过来的url编码对比他们是一致的。
url = 'https://cn.bing.com/search?q=%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81'
使用转码后的字符发送请求,看看运行结果。
import ssl
import urllib.request
# 全局取消ssl证书验证
ssl._create_default_https_context = ssl._create_unverified_context
url = 'https://cn.bing.com/search?q='
name = urllib.parse.quote('字符编码')
url = url + name
# 将UA信息放到headers字典中
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
# 构建请求对象
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
运行示例,查看返回结果返回了请求信息。
<!DOCTYPE html><html dir="ltr" lang="zh" xml:lang="zh" xmlns="http://www.w3.org/1999/xhtml" xmlns:Web="http://schemas.live.com/Web/"><script type="text/javascript" nonce="VnKdTkMMGjdfKEp3CTC1udDuwBYGrOuXYMoe4JWtpDc=" >//<![CDATA[
si_ST=new Date
如果一个请求连接中有个多个参数且为中文,那么我们是不是要将所有中文参数都转换一遍那,如果好几十个都需要调用下urllib.parse.quote()函数转换,再拼接url是不是很麻烦。下面我们就来介绍一个新的解决中文参数方法。
批量转换Unicoe码
urlencode()函数接收一个字典对象,将需要转码的中文参数放到字典中,然后批量转换字符编码。
import urllib.parse
data = {
'wd': '周杰伦',
'sex': '男',
'location': '中国台湾省'
}
param = urllib.parse.urlencode(data)
print(param)
# 结果
wd=%E5%91%A8%E6%9D%B0%E4%BC%A6&sex=%E7%94%B7&location=%E4%B8%AD%E5%9B%BD%E5%8F%B0%E6%B9%BE%E7%9C%81
从运行的结果中可以看出它将多个中文参数转为Unicode码,同时将多个参数使用&符号拼接
下面使用这个函数发送请求批量转换多个中文参数
# !/usr/bin/env python3
# -*-coding: UTF-8 -*-
'''
@Author :Long
@Date :2022/10/21 11:14
'''
import ssl
import urllib.request
import urllib.parse
# 全局取消ssl证书验证
ssl._create_default_https_context = ssl._create_unverified_context
#url地址
url = 'http://www.baidu.com/s?'
data = {
'wd': '周杰伦',
'sex': '男',
'location': '中国台湾省'
}
# url参数中文转为unicode字符编码
param = urllib.parse.urlencode(data)
# 拼接URL
url = url +param
# 将UA信息放到headers字典中
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
# 构建请求对象
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
post请求和get请求最大的区别就是参数部分,post请求的参数不在url后面拼接,而是放在body中。
下面通过一个例子展示使用urllib库发送post请求。
# !/usr/bin/env python3
# -*-coding: UTF-8 -*-
'''
@Author :Long
@Date :2022/10/21 19:02
'''
import json
import ssl
import urllib.request
import urllib.parse
# 全局取消ssl证书验证
ssl._create_default_https_context = ssl._create_unverified_context
# 请求url地址
url = 'https://fanyi.baidu.com/sug'
# 请求headers
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
# post请求数据
data = {
'kw': 'spider'
}
# urlencode函数将请求数据转为ASCII码
# post请求的body数据必须为字节码,因此这里将数据编码为字节码
data = urllib.parse.urlencode(data).encode('utf-8')
# 构建请求对象,参数包含url、请求数据、headers
request = urllib.request.Request(url=url, data=data, headers=headers)
# 模拟浏览器发送请求
response = urllib.request.urlopen(request)
# 获取响应数据,将字节符转为字符串
content = response.read().decode('utf-8')
print(content)
urllib发送post请求的例子写好了,下面运行看看结果。
{"errno":0,"data":[{"k":"spider","v":"n. \u8718\u86db; \u661f\u5f62\u8f6e\uff0c\u5341\u5b57\u53c9; \u5e26\u67c4\u4e09\u811a\u5e73\u5e95\u9505; \u4e09\u811a\u67b6"},{"k":"Spider","v":"[\u7535\u5f71]\u8718\u86db"},{"k":"SPIDER","v":"abbr. SEMATECH process induced damage effect revea"},{"k":"spiders","v":"n. \u8718\u86db( spider\u7684\u540d\u8bcd\u590d\u6570 )"},{"k":"spidery","v":"adj. \u50cf\u8718\u86db\u817f\u4e00\u822c\u7ec6\u957f\u7684; \u8c61\u8718\u86db\u7f51\u7684\uff0c\u5341\u5206\u7cbe\u81f4\u7684"}]}
运行结果显示post请求成功返回数据,但是发现中文不能正确显示都是\u5e26格式。观察下这个返回内容是不是一个json格式,那么在打印下他的格式。
# 打印类型
print(type(content))
# 结果
<class 'str'>
输出结果显示内容是str字符串类型,那么我们将它转为json格式
# str 转 json
content_json = json.loads(content)
print(content_json)
运行代码查看结果
{'errno': 0, 'data': [{'k': 'spider', 'v': 'n. 蜘蛛; 星形轮,十字叉; 带柄三脚平底锅; 三脚架'}, {'k': 'Spider', 'v': '[电影]蜘蛛'}, {'k': 'SPIDER', 'v': 'abbr. SEMATECH process induced damage effect revea'}, {'k': 'spiders', 'v': 'n. 蜘蛛( spider的名词复数 )'}, {'k': 'spidery', 'v': 'adj. 像蜘蛛腿一般细长的; 象蜘蛛网的,十分精致的'}]}
在上面的学习中为了攻破UA反爬机制,我们使用urllib.request.Request()定制请求信息完成请求。这些定制的参数都是固定不变的,当发送请求遇到动态参数例如token、cookie每次请求都会变化的数据就不能用这种方式定制请求。
因此我们需要用Handler处理器定制动态参数请求头
下面通过一个简单示例,完成一个post请求了解下handler基本使用。
import urllib.request
import ssl
# 全局取消ssl证书验证
ssl._create_default_https_context = ssl._create_unverified_context
url = 'https://fanyi.baidu.com'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
request = urllib.request.Request(url=url, headers=headers)
# 构建handler对象 (handler、build.opener、open)
# 获取handler对象
handler = urllib.request.HTTPHandler()
# 获取opener对象
opener = urllib.request.build_opener(handler)
# 获取open方法,发送请求
response = opener.open(request)
#读取响应数据
content = response.read().decode('utf-8')
print(content)
在爬虫过程中可能会遇到IP封锁这也是一个反爬虫策略,当检测到同一个IP在短时间内重复方法网站时就可以对这个IP封锁,禁止它访问,以此来达到防止爬虫作用。下面将通过Handler实现代理方式攻破这个反爬虫策略。
import urllib.request
import ssl
# 全局取消ssl证书验证
ssl._create_default_https_context = ssl._create_unverified_context
url = 'https://www.baidu.com/s?wd=ip'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
request = urllib.request.Request(url=url, headers=headers)
# 设置代理访问地址
proxies = {
'http':'223.96.90.216:8085'
}
# 创建代理处理器
handler = urllib.request.ProxyHandler(proxies=proxies)
# 获取opener对象
opener = urllib.request.build_opener(handler)
# 通过代理地址发送请求
response = opener.open(request)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
with open('dali.html', 'w', encoding='utf-8') as fp:
fp.write(content)
当我们使用一个代理IP地址访问同一个网站,数次请求后也会被封,所以我们需要使用许多的代理IP访问,这个就是代理池。
import urllib.request
import ssl
# 全局取消ssl证书验证
ssl._create_default_https_context = ssl._create_unverified_context
url = 'https://www.baidu.com/s?wd=ip'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.8'
}
request = urllib.request.Request(url=url, headers=headers)
proxies_pool = [
{'http':'223.96.90.216:8085'},
{'http':'223.96.90.216:8085'}
]
# 随机选择代理池中的IP
import random
proxies = random.choice(proxies_pool)
handler = urllib.request.ProxyHandler(proxies=proxies)
# 获取opener对象
opener = urllib.request.build_opener(handler)
# 获取open方法
response = opener.open(request)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
with open('dali.html', 'w', encoding='utf-8') as fp:
fp.write(content)