Python爬虫-urllib模块

【爬虫大世界】php

  学习爬虫,最初的操做即是模拟浏览器向服务器发出请求。至于怎么作,没必要感到无从下手,Python提供了功能齐全的类库来帮助咱们完成这一操做html

  最基础的HTTP库有urllibhttplib2requesttreqpython

3.1使用urllibnginx

  在Python2中,有urlliburllib2两个库来实现请求的发送;而在Python3中,已经不存在urllib2了,统一为urllib,其官方文档为:https://docs.python.org/3/library/urllib.html
编程

  urllib库是Python内置的HTTP请求库,它包含4个模块:浏览器

   request:它是最基本的HTTP请求模块,用来模拟发送请求。只需给库方法传入URL以及额外的参数,就能够模拟[在浏览器输入网址,按下回车]这一过程服务器

  △error:异常处理模块,若是出现请求错误,咱们能够捕捉这些异常,而后进行重试或其余操做,保证程序不会意外终止cookie

      parse  /pɑ:rs/  v.从语法上描述或分析(词句等)网络

  △parse:一个工具模块,提供了许多URL处理方法,好比拆分、解析、合并等数据结构

  △robotparser:主要用来识别网站的robots.txt文件,而后判断哪些网站能够爬,哪些不能够

3.1.1发送请求】

一、urlopen( )

  urllib.request模块提供了最基本的构造HTTP请求的方法,利用它能够模拟浏览器的一个请求发起过程。以抓取Python官网为例:

import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))

  运行这两行代码,能够获得:

 

  这里输出了网页的源代码

  △若是在上述代码中不添加decode( )方法,源代码中的一些转义字符将没法被转义,好比上图中的空行是经过'\n'实现的,若是删去了decode( )方法,将会直接显示\n,而不是空行

  若是删去decode( )方法,除了不转义以外,还有就是返回的数据类型为bytes,而不是str。因为返回的数据过多,须要将终端窗口扩充才能看到完整的数据,也就是在原有的str数据添加:b' ',用以表示bytes类型的数据

 

bytes(字节流)类型数据】

  事实上,一切数据在传输时都是bytes类型,缘由在于为了更好地传输非英文系的数据(诸如汉字、韩文等),须要根据Unicode将这些字进行encode(编码),好比汉字'',它对应的bytes类型数据为b'\xe4\xb8\xad',进行编码后,汉字''就可以进行数据传输了

  为了统一,本来不须要进行编码的英文系数据也进行了编码,好比字母'a',它对应的bytes类型就直接是b'a'。这种编码只是形式上的,'a'b'a'并无一目了然的区别

  咱们基本上不须要关心bytes类型数据的编码和解码,由于这一切都是计算机自动完成的,普通用户不须要了解这么深,但对于学计算机的人必须了解,由于每每在写代码时进行数据操做,须要在bytes的编码和解码之间来回转换

 

  本来服务器传回的HTML代码的解码工做由浏览器自动完成,但这里咱们经过Python类库模拟浏览器向服务器发送请求,并无浏览器,所以但凡是以前浏览器的工做,都须要咱们自行完成

  下面来看看它返回的究竟是什么:

import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print(type(response))

  利用type( )方法输出响应的类型,获得以下结果:

    <class 'http.client.HTTPResponse'>

  能够发现,它是一个HTTPResponse类型的对象,再经过help(response),能够得知HTTPResponse类型的对象主要包含read( )readinto( )getheader(name)getheaders( )fileno( )等方法,以及msgversionstatusreasondebuglevelclosed等属性

  将这个对象赋值为response变量后,就能够调用这些方法和属性了

 

  read( )方法能够获得返回的网页内容,咱们能够试试调用其余的方法和属性:好比status属性能够获得返回结果的状态码,最多见的200表明请求成功、404表明网页未找到

import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print(response.stauts)
print(response.getheaders())
print(response.getheader('Server'))

  获得输出:

 

    nginx(engine X)  n.一款轻量级的Web服务器/反向代理服务器/电子邮件代理服务器

  调用status属性直接输出了响应状态码200;而后调用getheaders( )方法,输出了响应的头信息,显示为一个元组列表(a sequence of two-element tuples);最后经过getheader( )方法并传递一个'Server'参数获取响应头中的值【为何是元组列表而不是字典?】

  利用最基本的urlopen( )方法,能够完成最基本的简单网页的GET请求抓取

 

