python爬虫之urllib库(二)

python爬虫之urllib库(二)

  urllib库

  超时设置

   网页长时间没法响应的,系统会判断网页超时,没法打开网页。对于爬虫而言,咱们做为网页的访问者,不能一直等着服务器给咱们返回错误信息,耗费时间过久。所以,咱们在爬取网页的时候能够设置超时异常的值。html

import urllib.request


file=urllib.request.urlopen("http://yum.iqianyue.com",timeout=30) #timeout=30,表示30秒之后产生超时异常 data=file.read()

   HTTP协议请求

  HTTP请求即HTTP请求报文首行(协议方法,请求URL,协议版本)中的协议方法,HTTP请求方法主要有:python

  GET请求:经过URL来传递请求信息,获取服务器资源。因为GET请求能够把要传递的请求信息添加在URL上,不安全性表如今能够经过地址栏URL信息看到传递的信息。浏览器

  POST请求:向服务器提交数据,如表单提交,一般使用Post请求。安全

  PUT请求:请求服务器存储一个资源,一般须要指定存储位置。服务器

  DELETE请求:请求服务器删除一个资源。网络

  HEAD请求:请求获取响应的报头信息,对于响应的主体内容不须要。app

  OPTIONS请求:得到请求URL所支持的HTTP方法python爬虫

  四种HTTP请求方法区别:GET--查,POST--增,PUT--改,DELETE--删ide

  GET请求实现

  在百度首页输入关键词查询,不断更换关键词能够看到地址栏中URL的变化。分析能够得出:‘https://www.baidu.com/s?wd=’为URL主要部分,在关键词检索字段wd后添加关键词,便可实现百度搜索。函数

  构造get请求,实现自动爬取百度查询关键词为hello的结果。

import urllib.request


core_url = 'http://www.baidu.com/s?wd='
keywords = 'hello'
full_url = core_url + keywords
req = urllib.request.Request(full_url)
data = urllib.request.urlopen(req).read()

with open('hello.html', 'wb') as f:
    f.write(data)

  上述关键词若是变成中文,会出现报错:UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-11: ordinal not in range(128),缘由:python爬虫之urllib库(一)提到过URL编码,URL只会认可一部分ASCII码中字符,对于汉字等特殊符号是须要编码的。对于一个参数使用字符串结合request模块给URL传参:urllib.request.quote(str);对于多个参数使用字典结合parse模块给URL传参:urllib.parse.urlencode(dict)。

  一个参数

import urllib.request


core_url = 'http://www.baidu.com/s?wd='
keywords = '您好'
keywords_encode = urllib.request.quote(keywords)  # URL参数编码
full_url = core_url + keywords_encode
req = urllib.request.Request(full_url)
data = urllib.request.urlopen(req).read()

with open('hello.html', 'wb') as f:
    f.write(data)

  多个参数

import urllib.request
import urllib.parse


core_url = 'http://www.baidu.com/s?'  # 关键词字段减掉
keywords = {  # 多个参数
    'wd': '您好',
    'rsv_spt': 1,  
    'rsv_iqid': 0x8c77175600037633,
}
keywords_encode = urllib.parse.urlencode(keywords)  # 多个参数url编码
full_url = core_url + keywords_encode
req = urllib.request.Request(full_url)
data = urllib.request.urlopen(req).read()

with open('hello.html', 'wb') as f:
    f.write(data)

  POST请求实现

  POST请求多用于提交表单来实现注册登陆。爬虫面对须要注册登陆的网页必定是须要登陆访问网页之后才能够对网页内容进行爬取的,能够构造POST请求实现自动登陆爬取网页。

import urllib.request
import urllib.parse


url = 'http://data.stats.gov.cn/login.htm'  # url必须是登陆或者注册页面的url地址  国家数据统计局官网登陆url
form_data = {
    'username': '545859297@qq.com',  # 表单数据,登陆时输入的信息,对应邮箱和密码。再也不是url参数了,注意区分
    'keyp': 'bushizhenmima',  # 注意字典中的key须要使用页面中input输入框的name属性的属性值。别试我帐号密码!!!
    # 浏览器打开上述网页,确实验证码输入,登陆不会成功
}
form_data_deal = urllib.parse.urlencode(form_data).encode('utf-8')  # POST请求data属性须要传入bytes类型,并且字典须要经过urlencode链接
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36'
}
req = urllib.request.Request(url, data=form_data_deal, headers=headers)
data = urllib.request.urlopen(req).read()

