一、使用urllib
urllib库的四大模块:
- urllib.request:最基本的HTTP请求模块,可以用来模拟请求
- urllib.error:异常处理模块
- urllib.parse:一个工具模块,提供了许多URL处理方法,比如拆分、解析、合并等
- urllib.robotparser:用来识别网站的robots.txt文件
(1)发送请求
1、urlopen()
import urllib.request
response = urlib.request.urlopen("https://www.python.org")
print(response.read().decode("utf-8"))
# 编码和解码:
# decode():解码,将括号里的格式解码成unicode编码格式
# encode():编码,将unicode格式的数据编码成括号里的格式
import urllib.request
response = urllib.request.urlopen("https://www.python.org")
print(type(response))
# 结果:<class 'http.client.HTTPResponse'>
根据上面代码的结果分析:它是一个HTTPResponse类型的对象。
主要包含了:
read()、readinto()、getheader(name)、getheaders()、fileno()等方法
msg、version、status、reason、debuglevel、closed等属性
import urllib.request
response = urllib.request.urlopen("https://www.python.org")
print(response.status) # 获取状态码
print(response.getheaders()) # 获取响应头的全部信息
print(response.getheader('Server')) # 获取响应头的指定信息
urlopen()方法,可以完成最基本的GET请求
1.1 data参数
data参数是可选的。
如果要添加该参数,必须将参数进行转码,转为字节型数据
如果使用data参数传递,则请求方式就变为了POST请求方式,不再时GET请求方式
import urllib.parse
improt urllib.request
data = urllib.parse.urlencode({"world": "Hello"})
data = bytes(data, encodeing="utf8")
response = urllib.request.urlopen("http://httpbin.org/post",
data=data)
print(response.read().decode("utf-8"))
'''
结果:
{
"args": {},
"data": "",
"files": {},
"form": {
"world": "Hello"
},
"headers": {
"Accept-Encoding": "identity",
"Content-Length": "11",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Python-urllib/3.6",
"X-Amzn-Trace-Id": "Root=1-601527bc-6cc1b04e0aa3fd946d2f48a6"
},
"json": null,
"origin": "101.27.236.254",
"url": "http://httpbin.org/post"
}
'''
传递的参数在结果中的from字段中,这表明是模拟了表单的提交方式,以POST方式传输数据。
1.2 timeout参数
用于设置超时时间的参数,单位是秒。
如果请求超出了设定的时间,还没有得到响应,就会抛出异常。如果不指定该参数就会使用全局默认时间。
import urllib.request
response = urllib.request.urlopen("http://httpbin.org/get",
timeout=0.1)
print(response.read())
'''
结果:
urllib.error.URLError: <urlopen error timed out>
'''
上面代码是在请求的时候设置了超时时间为0.1秒。这个请求在0.1秒内并没有得到任何响应。于是抛出了URLError异常,这异常属于urllib.error模块,错误原因是超时。
知道这个参数的使用。因此可以通过设置这个超时时间来控制一个网页,如果长时间未响应,就跳过它的抓取。可以使用try except语句来实现:
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen("http://httpbin.org/get",
timeout=0.1)
except urllib.error as e:
if isinstance(e.reason, socker.timeout):
print("TIME OUT")
'''
结果:TIME OUT
isinstance(object, classinfo):判断object是否是属于classinfo
e.reason:也就是urllib.error下面的reason属性是抛出异常的原因
if语句是判断:该异常原因是否属于socket.timeout。如果是就打印TIME OUT
经过if判断从而得出确实是因为超时而报错的
'''
1.3 其他参数
context:必须是 ssl.SSLContext类型 , 用来指定SSL设置
cafile:指定CA证书
capath:指定CA证书的路径
CA证书在请求HTTPS链接时会有用
2、Requst
前面利用urlopen()方法即可进行简单的请求,但是如果在请求中加入Headers的就需要使用Request()方法来构建了
import urllib.request
request = urllib.request.Request("https://python.org")
response = urllib.request.urlopen(request)
print(response.read().decode("utf-8"))
看上面的代码。还是依旧使用了urlopen()方法,但是之前请求的是一个链接,现在请求的是一个Request类型的对象。
class urllib.request.Request(url,
data=None,
headers={},
origin_req_host=None,
unverifiable=False,
method=None
)
url:用于请求的URL,必传参数,其他参数都是可选参数
data:必须传bytes(字节流)类型的。如果是字典,可以使用urllib.parse模块里的
urlencode()方法编码
headers:请求头
origin_req_host:请求方的host名称和IP地址
unverifiable:是否无法验证的,默认是False。也就是说用户没有足够权限来选择接
接受这个请求的结果。
例如:我们请求一个HTML文档中的图片,但是没有自动抓取图片的权
限,这时的unverifiable的值就是True。
method:请求方式,例如GET、POST、PUT等
from urllib import request, parse
url = "http://httpbin.org/post"
headers = {
"User-Agent": "此处省略,
"Host": 'httpbin.org'
}
dict = {
"name": "Germey"
}
data = bytes(parse.urlencode(dict), encoding="utf-8")
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
当已经设置了headers还可以通过add_header()方法来添加:
from urllib import request, parse
url = "http://httpbin.org/post"
headers = {
'Host': 'httpbin.org'
}
dict = {'name', 'Germey'}
data = bytes(parst.urlencode(dict), encoding='utf-8')
req = resquest.Request(url=url, data=data, headers=headers,
method='POST')
req.add_header('User-Agent', '此处省略')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
3、高级用法
HTTPDefaultErrorHandler:处理HTTP响应错误,会抛出HTTPError类型的错误
HTTPRedirectHandler:用于处理重定向
HTTPCookieProcessor:用于处理cookies
ProxyHandler:用于设置代理
HTTPPasswordMgr:用于管理密码
HTTPBasicAuthHandlers:用于管理认证
认证
from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError
'''
HTTPPasswordMgrWithDefaultRealm:用于密码管理的
HTTPBasicAuthHandler:用于认证的
build_opener:用于创建OpenerDirector对象用的
'''
username = '***'
password = '***'
url = 'https://qzone.qq.com/'
p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
# 第一个参数是realm参数,是与远程服务器相关的参数,一般都是None
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)
try:
result = opener.open(url)
html = result.read().decode("utf-8")
print(html)
except URLError as e:
print(e.reason)
实例化了一个HTTPBasicAuthHandler对象,其参数是HTTPPasswordMgrWithDefaultRealm对象,它利用add_password()添加进入了用户名和密码,这样就建立了一个用于验证的Heandler。
接下来,利用这个Handler并使用了build_opener()方法构建了一个Opener,这个Opener在发送请求时就相当于已经验证成功了。
接下来,利用Opener对象的open()方法打开链接,就可以完成验证了。
代理
from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener
proxy_handler = {
'Http': 'http://127.0.0.1:9743',
'Https': 'https://127.0.0.1:9743'
}
proxy_handler = ProxyHandler(proxy_handler)
opener = build_opener(proxy_handler)
try:
result = opener.open("https://www.baidu.com/")
html = result.read().decode('utf-8')
print(html)
except URLError as e:
print(e.reason)
创建一个字典,键为协议类型,值为代理链接。
然后实例化一个ProxyHandler对象,其参数是刚刚创建的字典。
然后使用build_opener构建了一个Opener,其参数为刚刚实例化ProxyHandler对象。
使用刚刚构建的Opener的open方法请求一个链接。
cookie
①生成cookie文件
先看下怎么从网站的cookies获取下来
import http.cookiejar
import urllib.request
cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
for item in cookie:
print(item.name+'='+item.value)
'''
运行结果:
BAIDUID=5569DB********************F97478:FG=1
BIDUPSID=5569D********************5013105
H_PS_PSSID=33423_*****_*****_*****_*****_*****_33567
PSTM=16********
BDSVRTM=0
BD_HOME=1
'''
首先声明一个Cookiejar对象。
接下来,利用HTTPCookieProcessor创建一个Handler对象
然后利用build_opener()构建出Opener,执行open()函数
将cookie保存到文件中
import http.cookiejar
import urllib.request
filename = 'cookies.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
# MozillaCookieJar:读取和保存cookie
# ignore_discard:即使是将要丢弃的cookie,True表示保存
# ignore_expires:已经过期的cookie,True表示保存
首先,定义一个文件名字。
接下来,声明一个MozillaCookieJar对象,其参数为刚定义的文件名字。 此时将CookieJar换为MozillaCookieJar
接下来,利用HTTPCookieProcessor生成一个Handler对象,其参数是刚刚生成的MozillaCookieJar对象
接下来,利用bulid_opener生成一个Opener对象,其参数是:Handler对象
接下来,使用Opener对象的open方法访问网站
最后使用MozillaCookieJar对象的save方法将其保存。
将cookie保存成libwww-perl(LWP)格式的
import http.cookiejar
import urllib.request
filename = 'cookies.txt'
cookie = http.cookiejar.LWPCookie(filename)
handler = urllib.request.HTTPCookieProcessor(coookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
和上一个保存cookie类似。仅仅是将MozillaCookieJar更换为LWPCookie。
②生成后使用
已经生成了cookie文件后,就要使用cookie文件了。
以LWP格式文件举例:
import http.cookiejar, urllib.request
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookies.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response =opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))
# ignore_discard:即将过期的cookie,True在这里表示读取
# ignore_expires:已经过去的cookie,True在这里表示读取
首先,生成一个与cookies文件格式对应的对象,LWP格式使用:LWPCookieJar
接下来,将使用上一步生成的对象的load方法将cookie读取出来。
接下来,利用HTTPCookieProcessor生成一个Handler对象,其参数为:第一步生成的与cookies文件对应的对象
接下来,利用build_opener生成一个Opener对象,其参数为Hanlder
接下来,利用Opener对象的open()方法访问链接,其参数是:要访问的url
(2)异常处理
1、URLError
URLError类来自urllib库的error的模块,它继承自OSError,OSError是error异常模块的基类。
由request模块生成的异常都可以通过捕获这类来处理
具有一个reason属性,作用:返回错误原因
from urllib import request, error
try:
response = request.urlopen('http://cjsdkf.com/index.htm')
except error.URLError as e:
print(e.reason)
# 结果:Not Found
案例中,打开了一个不存在的链接。但是使用了URLError捕获了异常,运行结果就是Not Found。
程序没有直接报错,这样可以避免程序异常终止,同时异常得到了有效处理。
2、HTTPError
HTTPError是URLError的子类。
HTTPError,是专门用来处理HTTP请求错误的,比如认证请求失败等。
HTTPError有以下三个属性:
code:返回HTTP请求的状态码
reason:返回错误原因
headers:返回请求头
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/index.htm')
except error.HTTPError as e:
print(e.code, e.reason, e.headers, sep='\n')
# sep:分割值与值,默认使用空格,案例中使用换行
由于URLError是HTTPError的父类,所以可以先捕获子类的异常,在进行捕获父类的异常。
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/index.htm')
except error.HTTPError as e:
print('>>>>>>HTTPError捕获到的内容:')
print(e.code, e.headers, sep='\n')
except error.URLError as e:
print('>>>>>>URLError捕获到的内容:')
print(e.reason)
else:
print('Request Successfully! 请求成功!')
先使用HTTPError捕获异常,获取他的错误状态码,headers等信息。
如果不是HTTPError异常,则使用URLError捕获异常,打印其原因。
最后使用else来处理请求成功的正常的逻辑。
有时候reason返回的不一定是字符串,还可能是一个对象
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('https://www.baidu.com', timeout=0.01)
except urllib.error.URLError as e:
print(type(e.reason))
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
'''
结果:
<class 'socket.timeout'>
TIME OUT
'''
这里reason属性的结果是socket.timeout类型,我们用isinstance判断其类型。作出更详细的异常判断。
(3)解析链接
urllib库中有个parse模块。它定义了处理URL的标准接口。
方法:urlparse()、urlunparse()、urlsplit()、urlunspilt()、urljoin()、urlencode()、parse_qs()、parse_qsl()
quote()、unquote()
1、urlparse()
将给出的URL进行分解。
该方法可以实现URL的识别和分段。
import urllib.parse
result = urllib.parse.urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)
'''
结果:
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
'''
结果是一个ParseResult类型的对象。
urlparse()将url分为了以下6个部分:
scheme:协议
netloc:域名
path:访问路径
params:参数
query:查询条件
fragment:锚点
所以可以得出一个标准的连接格式:
scheme://nteloc/path;params?query#fragment
一个标准的URL都会符合这个规则。可以利用urlparse()将它拆分开。
urlparse的API用法还有以下3个参数:
urlstring:必填项,带解析的URL
schme:默认的协议(比如http或https等)如果连接没有带协议,将使用你设置的这个参数的协议,如果有了这 个参数将不起作用。
from urllib.parse import urlparse
result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='https')
print(result)
'''
结果:
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
提供的URL没有最前面的scheme信息的,但是通过指定默认scheme参数,返回的结果是https
'''
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', scheme='http')
print(result)
'''
结果:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
提供的URL有最前面的scheme字段为https,我们指定的默认scheme参数为http。当URL中有scheme字段,指定的默认scheme字段将不生效。
'''
allow_fragments:是否忽略fragments,如果设置为False则fragments参数将被忽略。
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)
'''
结果:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')
当给allow_fragments参数设定为False,URL中给出fragment在结果中fragment还是为空
'''
如果取消params和query后:
# 案例一:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)
'''
结果:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')
当URL中不包含params和query后,并且allow_fragment参数为False时,fragment会被解析成path的一部分
'''
# 案例二:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=True)
print(result)
'''
结果:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='', query='', fragment='comment')
当URL中不包含params和query后,没有指定allow_fragment或者allow_fragment=True的时候,
fragment还是会解析成fragment,不会变成path的一部分。
'''
返回结果ParseResult实际上是一个元组,可以按照索引顺序来获取,也可以按照属性名获取:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result.scheme, result[0])
print(result.netloc, result[1])
'''
结果:
https https
www.baidu.com www.baidu.com
'''
2、urlunparse()
按照给出的参数,合并成一个URL;
urlunparse()和urlparse()功能相反。
urlunparse()方法必须接受一个可迭代对象,但是长度必须是6,否则会抛出参数过多或者过少。
from urllib.parse import urlunparse
data = ['http', 'www.baidu.com', 'index.html', 'user', 'id=5', 'comment']
print(urlunparse(data))
'''
结果:
http://www.baidu.com/index.html;user?id=5#comment
可以使用列表、元组
不能使用集合,因为集合是无序的。不能使用字典,因为会将字典的键组合
'''
3、urlsplit()
urlsplit()和urlparse()功能详细,只是不把params单独解析出来。将params合并到path中。返回5个值。
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment', )
print(result)
print(result.scheme, result[0])
print(result.netloc, result[1])
'''
结果:
SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
https https
www.baidu.com www.baidu.com
其中的scheme参数和allow_fragment的使用和urlparse()方法是一样的
'''
4、urlunspilt()
urlinsplit()方法:将参数合并成URL,传入的参数必须是一个可迭代对象。参数必须为5
urlinsplit()和urlinparse()类似。
from urllib.parse import urlunsplit
data = ['https', 'www.baidu.com', 'index.html;user', 'id=5', 'comment']
print(urlunsplit(data))
5、urljoin()
可通过一个base_url(基础链接)作为第一个参数,将新的链接作为第二个参数,该方法会分析base_url的scheme、netloc和path三个内容并对新链接缺失部分进行补充,最后返回结果。
from urllib.parse import urljoin
urljoin("http://www.chachabei.com/folder/currentpage.html", "anotherpage.html")
# 结果:'http://www.chachabei.com/folder/anotherpage.html'
urljoin("http://www.chachabei.com/folder/currentpage.html", "/anotherpage.html")
# 结果:'http://www.chachabei.com/anotherpage.html'
urljoin("http://www.chachabei.com/folder/currentpage.html", "folder2/anotherpage.html")
# 结果:'http://www.chachabei.com/folder/folder2/anotherpage.html'
urljoin("http://www.chachabei.com/folder/currentpage.html", "/folder2/anotherpage.html")
# 结果:'http://www.chachabei.com/folder2/anotherpage.html'
urljoin("http://www.chachabei.com/abc/folder/currentpage.html",
"/folder2/anotherpage.html")
# 结果:'http://www.chachabei.com/folder2/anotherpage.html'
urljoin("http://www.chachabei.com/abc/folder/currentpage.html",
"../anotherpage.html")
# 结果:'http://www.chachabei.com/abc/anotherpage.html'
6、urlencode()
在构造GET请求参数的时候非常有用
from urllib.parse import urlencode
params = {
'name': 'dong',
'age': 22
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
'''
结果:https://www.baidu.com?name=dong&age=22
'''
先声明一个字典来将参数表示出来。然后调用urlencode()方法将其序列化为GET请求参数
7、parse_qs()
有了序列化,必有反序列化。
如果有一串GET请求参数,利用parse_qs()方法,就可以将它转换为字典
from urllib.parse import parse_qs
query = 'name=dong&age=22'
print(parse_qs(query))
# 结果:{'name': ['dong'], 'age': ['22']}
8、parse_qsl()
将一串GET请求参数,转换成列表,列表的里面是一个个的元组。
from urllib.parse import parse_qsl
query = 'name=dong&age=22'
print(parse_qsl(query))
# 结果:[('name', 'dong'), ('age', '22')]
9、quote()
该方法可以将内容转化成URL编码格式。
from urllib.parse import quote
keyword = '壁纸'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
# 结果:https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8
10、unquoto()
它可以对URL进行解码
from urllib.parse import unquote
url = 'https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))
# 结果:https://www.baidu.com/s?wd=壁纸
(4)分析Robots协议
利用urllib的Robotparser模块,可以实现网站Robots协议的分析。
1、Robots协议
Robots协议,又叫爬虫协议、机器人协议,全名叫网络爬虫排除标准。
用来告诉爬虫和搜索引擎,哪些内容可以抓取,哪些不可以抓取。
通常是一个叫tobots.txt文本文件。
当爬虫爬取一个网站的时候,首先会检查这个网站根目录下是否存在robots.txt文件,如果存在搜索爬虫会根据其中定义的爬取范围来爬取。如果没有找到这个文件,搜索爬虫就会访问所有可直接访问的页面。
2、robotparser
robotparser模块提供了一个类RobotFileParser。可以根据robots.txt文件来判断一个爬虫是否有权限来爬取这个网页。
import urllib.robotparser
urllib.robotparser.RobotFileParser(url='robots.txt文件链接')
# 也可以暂时默认为空,后期使用set_url()方法传入
常用方法:
set_url():设置robots.txt文件的链接
read():读取robots.txt并进行分析
parse():用来解析robots.txt文件
can_fetch():该方法传入两个参数,第一个是User-Agent,第二个是:要抓取网页的URL,判断是否可以抓取网 页。
mtime():返回上次抓取的分析robots.txt的时间
modified():将当前时间设置为上次抓取和分析robots.txt的时间。
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('https://www.jianshu.com/robots.txt')
rp.read()
print(rp.can_fetch('*', 'https://www.jianshu.com/p/b67554025d7d'))
print(rp.can_fetch('*', 'https://www.jianshu.com/searchq=python&page=1&type=collections'))
'''
结果:
False
False
先创建了一个RobotFileParser对象,然后在使用set_url()方法设置了robots.txt文件的链接。
接着利用can_fetch()方法判断网页是否可以被抓取。
'''
也可以同样适用parse()方法执行读取和分析:
from urllib.request import urlopen
from urllib.robotparser import RobotFileParser
response = urlopen('https://www.jianshu.com/robots.txt').read().decode('utf-
8').split('\n')
rp.parse(response)
print(rp.can_fetch('*', 'https://www.jianshu.com/p/b67554025d7d'))
print(rp.can_fetch('*', 'https://www.jianshu.com/searchq=python&page=1&type=collections'))