API(Application Programming Interface),应用程序编程接口】

  API是一些预先定义的函数,目的是“提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工做机制的细节”

 

  若是咱们要给连接传递一些参数,该如何实现?首先看一下urlopen( )函数的API

    urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)

  直接在Python官网中搜索'urllib.request.urlopen'便可查看该函数的API,能够发现,除了第一个参数能够传递URL外,咱们还能够传递其余内容,好比data(附加数据)timeout(超时时间)

 

data参数

  data参数是可选的,对于urlopen( )函数而言,data参数必须是字节流编码格式的内容,即bytes类型,所以咱们使用Pythonbytes( )方法进行转化

  另外,若是传递了这个参数,则它的请求就再也不是GET方式,而是POST方式

import urllib.request
import urllib.parse

data = bytes(urllib.parse.urlencode({'word' : 'hello'}), encoding = 'utf-8')
response = urllib.request.urlopen('http://httpbin.org/post', data = data)
print(response.read().encode('utf-8'))

  首先咱们假设要传递{'word' : 'hello'}这个数据,先是经过parse模块中的urlencode( )方法将字典转化为字符串;而后因为data参数类型必须是bytes,再用bytes( )函数,且经过第二个参数encoding指定编码格式,将本来为str类型数据的data转换为bytes类型数据

 

urlencode( )

  留意一下,常见的URL中若是包含参数,它们出如今URL中的形式不会是键值对,而是相似,即经过'&'字符链接两个键值对,而键值对的键与值之间直接用'='链接,总体是一个字符串

  urlencode( )方法就是帮助咱们把键值对转化为上述这种形式的:

 

  这里请求的站点是httpbin.org,它提供HTTP请求测试;这里请求的URLhttp://httpbin.org/post,这个连接能够用来测试POST请求,输出请求的一些信息,其中包含咱们传递的data参数:

 

  咱们传递的参数出如今了form字段中,这代表是模拟了表单提交的方式,以POST方式传输数据

 

timeout参数

  timeout参数用于设置超时时间,单位为秒。当请求超出了设置的这个时间,尚未获得响应,就会抛出异常。若是不指定该参数,就会使用全局默认时间。

  它支持HTTPHTTPSFTP请求

import urllib.request

response = urllib.request.urlopen('http://httpbin.org/get', timeout = 0.1)
print(reponse.read().decode('utf-8'))

  输出结果为:

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last): File ''xxx.py'' , line 3 , in <module>

    reponse = urllib.request.urlopen('http://httpbin.org/get' , timeout = 1)

    ...

    urllib.error.URLError: <urlopen error timed out>

  这里咱们设置超时时间是0.1秒。程序过0.1秒后,服务器依然没有响应,就会抛出URLError异常。该异常属于urllib.error模块,错误缘由是超时

  所以,能够经过设置这个超时时间来控制一个网页若是长时间未响应,就跳过它的抓取。能够利用try except语句来实现:

    socket  ()n.孔,插座  ()n.套接字

      网络上两个程序经过一个双向的通讯链接实现数据的交换,这个链接的一端称为一个socket

import socket
import urllib.request
import urllib.error

try:
    response = 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!')

  咱们请求了http://httpbin.org/get测试连接,设置超时时间为0.1秒,而后捕获了URLError异常

  一开始导入的socket模块暂时难以理解透彻,只需知道,socket模块中有timeout属性,若是要判断一个异常的发生是否真的是因为timeout,那么就须要经过isinstance( )函数来判断

  对于全部的异常,都有一个reason属性,用以显示该异常的具体状况

  就上述代码而言:

 

能够看到,其显示异常的具体状况为'socket.timeout'

按照常理来讲,0.1秒内基本不可能获得服务器的响应,所以上述代码中成功输出了'Time Out!'

 

●其余参数

  咱们回顾urlopen( )函数的其余API

    urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)

  除了datatimeout参数,还有其余参数,下面来简要概述:

    context  n.语境,上下文,背景,环境

  △context参数:用来指定SSL设置,它必须是ssl.SSLContext类型

  △cafilecapath参数:分别指定CA证书和它的路径,应用于请求HTTPS连接

  △cadefault参数:现已弃用,默认值为False

 

  综上,即是urlopen( )方法的用法,详见https://docs.python.org/3/library/urllib.request.html

 

二、Request

  咱们能够利用urlopen( )函数实现最基本请求的发起,但查看urlopen( )函数的API,彷佛这几个参数并不足以构建一个完整的请求

  若是请求中须要加入Headers等信息,就能够利用更强大的Request类来构建

import urllib.request

request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(reponse.read().decode('utf-8'))

  以上述代码为例,咱们依然使用urlopen( )来发送这个请求,只是此次的参数再也不是URL,而是一个Request类型的对象

  经过构造这个数据结构,一方面能够将请求独立成一个对象,另外一方面实现灵活且丰富地配置参数

 

  查看Request的构造方法:【为何官网上没有相关资料?】

    urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

  Request存在的意义即是在发送请求时附加上一些信息,而仅靠urlopen( )则不能完成

 

