Skip to content

Pyhon类库——urllib

约 3002 个字 542 行代码 预计阅读时间 19 分钟

urllib类库介绍

urllib官方文档,可供参考

urllib库是Python内置的HTTP请求库,包含有:

  • request:最基本的HTPP请求模块,用于模拟发送请求
  • error:异常处理模块,用于捕获异常
  • parse:工具模块,用于URL的拆分解析和合并
  • robotparse:用于识别网站的robots.txt文件

使用urllib发送请求——request模块

如果我们要向一个服务器发送请求,可以使用urllib的request模块,以下有具体用法:

urlopen()

# urlopen()函数的API
urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False,context=None)

urlopen函数可以模拟浏览器请求发起的过程,同时具有处理授权验证,重定向,浏览器Cookies以及其他内容的能力。


urlopen()函数的简单应用

示例如下:

#引入urllib中的request模块
from urllib import request

#设定我们要请求的url
url = "https://www.baidu.com"

#向服务器请求,并抓取与打印网页
response = request.urlopen(url)
print(response.read().decode('utf-8'))

#输出为:
'''
<html>
<head>
        <script>
                location.replace(location.href.replace("https://","http://"));
        </script>
</head>
<body>
        <noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
'''

关于urlopen的返回类型:

print(type(response))

#输出为:
'''
<class 'http.client.HTTPResponse'>
'''

可见其返回了一个HTTPResponse类型的对象,主要包含:

  • read()、readinto()、getheader(name)、getheaders()、fileno()等方法
  • msg、version、status、reason、debuglevel、closed等属性

在先前的示例中,将urlopen返回的对象赋值给了response,并通过read()方法得到了返回的网页内容

再举一实例:

from urllib import request

url = "https://limestart.cn"
response = request.urlopen(url)

# HTTP状态码
print(response.status)
# ResponseHeaders的信息
print(response.getheaders())
# ResponseHeaders中Server的值(键值对列表)
print(response.getheader('Server'))

# 输出为:
'''
200
[('Server', 'marco/3.2'), ('Date', 'Fri, 10 Jan 2025 06:31:41 GMT'), ('Content-Type', 'text/html'), ('Content-Length', '34816'), ('Connection', 'close'), ('Vary', 'Accept-Encoding'), ('X-Request-Id', '94a2e047a38db6f41d0a7e8baa743d79; 604b32f5000d0dd6fe198ad8df643366; 1e85d223d6fbe69ee3bb255e575aebdc; cf33fdf0a3ccedbb3b4553f02cc2860f'), ('X-Source', 'U/200'), ('X-Upyun-Content-Length', '34816'), ('ETag', '"898bd6c8e4bde082079bcb10bd61b5f6"'), ('Last-Modified', 'Fri, 20 Dec 2024 19:51:18 GMT'), ('X-Upyun-Content-Type', 'text/html'), ('Expires', 'Fri, 10 Jan 2025 06:31:41 GMT'), ('Cache-Control', 'max-age=0'), ('Accept-Ranges', 'bytes'), ('Via', 'T.105.H, V.403-zj-sad-106, S.mix-zj-sad3-001, T.1.H, V.mix-zj-sad3-005, T.36.H, M.ctn-fj-quz-038'), ('Strict-Transport-Security', 'max-age=31536000'), ('Content-Security-Policy', "frame-ancestors 'self' chrome-extension: moz-extension:")]
marco/3.2
'''

我们通过status和调用getheader(),分别得到了响应的状态码(200)与目标服务器是用marco搭建的信息。


用urlopen()函数传递更多参数

首先回顾urlopen()的API定义:

