Python网络爬虫知识点汇总

前言

本篇博客用于记录学习到的Python网络爬虫相关知识点。

三、请求模块

3.1 urllib 模块

3.1.1 urllib 简介

urllib库,Python内置的HTTP请求库,官方文档链接在这里。它包含如下4个模块:

  • request:最基本的HTTP请求模块,用来模拟浏览器发送请求,还带有处理授权验证(authentication)、重定向(redirection)、浏览器 Cookies 以及其他内容。
  • error:异常处理模块,该模块定义了由 request 模块产生的异常。如果出现了请求错误,request 模块便会抛出 error 模块中定义的异常。
  • parse:解析工具模块,包含许多URL处理方法,比如拆分、解析、合并等。
  • robotparser:主要用于解析网站的robots.txt文件,判断网站是否可以爬取信息(该模块使用较少)。

3.1.2 urllib.request 请求模块

简介: 最基本的HTTP请求模块,用来模拟浏览器发送请求,还带有处理授权验证(authentication)、重定向(redirection)、浏览器 Cookies 以及其他内容。

1. urlopen()方法
  • 格式: urllib.request.urlopen(url,data=None,[timeout,]*,cafile=None,capath=None,cadefault=False,context=None)
  • 作用: 实现最基本的 HTTP 请求,并接收服务端所返回的响应数据。
  • 返回类型: http.client.HTTPResponse 对象。
  • 主要方法: read()、readinto()、getheader(name)、getheaders()、fileno()
    • read(): 得到返回的网页内容(bytes类型),使用.decode("utf-8)方法解码成可读形式,解码方式为unicode_escape可正常显示中文。。
    • getheaders():得到返回的所有响应头信息
    • getheader(name):得到响应头信息中指定的参数值,如果属性不存在,返回None。
  • 主要属性: msg、version、status、reason、debuglevel、closed
    • status:得到返回的响应状态码
  • 参数:
    • url:需要访问网站的URL完整地址。
    • data:通过该参数确认请求方式。默认值为None,表示请求方式为GET,否则为POST。在发送 POST 请求时,需要将字典类型的参数值转换为字节bytes类型的数据,传递给参数 data。
    • timeout:设置请求超时时间,以秒为单位,默认不超时。如果请求超出了设置的时间,还没有得到响应,就会抛出异常。
    • cafile、capath:指定一组 HTTPS 请求受信任的 CA (Certificate Authority)证书单个文件和文件所在目录。这两个参数在请求 HTTPS 链接时会有用。
    • cadefault:(参数已经弃用)CA 证书默认值,默认值为 False。
    • context:ssl.SSLContext 类型,用来指定 SSL 设置。(SSL,Secure Scoket Layer,安全套接层,在传输通信协议(TCP/IP)上实现的一种安全协议。)
  • 编程举例:
    import socket
    import urllib.parse
    import urllib.error
    import urllib.request
    
    try:
    	data = bytes(urllib.parse.urlencode({'hello': 'world'}), encoding='utf-8')
    	response = urllib.request.urlopen("http://httpbin.org/post", data=data, timeout=0.1)
    	print(response.read().decode('utf-8'))
    except urllib.error.URLError as e:
    	if isinstance(e.reason, socket.timeout):
    		print("TIME OUT")
    
    • bytes():第一个参数是需要转换的字符串,第二个参数指定编码格式。
    • httpbin.org:提供HTTP请求测试的网站。/post测试 POST 请求,/get测试 GET 请求。
    • socket.timeout 类型:超时异常
2. Request()方法
  • 格式: urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
  • 作用: 构建一个含有多种功能(如 Headers、Cookies、代理IP等)的请求对象。
  • 参数:
    • url:需要访问网站的URL完整地址。
    • data:通过该参数确认请求方式。默认值为None,表示请求方式为GET,否则为POST。在发送 POST 请求时,需要将字典类型的参数值转换为字节bytes类型的数据,传递给参数 data。
    • headers:字典类型参数,设置请求头部信息(也可以通过调用请求实例的 add_header()方法设置)。添加请求头信息最常见的用法就是修改 User-Agent 来伪装成浏览器,默认的 User-Agent 是 Python-urllib。
    • origin_req_host:设置请求方的 host 名称或者 IP 地址。
    • unverifiable:请求是否无法验证,即用户是否缺少足够权限来接收这个请求结果,默认为False。
    • method:设置请求方式,比如 GET、POST 和 PUT 等,默认 GET。
  • 关于请求头: 在浏览器中寻找有效的请求头信息步骤如下:
    1. F12开发者工具
    2. 点击“网络”
    3. 任意打开一个网页
    4. 在请求列表里选中一项请求信息
    5. 在“消息头”中找到请求头信息
  • 编程举例:
    import urllib.parse
    import urllib.request
    
    url = "http://httpbin.org/post"
    data = bytes(urllib.parse.urlencode({'hello': 'world'}), encoding='utf-8')
    # 示例1:伪装成谷歌浏览器
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
    # 示例2:伪装成火狐浏览器
    headers = {'User-Agent':'Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11'}
    request = urllib.request.Request(url=url, data=data, headers=headers, method='POST')
    response = urllib.request.urlopen(request)
    print(response.read().decode('utf-8'))
    
    # 使用 add_header()方法添加请求头
    request = urllib.request.Request(url=url, data=data, method='POST')
    request.add_header('User-Agent', 'Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11')
    
3. Handler 处理器
  • 作用: 利用 Handler 处理器,几乎可以做到 HTTP 请求中所有的事情。
  • 分类: 所有的 Handler 子类均继承自 urllib.request.BaseHandler 类,常见的 Handler 处理器如下:
    • HTTPDefaultErrorHandler:用于处理 HTTP 响应错误,错误都会抛出 HTTPError 类型的异常。
    • HTTPRedirectHandler:用于处理重定向。
    • HTTPCookieProcessor:用于处理 Cookies。
    • ProxyHandler:用于设置代理,默认代理为空。
    • HTTPPasswordMgr:用于管理密码,维护用户名和密码的表。
    • HTTPBasicAuthHandler:用于管理认证,解决链接打开时需要认证的问题。
  • Opener: Handler 处理器常与 OpenerDirector (简称 Opener) 类搭配使用(利用 Handler 来构建 Opener),从而实现更高级的功能。Opener 可以使用 open() 方法,返回的类型与 urlopen() 一致。

(1) Cookie

  • 定义: Cookie 指某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据。通俗讲,Cookie 是服务器向客户端返回响应数据时所留下的标记,当客户端再次访问服务器时将携带这个标记。
  1. 模拟登录: 获取登录验证的请求地址,并通过 POST 请求的方式将表单数据发送至该地址。
    • 火狐浏览器打开至登录界面,开发者工具 -> 网络 -> “设置”按钮 -> 持续记录
    • 输入用户名和密码,点击“登录”,在开发者工具的网络请求列表点击“chklogin.html”的网络请求文件,获取登录验证的请求地址,点击“请求”选项获取所需表单数据。
    • 模拟登录示例:
      import urllib.parse
      import urllib.request
      
      url = url
      data = bytes(urllib.parse.urlencode({'username':'admin', 'password':'123456'}), encoding='utf-8')
      headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
      request = urllib.request.Request(url=url, data=data, headers=headers, method='POST')
      response = urllib.request.urlopen(request)
      print(response.read().decode('utf-8'))
      
  2. 获取Cookie: 获取登录成功所生成的 Cookie 信息。
    • 创建 CookieJar 对象
    • 利用 HTTPCookieProcessor 来构建一个 Handler
    • 利用 build_opener() 方法构建出 Opener
    • 发送登录请求
     import urllib.parse
     import urllib.request
     import http.cookiejar
     
     url = url
     data = bytes(urllib.parse.urlencode({'username':'admin', 'password':'123456'}), encoding='utf-8')
     cookie = http.cookiejar.CookieJar()
     handler = urllib.request.HTTPCookieProcessor(cookie)
     opener = urllib.request.build_opener(handler) 
     response = opener.open(url, data=data)
    
     for item in cookie:  # 注意这里是cookie
         print(item.name+"="+item.value)
    
  3. 保存/读取 Cookie 文件: 将 Cookie 信息保存成指定的文件形式,下次登录请求时直接读取文件中的 Cookie 信息。
    # 保存成文件格式
    filename = 'cookies.txt'
    
    # 将 Cookies 保存成 Mozilla型浏览器的 Cookies 格式
    cookie = http.cookiejar.MozillaCookieJar(filename)
    
    # 将 Cookies 保存成 libwww-perl(LWP)格式
    cookie = http.cookiejar.MozillaCookieJar(filename)
    
    handler = urllib.request.HTTPCookieProcessor(cookie)  
    opener = urllib.request.build_opener(handler)  
    response = opener.open(url, data=data)  
    cookie.save(ignore_discard=True, ignore_expires=True)
    # ignore_discard=True 即使 Cookie 将被丢弃,也将它保存下来。
    # ignore_expires=True 如果在该文件中 Cookie 已经存在,则覆盖原文件写入。
    
    # 从文件中读取 Cookies文件
    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(url) #  不需要data了,已经有Cookie了
    print(response.read().decode('utf-8'))
    

(2) 设置代理IP

  • 作用: 对网络请求设置代理IP,可以避免在短时间内同一IP访问服务器的大量数据时,客户端IP被禁用,从而解决该情况下的反爬虫技术。
  • 步骤:
    • 创建 ProxyHandler 对象:参数为字典类型的代理 IP,键为协议类型(如HTTP、HTTPS等),键为代理链接,可以添加多个代理。
    • 利用 build_opener() 方法构建出 Opener
    • 发送登录请求
    import urllib.request
    
    proxy_handler = urllib.request.ProxyHandler({  
        'https': '58.220.95.114:10053',
        'http': 'http://127.0.0.1:9743'  
    })  
    opener = urllib.request.build_opener(proxy_handler)
    response = opener.open(url, timeout=2)
    print(response.read().decode('utf-8'))
    

(3) 验证

  • 使用场景: 网站在打开时弹出提示框,要求输入用户名和密码,验证成功后才能查看页面。
  • 步骤 :
    • 创建 HTTPBasicAuthHandler 对象:参数为 HTTPPasswordMgrWithDefaultRealm 对象,利用 add_password() 方法添加用户名和密码。
    • 利用 build_opener() 方法构建出 Opener
    • 发送登录请求
    from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener  
    
    username = 'admin'  
    password = '123456'   
    
    p = HTTPPasswordMgrWithDefaultRealm()
    p.add_password(None, url, username, password)
    handler = HTTPBasicAuthHandler(p)
    opener = build_opener(handler)
    response = opener.open(url)
    print(response.read().decode('utf-8'))
    

3.1.3 urllib.error 异常模块

简介: 异常处理模块,该模块定义了由 request 模块产生的异常。如果出现了请求错误,request 模块便会抛出 error 模块中定义的异常。

1. URLError 类
  • 含义: urllib.error 异常模块的基类,继承自 OSError 类,由 urllib.request 模块产生的异常都可以使用该类进行捕获
  • 主要属性:
    • reason:返回错误的原因
  • 编程举例:
    from urllib import request, error
    
    try:
    	response = request.urlopen(url)
    except error.URLError as e:
    	print(e.reason)
    
2. HTTPError 类
  • 含义: URLError 的子类,专门用来处理 HTTP 请求错误,比如认证请求失败等。
  • 主要属性:
    • code:返回 HTTP 状态码
    • reason:返回错误的原因
    • headers:返回请求头
  • 编程举例:
    from urllib import request, error
    
    try:
    	response = request.urlopen(url)
    except error.HTTPError as e:
    	print(e.reason, e.code, e.headers)
    
3. 综合举例
  • 使用技巧: 可以先捕获子类的错误,再捕获父类的错误,最后用 else 来处理正常的逻辑。
  • 关于 reason 属性: reason 属性返回的不一定是字符串,也可能是一个对象,如 socket.timeout 类
  • 编程举例:
    from urllib import request, error
    
    try:
    	response = request.urlopen(url)
    except error.HTTPError as e:
    	print(e.reason, e.code, e.headers)
    except error.URLError as e:
    	print(e.reason)
    else:
    	print("Request Successfully!")
    

3.1.4 urllib.parse 解析模块

  • 简介: 解析工具模块,包含许多URL处理方法,比如拆分、解析、合并等。
  • 标准的链接格式:
    • 格式:scheme://netloc/path;params?query#fragment
    • 解释:://前面的就是 scheme,代表协议;第一个/符号前面是 netloc,即域名,后面是 path,即访问路径;分号;后面是 params,代表参数;问号?后面是查询条件 query,一般用作 GET 类型的 URL;井号#后面是锚点,用于直接定位页面内部的下拉位置。
1. urlparse()
  • 格式: urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
  • 作用: 识别 URL 并分段。
  • 返回类型: urllib.parse.ParseResult 类型对象,包含了 6 部分:scheme、netloc、path、params、query、fragment。
  • 参数:
    • urlstring: 必填参数,表示待解析的 URL。
    • scheme: 可选参数,默认为空字符串,表示需要设置的默认协议。scheme 参数只有在 URL 中不包含 scheme 信息时才生效。
    • allow_fragments: 是否忽略 fragment。如果设置为 False,fragment 部分就会被忽略,它会被解析为 path、parameters 或者 query 的一部分,而 fragment 部分为空。
  • 方法属性: 返回的 ParseResult 实际上是一个元组,既可以用索引顺序,也可以用属性名来获取对应的属性值。
  • 编程示例:
    from urllib.parse import urlparse
    
    result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='http', allow_fragments=False)
    print(result)
    # 使用索引顺序获取属性值
    print(result[0],result[1],result[2],result[3],result[4],result[5])
    # 使用属性名获取属性值
    print(result.scheme,result.netloc,result.path,result.params,result.query,result.fragment)
    