下面来分析一下各个参数:

  □ url:为用于请求的URL,是必传参数,其它都是可选参数

  □ data:该参数与urlopen( )函数中的data参数同样,限定为bytes类型数据;且若是是字典,需用urllib.parse模块中的urlencode( )函数编码

  □ headers:该参数为一个字典,它就是请求头

    经过该函数,能够修改本来请求头中的信息,好比User-Agent,默认的User-AgentPython-urllib,若是要假装成火狐浏览器,能够把它设置为:

Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11

    从而实现爬虫假装

    咱们能够在构建请求时经过headers参数直接构造,也能够经过请求实例的add_header( )方法

  □ origin_req_host:请求方的host名称或IP地址

    verify  /ˈverɪfaɪ/  v.核实,证实,断定

  □ unverifiable:默认为False,表示这个请求是不是没法验证的,意思是用户没有足够的权限来选择接收这个结果的请求

    “例如,咱们请求一个HTML文档中的图片,可是咱们没有自动抓取图像的权限,此时unverifiable的值就是True

  □ method:该参数为一个字符串,用来指示请求使用的方法,如'GET''POST''PUT'

from urllib import request , parse

url = 'http://httpbin.org/post'
headers = {
'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' ,
'Host' : 'httpbin.org'
}
dict = {'name' : 'Examine2'}

data = bytes(parse.urlencode(dict), encoding = 'utf-8')
req = request.Request(url = url, headers = headers, data = data, method = 'POST')
response = request.urlopen(req)

print(response.read().decode('utf-8'))

  咱们尝试传入多个参数来构建一个请求,url指定请求URLheaders中指定User-AgentHost,参数dataurlencode( )bytes( )转换成字节流,指定请求方式为POST

执行上面示例代码,可获得输出:

 

  能够看到,除却data中的数据成功发送外,咱们还设置了headersmethod

 

  以前有说起,headers修改请求头中的信息“能够经过请求实例的add_header( )方法”,即:

req = request.Request(url = url, data = data, method = 'POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')

  注意add_header( )方法接收两个字符串

 

三、高级用法

  在上面的过程当中,咱们虽然能够构造请求,但对于一些更高级的操做(Cookies处理、代理设置等)仍是没法实现

  为此,咱们须要更强大的工具【Handler

  Handler能够理解为各类处理器,有专门处理登陆验证的,有处理Cookies的,有处理代理设置的...

 

  首先介绍一下urllib.request模块中的BaseHandler类,它是全部其余Handler的父类,它提供了最基本的方法,如default_open( )protocol_request( )

  接下来就有各类Handler子类继承这个BaseHandler类,如:

  □ HTTPDefaultErrorHandler:用于处理HTTP响应错误,错误都会抛出HTTPError类型的异常

  □ HTTPRedirectHandler:用于处理重定向

  □ HTTPCookieProcessor:用于处理Cookies

  □ ProxyHandler:用于设置代理,默认代理为空

    Mgr  →  manager

  □ HTTPPasswordMgr:用于管理密码,它维护了用户名和密码的表

    Auth  →  authentication  /ɔ:ˌθentɪ'keɪʃn/  n.认证

    authentic  /ɔ:ˈθentɪk/  adj.真的,可信的,认证了的

  □ HTTPBasicAuthHandler:用于管理认证(连接打开时可能须要认证)

  其他Handler请查阅:https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler

 

  另一个比较重要的类是【OpenerDirector】,简称为【Opener】;咱们以前用过的urlopen( )方法就是urllib为咱们提供的一个Opener

  为何要引入Opener

  以前使用的Requesturlopen( )至关于类库为你封装好了极其经常使用的请求方法,利用它们能够完成最基本的请求,但为了实现更高级的功能,咱们须要深刻一层进行配置,使用更底层的实例来完成操做

  Opener可使用open( )方法,返回的类型和urlopen( )一模一样(记住urlopen( )也是Opener

 

  OpenerHandler的关系是:利用Handler来构建Opener

 

下面来看看几个实例:(可暂时不过于深究,待须要时回来复习

●验证

  有时打开某些网页,会出现如下对话框:

  假若要请求这样的页面,就须要借助HTTPBasicAuthHandler

    realm  /rɛlm]/  n.王国,领域

from urllib.request import HTTPBasciAuthHandler, HTTPPasswordMgrWithDefaultRealm, build_opener
from urllib.error import URLError

url = 'http://localhost:5000/'#←此为书上示范网址,未知缘由没法打开
username = 'username'
password = 'password'

p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)