# urlopen()函数的API
urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False,context=None)

  • data参数
    data参数是可选的,默认情况下其值为None,如果为其赋值,则urlopen默认采用POST方式请求,也就是向服务器请求提交表单数据。举一实例如下:
    from urllib import request, parse
    
    # urlencode将键值对转化为字符串,bytes()函数将字符串转化为字节流
    data = bytes(parse.urlencode    ({'word':'hello'}), encoding='utf-8')
    
    # 此处urlopen函数默认采用POST的方法
    response = request.urlopen('http:// httpbin.org/post', data = data)
    
    print(response.read().decode('utf-8'))
    
    # 输出为
    '''
    {
      "args": {}, 
      "data": "", 
      "files": {}, 
      "form": {
        "word": "hello"
      }, 
      "headers": {
        "Accept-Encoding": "identity", 
        "Content-Length": "10", 
        "Content-Type": "application/   x-www-form-urlencoded", 
        "Host": "httpbin.org", 
        "User-Agent": "Python-urllib/3.12", 
        "X-Amzn-Trace-Id":  "Root=1-6780c418-7daa14d04d84250879f8d   3d4"
      }, 
      "json": null, 
      "origin": "27.149.23.110", 
      "url": "http://httpbin.org/post"
    }
    '''
    
    可以看到我们POST的参数与值出现在了form中
  • timeout参数
    字面意思,timeout参数用于设置超时时间,单位为秒,默认为全局默认时间(socket._GLOBAL_DEFAULT_TIMEOUT)。它支持HTTP,HTTPS和FTP请求。下举一实例:
    import socket
    from urllib import request, error
    
    url = 'http://httpbin.org/get'
    
    # try except语句,先执行try内的语句,如果没出错,则执行else内语句(如果有的话),如果出错,则执行except。最后执行finally的语句(如果有的话)
    try:
        response = request.urlopen(url, timeout = 0.1)
    
    # 如果超时,那么通过error.URLError捕获异常,赋值给e
    except error.URLError as e:
    
    # isinstance进行类型比较,判断异常是否是socket.timeout类型(超时)
        if isinstance(e.reason, socket.timeout):
            print("TIME OUT")
    
    # 输出为
    '''
    TIME OUT
    '''
    
    当然我们这里稍微“欺负”了一下服务器,大部分情况下,一个服务器都很难在0.1s内响应,除非坐在服务器上()
  • 其他参数
    • context:ssl.SSLContext类型,指定SSL设置。
    • cafile:指定CA证书,用于请求HTTPS链接
    • capath:指定CA证书路径,用于请求HTTPS链接
    • cadefault:已弃用,默认为False

更多信息,请参阅此处

Request

简单的urlopen()函数有时候不能满足更复杂的请求需求,我们需要Request类的援助!

我们先通过一个简单的实例来体会一下Request:

from urllib import request

# request_1是一个Request类型的对象
request_1 = request.Request('https://www.baidu.com')

# urlopen操作的是对象而不是原先的url
response = request.urlopen(request_1)
print(response.read().decode('utf-8'))

Request类的构造方法:

class urllib.request.Request(url, data=None, headers={},origin_req_host=None,unverifiable=False,
method=None):
  • url参数:必传参数
  • data参数:只接受字节流(bytes)类型
  • headers参数:请求头(Request Headers),字典类型。最常用的用法是通过在headers中修改User-Agent来伪装浏览器
  • origin_req_host参数:请求方的host名称或者IP地址
  • unverifiable参数:请求是否无法验证,默认为False
  • method参数:字符串类型,指示请求的方法,比如GET、POST和PUT等,默认为GET

下举一个实例来展示Request参数如何利用:

from urllib import request, parse

url = 'http://httpbin.org/post'

# 需要向服务器post的数据
dict = {
    'password' : '123456'
}

# 用于伪装chrome的headers,顺带指出了host
headers = {
    'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
    'origin_req_host' : 'httpbin.org'   
}

# 转码
data = bytes(parse.urlencode(dict), encoding='utf-8')

# 建立Request类的对象并调用
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

#输出为
'''
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "password": "123456"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "15", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "Origin-Req-Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", 
    "X-Amzn-Trace-Id": "Root=1-6780edef-4d476d9b114997e62daf1715"
  }, 
  "json": null, 
  "origin": "112.49.107.93", 
  "url": "http://httpbin.org/post"
}
'''
可以看到我们成功设置了datas,headers和method。

关于通过add_header添加headers:

req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36')

高级用法——Handler和Opener

python在urllib.request中提供了一堆Handler来处理我们可能遇到的任何事情,比如通过登录验证,设置代理等等。

python还在其中提供了OpenerDirector类,简称为Opener,先前的urlopen就是一个Opener。

我们可以通过build_opener,以Handler为参数建立一个Opener,这个Opener可以通过open()来调用,返回类型和urlopen()一模一样。

形象的解释是:Opener相当于一个工具箱,而每一个Handler都是一种特定的工具,这些工具有些需要我们实现进行“定制”使得它可以胜任要进行的工作,build_opener()负责将工具装入工具箱,提供给open()去完成对应的任务。

