2.1 urllib的使用

1.urllib的介绍

  • urllib可以实现HTTP请求的发送,且不需要关心HTTP协议本身甚至更底层的实现,我们要做的是指定请求的URL、请求头、请求体等信息。此外urllib还可以把服务器返回的响应转化为Python对象,通过该对象便可以获取响应内容,如响应状态码、响应头、响应体等。
  • 注意:在Python2中,有urllib和urllib2两个库来实现HTTP请求的发送。而在Python3中,urllib2库已经不存在了,统一为了urllib。
  • urllib库是Python内置的HTTP请求库,也就是说不需要额外安装,可直接使用。
  • urllib有4个模块:
    • request:这是最基本的HTTP请求模块,可以模拟请求的发送。就像在浏览器里输入网址然后按下回车一样,只需要给库方法传入URL以及额外的参数,就可以模拟实现发送的过程了。
    • error:异常处理模块。如果出现请求异常,那么我们可以捕获这些异常,然后进行重试或其他操作以保证程序运行不会意外终止
    • parse:一个工具模块。提供了许多URL的处理方法,例如拆分,解析,合并等。
    • robotparser:主要用来识别网站的robots.txt文件,然后判断哪些网站可以爬,哪些网站不可以,它其实用得比较少。

2.发送请求

  • urlopen

    • urllib.request模块提供了最基本的构造HTTP请求的方法,利用这个模块可以模拟浏览器的请求发送过程,同时它还具有处理授权验证(Authentication)、重定向(Redirection)、浏览器Cookie以及其他一些功能。
    • 下面我们实际操作体会,抓取新财富官网为例
    import urllib.request
    response = urllib.request.urlopen('https://www.xcf.cn/index.html')
    print(response.read().decode("utf-8"))
    
    • 运行结果如下:
    点击查看代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>新财富</title>
    <script type="module" crossorigin src="/assets/index-CuRSQ5ni.js"></script>
    <link rel="stylesheet" crossorigin 	href="/assets/index-CXq0w3ue.css">
    </head>
    <script></script>
    <body>
    <div id="app"></div>
    </body>
    <style></style>
    </html>
    
    • 这里只用了两行代码,便完成了对新财富官网的抓取,输出了其网页的源代码。得到源代码之后,我们想要的链接、图片地址、文本信息不就都可以提取出来了吗?
    • 接下来,看看返回的响应到底是什么。利用type方法输出响应的类型:
    import urllib.request
    response = urllib.request.urlopen('https://www.xcf.cn/index.html')
    print(type(response))
    
    • 输出结果如下:
      <class 'http.client.HTTPResponse'>
    • 可以看出,响应是一个HTTPResponse类型的对象,主要包含read、readinto、getgeader、getheaders、fileno等方法,以及msg、version、status、reason、debuglevel、closed等属性。
    • 得到响应之后,我们把它赋值给response变量,然后就可以调用上述那些方法和属性,得到返回结果的一些列信息了。
    • 例如,调用read方法就可以得到响应的网页内容,调用status属性可以得到响应结果的状态码
    • 下面再通过一个实例来看看:
    import urllib.request
    response = urllib.request.urlopen('https://www.xcf.cn/index.html')
    print(response.status)
    print(response.getheaders())
    print(response.getheader('Server'))
    
    • 输出结果如下:
    200
    [('Server', 'Tengine'), ('Content-Type', 'text/html'), ('Transfer-Encoding', 'chunked'), ('Connection', 'close'), ('Vary', 'Accept-Encoding'), ('Date', 'Sat, 05 Sep 2026 07:37:06 GMT'), ('Access-Control-Allow-Headers', '*'), ('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'), ('Access-Control-Allow-Origin', '*'), ('Via', 'cache46.l2cn2656[0,0,304-0,H], cache38.l2cn2656[1,0], ens-cache5.cn8109[0,0,200-0,H], ens-cache16.cn8109[2,0]'), ('Last-Modified', 'Tue, 01 Sep 2026 10:32:04 GMT'), ('ETag', '"6a96a9a4-25d"'), ('Age', '1951'), ('Ali-Swift-Global-Savetime', '1788593826'), ('X-Cache', 'HIT TCP_MEM_HIT dirn:-2:-2'), ('X-Swift-SaveTime', 'Sat, 05 Sep 2026 07:48:15 GMT'), ('X-Swift-CacheTime', '6531'), ('Timing-Allow-Origin', '*'), ('EagleId', '24973da417885957775163823e')]
    Tengine
    
    • 其中前两个输出分别是响应的状态码和响应的头信息;最后一个输出的是调用getheader方法,并传入参数Server,获取响应头中Server的值,结果是Tengine,意思为服务器是用Tengine搭建的。
    • 利用最基本的urlopen方法,已经可以完成对简单网页的GET请求抓取。
    • 如果想要给链接传递一些参数,又该怎么实现呢?首先看一下urlopen方法的API:
      urllib.request.urlopen(url, data=None, [timeout,]
      *,cafile=None,capath=None,cadefault=False, context=None)
    • 可以发现,除了第一个参数用于传递URL之外,我们还可以传递其他内容,例如data(附加数据)、timeout(超时时间)等。
    • 接下来就详细说明一下urlopen方法中几个参数的用法
      • data 参数

        • data参数是可选的。在添加该参数时,需要使用bytes方法将参数转化为字节流编码格式的内容,即bytes类型。另外,如果传递了这个参数,那么它的请求方式就不再是GET,而是POST了
        • 下面用实例来看一下:
        import urllib.request
        import urllib.parse
        
        data = bytes(urllib.parse.urlencode({'name':'germey'}),encoding='utf8')
        response = urllib.request.urlopen('https://www.httpbin.org/post',data=data)
        print(response.read().decode('utf8'))
        
        • 这里我们传递了一个参数name,值为germey,需要将它转码成bytes类型。转码时采用了bytes方法,该方法的第一个参数得是str类型,因此用urllib.parse模块里的urlencode方法将字典参数转化为字符串;第二个参数用于指定编码格式,这里指定为utf-8.
        • 此处我们请求的站点时www.httpbin.org,它可以提供HTTP请求测试。本次我们请求的URL为https://www.httpbin.org/post ,这个链接可以用来测试POST请求,能够输出请求的一些信息,其中包含我们传递的data参数。
        • 上面实例的运行结果如下:
        点击查看代码
        {
        	"args": {}, 
        	"data": "", 
        	"files": {}, 
        	"form": {
        		 "name": "germey"
        		 }, 
        "headers": {
        "Accept-Encoding": "identity", 
        "Content-Length": "11", 
        "Content-Type": "application/x-www-form-urlencoded", 
        "Host": "www.httpbin.org", 
        "User-Agent": "Python-urllib/3.14", 
        "X-Amzn-Trace-Id": "Root=1-6a9be0e5-04df21a7368666e3675a6e0c"
        }, 
        "json": null, 
        "origin": "183.212.85.25", 
        "url": "https://www.httpbin.org/post"
        }
        
        - 可以发现我们传递的参数出现在了form字段中,这表明时模拟表单提交,以POST方式传输数据。
      • timeout参数

        • timeout参数用于设置超时时间,单位为秒,意思是如果请求超出了设置的这个时间,还没没有得到响应,就会抛出异常。如果不指定该参数,则会使用全部默认时间。这个参数支持HTTP、HTTPS、FTP请求。
        • 下面用实例来看一下:
        import urllib.request
        import urllib.parse
        
        response = urllib.request.urlopen('https://www.httpbin.org/get',timeout=0.1)
        print(response.read().decode('utf8'))
        
        • 运行结果可能如下:
          urllib.error.URLError: <urlopen error timed out>
        • 这里我们设置超时时间为0.1秒。程序运行了0.1秒后,服务器依然没有响应,于是抛出了URLError异常。该异常属于urllib.error模块,错误原因是超时。
        • 因此可以通过设置这个超时时间,实现当一个网页长时间未响应时,就跳过对它的抓取。此外,利用try-except语句也可以实现,相关代码如下:
        import urllib.request
        import urllib.error
        import socket
        
        try:
            response = 			urllib.request.urlopen('https://www.httpbin.org/get',timeout=0.1)
        except urllib.error.URLError as e:
            if isinstance(e.reason,socket.timeout):
                print('TIME OUT!')
        
        • 这里我们请求了https://www.httpbin.org/get 这个测试链接,设置超时时间为0.1秒,然后铺获到URLError这个异常,并判断异常类型是socket.timeout,意思是超时异常,因此得出确实是因此超时而报错的结论,最后打印输出了TIME OUT!
        • 运行结果如下:
          TIME OUT!
        • 按照常理来说,0.1秒几乎不可能得到服务器响应,因此输出了TIME OUT!的提示
        • 通过设置timeout参数实现超时处理,有时还有很有用的。
      • 其他参数

        • 除了data参数和timeout参数,urlopen方法还有context参数,该参数必须是ssl.SSLContext类型,用来指定SSL的设置。
        • 此外,cafile和capath这两个参数分别用来指定CA证书和其路径,这两个在请求HTTPS链接时会有用。
        • cadefalt参数现在已经弃用了,其默认值为False。
        • 至此,我们讲解了urlopen方法的用法,通过这个最基本的用法,就可以完成简单的请求和网页的抓取。
  • Request

    • 利用urlopen方法可以发起最基本的请求,但它那几个简单的参数并不足以构建一个完整的请求。如果需要往请求中加入Headers等信息,就得利用更强大得Request类来构建请求了。
    • 首先,我们用实例感受一下Request类得用法:
    import urllib.request
    
    request = urllib.request.Request('http://www.baidu.com')
    response = urllib.request.urlopen(request)
    print(response.read().decode('utf-8'))
    
    • 可以发现,我们依然是用urlopen方法来发送请求,只不过这次该方法得参数不再是URL,而是一个Request类型的对象。通过构造这个数据结构,一方面可以将请求独立成一个对象,另一方面更加丰富和灵活的配置参数。
    • 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是一个字典,这就是请求头,我们在构造请求时,既可以通过headers参数直接构造此项,也可以通过调用请求实例的add_header方法添加。
    • 添加请求头最常见的方法就是通过修改User-Agent来伪装浏览器。默认的User-Agent是Python-urllib,我们可以通过修改这个值来伪装浏览器。例如要伪装谷歌浏览器,就可以把User-Agent设置为:
      Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36
    • 第四个参数origin_req_host指的是请求方的host名称或者IP地址。
    • 第五个参数unverifiable表示请求是否是无法验证的,默认取值是False,意思是用户没有足够的权限来接收这个请求的结果。例如,请求一个HTML文档中的图片,但是没有自动抓取图像的权限,这时unverifiable的值就是True。
    • 第六个参数method是一个字符串,用来指示请求使用的方法,例如GET、POST和PUT等。
    • 下面我们传入多个参数尝试构建Request类:
    import urllib.request
    from urllib import parse
    
    url = 'https://httpbin.org/post'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36',
        'host': 'httpbin.org',
        'referer': 'https://httpbin.org/',
    }
    dict = {'name':'abner'}
    data = bytes(parse.urlencode(dict),encoding = 'utf-8')
    request = urllib.request.Request(url=url, headers=headers,data=data,method='POST')
    response = urllib.request.urlopen(request)
    print(response.read().decode('utf-8'))
    
    • 这里通过4个参数构造了一个Request类,其中的url即请求URL,headers中指定了User-Agent和Host,data用urlencode方法和bytes方法把字典数据转成字节流格式。另外,指定了请求方式为POST。
    • 运行结果如下:
    点击查看代码
    {
      "args": {}, 
      "data": "", 
      "files": {}, 
      "form": {
        "name": "abner"
      }, 
      "headers": {
        "Accept-Encoding": "identity", 
        "Content-Length": "10", 
        "Content-Type": "application/x-www-form-urlencoded", 
        "Host": "httpbin.org", 
        "Referer": "https://httpbin.org/", 
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36", 
        "X-Amzn-Trace-Id": "Root=1-6a9c10f9-373fb94478ed468272fb957c"
      }, 
      "json": null, 
      "origin": "183.212.85.25", 
      "url": "https://httpbin.org/post"
    }
    
    
    - 观察结果可以发现,我们成功设置了data、headers和method。 - 通过add_header方法添加headers的方式如下:
    request = urllib.request.Request(url=url,data=data,method='POST')
    request.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36')
    request.add_header('host','httpbin.org')
    request.add_header('referer','https://httpbin.org/')
    
    • 有了Request类,我们就可以更加方便构建请求,并实现请求的发送啦。
  • 高级用法

    • 我们已经可以构建请求了,那么对于一些更高级的操作(例如Cookie处理、代理设置等),又该怎么实现呢?
    • 此时需要更强大得的工具,于是Handler登场了。简而言之,Handler可以理解为各种处理器,有专门处理登录验证的、处理Cookie的、处理代理设置的。利用这些Handler,我们几乎可以实现HTTP请求中所有的功能。
    • 首先介绍一下urllib.request模块里的BaseHandler类,这是其他所有Handler类的父类。它提供了最基本的方法,如理default_open、protocol_request等。
    • 会有这种Handler子类继承BaseHandler类,接下来举几个子类的例子如下
      • HTTPDefaultErrorHandler用于处理HTTP响应错误,所有 错误都会抛出HTTPError类型的异常。
      • HTTPRedirectHandler用于处理重定向。
      • HTTPCookieProcessor用于处理Cookie。
      • ProxyHandler用于设置代理,代理默认为空。
      • HTTPPasswordMgr用于管理密码,它维护着用户密码和对照表。
      • HTTPBasicAuthHandler用于管理认证,如果一个链接在打开时需要认证,那么可以用这个类来解决认证问题。
    • 关于这些类如何使用,我们先不着急了解,后面会用实例演示、
    • 另一个比较重要的类是OpenerDirector,我们称之为Opener。我们之前用过的urlopen方法实际上就是urllib库位我们提供的一个Opener。
    • 那么,为什么要引入Opener呢?因为需要实现更高级的功能。之前使用的Request类和urlopen类相当于类库已经封装好的极其常用的请求方法,利用这两个类可以完成基本的请求,但是现在我们需要实现更高级的功能,就需要深入一层进行配置,使用更底层的实例来完成操作,所以这里就用到了Opener。
    • 下面用几个实例来看看Handler类和Opener类的用法
    • 验证
    • 在访问某些网站时,例如https://ssr3.scrape.center ,可能会弹出这样的认证窗口,如下所示
      image
    • 遇到这种情况,就表示这个网站启用了基本身份认证,英文叫做HTTP Basic Access Authentication,这是一种登录验证方式,允许网页浏览器或其他客户端程序在请求网站时提供用户名和口令形式的身份凭证。
    • 那么爬虫如何请求这样的页面呢?借助HTTPBasicAuthHandler模块就可以完成,相关代码如下:
    from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
    from urllib.error import URLError
    
    username = 'admin'
    password = 'admin'
    url = 'https://ssr3.scrape.center/'
    
    p = HTTPPasswordMgrWithDefaultRealm()
    p.add_password(None,url,username,password)
    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对象auth_handler,其参数是HTTPPasswordMgrWithDefaultRealm对象,它利用add_password方法添加用户名和密码,这样就建立了一个用来处理验证的Handler类
    • 然后将刚建立的auth_handler类当作参数传入build_opener方法,构建了Opener,这个Opener在发送请求时就相当于已经验证成功了。
    • 最后利用Opener类中的open方法打开链接,即可完成验证。这里获取的结构就是成功后的页面源码内容。
    • 代理
    • 做爬虫的时候,免不了使用代理,如果要添加代理,可以这样做:
    from urllib.request import ProxyHandler,build_opener
    from urllib.error import URLError
    
    porxy_handler = ProxyHandler({
    		'https': '121.226.205.180:18262',
    		'https':'49.89.247.133:16674'
    		})
    opener = build_opener()
    
    try:
        resp = opener.open('http://www.baidu.com')
        print(resp.read().decode('utf-8'))
    except URLError as e:
        print(e.reason)
    
    • 这里需要我们事先在本地搭建一个HTTP代理,并让其运行在8080端口上。
    • 上面使用了ProxyHandler,其参数是一个字典,键名时协议类型(例如HTTP或者HTTPS等)、键值时代理链接,可以添加多个代理
    • 然后利用这个Handler和build_opener方法构建了一个Opener,之后发送请求即可。
    • Cookie
    • 处理Cookie需要用到相关的Handler
    • 我们先用实例来看看怎样获取网站的Cookie,相关代码如下:
    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,最后利用build_opner方法构建Opener,执行open函数即可.
    • 运行结果如下:
    BIDUPSID=5A9E8CFBC4806148E1873EC8D7948BA0
    PSTM=1788674044
    BAIDUID=5A9E8CFBC4806148E1873EC8D7948BA0:FG=1
    BAIDUID_BFESS=5A9E8CFBC4806148E1873EC8D7948BA0:FG=1
    
    • 可以看到,这里分别输出了每个Cookie条目的名称和值
    • 既然能输出,那么可不可以输出文件格式的内容跟呢?我们知道Cookie实际上也是以文本形式保存的。因此答案时肯定的,这里通过下面实例来看看:
    import http.cookiejar, urllib.request
    
    filename = 'cookie.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的子类,可以用来处理跟Cookie和文件相关的事件,例如读取和保存Cookie,可以将Cookie保存成Mozilla型浏览器的Cookie格式
    • 运行上面的实例之后,会发现生成一个cookie.txt文件。
    • 另外,LWPCookieJar同样可以读取和保存Cookie,只是Cookie文件的保存格式和MozillaCookieJar不一样,它会保存成LWP格式。
    • 要保存LWP格式的Cookie文件,可以在声明时就进行修改
      cookie = http.cookiejar.LWPCookieJar(filename)
    • 以上提到的两种保存格式得到的Cookie文件差异还是比较大的。
    • 那么生成Cookie文件,怎么从其中读取内容并加以利用呢?
    • 下面我们以LWPCookieJar格式为例来看一下:
    import http.cookiejar, urllib.request
    
    filename = 'cookie.txt'
    cookie = http.cookiejar.LWPCookieJar(filename)
    cookie.load('cookie.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'))
    
    • 可以看到,这里调用load方法来读取本地的cookie文件,获取了cookie的内容。这样做的前提是我们首先生成了LWOCookieJar格式的cookie,并保存了文件。读取cookie之后,使用同样的方法构建Handler类和Opener类即可操作。
    • 运行结果正常的话,会输出百度网页的源代码。