try:
    result = opener.open(url)
    print(result.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

  这里实例化了HTTPBasicAuthHandler对象,其参数是HTTPPasswordMgrWithDefaultRealm对象,它利用add_password( )添加用户名和密码,这样就创建了一个处理验证的Handler

  而后利用这个Handler经过build_opener( )构建一个Opener,使用Openeropen( )方法打开连接,调用open( )就至关于调用了urlopen( ),剩下步骤类似

    integrate  /ˈɪntɪgreɪt/  v.使一体化,合并  adj.总体的、完整的

    IDE  →  Integrated Development Environment 集成开发环境,是用于程序开发环境的应用程序,通常包括代码编辑器、编译器、调试器和图形用户界面等工具

  实践过程当中始终出现Error[WinError 10061] 因为目标计算机积极拒绝,没法链接。

  尝试了许多方法,如:http://tieba.baidu.com/p/5995082291?pid=123475984092&cid=0#123475984092

  其给出的方案是将服务器代码和客户端代码分别置于两个终端命令窗口执行;尝试了将范例中的代码搬运,依瓢画葫芦成功打开http://localhost:5000/网址,但依旧爬取失败,缘由未知

 

●代理

  作爬虫时,免不了要使用代理,若是要添加代理,能够这样作:

 

from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener

proxy_handler = ProxyHandler({
'http' : 'http://127.0.0.1:9743' ,
'https' : 'https://127.0.0.1:9743'
})
opener = build_opener(proxy_handler)

try:
    result = opener.open('https://www.baidu.com')
    print(result.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

  这次爬取一样出现Error[WinError 10061] 因为目标计算机积极拒绝,没法链接。

 

  “这里咱们搭建了一个本地代理,它运行在9743端口上”  【?】

  这里使用的ProxyHandler,其参数是一个字典,键名是协议类型(HTTPHTTPS),键值是代理连接,能够添加多个代理

  而后利用这个Handlerbuild_opener( )方法构建Opener,发送请求便可

 

Cookies

    jar  /dʒɑr/  n.罐子、缸、杯  v.猛然震动,不一致

    Mozilla  /məuzilə/  Mosaic+Godzilla中文名称摩斯拉,Mozilla FireFox(火狐浏览器)的生产厂商

  Cookies的处理就须要相关的Handler

  首先看看如何将网站的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来构建一个Handler,最后构建出Opener,执行函数open( )便可

  执行open( )函数后,目标网址上的Cookies就被获取,并存储到变量cookie中,结果输出:

 

  这里能够看到输出了每条Cookie的名称和值

  此外,获取的Cookies存储在变量cookie中,并不是是单纯地以字典的形式储存,而是CookieJar类型,咱们在上述代码后面添加print(cookie),能够看到:

 

  所以不能简单地经过for key , value in cookie.items( ):来输出

 

  不过,既然能输出,那就有可能输出成文件格式。Cookies实际上也是以文本形式保存的。

firename = '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就须要换成MozillaCookieJar

 

CookieJar子类】

  CookieJar自己是一种类型对象,用于

    ①管理HTTP Cookie

    ②存储HTTP请求生成的Cookie

    ③向传出的HTTP请求添加Cookie对象

 

    FileCookieJar(filename , delayload = None , policy = None)】:

  从CookieJar派生而来,用来建立FileCookieJar实例,检索Cookie信息并将Cookie存储到文件中

  filename是存储cookie的文件名;delayloadTrue时,支持延时访问文件,即只有在须要时才读取文件或在文件中储存数据

    MozillaCookieJar(filename , delayload = None , policy = None)】:

  从FileCookieJar派生而来,建立与Mozilla浏览器cookie.txt兼容的FileCookieJar实例

    LWPCookieJar(filename , delayload = None , policy = None)】:

  从FileCookieJar派生而来,建立与libwww-perl标准的Set-Cookie3格式兼容的FileCookieJar实例

 

  大多数状况下,咱们只用CookieJar;若是须要和本地文件交互,就用MozillaCookieJarLWPCookieJar

  上述代码将CookieJar替换成MozillaCookieJar,并调用了save( )函数:save(ignore_discard = True , ignore_expires = True),成功将Cookies保存成Mozilla型浏览器Cookies格式

    discard  /dɪsˈkɑ:d/  v.丢弃,解雇,出牌  n.被抛弃的人,丢出的牌

 

Mozilla型浏览器】

  以外咱们介绍Request类型对象时,介绍到了headers,而使用Request对象做为urlopen( )参数而不是单纯的URL的根本缘由是,Request能在请求时发送一些附加信息,如headers

  咱们有说到“代理”,headers中的User-Agent是方便咱们假装成浏览器的,默认为Python-urllib;咱们以前假装成火狐浏览器,就是将User-Agent修改成

Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11

  咱们查阅一下其余浏览器的User-Agent,发现:

搜狗浏览器 1.x

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)

360浏览器

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)