下面举出几个具体的例子:

  • 验证登录
    有些网站在打开时候会提示输入用户名和密码进行验证,要请求这样的页面,需要借助HTTPBasicAuthHanlder。
    from urllib.request import HTTPBasicAuthHandler, HTTPPasswordMgrWithDefaultRealm, build_opener
    from urllib.error import URLError
    
    # 设定我们的用户名与密码
    username = 'Yangshu233'
    password = '123456'
    url = 'http://localhost:5000/'
    
    # 实例化一个HHTTPPasswordMgrWithDefaultRealm对象,并通过add_password向其中添加指定url下的用户和密码
    p = HTTPPasswordMgrWithDefaultRealm()
    p.add_password(None, uri=url, user=username, passwd=password)
    
    # 以HTTPPasswordMgrWithDefaultRealm对象为参数,实例化一个HTTPBasicAuthHandler,这就是一个可以处理验证登录的Handler,并通过build_opener来build一个opener
    auth_handler = HTTPBasicAuthHandler(p)
    opener = build_opener(auth_handler)
    
    
    # 通过Opener来请求网址,处理验证
    try:
        response = opener.open(url)
        html = response.read().decode('utf-8')
        print(html)
    except URLError as e:
        print(e.reason)
    
  • 代理
    做爬虫时候要用到代理服务?考虑一下ProxyHandler吧!
      from urllib.request import ProxyHandler, build_opener
      from urllib.error import URLError
    
      # 实例化一个代理Handler
      proxy = ProxyHandler({
          'http': 'http://203.19.38.114:1080',
          'https': 'https://123.123.123.123:8080'
      })
    
      # 构建一个包含代理Handler的Opener
      opener = build_opener(proxy)
    
      url_1 = 'http://www.baidu.com'
      url_2 = 'https://www.baidu.com'
    
      # 用Opener发起请求
      try:
          response = opener.open(url_1, timeout=10)
          print(response.read().decode('utf-8'))
      except URLError as e:
          print(e.reason)
    
      try:
          response = opener.open(url_2, timeout=2)
          print(response.read().decode('utf-8'))
      except URLError as e:
          print(e.reason)
    
  • Cookies
    如何处理Cookies也是很重要的一环,我们可以借助相应的Handler来完成,举一实例如下:

    import http.cookiejar, urllib.request, urllib.error
    
    url = 'http://www.baidu.com'
    
    # 实例化一个CookierJar来存下Cookies
    cookie = http.cookiejar.CookieJar()
    
    # HTTPCookieProcessor提供了如何接收Cookie的方法
    handler = urllib.request.HTTPCookieProcessor(cookiejar=cookie)
    opener = urllib.request.build_opener(handler)
    
    try:
        response = opener.open(url)
        # 遍历cookie中的键值并打印
        for item in cookie:
            print(item.name + '=' + item.value)
    except urllib.error.URLError as e:
        print(e.reason)
    
    # 输出为:
    '''
    BAIDUID=F7E6BA151AD553B7443AEC5F52A7C0F7:FG=1
    BIDUPSID=F7E6BA151AD553B7C4468B6990F3AE47
    PSTM=1736572623
    BDSVRTM=1
    BD_HOME=1
    '''
    
    输出的内容就是我们得到的Cookies信息了,它记录了上一次我们和服务器之间的会话状态。

    当然,Cookies大多是以文件的形式保留的,我们是否也能将获得的Cookies保留为文件呢?

    显然我所说的不是直接将上面的输出用Python的文件处理方法来保存,而是使用http.cookiejar库中的MazillaCookierjar类来实现,下举一实例:

    import http.cookiejar, urllib.request, urllib.error
    
    url = 'http://www.baidu.com'
    
    # 设定一个要用到的文件名
    filename = 'cookies.txt'
    
    # 实例化一个MozillaCookieJar,传入文件名
    cookie = http.cookiejar.MozillaCookieJar(filename=filename)
    handler = urllib.request.HTTPCookieProcessor(cookie)
    opener = urllib.request.build_opener(handler)
    
    try:
        response = opener.open(url)
        # 调用MozillaCookieJar继承父类FileCookieJar内的save
        cookie.save(ignore_discard=True, ignore_expires=True)
    except urllib.error.URLError as e:
        print(e.reason)
    
    # 输出文件cookie.txt,内容为:
    '''
    # Netscape HTTP Cookie File
    # http://curl.haxx.se/rfc/cookie_spec.html
    # This is a generated file!  Do not edit.
    
    .baidu.com    TRUE    /   FALSE   1768110434  BAIDUID AEB83DAB7659350B81973D3E1836A7C5:FG=1
    .baidu.com    TRUE    /   FALSE   3884058081  BIDUPSID    AEB83DAB7659350B1F6AE8479DB6A802
    .baidu.com    TRUE    /   FALSE   3884058081  PSTM    1736574433
    www.baidu.com FALSE   /   FALSE       BDSVRTM 3
    www.baidu.com FALSE   /   FALSE       BD_HOME 1
    '''
    
    如果采用保存为LWP格式的LWPCookieJar类,保存后的cookies文件生成格式会与Mozilla的相差甚远,但是内容都差不多。

    #LWP-Cookies-2.0
    Set-Cookie3: BAIDUID="0E5946AF9D27A483F82164879F9976F1:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2026-01-11 06:06:17Z"; comment=bd; version=0
    Set-Cookie3: BIDUPSID=0E5946AF9D27A48334F7213F921CC5A8; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2093-01-29 09:20:24Z"; version=0
    Set-Cookie3: PSTM=1736575576; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2093-01-29 09:20:24Z"; version=0
    Set-Cookie3: BDSVRTM=2; path="/"; domain="www.baidu.com"; path_spec; discard; version=0
    Set-Cookie3: BD_HOME=1; path="/"; domain="www.baidu.com"; path_spec; discard; version=0
    

    那我们应该如何读取并利用我们得到的Cookies呢?

    答案是调用load()方法来读取本地已有的Cookies文件,加载到实例化的对象中。举一实例如下:

    import http.cookiejar, urllib.request, urllib.error
    
    url = 'http://www.baidu.com'
    
    # 实例化一个空的MozillarCookieJar
    cookie = http.cookiejar.MozillaCookieJar()
    
    # 加载当前目录下cookies.txt文件
    cookie.load('cookies.txt', ignore_discard=True, ignore_expires=True)
    handler = urllib.request.HTTPCookieProcessor(cookie)
    opener = urllib.request.build_opener(handler)
    
    try:
        response = opener.open(url)
        print(response.read().decode('utf-8'))
    except urllib.error.URLError as e:
        print(e.reason)
    
    这样子我们就可以将获取到的Cookies用到请求里啦!