with open('country-data.html', 'wb') as f:
    f.write(data)

  代理服务器设置

  屡次使用一个IP地址去爬取网页,服务器能够轻易察觉属于不正常访问行为,可能会对该IP地址设置拒绝访问(封IP),也可能会对用户帐号处理(封帐号)。使用代理服务器能够轻松换IP地址,即便用代理服务器的IP地址访问页面,而不使用咱们真正的IP地址,所谓“偷梁换柱”。

  代理服务器能够分为:免费代理、付费代理。免费代理能够经过网页搜索“免费代理服务器”得到,付费代理能够经过购买得到。代理基本格式:IP:port(代理IP:端口号)

  免费代理添加须要先建立处理器handler,再由build_open()建立opener对象,调用opener中的open()方法爬取页面,不能再使用urlopen()发送请求爬取了。

  使用handle+opener发送请求爬取页面的方法:

import urllib.request


def handler_opener():
    url = 'https://www.baidu.com'

    handler = urllib.request.HTTPHandler()  # 常见HTTP处理器
    opener = urllib.request.build_opener(handler)  # 调用buile_open()建立opener对象

    response = opener.open(url)  # 调用open()方法发送HTTP请求
    response_str = response.read().decode('utf-8')

    return response_str


result = handler_opener()
with open('baidu.html', 'w', encoding='utf-8') as f:
    f.write(result)

  免费代理添加及使用方式:

import urllib.request


def free_proxy():
    url = 'http://www.baidu.com'

    proxy = {
        'http': 'http;//116.209.57.195:9999',  # 分为http和https两种协议版本,https是更加安全的http,在http基础上加入安全层SSL
        # 'https': 'https://118.182.33.7:42801'
    }

    proxy_handler = urllib.request.ProxyHandler(proxy)  # 建立代理处理器,使用ProxyHandle
    opener = urllib.request.build_opener(proxy_handler)  

    response = opener.open(url)
    response_str = response.read()  # 注意与上例不一样

    return response_str


result = free_proxy()
with open('baidu-free.html', 'wb') as f:  # 注意与上例不一样
    f.write(result)

  付费代理添加有两种方式:

  方式一

import urllib.request


def free_proxy():
    url = 'https://www.baidu.com'

    proxy = {
        'http': 'http;//222.139.245.130:58424',  # 分为http和https两种协议版本,https是更加安全的http,在http基础上加入安全层SSL
        # 'https': 'https://118.182.33.7:42801'
    }

    proxy_handler = urllib.request.ProxyHandler(proxy)  # 建立代理处理器
    opener = urllib.request.build_opener(proxy_handler, urllib.request.HTTPHandler)  # 这个能够缺省HTTPHandler,下面为源码解释
    '''
    The opener will use several default handlers, including support for HTTP, FTP and when applicable HTTPS.
    If any of the handlers passed as arguments are subclasses of the default handlers, the default handlers will not be used.
    '''

    response = opener.open(url)
    response_str = response.read()  # 注意与上例不一样

    return response_str


result = free_proxy()
with open('baidu-free.html', 'wb') as f:  # 注意与上例不一样
    f.write(result)

  方式二

import urllib.request


def fee_proxy():
    url = 'http://www.baidu.com'

    # 付费代理IP第二种方式
    user_name = 'admin'
    password = '123456'
    proxy_ip = 'http://121.61.1.222:9999'
    proxy_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()  # 建立密码管理器
    proxy_manager.add_password(None, proxy_ip, user_name, password)

    proxy_handler = urllib.request.ProxyBasicAuthHandler(proxy_manager)  # 代理IP验证处理器
    proxy_opener = urllib.request.build_opener(proxy_handler)

    response = proxy_opener.open(url)
    response_str = response.read().decode('utf-8')

    return response_str


data = fee_proxy()
with open('baidu-fee.html', 'w', encoding='utf-8') as f:
    f.write(data)

   代理服务器地址是具备时效性的,尤为是免费代理IP,若是代理服务器地址失效或者填写错误,会返回URLError。一般使用代理服务器进行网页爬取出现URLError,要考虑是不是代理IP失效的缘由。

  DebugLog

  调试日志是记录程序运行状态的记录,对于自动化爬虫而言,调试日志是不可或缺的。经过设置Debuglog,能够实现程序边运行边打印调试日志的需求。

  调试日志的配置方法:

  1. 分别使用urllib.request.HTTPHandler()和urllib.request.HTTPSHandler()设置参数debuglevel=1,开启并设置bug级别。
  2. 使用urllib.request.build_opener()建立自定义的opener对象,并设置1中的值做为参数。
  3. 使用urllib.request.install_opener()建立全局的opener对象,使得opener对象不只能够调用open()方法,也可使用urlopen()发送HTTP请求。
  4. 调用open()方法或者使用urllib.request.urlopen()发送HTTP请求。
import urllib.request


url = 'http://www.baidu.com'

http_handler = urllib.request.HTTPHandler(debuglevel=1)
https_handler = urllib.request.HTTPSHandler(debuglevel=1)
opener = urllib.request.build_opener(http_handler, https_handler)
urllib.request.install_opener(opener)