世界之窗(The World3.x

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)

IE 9.0

User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0

 

  你会发现,几乎全部的浏览器的User-Agent都带有'Mozilla'字符串,这也是为何MozillaCookieJar会成为经常使用的Cookie存储格式

  至于为何都会带有'Mozilla'字符串,详细请参阅:https://blog.csdn.net/puppylpg/article/details/47319401

 

总结下来就是,

  最初的Mozilla浏览器功能卓越,拥有Mozilla Frame(框架);当其余浏览器被研发出来时,不想放弃Mozilla Frame,统统就在User-Agent上声明本身支持Mozilla(事实上是假装成Mozilla)

  而当时假装成Mozilla的其余浏览器又拥有各自的优势,当后续浏览器被研发,为了兼容其余浏览器的优势,也老是在User-Agent上假装成其余浏览器,追根溯源,致使绝大部分浏览器都在User-Agent上标注为'Mozilla'。所谓User-Agent,也变得混乱不堪,失去了本来的意义

  尽管如今Mozilla浏览器已经被淘汰(继承的是Mozilla FireFox浏览器),但就是这个已经不存在的浏览器,“在形式上”存活于其余各大浏览器的User-Agent

  这就是Mozilla型浏览器

 

  题外话到此结束

  上述代码将CookieJar修改成MozillaCookieJar,运行代码,会生成一个cookie.txt文件,内容以下:

 

  这也就是Mozilla型浏览器储存Cookies的格式

  生成文件是经过在建立MozillaCookieJar实例时传递文件名、调用save( )函数实现的,除此以外,步骤与普通的CookieJar同样;事实上,若是这时调用for item in cookie: print(item.name +'='+ item.value),仍可输出原来的结果

  这代表MozillaCookieJar生成的cookie实例在后续操做中产生的变化与原来的CookieJar是同样(除了实例类型从CookieJar变成MozillaCookieJar),只是在保存成文本时修改了格式,与Mozilla浏览器兼容

  另外,上述在介绍CookieJar子类时说起到了LWPCookieJar

  LWPLibrary for WWW access Perl的缩写,Perl是一门编程语言,LWP是访问Web服务器的Perl包,利用这个包,能够很方便地在Perl脚本里面访问外部Web服务器上的资源

  所以将Cookie保存成libwww-perl(LWP)格式的Cookies文件有好处

  

  要生成LWP格式的Cookies文件,只需修改:

cookie = http.CookieJar.LWPCookieJar(filename)

  能够获得一个新的cookie.txt文件:

 

  生成了Cookies文件能够对其进行读取并利用(LWPCookieJar格式为例,MozillaCookieJar格式的一样适用),当请求的网站须要Cookies时:

import http.cookiejar , urllib.reqeust

cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt')
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))

  这里,在已经生成了LWPCookieJar格式的Cookies并保存成文件的前提下,经过调用load( )方法来读取本地的Cookies文件,获取到Cookies的内容

经过一样的方法构建HandlerOpener后,便可输出百度网页的源码

 

3.1.2处理异常】

  前一节咱们了解了请求的发送过程,但某些时候会出现各类异常,假若不处理这些异常,程序极可能因报错而终止运行。所以咱们颇有必要学会异常处理

urlliberror模块定义了由request模块产生的异常;若是出现了问题,request模块就会抛出error模块中定义的异常

 

一、URLError

  URLError类来自urllib库的error模块,它继承自OSError,是error异常模块的基类,由request模块产生的异常均可以经过捕获这个类来处理”

  它具备一个reason属性,即返回错误的缘由

from urllib import request, error
try:
    response = request.urlopen('https://cuiqingcai.com/index.htm')
except error.URLError as e:
    print(e.reason)

  咱们尝试打开一个不存在的页面,按理说应该会报错,但这时咱们捕获了URLError这个异常,成功输出了'Not Found'字样

  所谓不存在的页面,是服务器成功响应(状态码200)后,但服务器检测到该域名下并不存在的网页;而在地址栏上随便打上不存在的URL(诸如baaidu.com),属于根本没有做出响应;两种状况不一样

  一样的还有https://www.52pojie.cn/thread-666632-1-1.htmlnn

  程序输出了e.reason而没有直接报错,就避免了程序异常终止,同时有效处理异常

 