3.处理异常

  • 我们已经了解了如何发送请求,但是在网络不好的情况下,如果出现了异常,该怎么办呢?这时要是不处理这些异常,程序很可能会因为报错而终止运行,所以异常处理还是十分有必须的

  • urllib库中error模块定义了由request模块产生的异常。当出现问题时,request模块便会抛出error模块中的定义的异常。

  • URLError

    • URLError类来自urllib库中的error模块,继承自OSError类,是error异常模块的基类,由request模块产生的异常都可以通过捕获这个类来处理。
    • 它具有一个属性reason,即返回错误的原因。
    • 下面用一个实例来看一下
    from urllib import request,error
    
    try:
        response = request.urlopen('https://cuiqingcai.com/404')
    except error.URLError as e:
        print(e.reason)
    
    • 我们打开了一个不存在的页面,照理来说应该会报错,但是我们捕获了URLError这个异常,运行结果如下:
      Not Found
    • 程序没有直接报错,而是输出了错误原因,这样可以避免程序异常终止,同时异常得到了有效处理。
      -HTTPError
    • HTTPError是URLError的子类,专门用来处理HTTP请求错误,例如认证请求失败等。它由如下3个属性:
      • code:返回HTTP状态码,例如404表示网页不存在,500表示服务器内部错误等
      • reason:同父类一样,用于返回错误的原因
      • headers:返回请求头
    • 下面我们用几个实例来看看:
    from urllib import request,error
    
    try:
        response = request.urlopen('https://cuiqingcai.com/404')
    except error.HTTPError as e:
        print(e.reason,e.code,e.headers,sep='\\n')
    
    • 运行结构如下:
    Not Found
    404
    Connection: close
    Content-Length: 9379
    Server: GitHub.com
    Content-Type: text/html; charset=utf-8
    Access-Control-Allow-Origin: *
    ETag: "6a99a332-24a3"
    Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src data:; connect-src 'self'
    x-proxy-cache: MISS
    X-GitHub-Request-Id: 8FC2:25BFE1:4DFBB4C:4EDF6F3:6A9EC361
    x-github-edge-region: fra
    Accept-Ranges: bytes
    Date: Mon, 07 Sep 2026 14:17:51 GMT
    Via: 1.1 varnish
    Age: 1070
    X-Served-By: cache-sin-wsss1830046-SIN
    X-Cache: HIT
    X-Cache-Hits: 1
    X-Timer: S1788790672.905354,VS0,VE9
    Vary: Accept-Encoding
    X-Fastly-Request-ID: 16931be883888c7038d1b9ac2185789090822da5
    
    • 依然是打开同样的网址,这里捕获了HTTPError异常,输出了reason、code和headers属性。
    • 因为URLError是HTTPError的父类,所以可以先选择捕获子类的错误,再捕获父类的错误,于是上述代码的更好写法如下:
    from urllib import request,error
    
    try:
        response = request.urlopen('https://cuiqingcai.com/404')
    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')
    
    • 这样就可以做到先捕获HTTPError,获取它的错误原因,状态码,请求头等信息。如果不是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!")
    
    • 这里我们直接设置超时时间来强制抛出timeout异常。
    • 运行结果如下:
    <class 'TimeoutError'>
    TIME OUT!
    
    • 可以发现,reason属性的结果是socket.timeout类。所以这里可以用isinstance方法来判断它的类型,做出更详细的异常判断。