使用urllib处理异常——error模块

error模块介绍

上一节主要讲了如何请求,但是如果网络不好或者出现了什么问题,请求失败出现了异常,该怎么办呢?

urllib的error模块定义了由request模块产生的异常,也就是说,如果request模块内的类或者方法发生了错误,就会“抛出”定义在error内的异常。

利用error模块来捕获这些异常并正确处理可以帮助我们避免程序因为报错而停止运行。

URLError类

URLError类继承自OSError类,有request模块产生的异常都可以通过捕获这个类来处理。

URLError类的定义如下:

class URLError(reason, filename=None)

其带有reason的属性,可以返回错误原因。

from urllib import request, error

url = 'http://omg.com/index.html'

header = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
}

req = request.Request(url=url, headers=header, method='GET')
try:
    response = request.urlopen(req)
except error.URLError as e:
    print(e.reason)

# 输出为
'''
Not Fonud
'''

如果实例中未通过Request类模拟浏览器行为,则输出为Forbidden。

通过URLError,我们轻松处理可能遇到的问题,防止程序的意外终止。

HTTPError类

HTTPError类是URLError类的子类,专门用于处理HTPP请求错误,

HTTPError类定义如下

class HTTPError(url, code, msg, hdrs, fp)

HTTPError有三个属性:

  • code:返回HTTP状态码,比如404,200等
  • reason:返回错误原因
  • headers:返回请求头

下举一实例来说明HTTPError用法:

from urllib import request, error

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

header = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
}

req = request.Request(url=url, headers=header, method='GET')

try:
    response = request.urlopen(req)
except error.HTTPError as e:
    print(e.reason)
    print(e.code)
    print(e.headers)
except error.URLError as e:
    print(e.reason)
else:
    print('Request Successfully')

# 输出为
'''
Not Found
404
Content-Length: 206
Content-Type: text/html; charset=iso-8859-1
Date: Sat, 11 Jan 2025 07:44:08 GMT
Server: Apache
Connection: close
'''

注意到以上代码我先尝试捕获子类错误,然后再尝试捕获父类错误,如果一切正常则执行else块内的代码,这是一个较好的捕获错误的代码。

我们可以看到,借助HTTPerror类可以更详细获得错误信息。

请注意,有时候抛出的错误可能不是字符串,而是一个对象,比如下面这个实例:

# 返回的error是对象
from urllib import request, error
import socket

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

try:
    response = request.urlopen(url, timeout=0.01)