response = urllib.request.urlopen(url)  # 请求方式一
# response = opener.open(url)  # 请求方式二

  URLError

  程序在执行或者搭建过程当中,不可避免的会出现错误或者异常,错误一般是指不合语言自己规则且不可控的使用方式,异常是指合乎语言规则且可控的使用方式。网络爬虫中,网页内容和结构的迭代更新以及网络环境等因素都会产生影响,甚至异常。所以,合理处理异常对于爬虫而言是很重要的。

  异常主要为URLError类以及其子类HTTP类,处理方法是使用urllib.error模块和try...except语句,产生URLError异常的缘由有:

  1. 链接不上服务器
  2. 远程URL不存在
  3. 无网络
  4. 触发了HTTPError
import urllib.request
import urllib.error


url = 'http://sad.blog.csdn.net'

try:
    rep = urllib.request.urlopen(url)
except urllib.error.URLError as e:
    print(e)
else:
    print(rep)

  当触发HTTPError,能够直接使用HTTPError类,能够查看异常后的状态码以及缘由短语。

import urllib.request
import urllib.error


url = 'http://sad.blog.csdn.net'

try:
    rep = urllib.request.urlopen(url)
except urllib.error.HTTPError as e:
    print(e.code, e.reason)
else:
    print(rep)

常见的状态码以及缘由短语有:

状态码 缘由短语(英文) 缘由短语(中文)
200 OK 正常
301 Moved Permanently 从新定向新的URL,永久性
302 Found 从新定向新的URL,非永久性
304 Not Modified 请求资源未更新
400 Bad Request 非法请求
401 Unauthorized 请求未经受权
403 Forbidden 禁止访问
404 Not Found 没有找到页面
500 Internal Server Error 服务器内部错误
501 Not Implemented 服务器不支持实现请求功能

  URLError和HTTPError中的属性及源码error.py:

 1 """Exception classes raised by urllib.
 2 
 3 The base exception class is URLError, which inherits from OSError.  It
 4 doesn't define any behavior of its own, but is the base class for all
 5 exceptions defined in this package.
 6 
 7 HTTPError is an exception class that is also a valid HTTP response
 8 instance.  It behaves this way because HTTP protocol errors are valid
 9 responses, with a status code, headers, and a body.  In some contexts,
10 an application may want to handle an exception like a regular
11 response.
12 """
13 
14 import urllib.response
15 
16 __all__ = ['URLError', 'HTTPError', 'ContentTooShortError']
17 
18 
19 class URLError(OSError):
20     # URLError is a sub-type of OSError, but it doesn't share any of
21     # the implementation.  need to override __init__ and __str__.
22     # It sets self.args for compatibility with other OSError
23     # subclasses, but args doesn't have the typical format with errno in
24     # slot 0 and strerror in slot 1.  This may be better than nothing.
25     def __init__(self, reason, filename=None):
26         self.args = reason,
27         self.reason = reason
28         if filename is not None:
29             self.filename = filename
30 
31     def __str__(self):
32         return '<urlopen error %s>' % self.reason
33 
34 
35 class HTTPError(URLError, urllib.response.addinfourl):
36     """Raised when HTTP error occurs, but also acts like non-error return"""
37     __super_init = urllib.response.addinfourl.__init__
38 
39     def __init__(self, url, code, msg, hdrs, fp):
40         self.code = code
41         self.msg = msg
42         self.hdrs = hdrs
43         self.fp = fp
44         self.filename = url
45         # The addinfourl classes depend on fp being a valid file
46         # object.  In some cases, the HTTPError may not have a valid
47         # file object.  If this happens, the simplest workaround is to
48         # not initialize the base classes.
49         if fp is not None:
50             self.__super_init(fp, hdrs, url, code)
51 
52     def __str__(self):
53         return 'HTTP Error %s: %s' % (self.code, self.msg)
54 
55     def __repr__(self):
56         return '<HTTPError %s: %r>' % (self.code, self.msg)
57 
58     # since URLError specifies a .reason attribute, HTTPError should also
59     #  provide this attribute. See issue13211 for discussion.
60     @property
61     def reason(self):
62         return self.msg
63 
64     @property
65     def headers(self):
66         return self.hdrs
67 
68     @headers.setter
69     def headers(self, headers):
70         self.hdrs = headers
View Code

  源码中能够看到,URLError类中有reason属性,HTTPError类具备code属性,HTTPError能够继承父类URLError中的reason属性,而HTTPError是引发URLError的一个缘由,即当触发HTTPError引发的URLError异常时,URLError是具备code和reason属性,而HTTPError一直具备code和reason属性。所以,可使用hasattr()函数在使用前判断是否存在属性,进而经过状态码的存在断定异常URLError的缘由与HTTPError是否有关。

import urllib.request
import urllib.error


try:
    urllib.request.urlopen("http://blog.csdn.net")
except urllib.error.URLError as e:
    if hasattr(e, "code"):
        print(e.code)
    if hasattr(e, "reason"):
        print(e.reason)