爬虫urllib库的基本使用——学习笔记

一、使用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'))
                     
上一篇:Python爬取有道词典,有道的反爬很难吗?也就这样啊!


下一篇:Python3下urllib.parse.urlencode()编码