2. urlsplit()
  • 格式: urllib.parse.urlsplit(urlstring, scheme='', allow_fragments=True)
  • 作用: 方法与 urlparse() 类似,但是不再单独解析 params 这部分,将其合并到 path 中。(解析识别 URL 并分段。)
  • 返回类型: urllib.parse.SplitResult 类型对象,包含了 5 部分:scheme、netloc、path、query、fragment。
  • 参数:
    • urlstring: 必填参数,表示待解析的 URL。
    • scheme: 可选参数,默认为空字符串,表示需要设置的默认协议。scheme 参数只有在 URL 中不包含 scheme 信息时才生效。
    • allow_fragments: 是否忽略 fragment。如果设置为 False,fragment 部分就会被忽略,它会被解析为 path 或者 query 的一部分,而 fragment 部分为空。
  • 方法属性: 返回的 SplitResult 实际上是一个元组,既可以用索引顺序,也可以用属性名来获取对应的属性值。
  • 编程示例:
    from urllib.parse import urlsplit
    
    result = urlsplit('www.baidu.com/index.html;user?id=5#comment', scheme='http', allow_fragments=False)
    print(result)
    # 使用索引顺序获取属性值
    print(result[0],result[1],result[2],result[3],result[4])
    # 使用属性名获取属性值
    print(result.scheme,result.netloc,result.path,result.query,result.fragment)
    
