详细介绍:
urlopen函数的API:
urllib.request.urlopen(url, data=None,[timeout, ]*,cafile = None, capath = None, cadefault = False,context = None)
可以看到第一个数据是URL,其中data表示附加数据,timeout表示(超时时间)。
data 参数
data参数使可选择的,如果要添加改参数要使用bytes()方法将参数,转化为字节流编码格式内容为bytes类型,另外如果传入了改参数那么该请求方法,不再是GET方式,而是POST方式
import urllib.request
import urllib.parse
data = bytes(urllib.parse.urlencode({'word': "hello"}), encoding='utf8')
responce = urllib.request.urlopen('http://httpbin.org/post', data=data,timeout=10)
print(responce.read())
#
解析我们传递了一个参数word值是hello,需用通过bytes函数将他们转化为字节流,函数的第一个参数去需要是字符类型,
通过使用urllib.parse.urlencode()函数将字典转化为字符串的形式,第二个参数是编码格式,这里用到的是utf8,
timeout参数
timeout参数用于设置超时时间,单位为妙,意思就是如果请求超出了这个时间就会抛出异常,如果不指定该参数就会使用全局默认时间。
如果超出设置时间,服务器仍然没有响应,于是抛出了URLError异常。该异常,属于urllib.error模块,
import urllib.request
import socket
import urllib.error
try :
responce = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print("time out")
isinstance(object, classinfo)
如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True。如果object不是一个给定类型的的对象, 则返回结果总是False。
socket.timeout表示的超时异常。
其他参数不做介绍
可查python3->106页.
Request
urlopen()方法可以实现最基本请求的发起,但这几个简单的参数,并不足以构建一个完整的请求。如果请求需要加入Headers等信息,就可以利更强大的Request类来构建。
import urllib.request
request = urllib.request.Request('https://python.org')
responce = urllib.request.orlopen(request)
print(responce.read().decode('utf-8'))
此时urlopen方法的参数不在书url而是一个Request对象,Request函数得API
urllib.request.Request(url, data = None, headers = {},origin_host,urverifiable = False, method = None)
第一个参数url用于请求URL,这是必传参数,其他的都是可选参数
第二个参数data如果要传,必须传bytes(字节流)类型。如果他是字典的话可以通过,urllib.pase模块里的urlencode()编码。
第三个参数headers是一个字典他就是请求头,可以在构造请求时直接添加也可以通过headers参数直接构造,也可以通过请求实例add_header()方法添加。
添加请求头最常用,的方法就是,通过修改,User-Agent来伪装浏览器,可一通过修改他来伪装浏览器。
第四个参数origin_req_host指的是请求host名称或者IP地址,
第五个参数暂时用不到
第六个参数method是一个字符串用来指示使用的方法,比如GET,POST,PUT等。
from urllib import request, parse
url = 'http://httpbin.org/post'
headers ={
"User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0'
}
dict = {
'name' :'Ghelo'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data = data,headers =headers,method='POST')
responce = request.urlopen(req)
print(responce.read().decode('utf-8'))
另外,headers也可以add_headers()法,方法来添加:
req = request.Request(url=url,data = data,method = "POST")
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0')
请注意好书写的形式。
高级用法
已经可以自己构建请求了,但是对于一些高级的操作(比如cookie处理,代理设置等)还是没有办法处理。
而Handler就是解决这些问题强大工具,可以把它理解为处理器,一下就是这些所谓的"处理器"的作用。
介绍一下urllib.request模块里的BaseHandler类,它是所有其他Handler的父亲,
各种Handler继承这个BaseHandler类,举例如下。
HTTPDfaultErrorHandler:用于处理HTTP响应错误,错误都会抛出HTTPError类型的异常。
HTTPRedirectHander:用于处理重定向。
HTTPCookiePreocssor:用于处理Cookies。
HTTPPasswordMgr:用于管理密码。
HTTPBasicAtuHandler:用于管理认证如果一个连接打开时需要认证那么可以用它来解决认证问题。
官方文档:https://docs.python.org/zh-cn/3/library/urllib.request.html#urllib.request.Base###
实战
from urllib.request import HTTPPasswordMgrWithPriorAuth, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError
username = 'username'
password = 'password'
url = 'http://localhost:5000/'
p = HTTPPasswordMgrWithPriorAuth()
p.add_password(None, url, username, password)
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)
opener 可以使用open(url)方法返回类型,和urlopen如出一辙,即一个、HTTPresponse对象。4
这里首先实例化HTTPBasicAuthHandler对象,其参数是HTTPPasswordMgrWithPriorAuth对象,他利用
add_password()添加进去用户名和密码后面有介绍,这样创建了一个Handler。接下来通过Handler并使用build.opener()创建一个Opener,通过open()方法打开连接就可以完成验证。
HTTPPasswordMgrWithDefaultRealm()
HTTPPasswordMgrWithDefaultRealm类创建一个密码管理对象,用于保存HTTP请求相关的用户名和密码
主要应用两个场景:
1.验证代理授权的用户名和密码 (ProxyBasicAuthHandler())
2.验证Web客户端的的用户名和密码 (HTTPBasicAuthHandler())
ProxyBasicAuthHandler(代理授权验证)
如果我们使用之前的代码来使用私密代理,会报 HTTP 407 错误,表示代理没有通过身份验证:
- HTTPPasswordMgrWithDefaultRealm():来保存私密代理的用户密码啊
- ProxyBasicAuthHandler():来处理代理的身份验证。
HTTPBasicAuthHandler处理器(Web客户端授权验证)
有些Web服务器(包括HTTP/FTP等)访问时,需要进行用户身份验证,爬虫直接访问会报HTTP 401 错误,表示访问身份未经授权:
add_password(官方文档样例)
import urllib.request
# 创建支持基本HTTP身份验证的OpenerDirector
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm='PDQ Application',#翻译:领域
uri='https://mahler:8092/site-updates.py',
user='klem',
passwd='kadidd!ehopper')
opener = urllib.request.build_opener(auth_handler)
# ...and install it globally so it can be used with urlopen.
urllib.request.install_opener(opener)
urllib.request.urlopen('http://www.example.com/login.html')
build_opener([handler, …])
返回OpenerDirector实例。urlopen就是该类实例。
install_opener(opener)
install(全局变量) it globally so it can be used with urlopen.
代理
在使用爬虫免不了要使用代理,如果添加代理可以这样做:
from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener
proxy_handler = ProxyHandler({
'http':'http://XXX',
'https':'https://XXX'
})
opener = build_opener(proxy_handler)
try:
responce = opener.open('http://www.baidu.com',timeout=1)
print(responce.read().decode('utf-8'))
except URLError as e:
print(e.reason)
这里使用了ProxyHander,其参数是一个字典,键名是协议名类型(比如HTTP或则HTTPS等),键值是代理链接,可以添加多个。
Cookies
我们先用实例来看怎样将网站Cookies获取下来,
import http.cookiejar, 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)
首先,我们必须声明一个CookieJar对象,接下来,就需要利用HTTPCookieProcessor来构建一个Hander,最后利用build_opener()方法构建出Opener,执行open()函数即可。
不过能输出那可不可以输出成文件格式呢?我们知道Cookies实际上是以文本形式保存的。
import http.cookiejar, 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)
这时CookieJar就需要换成MozillCookieJar,他在生成文件的时候会使用到,是CookieJar的子类,可以用来处理Cookies和文件相关的事件,比如读取求和保存Cookies,可以将Cookies保存成Mozilla型浏览器的Cookies格式。
另外,LWPCookieJar同样可以读取和保存Cookies,但是保存的格式和MozillaCookieJar’不一样,他会保存成libwww-perl(LWP)格式的cookies文件。
此时只需要改变一行代码即可。
cookie = http.cookiejar.LWPCookieJar(filename)
那么如何从文件中读取并利用呢?
如下代码是以LWPCookieJar格式为例,来看一下:
import urllib.request
import http.cookiejar
cookie = http.cookiejar.MozillaCookieJar()
cookie.load('cookies.txt', ignore_discard= True,ignore_expires= True)
hander = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(hander)
responce = opener.open('http://www.baidu.com')
print(responce.read().decode('utf-8'))
读取本地为文件的前提是首先生成了LWPCookieJar格式的Cookies,并保存成文件使用load方法读取本地的cookies。
处理异常
1.URLError
URLError类来自urllib库的error模块,他继承自OSError类,是error异常模块的基类,由request模块产生的异常多可以通过这个来捕捉,
它具有一个属性reason即返回这个类来处理。
2.HTTPError
它是URLError的子类,专门用来处理HTTP请求错误,比如请求失败等,他有如下3个属性,
code:返回一个HTTP状态码,比如404表示网页不存在。
reason :同父类一样,用于返回错误的原因。
headers:返回请求头。
因为URLError是HTTPError的父类,所以可以通过先选择捕获子类的错误,再去捕获父类的的错误,所以上述代码更好的捕获异常的写法是。
from urllib import request, error
try:
respose = request.urlopen('http://XXXX')
expect error.HTTPError as e:
print(e.reason, e.headers, e.code,sep = '\n')
expect error.URLError as e:
print(e.reason)
else:
print('Request')
sep是状态码之前的符号。
有时候reason属性返回的不一定是字符串,也可能是一个对象,
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('https://www.baidu.com', timeout = 0.01)
expect urllib.error.URLError as e:
print(type(e.reason))
if insteance(e.reason, socket.timeout):
print('TIME OUT')
输出结果为:
<class 'socket.timeout'>
TIME OUT
解析链接
下面来介绍urllib库里面的parse模块,翻译parse:解析。
1.urlparse()
该方法实现我URL的识别和分段。
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result),result,sep='\n')
输出结果如下:
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可以观察到返回的类型是一个ParseResult类型的对象,它包含6个部分,分别是,"scheme"在//前面代表协议, “netloc”:在第一个/符号前面代表域名,“path"在/后面代表访问路径,“params”:在分号后面代表参数"query”:在?后面是查询条件,"fragment"在#号后面是锚点
所以URL的基本格式可以是:
scheme://netloc/path;params?query#fragment
API如下:
urllib.parse.urlparse(urlstring, scheme='',allow_framentss = True)
可以看出他有三个参数
1.urlstring:它是必填项,即带解析的URL。
2.scheme:它是默认协议比如(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,并且后面再加上seceme=“XXX”这时呢会以URL里面的scheme为主。
allow_fragments: 及是否忽略fragment,如果它被设置为False,fragment部分就会被忽略,它会被解析为path,
parameters,或则是query的一部分,而fragment部分为空。(翻译:片段)
from urllib.parse import urlparse
result = urlopen('http://w.baidu.com/index.html;user#comment',allow_fragments = False)
print(result)
运行结果如下:
ParseResult(scheme='http', netloc='w.baidu.com', path='/index.html', params='user#comment', query='', fragment='')
返回结果ParseResult实际上是一个元组,我们可以通过索引顺序来获取,也可以用属性名获取。
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html#comment',allow_fragments=False)
print(result.scheme, result[0],result.netloc,result[1], sep='\n')
分别用索引和属性名scheme和netloc,其运行结果如下:
http
http
www.baidu.com
www.baidu.com
urlunparse
有了urlurlparse(),相应的就有它的对立方法urlunparse(),他接受的参数是一个可迭代对象,但是它的长度必须是6,否则会抛出参数数量不足或则是参数数量过多的问题。
from urllib.parse import urlunparse
data = ['http','www.baidu.com','index.html','user','a=6','comment']
print(urlunparse(data))
#这里的data的列表的形式,也可以变为是元组的形式。
输出结果为:
http://www.baidu.com/index.html;user?a=6#comment
就构造成了一个URL。
urlsplit()
该方法和urlparse方法非常的相似,不过他不再单独解析params这一部分,只返回5个结果,即params会合并到path中。
from urllib.parse import urlsplit
result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment')
print(result)
运行结果如下:
SplitResult(scheme='http', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
可以发现返回结果是一个SplitResult,他其实也是一个元组类型。可以通过属性,索引获取值。
urlunsplit
与urlunparse()类似,他也是将链表哥哥部分组合完整链接的方法,传入的参数也是一个迭代,对象,例如列表,元组,唯一的区别是长度必须为5,
from urllib.parse import urlunsplt
data = ['http','www.baidu.com','index.html','a=6','comment']
print(urlunsplit(data))
urljoin()
有了urlunparse()和urlsplit()方法,我们完成链接的合并,不过前提必须要有特定长度的对象,连接器的每一部分都要清洗分开。
urljoin(),base_url作为第一个参数,new_url,作为第二个参数。该方法会分析base_url和scheme,netloc和path这三个部分内容并对链接缺失的部分进行补充,最后返回结果。
print(urljoin('http://www.baidu.com','FAQ.html'))
print(urljoin('http://ww.baidu.,com','https://cuiqingcai.com/FAQ.html'))
print(urljoin('http://www.baidu.com/about.html','https://cuiqingcai.com/FAQ.html'))
print(urljoin('www.baidu.com#comment','?category=2'))
输出结果入:
http://www.baidu.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html
www.baidu.com?category=2
可以发现,base_url提供了三项内容,scheme,netloc,和path。如果这3项新的链接里不存在,就予以补充;如果新的链接存在,就是用亲的链接部分,而,base_url,中params,query和fragment是不起作用的。
用过urljoin()方法,我们可以轻松实现链接的解析,拼合,和生成。
urlencode()
该函数常用于、GET请求方法。
from urllib.parse import urlencode
params = {
'name' = 'germey'
'age' = 22
}
base_url = 'http://www.baidu.com?'
url = base_url+urlencode(params)
print(url)
运行结果:
http://www.baidu.com?name=germey&age=22
这个函数会非常的常用。有时为了更加方便的构造参数,我们会事先,用字典来表示,要转化为URL的参数时,只需要代用该方法即可。
parse_qs()
有了序列化,必然就有反序列化。如果我们有一串GET请求参数,利用parse_qs()方法,就可以将它转化为字典。
from urllib.parse import parse_qs
query = 'name=germey&age=22'
print(parse_qs(query))
运行结果:
{‘name’:[‘germey’],‘age’:[‘22’]}
可以看到,这样就成功转回字典类型。
parse_qsl()
另外,还有一个parse_qsl()方法,它用于将参数转化为元组主成的列表,实例如下:
from urllib.parse import parse_qsl
query = 'name = germe&age=22'
print(parse_qsl(query))
#运行结果如下:
[('name','germey'),('age','22')]
可以看到,运行结果是一个列表,而列表中的每一个元素都是一个元组,元组的第一个内容是数名,第二个内容是参数值。
quote
该方法可以将内容转化为URL编码格式。URL中带有中文参数时,有时会导致乱码的问题。此时用这个方法可以将中文字转化为URL编码,实例如下:
from urllib.parse import quote
keyword = '壁纸'
url= 'http://www.baidu.com/s?wd='+quote(keyword)
print(url)
输出结果为:http://www.baidu.com/s?wd=壁纸
unquote()
unquote()方法,可以进行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=壁纸
以上介绍的方法方便地实现URL的解析我和构造。
分析Robots协议
利用urllib的robotparser模块,我们可以实现网站Roots协议的分析,
1.Rotbots协议
Robots协议也称为作爬虫协议,机器协议,它的全名叫做网络爬虫爬虫排除标准,用来告诉爬虫和搜索引擎哪些网页可以爬取,哪些不可以爬取,他通常是一个叫作robots.txt的文本文件,一般放在网站的根目录下。
当爬虫访问一个站点时,他首先会检查这个站点根目录下是否存在robots.txt文件,如果存在,搜索爬虫会根据其中定义的爬取范围来爬取。如果没有找到这个文件,搜索爬虫便会访问所有可直接访问到的页面。
爬虫名称
一些常见的搜索爬虫的名称及其对应的网站
爬虫名称 | 名称 | 网站 |
---|---|---|
BaiduSpider | 百度 | www.baidu.com |
Googlebot | 谷歌 | www.goole.com |
360pider | 360搜索 | www.so.com |
Yodao | bot | 有道 |
ia_archiver | Alexa | www.alwxa.cn |
Scooter | altavista | www.altavista.com |
robotParser
了解Robots协议之后,我们可以使用robotsparser模块来解析robots.txt了。该模块提供了一个类RobotFileParser
,他可一根据某网站的robotss,txt文件来判断一个爬虫是否具有权限1爬取这个网页。
API:
urllib.robotparser.RobotFileParser(url='')
当然,也可以声明时不传入,默认为为空,最后在使用set_url()方法设置一下也好。
下面列出这个类常用的几个方法。
set_url()
用来设置Robot.txt文件的链接。如果创建文件RobotFileParser对象时传入了链接,那么就不需要使用该方法设置了。
read()
读取robots.txt文件并进行分析。注意这个方法执行一个读取和分析操作,如果不调用这个方法,接下来的判断都会为False,所以一定要记得调用这个方法。这个方法不会返回任何内容,但是执行了读取操作。
parse()
用来解析robots.txt文件,传入的参数是robots.txt某些行的内容,他会按照robots.txt的语法规则来分析这些内容。
can_fatch()
该方法传入两个参数,第一个参数是User-agent,而第二个参数是要抓取的URL,返回的内容是该所受引擎是否可以爬取这个URL,返回结果是True,或False。
mtime()
返回的是上次爬取和分析robots.txt的时间,这对于时间分析和爬取的搜索爬虫是很有必要的,你可可能需要定期检查来怕去的最新的robots.txt。
modified()
他同样对长时间分析和爬取很有帮助,将当前时间设置为上次爬取和分析robots.txt的时间。
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('http://www.jianshu.com/robots.txt')
rp.read()
print(rp.can_fetch('*','http://www.jianshu.com/p/b67554025d7d'))
print(rp.can_fetch('*','http://www.jianshu.com/search?q=python&page=1&type=collections'))
#False
#False
这里我们也可以通过parse来解析
from urllib.request import urlopen
from urllib.robotparser import RobotFileParser
import urllib
rp = RobotFileParser()
url = 'http://jianshu.com/robots.txt'
req = urllib.request.Request(url)
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134')
rp.parse(urlopen(req).read().decode('utf-8').split('\n'))
print(rp.can_fetch('*','http://www.jianshu.com/p/b67554025dd7d'))
print(rp.can_fetch('*','http://www.jianshu.com/search?q=python&page=1&type=collections'))
输出结果为
True
False
两种方法其实并在理想状态下的处理应该是一样的结果但是输出结果却不一样,只是因为第二个加入了报头。