python爬虫之urllib库(二)

时间:2023-03-08 23:41:15
python爬虫之urllib库(二)

python爬虫之urllib库(二)

  urllib库

  超时设置

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

import urllib.request

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

  HTTP协议请求

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

  GET请求:通过URL来传递请求信息,获取服务器资源。由于GET请求可以把要传递的请求信息添加在URL上,不安全性表现在可以通过地址栏URL信息看到传递的信息。

  POST请求:向服务器提交数据,如表单提交,通常使用Post请求。

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

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

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

  OPTIONS请求:获得请求URL所支持的HTTP方法

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

  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 = ''
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:

 """Exception classes raised by urllib.

 The base exception class is URLError, which inherits from OSError.  It
doesn't define any behavior of its own, but is the base class for all
exceptions defined in this package. HTTPError is an exception class that is also a valid HTTP response
instance. It behaves this way because HTTP protocol errors are valid
responses, with a status code, headers, and a body. In some contexts,
an application may want to handle an exception like a regular
response.
""" import urllib.response __all__ = ['URLError', 'HTTPError', 'ContentTooShortError'] class URLError(OSError):
# URLError is a sub-type of OSError, but it doesn't share any of
# the implementation. need to override __init__ and __str__.
# It sets self.args for compatibility with other OSError
# subclasses, but args doesn't have the typical format with errno in
# slot 0 and strerror in slot 1. This may be better than nothing.
def __init__(self, reason, filename=None):
self.args = reason,
self.reason = reason
if filename is not None:
self.filename = filename def __str__(self):
return '<urlopen error %s>' % self.reason class HTTPError(URLError, urllib.response.addinfourl):
"""Raised when HTTP error occurs, but also acts like non-error return"""
__super_init = urllib.response.addinfourl.__init__ def __init__(self, url, code, msg, hdrs, fp):
self.code = code
self.msg = msg
self.hdrs = hdrs
self.fp = fp
self.filename = url
# The addinfourl classes depend on fp being a valid file
# object. In some cases, the HTTPError may not have a valid
# file object. If this happens, the simplest workaround is to
# not initialize the base classes.
if fp is not None:
self.__super_init(fp, hdrs, url, code) def __str__(self):
return 'HTTP Error %s: %s' % (self.code, self.msg) def __repr__(self):
return '<HTTPError %s: %r>' % (self.code, self.msg) # since URLError specifies a .reason attribute, HTTPError should also
# provide this attribute. See issue13211 for discussion.
@property
def reason(self):
return self.msg @property
def headers(self):
return self.hdrs @headers.setter
def headers(self, headers):
self.hdrs = headers

  源码中可以看到,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)