4.解析链接

  • 前面说过,urllib库里还提供了parse模块,这个模块定义了处理URL的标准接口,例如实现URL各部分的抽取、合并以及链接转换。它支持如下协议的URL处理:file、ftp、gopher、hdl、http、https、imap、mailto、mms、news、nntp、prospero、rsync、rtsp、rtspu、sftp、sip、sips、snews、svn、svn+ssh、telnet和wais。

  • 下面我们将介绍parse模块中的常用方法,看一下它的便捷之处。

  • urlparse

    • 该方法可以实现URL的识别和分段,这里先用一个实例来看一下:
    from urllib.parse import urlparse
    
    result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
    print(type(result))
    print(result)
    
    • 这里我们利用urlparse方法对一个URL进行了解析,然后输出了解析结果的类型以及结果本身。
    • 运行结果如下:
    <class 'urllib.parse.ParseResult'>
    ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
    
    • 可以看到,解析结果是一个ParseResult类型的对象,包含6部分,分别是scheme,netloc,path,params,query和fragment。

    • 再观察一下上述实例中的URL:http://www.baidu.com/index.html;user?id=5#comment

    • 可以发现,urlparse方法在解析URL时有特定的分隔符。例如://前面的内容就是scheme,代表协议。第一个/符号前面便是netloc,即域名;后面是path,即访问路径。分号;后面是params,代表参数。问号?后面是查询条件query,一般用作GET类型的URL。井号#后面是锚点fragment,用于直接定位页面内部的下拉位置。

    • 于是可以得出一个标准的链接格式,具体如下:

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

    • 一个标准的URL都会符合这个规则,利用urlparse方法就可以将它拆分开来。

    • 除了这种最基本的解析方式外,urlparse方法还有其他配置吗?接下来,看一下它的API用法:

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

    • 可以看到,urlparse方法有3个参数:

      • urlstring:这时必填项,即待解析的URL
      • scheme:这是默认的协议。如果待解析的URL没有带协议信息,就会将这个作为默认协议。我们用实例来看一下:
      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参数,返回了https。
      • 假设带上协议信息:
        result = urlparse('http://www.baidu.com/index.html;user?id=5#comment',scheme='https')
      • 则结果如下:
        ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
      • 可见,scheme参数只有在URL中不包含协议信息的时候才生效。如果URL中有,就会返回解析出的scheme
      • allow_fragments:是否忽略fragment。如果此项被设置位False,那么fragment部分就会被忽略,它会被解析位path、params或者query的一部分,而fragment部分为空。
      • 下面我们用实例来看一下
      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='')
      • 假设URL中不包含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='')
      • 可以发现,此时fragment会被解析为path的一部分
      • 返回结果ParseResult实际上是一个元组,既可以用属性名获取其内容,也可以用索引来顺序获取,实例如下:
      from urllib.parse import urlparse
      
      result = urlparse('https://www.baidu.com/index.html#comment',allow_fragments=False)
      print(result.scheme,result[0],result.netloc,result[1],sep='\n')
      
      • 运行结果如下:
      https
      https
      www.baidu.com
      www.baidu.com
      
      • 可以发现,两种获取方式都可以成功获取,且结果是一致的。
  • urlunparse

    • 有了urlparse方法,响应就会有它对立方法urlunparse,用于构造URL。这个方法接收的参数是一个可迭代对象,其长度必须是6,否则会抛出参数数量不足或者过多的问题。先用一个实例看一下:
    from urllib.parse import urlunparse
    data = ['https','www.baidu.com','index.html','user','a=6','comment']
    print(urlunparse(data))
    - 这里参数data用了列表类型。当然,也可以用其他类型,例如元组或者特定的数据结构。
    - 运行结果如下:
    `https://www.baidu.com/index.html;user?a=6#comment`
    - 这样我们就成功实现了URL的构造。
    
  • urlsplit

    • 这个方法和urlparse方法非常相似,只不过它不再单独解析params这一部分,params会合并到path中,只返回5个结果。实例如下
    from urllib.parse import urlsplit
    result = urlsplit(r'https://www.baidu.com/index.html;user?a=6#comment')
    print(result)
    
    • 运行结果如下:
      SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='a=6', fragment='comment')
    • 可以发现,返回结果是SplitResult,这其实也是一个元组,即可以用属性名获取其值,也可以用索引获取。实例如下:
    from urllib.parse import urlsplit
    result = urlsplit(r'https://www.baidu.com/index.html;user?a=6#comment')
    print(result.scheme, result[0])
    
    • 运行结果如下:
      https https
  • urlunsplit

    • 与urlunparse方法类似,这也是将链接各个部分组合成完整链接的方法,传入的参数也是一个可迭代对象,例如,列表、元组等,唯一区别是这里参数的长度必须是5.实例如下:
    from urllib.parse import urlunsplit
    data = ['https','www.baidu.com','index.html','a=6','comment']
    print(urlunsplit(data))
    
    • 运行结果如下:
      https://www.baidu.com/index.html?a=6#comment
  • urljoin

    • urlunparse和urlunsplit方法都可以完成链接的合并,不过前提都是必须有特定长度的对象,链接的每一个部分都要清晰分开。
    • 除了这两种方法,还有一种生成链接的方法,是urljoin.我们可以提供一个base_url(基础链接)作为该方法的第一个参数,将新的连接作为第二个参数。urljoin方法会分析base_url的scheme、netloc和path这3个内容,并对新链接缺失的部分进行补充,最后返回结果。
    • 下面通过几个实例看一下:
    from urllib.parse import urljoin
    
    print(urljoin('https://www.baidu.com','index.html'))
    print(urljoin('https://www.baidu.com/ab.html','https://www.cqc.com/index.html'))
    print(urljoin('https://www.baidu.com/ab.html','https://www.cqc.com/index.html?wd=python'))
    print(urljoin('https://www.baidu.com?wd=python','https://www.cqc.com/index.html'))
    print(urljoin('https://www.baidu.com','?a=6#comment'))
    print(urljoin('www.baidu.com','?a=6#comment'))
    print(urljoin('www.baidu.com#comment','?a=6'))
    
    • 运行结果如下:
    https://www.baidu.com/index.html
    https://www.cqc.com/index.html
    https://www.cqc.com/index.html?wd=python
    https://www.cqc.com/index.html
    https://www.baidu.com?a=6#comment
    www.baidu.com?a=6#comment
    www.baidu.com?a=6
    
    • 可以发现,base_url提供了三项内容:scheme、netloc和path。如果新的链接里不存在这三项,就予以补充;如果存在,就使用新的连接里面的,base_url中的是不起作用的
    • 通过urljoin方法,我们可以轻松是实现链接的解析、拼合与生成。
posted @ 2026-09-05 17:35  abner_pan  阅读(13)  评论(0)    收藏  举报