二、HTTPError

  HTTPErrorURLError的子类,专门用来处理HTTP请求错误,好比认证请求失败等

  它有3个属性:

  □ code:返回HTTP状态码,404网页不存在、500服务器内部错误...

  □ reason:继承自父类URLError,一样返回错误的缘由

  □ headers:返回请求头

from urllib import request, error
try:
    response = request.urlopen('https://cuiqingcai.com/index.htm')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, sep = '\n')

    sep  →  separator,默认为' ',替换print( )函数中的逗号

  输出为:

 

  依旧是一样的网址,这里捕获了HTTPError,并输出了reasoncodeheaders属性

 

  由于URLErrorHTTPError的父类,因此通常推荐先选择捕获子类的错误,再去捕获父类的错误:

from urllib import request, error
try:
    response = request.urlopen('https://cuiqingcai.com/index.htm')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, sep = '\n')
except error.URLError as e:
    print(e.reason)
else:
    print('Request Successfully!')

  有时候,reason返回的是诸如'Not Found'的字符串,但有时候返回的也多是一个对象

  咱们在学习urlopen( )timeout参数时,有代码行if isinstance(e.reason , socket.timeout),若是这时输入代码print(type(e.reason)),结果会输出<class 'socket.timeout'>

 

3.1.3解析连接】

  前面有说起urllibparse模块,它定义了处理URL的标准接口,实现URL各部分的抽取、合并以及连接转换等

  它支持以下协议的URL处理:fileftpgopherhdlhttphttpsimapmailtommsnewsnntpprosperorsyncrtsprtspusftpsipsipssnewssvnsvn+sshtelnetwais

 

①、urlparse( )

  该方法能够实现URL的识别和分段:

from urllib import parse
result = urllib.parse.urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result), result)

  利用urlparse( )对一个URL进行解析,输出解析结果的类型以及解析结果:

 

  能够看到,返回的结果是一个ParseResult类型的对象,它包含6个部分,分别是schemenetlocpathparamsqueryfragment

    scheme  /ski:m/  v./n.计划,图谋  n.体系   位于'://'以前,表明协议

    netloc  →  network locality   表明域名,也即服务器位置,位于'/'后面、';''?'前面的所有

    path   访问路径,也即网页文件在服务器中的位置

    params  →  parameters  n.形参()   位于';'后面,是可选参数

    query  /ˈkwɪəri/  v./n.询问,疑问  n.问题,问号  位于'?'后面的查询条件,通常用做GET类型的URL,用'&'链接键值对

    fragment  /ˈfrægmənt/  v.破碎、破裂  n.碎片,片断,分段,未完成的部分  位于'#'后面,是锚点,用于直接定位页面内部的下拉位置

  因此能够得出一个标准的连接格式:

scheme://netloc/path;params?query#fragment

  一个标准的URL都会符合这个规则,利用urlparse( )将其拆分出来

 

  除却最基本的解析方式外,urlparse( )还拥有其余配置。查阅它的API用法:

urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)

  □ urlstring:为必填项,即待解析的URL

  □ scheme:它是默认的协议。假若这个URL没有带协议信息,会将这个做为默认的协议

from urllib.parse import urlparse
result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme = 'https')
print(result)

  能够看到,URL没有包含最前面的scheme信息,但返回了指定默认的scheme参数

URL不携带scheme属性时,本来netloc的值为何搬运至path处?】

  假如咱们带上scheme

from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment', scheme = 'https')
print(result)

  可见,scheme参数只有在URL中不包含scheme信息时才会生效

  □ allow_fragments:便是否容许fragment,默认为True。当它被设置为False时,原fragment部分会被忽略,被解析为pathparams或者query的一部分,fragment部分为空

from urllib.parse import urlparse
reslut = urlparse('http://www.baidu.com/index.html;user?id=5#comment', allow_fragments = False)
print(result)

 

  若是URL中不包含paramsquery

from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html#comment', allow_fragments = False)
print(result)

 

  当URL中不包含paramsqueryfragment会被解析为path的一部分

  实际上返回的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')

  获得输出:

  经过索引和属性名来获取,其结果是一致的

 

②、urlunparse( )

  有了urlparse( ),就有它的对立方法urlunparse( )

  urlunprase( )接受的参数是一个可迭代对象(诸如listtuple),但它的长度必须是6,不然会抛出参数不足或过多的问题

from urllib.parse import urlunparse
data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))

  这样咱们就成功实现了URL的构造

 