3. urlunparse()
  • 格式: urllib.parse.urlunparse(components)
  • 作用: 将可迭代对象类型的参数合并成 URL。
  • 返回类型: 合并后的URL,str类型。
  • 参数:
    • components: 长度必须为 6 的可迭代对象,如列表、元组、字典等
  • 编程示例:
    from urllib.parse import urlunparse
    
    # 列表
    list_utrl = ['http','www.baidu.com','index.html','','','']
    # 元组
    tuple_utrl = ('http','www.baidu.com','index.html','','','')
    # 字典
    dict_utrl = {'scheme':'http','netloc':'www.baidu.com','path':'index.html','query':'','params':'','fragment':''}
    result = urlunparse(list_utrl)
    result = urlunparse(tuple_utrl)
    # dict_utrl 等价于 dict_utrl.keys()
    result = urlunparse(dict_utrl.values())
    print(result)
    
4. urlunsplit()
  • 格式: urllib.parse.urlunsplit(components)
  • 作用: 方法与 urlunsplit() 类似,但是不再单独需要 params 这部分,将其放在 path 中。(将可迭代对象类型的参数合并成 URL。)
  • 返回类型: 合并后的URL,str类型。
  • 参数:
    • components: 长度必须为 5 的可迭代对象,如列表、元组、字典等
  • 编程示例:
    from urllib.parse import urlunparse
    
    # 列表
    list_utrl = ['http','www.baidu.com','index.html','','']
    # 元组
    tuple_utrl = ('http','www.baidu.com','index.html','','')
    # 字典
    dict_utrl = {'scheme':'http','netloc':'www.baidu.com','path':'index.html','query':'','fragment':''}
    result = urlunparse(list_utrl)
    result = urlunparse(tuple_utrl)
    # dict_utrl 等价于 dict_utrl.keys()
    result = urlunparse(dict_utrl.values())
    print(result)
    
5. urljoin()
  • 格式: urllib.parse.urljoin(base, url, allow_fragments=True)
  • 作用: 解析基础链接base的 scheme、netloc 和 path 三部分内容,并对新链接url对应缺失的部分进行补充,最后返回新的 url。
  • 参数:
    • base:基础链接
    • url:新链接
    • allow_fragments:基础链接中的 params(如果存在)或者 fragment(如果 params 和 query 都不存在)是否合并到 path 中,默认为Ture,不合并。
  • 编程举例:
    from urllib.parse import urljoin
    
    result = urljoin('www.baidu.com/about.html#hahaha','?category=2#comment',allow_fragments=False)
    print(result)
    
6. urlencode() 方法
  • 格式: urllib.parse.urlencode(query)
  • 作用: 对请求地址中的参数进行编码,尤其是对于中文参数。
  • 参数:
    • query:需要进行编码的参数字典
  • 编程举例:
    from urllib.parse import urlencode
    
    base_url = 'http://httpbin.org/get?'
    params = {'name':'Chia','country':'中国','age':30}
    url = base_url + urlencode(params)
    print(url)
    