except error.URLError as e:
    print(type(e.reason))
    if isinstance(e.reason, socket.timeout):
        print('Time Out')

# 输出为
'''
<class 'TimeoutError'>
Time Out
'''

可见返回的是一个TimeoutError类的错误,socket.timeout继承自TimeoutError类

使用urllib解析链接——parse模块

urllib库内提供的parse模块有很多实用的功能,包括了定义处理URL的标准接口,实现URL各部分的抽取、合并与连接转换,同时它还支持很多协议下的URL处理。

urlparse()

urlparse()的定义如下

def urlparse(url, scheme='',allow_fragments=True)

该方法可以实现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')
'''

我们能从输出中了解到什么?

首先urlparse将我们提供的url拆成了6个部分,分别是:

  • scheme:协议
  • netloc:域名
  • path:访问路径
  • params:参数
  • query:查询条件,一般用在"GET"类型的URL中
  • fragment:锚点,用于定位页面内部下拉位置

总的来说,一个标准的url链接模式应该是:

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

而urlparse()可以根据以上规则来拆分每一个标准的URL

从urlparse()的定义中,可以看到它有三个参数:

  • url:必填项,待解析的URL
  • scheme:默认协议,可以在URL不携带协议的情况下指定协议类型,如果URL已经携带了协议,则返回URL中的协议类型
    举一实例如下:
    from urllib import parse
    
    url_1 = 'www.baidu.com/index.html;user?id=5#comment'
    url_2 = 'https://www.baidu.com/index.html;user?id=5#comment'
    
    result_1 = parse.urlparse(url=url_1, scheme='http')
    result_2 = parse.urlparse(url=url_2, scheme='http')
    
    print(result_1)
    print(result_2)
    
    # 输出为
    '''
    ParseResult(scheme='http', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
    
    ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
    '''
    
    # 未指明协议时urlparse()返回规定的默认协议,已经存在协议的则按照实际协议为准
    
  • allow_fragments:即是否忽略fragment,如果被设置为False,那么fragment部分就会被解析为path、parameters或者query的一部分,而解析出的fragment为空。
    下举一实例:
    from urllib import parse
    
    url = 'https://www.baidu.com/index.html;user?id=5#comment'
    
    result = parse.urlparse(url=url, allow_fragments=False)
    print(result)
    
    # 输出为
    '''
    ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')
    '''
    
    # 原先fragment的部分被并入了query内
    
    当然如果原先的url内不包含params和query,fragment就会被并入path内

观察返回的ParseResult类,可以发现其实际上是一个python的元组,这意味着我们实际上可以通过索引下标,属性两种方式来读取,下举一实例:

from urllib import parse

url = 'https://www.baidu.com/index.html;user?id=5#comment'

result = parse.urlparse(url=url)
print(result[0], result.scheme, result[1], result.netloc, sep='\n')

# 输出为
'''
https
https
www.baidu.com
www.baidu.com
'''

urlunparse()

urlunparse()是urlparse()的对立方法,其接受一个长度为6的可迭代对象,比如列表,下举一实例:

from urllib import parse

data = ['https', 'www.baidu.com', 'index.html', 'user', 'id=5', '']
print(parse.urlunparse(data))

# 输出为
'''
https://www.baidu.com/index.html;user?id=5
'''

urlsplit()

urlsplit()类似于urlparse(),只不过其不再单独解析params这一部分,而是将其合并至前一个元素中。

urlunsplit()

urlunsplit()类似于前文的urlunparas(),只不过其参数长度为5

urljoin()

前面提到的urlparse()和urlsplit()都是生成新链接的方法,而urljoin()则是在旧链接基础上生成新链接的方法。

urljoin()接收两个参数,前一个参数作为base_url,第二参数是新链,urljoin()会将base_url的scheme、netloc和path与新链做对比,如果新链缺失,则用base_url的部分进行补充,而base_url中的params、query和fragment是不起作用的。

下举一实例:

from urllib import parse

print(parse.urljoin('http://www.baidu.com', 'FAQ.html'))
print(parse.urljoin('http://www.baidu.com', 'http://cuiqingcai.com/FAQ.html'))
print(parse.urljoin('http://www.baidu.com/index.html;user?id=5#comment', 'FAQ.html'))

# 输出为
'''
http://www.baidu.com/FAQ.html
http://cuiqingcai.com/FAQ.html
http://www.baidu.com/FAQ.html
'''

urlencode()

urlencode()可以很方便地将字典类型转化为URL的GET请求参数,下举一实例:

from urllib.parse import urlencode

base_url = 'http://www.baidu.com?'
GET_params = {
    'name': 'Harry',
    'age': '18'
}

url =  base_url + urlencode(GET_params)
print(url)

# 输出为
'''
http://www.baidu.com?name=Harry&age=18
'''

parse_qs(),parse_qsl()

parse_qs(),parse_qsl()与urlencode()方法相反,可以将序列化的GET请求参数转化为非序列化的类型,下举一实例:

from urllib.parse import parse_qs, parse_qsl

query = 'name=Harry&age=18'

print(parse_qs(query))
print(parse_qsl(query))

# 输出为
'''
{'name': ['Harry'], 'age': ['18']}
[('name', 'Harry'), ('age', '18')]
'''

可见parse_qs()返回的是一个字典类型,而parse_qsl()返回一个元组类型。

quote()

如果URL中需要带有中文参数,而向服务器无法直接提交带有中文的URL,此时使用quote()可以将中文字符转化为URL编码,下举一实例:

from urllib.parse import quote

base_url = 'http://www.baidu.com/s?wd='
keyword = '中文'
url = base_url + quote(keyword)
print(url)

# 输出为
'''
http://www.baidu.com/s?wd=%E4%B8%AD%E6%96%87
'''

unquote()

方法如其名,unquote()可以对URL进行解码,下举一实例:

from urllib.parse import unquote

url = 'http://www.baidu.com/s?wd=%E4%B8%AD%E6%96%87'

print(unquote(url))

# 输出为
'''
http://www.baidu.com/s?wd=中文
'''

分析Robots协议

利用urllib提供的robotparser模块,我们可以分析网站Robots协议。

Robots协议介绍

网络爬虫作为一种实用工具,可以在很多场景下运用,但如果对其使用不当,可能会徒增服务器压力,造成信息泄露或者其他严重后果。为了向网络爬虫表明这个服务器的爬取规则,产生了Robots协议,也称为网络爬虫排除标准。这个协议通常被记录在网站根目录下的robots.txt文件里。

一个“遵纪守法”的爬虫在访问一个站点的时候,它首先应该检查站点根目录下的robots.txt文件,如果存在,则爬虫会根据其中的规则定义的爬取范围来找,如果没有,爬虫则会爬取所有能访问的网页。

一个robotrs.txt通常由三部分组成: + User-agent:规则使用的爬虫名称,如果对所有爬虫生效,则为"*" + Disallow:禁止访问的目录,如果全部禁止则为"/" + Allow:允许访问的目录,一般不单独使用,和Disallow一同使用

以下是一些常见的robots.txt写法:

# 禁止所有爬虫访问
'''
User-agent: *
Disallow: /
'''

# 允许所有爬虫访问
'''
User-agent: *
Disallow: 
'''

# 只允许访问某个目录
'''
Use-agent: *
Disallow: /
Allow: /temp/
'''

# 禁止访问某些目录
'''
Use-agent: *
Disallow: /private/
Allow: /temp/
'''

# 只允许某个爬虫访问某个目录
'''
Use-agent: WebSpider
Disallow: /
Allow: /temp/
User-agent: *
Disallow: /
'''

爬虫名称

很多著名的爬虫程序都是有自己的“名字”的,比如百度的是BaiduSpider,Google是Googlebot,360搜索是360Spider。

robotparser

robotparser提供了一个类RobotFileParser,它可以根据网站提供的robots.txt文件来判断爬取爬虫是否有权限来爬取这个网页。

类RobotFileParser的定义是:

class RobotFileParser(url='')

其中常见的方法是:

  • set_url():用来设置robots.txt的url
  • read():读取文件并分析,但是不返回任何值,可以理解为初始化
  • parse():解析robots.txt某些行的文件
  • can_fetch():传入User-agent与url两个参数,判断是否可以抓取,返回布尔值
  • mtime:返回上次抓取和分析robots.txt的时间
  • modified:设置抓取和分析robots.txt文件的时间

下举一实例展现其用法:

from urllib import robotparser

url = 'http://www.jianshu.com/robots.txt'

# RobotFileParser内直接提供了set_url方法进行初始化,此处直接传入
robotfile = robotparser.RobotFileParser(url=url)
robotfile.read()

print(robotfile.can_fetch('*', 'http://www.jianshu.com/p/b67554025d7d'))
print(robotfile.can_fetch('*', 'http://www.jianshu.com//search?q=python&page=l&type=collections'))

# 输出为
'''
False
False
'''

当然使用parse()方法也可以实现:

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