③、urlsplit( )

  这个方法与urlparse( )很是类似,只不过它再也不单独解析params部分,而是将params合并到path中,只返回5个结果:

from urllib.parse import urlsplit
result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment')
print(result)

 

  返回的SplitResult类型对象一样也是一个元组,能够经过以前提到的两种方式来获取

 

④、urlunsplit( )

  传入的参数必须是长度为5的可迭代对象,params部分提早合并到path中,详细参考urlunparse( )

 

⑤、urljoin( )

  对于如何生成连接,咱们可使用urlunparse( )urlunsplit( ),但前提是必须有特定长度的对象,且连接的每一部分都要清晰分开

  生成连接还有另一个方法,那就是urljoin( ),在学习urljoin( )以前,咱们先回顾join( )用法:

 

join( )(sep).join(seq)  →  (separator).join(sequence)

  join( )不能像print( )同样单独调用,它须要一个'sep',即分隔符(separator);而join( )接受一个序列

    ▷''.join(['1' , '2' , '3'])   ==>   '123'

    ▷''.join(['1' , '2' , '3'])   ==>   '123'

  能够看到,join( )用于将序列中的元素按指定的链接符合并成一个新字符串;当sep为空时,就是纯粹地将序列中的元素链接成一个新字符串

  既然是合并字符串,那么原有的序列中的元素也必须是字符串:

    ▷''.join([1 , 2 , 3])

  这样会抛出异常TypeError: sequence item 0: excepted str instance, int found

 

  urljoin( )APIurljoin(base, url, allow_fragment=True),它接收两个参数,base_url(基础连接)为第一个参数,新的URL做为第二个参数;该方法会分析base_urlschemenetlocpath这三个内容,并对新连接缺失的部分进行补充

  经过实例能够看出,urljoin( )base_url解析,剥离出schemenetlocpath三项内容(若是有),检查url参数是否在这三项内容上有残缺,若是有,将会用剥离出的schemenetlocpath来修补;若是url参数自己就含有schemenetlocpath,将保持自身的内容

  base_url中的paramsqueryfragment将不起做用

 

⑥、urlencode( )

  该方法在以前已经有提早学习,简而言之,其做用就是将字典转换为符合URL的字符串

  有时为了更加方便地构造参数,咱们会事先将参数用字典来表示,而后调用urlencode( )来构造URL

from urllib.parse import urlencode

params = {'name' : 'Examine2', 'age' : '19'}
base_url = 'http://www.baidu.com?'
url = base_url + urlencode(params)
print(url)

  结果输出:

 

  参数就成功地由字典转化为GET请求参数了

 

⑦、parse_qs( )

  qsquery string的缩写,特指URL中的query参数,而通常在URL中,query参数表现为:

  事实上parse_qs( )是反序列化,旨在将GET请求参数转化回字典,与urlencode( )相反,你甚至能够把parse_qs( )看做是urldecode( )

from urllib.parse import parse_qs
query = 'name=Examine2&age=19'
print(parse_qs(query))

  表明query参数的字符串成功转化为字典,而因为字典的值有时候并不是只有一个元素,所以parse_qs( )返回的字典中,值所有为含有str类型数据的list

 

⑧、parse_qsl( )

  qslquery string list的缩写,该方法与parse_qs( )十分类似,只是返回的结果再也不是dict,而是由tuple组成的list

from urllib.parse import parse_qsl
query = 'name=Examine2&age=19'
print(parse_qsl(query))

  鉴于list中每一个元素都是tuple,故能够经过parse_qsl(query)[0][1]的方式来读取数据

 

⑨、quote( )

    quote  /kwəʊt/  v.引用、引述,报价

  若是传递给URL中文参数,有时可能致使乱码,quote( )就是将中文字符转化为URL编码的

from ullib.parse import quote

keyword = '真实'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)

  能够看到,中文字符'真实'被编码成了'%E7%9C%9F%E5%AE%9E',而这也实际上是搜索关键字在网页中存在的真正形式:在百度搜索栏中输入'真实',尽管URL中显示出中文字符:

  但打开网页源文件,会发现这时变成了

  quote( )默认的编码方式为UTF-8

 

⑩、unquote( )

  quote( )的对立方法,可以对URL中隐藏的中文字符进行解码

from urllib.parse import unquote
print(unquote('https:///www.baidu.com/s?wd=%E7%9C%9F%E5%AE%9E'))

 

3.1.4分析Robots协议】