7. quote() 方法
  • 格式: urllib.parse.quote(string)
  • 作用: 与 urlencode() 方法类似,对请求地址中的参数进行编码,但是参数必须是字符串类型
  • 参数:
    • string:需要进行编码的参数字符串
  • 编程举例:
    from urllib.parse import quote
    
    base_url = 'http://httpbin.org/get?country='
    url = base_url + quote('中国')
    print(url)
    
8. unquote() 方法
  • 格式: urllib.parse.unquote(string)
  • 作用: 将编码后的 URL 字符串逆向解码。
  • 参数:
    • string:需要进行逆向编码的 URL 字符串
  • 编程举例:
    from urllib.parse import urlencode, unquote
    
    base_url = 'http://httpbin.org/get?'
    params = {'name':Chia,'country':'中国','age':30}
    url = base_url + urlencode(params)
    decode_url = unquote(url)
    print(decode_url)
    
9. parse_qs() 方法
  • 格式: urllib.parse.parse_qs(string)
  • 作用: 获取 URL 中的参数部分后,使用该方法将参数转换成字典类型的数据。
  • 返回类型: 字典 dict 类型。
  • 参数:
    • string:待转换的参数。
  • 编程举例:
    from urllib.parse import urlsplit, parse_qs
    
    url = 'http://httpbin.org/get?name=Chia&age=18'
    dict_query = parse_qs(urlsplit(url).query)
    print(dict_query)
    # 输出结果:{'name':['Chia'], 'age':['30']}
    
10. parse_qsl() 方法
  • 格式: urllib.parse.parse_qsl(string)
  • 作用: 获取 URL 中的参数部分后,使用该方法将参数转换成元组组成的列表数据。
  • 返回类型: 列表 list 类型。
  • 参数:
    • string:待转换的参数。
  • 编程举例:
    from urllib.parse import urlsplit, parse_qsl
    
    url = 'http://httpbin.org/get?name=Chia&age=18'
    list_query = parse_qsl(urlsplit(url).query)
    print(list_query)
    # 输出结果:[('name','Chia'), ('age','30')]
    

3.1.5 urllib.robotparser 模块

简介: 主要用于解析网站的robots.txt文件,判断网站是否可以爬取信息(该模块使用较少)。

1. Robots 协议
  • 定义: Robots 协议(又称爬虫协议、机器人协议),全名叫作网络爬虫排除标准(Robots Exclusion Protocol),用来告诉爬虫和搜索引擎页面的可抓取性。

  • 协议介绍:

    • 位置: Robots 协议通常是一个名为 robots.txt 的文本文件,放在网站根目录下,和网站的入口文件放在一起。
    • 规则: 当搜索爬虫访问站点时,首先会检查该站点根目录下是否存在 robots.txt 文件:如果存在,爬虫会根据其中定义的爬取范围来爬取;否则,爬虫便会访问所有可直接访问的页面。
    • 协议内容:
      • User-agent:描述了 Robots 协议的有效范围(指定搜索爬虫)
      • Disallow: 指定了不允许抓取的目录
      • Allow:通常与 Disallow 一起使用,用来排除某些限制
  • 协议举例:

    # 含义:对所有搜索爬虫只允许爬取 public 目录
    User-agent: *
    Disallow: /
    Allow: /public/
    
    # 含义:禁止所有搜索爬虫访问任何目录
    User-agent: *  
    Disallow: /
    
    # 含义:允许所有搜索爬虫访问任何目录,只可以直接把txt文件留空
    User-agent: *  
    Disallow:
    
    # 含义:禁止所有搜索爬虫访问private目录和tmp目录
    User-agent: *  
    Disallow: /private/
    Disallow: /tmp/
    
    # 含义:只允许WebCrawler爬虫访问所有目录,禁止其他所有搜索爬虫访问任何目录
    User-agent: WebCrawler
    Disallow:
    User-agent: *
    Disallow: /
    
  • 爬虫名称: 常见的搜索爬虫的名称及其对应的网站:

    爬虫名称名  称网  站
    BaiduSpider百度www.baidu.com
    Googlebot谷歌www.google.com
    360Spider360搜索www.so.com
    YodaoBot有道www.youdao.com
    ia_archiverAlexawww.alexa.cn
    Scooteraltavistawww.altavista.com
2. robotparser 模块
  • 格式: urllib.robotparser.RobotFileParser(url)
  • 作用: 根据 url 链接下包含的 robots.txt 文件来判断爬虫对网站的爬取权限。
  • 参数:
    • url:Robots 协议的 url 链接,允许默认置空,之后使用set_url()方法设置。
  • 常用方法:
    • set_url():用来设置 Robots 协议的 url 链接。
    • read():读取 Robots 协议文件并进行分析。注意,该方法会执行一个读取和分析操作,但不会返回任何内容,不可省略
    • parse():按照 Robots 协议的语法规则来解析文件,入参是 robots.txt 部分行的内容,该方法可以替代上述的 read() 方法。
    • can_fetch():入参有两个:(1) User-agent;(2) 要抓取的 URL。返回的布尔型结果代表该搜索引擎是否可以抓取该 URL。
    • mtime():返回上次抓取和分析 Robots 协议的时间。
    • modified():将当前时间设置为上次抓取和分析 Robots 协议的时间。
  • 编程举例:
    from urllib.robotparser import RobotFileParser
    from urllib.request import urlopen
    
    # 等价于 rp = RobotFileParse('http://www.jianshu.com/robot.txt')
    rp = RobotFileParser()
    rp.set_url('http://www.jianshu.com/robot.txt')
    
    rp.read()
    
    # 上述两行代码等价于 rp.parse(urlopen('http://www.jianshu.com/robot.txt').read().decode('utf-8').split('\n'))
    
    print(rp.can_fetch('*', 'http://www.jianshu.com/p/test'))
    

3.2 urllib3 模块

3.2.1 urllib3 简介

urllib3 是一个功能强于 urllib 标准库的第三方网络请求模块。可以通过pip install urllib3命令安装并使用该模块,也可以通过pip install requests命令安装 requests 模块后,通过代码from requests.packages import urllib3调用该模块。

3.2.2 urllib3 请求格式

  • 格式:
    import urllib3
    
    # 创建 PoolManager 连接池管理对象
    http = urllib3.PoolManager()
    # 调用 request() 方法实现网络请求的发送
    response = http.request(method,url,fields=None,headers=None,**urlopen_kw)
    
  • 返回类型: urllib3.response.HTTPResponse 对象。可以使用json.loads()方法将返回的 JSON 信息(解码后)转换为字典数据。
  • 参数:
    • method:必选参数,用于指定请求方式,如 GET、POST、PUT 等。
    • url:必选参数,用于设置所请求的 url 地址。
    • fields:可选参数,用于设置请求参数。
    • headers:可选参数,用于设置请求头,字典类型。
    • urlopen_kw:urllib 模块中 urlopen() 方法中一些可复用的参数。
  • 主要属性:
    • status:得到返回的响应状态码。
    • data:得到返回的网页内容(bytes类型),使用.decode("utf-8)方法解码成可读形式,解码方式为unicode_escape可正常显示中文。
    • retries.total:所设置的请求重试次数。
  • 主要方法:
    • info():获取 HTTP 响应头信息,该信息为字典类型的数据。
    • urllib3.disable_warnings():忽略有关 SSL 的警告。

3.2.3 urllib3 实现简单请求

1. GET 请求
  • 编程示例:
    # 向多个服务器发送请求
    import urllib3
    
    urllib3.disable_warnings()
    
    http = urllib3.PoolManager()
    response1 = http.request('GET',url1)
    print(response1.status)
    response2 = http.request('GET',url2)
    print(response2.status)
    
2. POST 请求
  • 注意: 需要将 fields 参数设置为字典类型的表单参数。
  • 编程示例:
    import urllib3
    
    params = {'name':'Chia','country':'中国','age':30}
    http = urllib3.PoolManager()
    response = http.request('POST', url, fields=params)
    # 中文正常显示
    print(response.data.decode('unicode_escape'))
    
3. retries 参数重试请求
  • 介绍: urllib3 可以自动重试请求,默认情况下,request() 方法的请求重试次数为 3 。允许设置为 0 或者 False。
  • 编程示例:
    # 向多个服务器发送请求
    import urllib3
    
    http = urllib3.PoolManager()
    response1 = http.request('GET',url)  # 默认 3 次重试请求
    print(response1.retries.total)
    response2 = http.request('GET',url,retries=5)  # 设定 5 次重试请求
    print(response2.retries.total)
    response3 = http.request('GET',url,retries=False)  # 关闭重试请求
    print(response3.retries.total)  # 输出为 False,不是0
    

3.2.4 urllib3 实现复杂请求

1. 设置请求头
  • 介绍: 为 headers 参数指定一个有效的字典类型的请求头信息。
  • 编程示例:
    import urllib3
    
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
    http = urllib3.PoolManager()
    response = http.request('GET',url,headers=headers)
    print(response.data.decode('utf-8'))
    
2. 设置超时
  • 介绍:
    • 超时参数既可以设置在 request() 方法里,也可以设置在 PoolManager 对象中。
    • 使用 urllib3.Timeout 实例对象,可以单独设置连接超时与读取超时,格式:Timeout(connect=0.5, read=0.1)
  • 编程示例:
    import urllib3
    from urllib3 import Timeout
    
    # 简单设置
    timeout = 0.1
    # 精确设置
    timeout = Timeout(connect=0.5, read=0.1)
    
    http1 = urllib3.PoolManager(timeout=timeout)
    response1 = http1.request('GET',url1)
    print(response1.status)
    
    http2 = urllib3.PoolManager()
    response2 = http2.request('GET',url2,timeout=timeout)
    print(response2.status)
    
3. 设置代理
  • 格式:
    import urllib3
    
    proxy = urllib3.ProxyManager(proxy_url,headers)
    response = proxy.request()
    
  • 参数: 为了模拟浏览器请求,避免后台服务器发现,建议填写以下两个参数:
    • proxy_url:需要使用的代理 IP,str 类型。
    • headers:请求头信息。

3.2.5 urllib3 上传文件

1. fields 参数上传文本文件
  • 介绍: 通过 fields 参数对 filefield 键参数以元组形式指定文件名&类型文件内容
  • 编程示例:
    import urllib3, json
    
    with open('test.txt') as f:
    	data = f.read()
    
    http = urllib3.PoolManager()
    response = http.request('POST', 'http://httpbin.org/post', fields={'filefield':('example.txt',data)})
    files = json.loads(response.data.decode('utf-8'))['files']
    print(files)
    
2. body 参数上传图片文件
  • 介绍: 在 request() 方法中指定 body 参数并传入对应图片的二进制数据,同时使用 headers 参数为其指定文件类型。
  • 编程示例:
    import urllib3
    
    with open('Python.jpg','rb') as f:
    	data = f.read()
    
    http = urllib3.PoolManager()
    response = http.request('POST', 'http://httpbin.org/post', body=data, headers={'Content-Type':'image/jpeg'})
    print(response.data.decode())
    

3.3 requests 模块

3.3.1 requests 简介

相比于 urllib、urllib3 模块,第三方模块 requests 模块在实现 HTTP 请求时所使用的方法更加简单和人性化。

3.3.2 requests 实现简单请求

1. GET 请求
  • 格式: requests.get(url, params=None, **kwargs)

  • 返回类型: requests.models.Response 对象

  • 主要属性:

    • text:得到返回的网页内容(str 类型)
    • content:得到返回的 bytes 类型的二进制数据
    • status_code:返回的状态码,int 类型
    • url:请求 URL
    • headers:响应头信息
    • cookies:cookie 信息(RequestsCookieJar 类型)
  • 主要方法:

    • encoding:对响应结果进行编码
    • json():如果响应数据是 str 类型的 json 信息,可以使用json()方法将其转换成字典格式的数据。(如果不是,便会出现解析错误)
  • 参数:

    • url:需要访问网站的URL完整地址
    • params:GET 请求参数。
  • 编程举例:

    import requests
    
    # (不推荐)也可以在url中通过 “?参数1=值1&参数2=值2” 添加参数
    params = {'name':'Chia','age':'28'}
    response = requests.get(url, params=params)
    # 不对响应数据进行 utf-8 编码,可能会到导致中文乱码
    response.encoding = 'utf-8'
    print(response.text)
    
2. POST 请求
  • 格式: requests.post(url, data=None, **kwargs)

  • 返回类型: requests.models.Response 对象

  • 主要属性:

    • text:得到返回的网页内容(str 类型)
    • content:得到返回的 bytes 类型的二进制数据
    • status_code:返回的状态码,int 类型
    • headers:响应头信息
    • cookies:cookies 信息(RequestsCookieJar 类型),可使用.items()方法遍历每一个 cookie 的名称和值
    • url:请求 URL
    • history:请求历史
  • 主要方法:

    • encoding:对响应结果进行编码
    • json():如果响应数据是 str 类型的 json 信息,可以使用json()方法将其转换成字典格式的数据。(如果不是,便会出现解析错误)
  • 参数:

    • url:需要访问网站的URL完整地址
    • data:POST请求参数,数据格式允许为列表、元组、字典或者JSON
  • 编程举例:

    import requests, json
    
    data = {'user':'Chia','password':'123456'}
    data = json.dumps({'user':'Chia','password':'123456'})
    data = (('user','Chia'),('password','123456'))
    data = [['user','Chia'],['password','123456']]
    response = requests.post(url, data=data)
    print(response.text)
    
3. 其他信息
  • 其他请求: 其他类型的请求依旧可以用一行代码来实现requests.put()/delete()/head()/options()
  • 状态码查询对象: requests 提供了内置的状态码查询对象 requests.code,用来判断请求状态。
    • 示例1:状态码 200 包含了查询条件 “ok”,可以用response.status_code == requests.code.ok来判断请求是否成功。
    • 示例2:状态码 404 包含了查询条件 “not_found”,可以用response.status_code == requests.code.not_found来判断结果是否是 404 状态。

3.3.3 requests 实现复杂请求

1. 添加请求头 headers
  • 方法: 向请求方法的 headers 参数传入有效的字典类型信息。
  • 编程举例:
    import requests
    
    headers = {'User-Agent':'Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11'}
    response = requests.get(url, headers=headers)
    
2. 验证 cookies
  • 获取cookies
    • 利用 requests 请求的响应对象的 cookies 属性获取
    • 在浏览器中寻找:
      • 打开登录界面,F12 开发者工具,点击“Network”
      • 输入账号&密码,点击登录
      • 在 Network 里面选择响应的请求信息,依次点击“Headers -> Request Headers -> Cookie”
  • 设置 cookies
    • 通过 headers 参数设置:将请求信息直接设置到 headers 字典里,然后传入请求方法的 headers 参数中。
      import requests
      
      headers = {
      	'User-Agent':'Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11',
      	'Cookie':'cookies信息'
      }
      response = requests.get(url, headers=headers)
      
    • 通过 cookies 参数设置:构造 RequestsCookieJar 对象,并对 Cookie 信息进行处理,最后将处理后的对象传入请求方法的 cookies 参数。
      import requests
      
      cookies = 'cookies信息'
      jar = requests.cookies.RequestsCookieJar()
      for cookie in cookies.split(";"):
      	key, val = cookie.split("=", 1)
      	jar.set(kay,val)
      headers = {'User-Agent':'Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11'}
      response = requests.get(url, cookies=jar, headers=headers)
      
3. 会话维持
  • 介绍: 利用 Session 对象,维持同一个会话,即实现在同一会话内多次发送请求。通常用于模拟登录成功后再进行下一步的操作。
  • 编程举例:
    import requests
    
    s = requests.Session()
    data = {'user':'Chia','password':'123456'}
    response1 = s.post(url1, data=data)
    response2 = s.get(url2)
    
4. SSL 证书验证
  • 证书验证错误: 当使用 requests 发送 HTTP 请求时,默认(verify=True)会检查 SSL 证书,如果请求的网站没有被官方 CA 机构信任,就会报错提示证书验证错误requests.exceptions.SSLError。解决方法如下:
    • 忽略错误:在请求方法中,将 verify 参数设置为 Fasle 即可忽略该错误。
    • 指定本地证书:向 cert 传递单个文件(包含证书和密钥)路径,或者包含两个文件路径的元组。本地私有证书的 key 必须是解密状态。
  • 警告: 解决证书验证错误后,会抛出“建议指定证书”的警告,解决方法如下:
    • 忽略警告:urllib3.disable_warnings()
    • 捕获警告:logging.capturewarnings(True)
  • 编程举例:
    import requests
    
    # 忽略警告
    from requests.packages import urllib3
    urllib3.disable_warnings() 
    #  捕获警告
    import logging
    logging.capturewarnings(True)
    
    # 忽略错误
    response = requests.get(url, verify=False)
    # 指定本地证书
    response = requests.get(url, cert=('/path/server.crt','/path/key'))
    
5. 验证请求
  • 场景: 网站在打开时弹出提示框,要求输入用户名和密码,验证成功后才能查看页面。
  • 解决方法: 验证成功返回 200 状态码,验证失败返回 401 状态码。
    • HTTPBasicAuth 对象:向请求方法的 auth 参数传入传入带有验证参数(用户名与密码)的 HTTPBasicAuth 对象。
    • 元组:直接传一个元组,默认使用 HTTPBasicAuth 类来验证。
    • 其他验证方式:如 OAuth 验证(需要安装 oauth 包:requests_oauthlib)
  • 编程举例:
    import requests
    from requests.auth import HTTPBasicAuth
    from requests_oauthlib import OAuth1
    
    ah = HTTPBasicAuth('user','password')
    ah = ('user','password')
    ah = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET', 'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')
    response = requests.get(url, auth=ah)
    
6. 网络超时与常见异常
  • 网络超时:
    • 可以在请求方法中通过 timeout 参数设置请求的超时时间,这是时间是指发出请求到服务器返回响应的时间。
    • 请求分为连接(connect)和读取(read)两个阶段,timeout 将用作这二者的时间总和,也可以传入一个元组分别指定
    • 编程举例:
import requests

timeout = 0.1       # 连接 + 读取
timeout = (5,10)	# 连接: 5s, 读取: 10s
time = None			# 永久等待设置为 None 或不设置
response = requests.get(url, timeout=timeout)
  • 异常: requests 模块中 3 种常见的网络异常类如下:
    • ReadTimeout:超时异常
    • HTTPError:HTTP 异常
    • RequestException:请求异常
7. 文件上传
  • 方法:post()方法的 files 参数传入一个 BufferedReader 对象(open() 函数返回类型),即可实现向服务器上传文件。注意,post() 方法将上传的文件转换成了 base64 编码格式。
  • 编程举例:
    import requests
    
    files = {'file': open('test.png','rb')}
    response = requests.post(url, files=files)
    
8. 代理服务
  • 方法:
    • 向请求方法的 proxies 参数中传入字典类型的有效代理数据。
    • 如果代理需要使用 HTTP Basic Auth,对应的代理语法格式为scheme://user:password@host:port
    • requests 还支持 SOCKS 协议的代理。(需要安装 socks 库:‘requests[socks]’)
  • 编程举例:
    import requests
    
    # 普通代理
    proxies = {
    	'http':'http://host:port',
    	'https':'https://host:port'
    }
    # HTTP Basic Auth
    proxies = {
    	'http':'http://user:password@host:port',
    	'https':'https://user:password@host:port'
    }
    # SOCKS 协议
    proxies = {
    	'http':'SOCKS5://user:password@host:port',
    	'https':'SOCKS5://user:password@host:port'
    }
    response = requests.post(url, proxies=proxies, headers=headers, verify=False, timeout=2)
    
  • 检测代理 IP 是否可用: 遍历待检测的代理 IP,并使用代理 IP 向查询 IP 地址的网页发送请求,如果请求成功,说明该 IP 可以使用。
import requests

for ip_i in ip_list:
	proxies = {
		'http':f'http://{ip_i}',
		'https':f'https://{ip_i}'
	}
	
	response = requests.get('http://2020.ip138.com/', proxies=proxies, headers=headers, verify=False, timeout=2)
9. Prepared Request
  • 介绍: 在 requests 模块中,可以将请求表示为一个名为 prepared request 的数据结构,其中各个参数都可以通过一个 Request 对象来表示。
  • 编程示例:
from requests import Request, Session

s = Session()
request = Request('POST', url, data=data, headers=headers)
prepped = s.prepare_request(request)
response = s.send(prepped)
print(response.text)

3.3.4 高级请求模块

四、解析模块

4.1 正则表达式

4.1.1 正则表达式简介

获取的 web 资源(HTML 代码),大多以字符串类型返回,而正则表达式可以实现字符串的检索、替换、匹配验证等功能。Python的标准库 re 提供了整个正则表达式的实现。

4.1.2 字符

1. 元字符

不同的元字符规定了不同的字符集合。常见的元字符如下:

元字符说明
.匹配除换行符以外的任意字符
\w匹配字母、数字、下划线或汉字
\W匹配除字母、数字、下划线或汉字以外的字符
\s匹配任意空白字符,等价于 [\t\n\r\f] (\f是换页符)
\S匹配任意非空字符
\d匹配任意数字
\D匹配任意非数字
\A匹配字符串开头
\Z匹配字符串结尾,如果存在换行,只会匹配到换行前的字符串
\z匹配字符串结尾,如果存在换行,同时还会匹配换行符
\G匹配最后匹配完成的位置
\b匹配字符串边界,如在开始处、结尾处,或字符串的分界符为空格、标点以及换行
\B匹配非字符串边界
^匹配一行字符串的开头
$匹配一行字符串的结尾
[]匹配在 [] 中的字符,[\u4e00-\u9fa5] 表示匹配任意一个汉字
[^]匹配不在 [] 中的字符
()匹配括号内的表达式,也表示一个分组
2. 限定符

限定符可以匹配特定数量的指定字符,常见的限定符如下:

限定符说明
?匹配0个或1个字符,非贪婪模式
+匹配1个或多个字符
*匹配任意个字符
{n}匹配n个字符
{n,}匹配至少n个字符
{n,m}匹配至少n个、至多m个字符,贪婪模式
a|b匹配 a 或 b
(ab|cd)匹配 ab 或者 cd
3. 转义字符与原生字符串
  • 在正则表达式中使用转义字符\将特殊字符(如. ? \)转换为普通的字符,如\.表示匹配字符.
  • 考虑到模式字符串中可能存在大量的特殊字符和反斜杠,可以在模式字符串前面加上r或者R,将其转换成原生字符串。例如,模式字符串’\\bm\\w*\\b’,对应的原生字符串为r’\bm\w*\b’
    注意,并不是所有的反斜杠都需要转换,\+字母组成的整体如果在python中没有特殊意义,就不需要转换,如\d
4. 贪婪与非贪婪匹配
  • .*可以匹配任意长度的字符。
  • 在贪婪匹配下,.*会匹配到尽可能多的字符;通过在.*后面添加?,可以进行非贪婪匹配,匹配尽可能少的字符。
  • 在做匹配的时候,字符串中间尽量使用贪婪匹配,而字符串结尾使用非贪婪匹配有可能匹配不到任何内容。

4.1.3 匹配方法

1. match()
  • 格式: re.match(pattern, string[, flags])
  • 作用: 尝试从字符串开始的位置匹配正则表达式。(更适合用来检测整个字符串是否符合整个正则表达式的规则。)
  • 返回类型: 匹配成功,返回 SRE_match 对象;匹配失败,返回 None。
  • 主要方法与属性:
    • start():匹配值的起始位置
    • end():匹配值的结束位置
    • span():匹配位置的元组,左闭右开,符合python规范
    • string:要匹配的字符串
    • group():匹配到的字符串。如果要获取字符串中的部分内容,只需将这部分内容放在()内,接着调用group()方法获取指定分组的索引即可。索引从1开始。
  • 参数:
    • pattern:必选,表示正则表达式。
    • string:必选,表示要匹配的字符串。
    • flags:可选,表示修饰符,用于控制匹配方式。
  • 编程举例:
    import re
    
    pattern = ‘jct_\w+’
    string = ‘Jct_hahaha’
    match = re.match(pattern, string, re.I)
    

常见的修饰符如下:

修饰符说明
re.A 或 re.ASCII对于 \w、\W、\b、\B、\d、\D、\s、\S 只进行 ASCII 字符集的匹配
re.DEBUG显示编译时的 DEBUG 信息
re.I 或 re.IGNORECASE匹配时不区分大小写
re.L 或 re.LOCALE(不常用)做本地化识别(locale-aware)匹配,使用当前预定字符类\w\W\b\B\s\S时取决于当前区域设定
re.M 或re.MULTILINE多行匹配,即将 ^ 和 $ 用于包括整个字符串的开始和结尾的每一行
re.S 或 re.DOTALL使.匹配包括换行符在内的所有字符
re.U 或 re.UNICODE(不常用)根据 Unicode 字符集解析字符。这个标志影响\w\W\b\B\s\S\d\D
re.X 或 re.VERBOSE忽略正则表达式中未转义的空格和注释
2. search()
  • 格式: re.search(pattern, string[, flags])
  • 作用: 在整个字符串中搜索第一个匹配的值。
  • 返回类型: 匹配成功,返回 SRE_match 对象;匹配失败,返回 None。
  • 主要方法与属性:
    • start():匹配值的起始位置
    • end():匹配值的结束位置
    • span():匹配位置的元组,左闭右开,符合python规范
    • string:要匹配的字符串
    • group():匹配到的字符串。如果要获取字符串中的部分内容,只需将这部分内容放在()内,接着调用group()方法获取指定分组的索引即可。索引从1开始。
  • 参数:
    • pattern:必选,表示正则表达式。
    • string:必选,表示要匹配的字符串。
    • flags:可选,表示修饰符,用于控制匹配方式。
  • 编程举例:
    import re
    
    pattern = ‘jct_\w+’
    string = ‘hello Jct_hahaha’
    match = re.search(pattern, string, re.I)
    
3. findall()
  • 格式: re.findall(pattern, string[, flags])
  • 作用: 搜索整个字符串,返回匹配正则表达式的所有内容。
  • 返回类型: 匹配成功,返回包含所有匹配字符串的列表;匹配失败,返回空列表。
  • 参数:
    • pattern:必选,表示正则表达式。如果要返回所匹配正则表达式中的部分内容,可以在正则表达式中添加括号,当括号超过1个时,返回的列表元素为元组类型。
    • string:必选,表示要匹配的字符串。
    • flags:可选,表示修饰符,用于控制匹配方式。
  • 编程举例:
    import re
    
    pattern = ‘jct_\w+’
    string = ‘hello jct_123 JcT_hahaha’
    match = re.findall(pattern, string, re.I)
    

4.1.4 字符串处理

1. sub()
  • 格式: re.sub(pattern, repl, string[, count, flags])
  • 作用: 将字符串中所有匹配正则表达式的部分,替换成其他字符串。
  • 返回类型: 返回替换后的字符串。
  • 参数:
    • pattern:必选,表示正则表达式。
    • repl:必选,表示替换成的字符串,允许通过置空来删除字符串中不需要的数据。
    • string:必选,表示要被查找替换的原始字符串。
    • count:可选,表示模式匹配后替换的最大次数,默认值为0,表示替换所有的匹配。
    • flags:可选,表示修饰符,用于控制匹配方式。
  • 编程举例:
    import re
    
    pattern = ‘jct_\w+’
    string = ‘hello jct_123 JcT_hahaha’
    final_string = re.sub(pattern, "chia_nb", string, flags=re.I)
    
2. subn()
  • 格式: re.subn(pattern, repl, string[, count, flags])
  • 作用: 将字符串中所有匹配正则表达式的部分,替换成其他字符串。
  • 返回类型: 返回一个二元元组,第一个元素为替换后的字符串,第二个元素为替换次数。
  • 参数:
    • pattern:必选,表示正则表达式。
    • repl:必选,表示替换成的字符串,允许通过置空来删除字符串中不需要的数据。
    • string:必选,表示要被查找替换的原始字符串。
    • count:可选,表示模式匹配后替换的最大次数,默认值为0,表示替换所有的匹配。
    • flags:可选,表示修饰符,用于控制匹配方式。
  • 编程举例:
    import re
    
    pattern = ‘jct_\w+’
    string = ‘hello jct_123 JcT_hahaha’
    str_tuple = re.subn(pattern, "chia_nb", string, flags=re.I)
    
3. split()
  • 格式: re.split(pattern, string[, maxsplit, flags])
  • 作用: 根据正则表达式分割字符串。
  • 返回类型: 以列表形式返回分割后的每段字符串。
  • 参数:
    • pattern:必选,表示正则表达式。
    • string:必选,表示要匹配的字符串。
    • maxsplit:可选,表示最大的拆分次数。
    • flags:可选,表示修饰符,用于控制匹配方式。
  • 编程举例:
    import re
    
    pattern = ‘jct_\w+’
    string = ‘hello jct_123 JcT_hahaha’
    str_tuple = re.subn(pattern, "chia_nb", string, flags=re.I)
    
4. compile()
  • 格式: re.compile(pattern[, flags])
  • 作用: 将正则字符串编译成正则表达式对象,相当于给正则表达式做了一层封装。
  • 返回类型: 返回一个 Pattern 对象。
  • 参数:
    • pattern:必选,表示正则表达式(正则字符串)。
    • flags:可选,表示修饰符,用于控制匹配方式。在这里设置该参数,在 match()、search()、findall() 方法中就不再需要额外传入了。
  • 编程举例:
    import re
    
    re.compile(r"\d{2,}_\w+")
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值