一、Robots协议

    exclude  /ɪkˈsklu:d/  v.排除,排斥,驱除

  Robots协议(爬虫协议、机器人协议),全名为网络爬虫排除标准(Robots Exclusion Protocol),用来告诉爬虫和搜索引擎哪些页面能够爬取、哪些不能够

  它一般是一个叫做robots.txt的文本文件,通常放在网站的根目录下

  当搜索爬虫访问一个站点时,它首先会检查这个站点根目录下是否存在robots.txt文件,若是存在,搜索爬虫会根据其中定义的爬取范围来爬取;若是没有找到这个文件,搜索爬虫便会访问全部可直接访问的页面

 

  下面来看一个robots.txt的样例:

    User-agent: *

    Disallow: /

    Allow: /public/

  上述robots.txt限制了全部爬虫只能够爬取public目录的功能

  将上述内容保存成robots.txt文件,放在网站的根目录下,和网站的入口文件(好比index.phpindex.htmlindex.jsp等)放在一块儿

 

  上面的User-agent描述了搜索爬虫的名称,这里设置为*表示该协议对任何爬取爬虫都有效

  好比,设置为User-agent: Baiduspider表明咱们设置的规则对百度爬虫是有效的;若是有多条User-agent记录,则会有多个爬取爬虫受到限制

  Disallow指定了不能够抓取的页面,以上例子设置为/则表明不容许爬取任何页面

  Allow一般与Disallow一块儿使用,通常不会单独使用,它用来排除某些限制。如今设置为/public/,则表示全部页面不容许抓取,但能够抓取public目录

 

下面来看几个例子:

  ①禁止全部爬虫访问任何目录:

    User-agent: *

    Disallow: /

  ②容许全部爬虫访问任何目录:

    User-agent: *

    Disallow:

  (或者直接把robots.txt文件留空)

  ③禁止全部爬虫访问网站某些目录:

    User-agent: *

    Disallow: /private/

    Disallow: /tmp/

    tmp  →  temporary  .tmp文件是临时文件,不少都没有什么价值

  ④只容许某个爬虫访问:

    User-agent: WebCrawler

    Disallow:

    User-agent: *

    Disallow: /

二、爬虫名称

事实上,各个网站的爬虫都有了固定的名字,好比:

爬虫名称

名称

网站

BaiduSpider

百度

www.baidu.com

Googlebot

谷歌

www.google.com

360Spider

360搜索

www.so.com

YodaoBot

有道

www.youdao.com

ia_archiver

Alexa

www.alexa.cn

Scooter

altavista

www.altavista.com

  更多的能够在网上进行搜索

三、robotparser

  了解了Robots协议后,咱们可使用robotparser模块来解析robots.txt

  robotparser模块只提供了一个类RobotFileParser,它能够根据某网站的robots.txt文件来判断一个爬取爬虫是否有权限来爬取这个网页

  该类的API为:urllib.robotparser.RobotFileParser(url=' '),构造RobotFileParser的实例只需传入robots.txt的连接便可;固然,能够在声明时不传入,使其按默认的空URL建立一个实例,而后调用实例的set_url( )方法

  这个类的几个经常使用方法:

  □ set_url( ):用来设置robots.txt文件的连接。若是在建立RobotFileParser对象时就已经传入了连接,则不须要再使用这个方法设置了

  □ read( ):读取robots.txt文件并进行分析。注意这个方法执行了一个读取和分析的操做,若是不调用这个方法,接下来的判断都为False,因此必定要记得调用这个方法。该方法不会返回任何内容,但执行了读取操做

  □ parse( ):用来解析robots.txt文件,传入的参数是robots.txt某些行的内容,它会按照robots.txt的语法规则来分析这些内容。该方法容许直接分析已有的robots.txt文件,能够看做是read( )的替补

    fetch  /fetʃ/  v.取来、拿来,售得  n.诡计,风浪区

  □ can_fetch( ):该方法传入两个参数,第一个是User-agent(能够是表明全部爬虫的'*'),第二个是要抓取的URL(URL必须存在于构造时传入的URL的子目录下)。它返回TrueFalse,表明该搜索引擎是否能够爬取这个URL

  □ mtime( )'m'的含义未知,个人理解是'modified time',将返回上次抓取和分析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'))

  上述代码中,咱们首先建立了一个RobotFileParser对象,经过set_url( )方法设置了robots.txt的连接,接着利用can_fetch( )方法判断了网页是否能够被爬取:

  上面的代码是经过set_url( )read( )来获取即将要分析的robots.txt,其实也等价于:

rp.parse(urlopen('http://www.jianshu.com/robots.txt').read( ).decode('utf-8').split('\n'))

  以上即是robotparser模块的基本用法和实例,利用它判断出哪些页面时能够爬取、哪些不能够

 

''''''''''''''

相关文章
相关标签/搜索