urllib的使用

urllib时python的一个内置库,一共包含四个模块

1. request-请求

这是最基本的HTTP请求模块,可以模拟请求的发送。就像在浏览器输入URL,按下回车一样,只需要给库方法传入URL以及额外的参数,就可以模拟实现发送请求的过程了

urlopen

发送请求。

urllib.request模块提供了最基本的构造HTTP请求的方法,利用这个模块可以模拟浏览器请求的发起过程,同时它还具有处理授权验证,重定向, 浏览器Cookie以及其他的一些功能

这里以百度为例

说明: 这里只是单纯的使用一下 `urlopen` 所以获取的网页内容并不重要

import urllib.request
response = urllib.request.urlopen('https://www.baidu.com')
print(response.read().decode('utf-8'))

 查看返回的响应

import urllib.request
response = urllib.request.urlopen('https://www.baidu.com')
print(type(response))

输出: 

<class 'http.client.HTTPResponse'>

响应式一个 HTTPResponse 对象,主要包含: read, readinto, getheader, getheaders, fileno,

等方法, 以及 msg, version, status, reason, debuglevel, closed等属性

可以通过上述方法和属性来对网页进行具体操作

import urllib.request
response = urllib.request.urlopen('https://baidu.com')
print(response.status) # 查看状态码
print(response.getheaders()) # 查看相应头信息
print(response.getheader('Server')) # 获取响应头中的 Server 的值

 输出:

200
[('Bdpagetype', '1'), ('Bdqid', '0x89f2326c001257e1'), ('Content-Length', 省略一大堆
BWS/1.1

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None  )

参数说明:

url: 为必传, 网页地址

data: 可选参数。在添加该参数时,需要使用bytes方法将参数转化为字节流编码格式的内容,即bytes类型。另外如果传递了该内容,那么请求方式就不再时 GET 而是 POST 了

import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'name': 'germey'}), encoding='utf-8')
response = urllib.request.urlopen('https://www.httpbin.org/post', data=data)
print(response.read().decode())

 输出:

{
  "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.11", 
    "X-Amzn-Trace-Id": "Root=1-66934f46-661cf2f9337c4b704676c9d8"
  }, 
  "json": null, 
  "origin": "117.152.24.63", 
  "url": "https://www.httpbin.org/post"
}

timeout: 可选参数,设置超时时间,单位为秒,这个参数设置的是请求和响应的总时间。如果需要分别设置可以以元组的形式 例如 (3,2),如果不设置,则使用全局默认时间,这个参数支持HTTP , HTTPS, FTP请求

import urllib.request
response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
print(response.read())

超时报错,输出:

URLError: <urlopen error timed out>

也可以使用try  except 来捕获异常实现当网页长时间未响应时,跳过或继续等待

import urllib.request
import socket
import urllib.error
try:
    response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')

输出: 

TIME OUT

context: 可选参数,该参数必须是ssl.SSLContext类型, 用来指定SSL的设置

cafile和capath:可选参数, 分别来指定CA证书和其路径

cadefault: 可选参数, 貌似已经弃用了,默认为False

Request

urlopen 只能发送简单的请求,如果要加如 Headers 就需要使用Request来构建请求了

简单使用:

import urllib.request
request = urllib.request.Request('https://www.baidu.com')
response = urllib.request.urlopen(request)
print(response.read().decode())

构造方法:

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

origin_req_host: 是指请求方的host名称 或者 IP地址

unverifiable: 表示请求是否是无法验证的,默认值是False, 意思是用户没有足够的权限来接收这个请求的结果

method: 是一个字符串, 用来指定请求时使用的方法 例如 GET POST  PUT 等

简单使用

from urllib import request, parse
url = 'https://www.httpbin.org/post'
headers = {'User-Agen': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
          'Host': 'www.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'))

通过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/126.0.0.0 Safari/537.36')

Handler

Handler可以理解为各种处理器,有专门处理登录验证,处理Cookie, 处理代理设置利用这些Handler几乎可以实现HTTP请求中的所有功能

urllib.request模块中的BaseHandler类是所有Handler类的父类,它提供了最基本的方法

会有各种子类继承BaseHandler类

1.  HTTPDefaultErrorHandler 用来处理HTTP中的响应错误,所有错误都会抛出HTTPError类型异常

2. HTTPRedirectHandler 用来处理重定向

3. HTTPCookieProcessor 用于处理Cookie

4. ProxyHandler 用于处理代理,代理默认为空

5. HTTPPasswordMgr 用于管理密码,它维护着用户名密码的对照表

6. HTTPBasicAuthHandler 用于管理认证,如果一个链接在打开时需要认证,那么可以用这个类来解决认证问题

验证

在访问网站时,可能会弹出需要登录的窗口,遇到这种情况,就表示这个网站启用了基本身份认证,这是一种登录验证的方式,爬虫可以借助HTTPBasicAuthHandler模块来模拟登录

简单示例: 示例网站: https://ssr3.scrape.center

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 类 当作参数传入 bulid_opener方法, 构建一个Opener,这个Opener 发送请求时,就相当于验证成功了

最后利用Opener 类中中的open 方法打开链接,即可完成验证,这里获取的结果就是验证成功后的页面源码内容

代理

做爬虫的时候免不了要使用代理,如果要添加代理可以这样做

from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener

proxy_handler = ProxyHandler({
    'http': 'http://127.0.0.1:8080',
    'https': 'https://127.0.0.1:8080'
})
openr = build_opener('https://www.baidu.com')
try:
    response = opener.open('https://www.baidu.com')
    print(response.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

首先上面的代码时不能运行的,因为代理使用的时本地地址, 需要更改代理IP

代码说明:

        这里现在本地搭建了一个代理,并让其运行在8080端口上

上面使用了ProxyHandler, 其参数是一个字典,键名是协议类型(HTTP 或 HTTPS等)键是代理的链接,可以添加多个代理

然后利用这个Handler 和 build_opener 方法构建了一个Opener 之后发送请求即可

Cookie

要想处理Cookie 首先要获取Cookie 

示例代码: 

# 获取Cookie
import http.cookiejar, urllib.request

cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
for item in cookie:
    print(item.name + '=' + item.value)

首先必须声明一个Cookie对象,然后利用HTTPCookieProcessor构建一个Handler, 最后利用build_opener 方法构建一个Opener , 执行open即可

保存Cookie

# 保存Cookie
import urllib.request, http.cookiejar

filename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)

这里需要将CookieJar 换成 MozillaCookieJar, 它会在生成文件时用到, 是CookieJar的子类,可以用来处理跟Cookie和文件相关的事件, 例如读取和保存Cookie, 可以将Cookie保存成Mozilla型浏览器的Cookie格式

另外, LWPCookieJar 同样也可以读取和保存Cookie, 只是Cookie文件的保存格式和MozillaCookieJar不一样,它会保存成 LWP(libwww-perl)格式

cookie = http.cookiejar.LWPCookieJar(filename)

读取Cookie 文件 这里以 LWPCookieJar格式为例

# 读取
import urllib.request, http.cookiejar

cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
print(response.read().decode())

这里只是演示读取cookie文件, 所以不要纠结从百度读取了什么,因为这里没有设置headers所以不能读取到真正的内容

这里调用了load 方法来读取本地的Cookie文件, 获取了Cookie的内容,这样做的前提是我们实现生成LWPCookieJar格式的cookie, 并保存了文件,读取cookie后,使用同样的方法构建handler类和opener类 即可完成操作

2. error-异常

异常处理模块。如果出现请求异常,那么我们可以捕获这些异常,然后进行重试或者其他操作来保证程序不会意外终止

URLError

URLErroe类来自urllib库中error模块, 继承自OSError类, 是error异常模块的基类,由request模块产生的异常都可以通过捕获这个类来处理

它具有一个属性 reason 即返回错误的原因

示例: 

from urllib import request, error

try:
    # response = request.urlopen('https://www.baidu.com')
    response = request.urlopen('htttps://cuiqingcai.com/404')
    print('111')
except error.URLError as e:
    # print('not Found')
    print(e.reason)

输出: 

unknown url type: htttps

这里第一运行的时候报错了, 切换网址运行之后,再切换回来又好了,不明所以

HTTPError

HTTPError 是 URLError 的子类, 专门用来处理HTTP请求的错误, 例如认证请求失败等,

有如下三个属性:

code: 返回HTTP状态码

reason: 同父类一样,返回错误的原因

headers: 返回请求头

示例:

from urllib import request, error

try:
    # response = request.urlopen('https://www.baidu.com')
    response = request.urlopen('htttps://cuiqingcai.com/404')
except error.HTTPError as e:
    # print('not Found')
    print(e.reson, e.code, e.headers, sep='\n')
except error.URLError as e:
    print(e.reason)
else:
    print('Request Successfully')

这里并不能正确的捕获到HTTPError的异常, 暂时不知道原因

有的时候 reason 返回的不一定是字符串也有可能是一个对象

这个时候可以用 isinstance 方法判断类型, 做出更详细的异常判断

from urllib import request, error
import socket

try:
    response = request.urlopen('https://www.baidu.com', timeout=0.01)
    # response = request.urlopen('htttps://cuiqingcai.com/404')
except error.HTTPError as e:
    # print('not Found')
    print(e.reson, e.code, e.headers, sep='\n')
except error.URLError as e:
    print(e.reason)
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')
else:
    print('Request Successfully')

3. parse-解析链接

一个工具模块。它提供了很多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

urlparse

该方法可以实现URL的识别和分段

示例:

from urllib.parse import urlparse

result = 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类型的对象,包含六部分,分别是: scheme, netloc, path, params, query 和 fragment

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

于是可以得出一个标准的链接格式:

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

urlparse API:

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

urlstring: 必填, 待解析的URL

scheme: 这是默认的协议(如 http 或 https 等) 如果待解析的URL没有带协议信息,则取默认

示例:URL不带协议

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带协议

 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')

 从上面两个例子可以看出, scheme 参数只在 URL不带协议的时候生效

allow_fragments: 是否忽略fragment。 如果此项被设置为False, 那么fragment部分就会被忽略,它就会被解析为 path, params 或者 query 的一部分, 而 fragment部分为空

示例:设置allow_fragments为False

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)

输出:fragment 解析到了 query中

ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')

示例:URL中不带parmas和 query

result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)

输出:fragment 解析到了 path 中

ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')

返回结果 ParseResult 实际上是一个元组, 既可以用属性名获取其内容,也可用索引获取

示例:

result = urlparse('https://www.baidu.com/index.html;user?id=5#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))

输出:

https://www.baidu.com/index.html;user?a=6#comment

这里的参数data 用了列表类型, 也可以用其他类型, 例如元组或特定的数据结构

urlsplit

这个方法和urlparse 很相似, 只不过它不在解析 parmas 这一部分(parmas 会合并到 path中), 只返回 5 个结果

示例:

from urllib.parse import urlsplit

result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result)

输出:

SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')

 返回的结果是一个 SplitResult, 也是一个元组 ,可以通过索引和属性名获取内容

示例:

from urllib.parse import urlsplit

result = urlsplit('https://www.baidu.com/index.html;user?id=5#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 这三个内容, 并对新链接缺失的部分进行补充, 最后返回结果

示例:

from urllib.parse import urljoin

print(urljoin('https://www.baidu.com', 'FAQ.html'))
print(urljoin('https://www.baidu.com', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html?question=2'))
print(urljoin('https://www.baidu.com?wd=abc', 'https://cuiqingcai.com/index.php'))
print(urljoin('https://www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com', '?category=2'))

输出:

https://www.baidu.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html?question=2
https://cuiqingcai.com/index.php
https://www.baidu.com?category=2#comment
www.baidu.com?category=2#comment
www.baidu.com?category=2

可以发现, base_url 提供了三项内容: scheme, netloc 和 path 如果新链接里不存在这三项,

就予以补充, 如果存在,就用新链接里面的,base_url 中的不起作用

urlenclde

它再构造GET请求的时候很有用

示例:

from urllib.parse import urlencode

params = {
    'name' : 'germey',
    'age': 25
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)

输出:

https://www.baidu.com?name=germey&age=25

parse_qs

有了序列化,就有反序列化。 利用parse_qs 方法, 可以将一个GET请求参数转换成字典

示例:

from urllib.parse import parse_qs

query = 'name=germey&age=25'
print(parse_qs(query))

输出: 

{'name': ['germey'], 'age': ['25']}

parse_qsl

parse_qsl 方法将参数转化为 由 元组组成的列表

示例:

from urllib.parse import parse_qsl

query = 'name=germey&age=25'
print(parse_qsl(query))

输出:

[('name', 'germey'), ('age', '25')]

quote

该方法可以将内容转化为 URL 编码的格式。 当URL中带有中文参数时, 有可能会导致乱码问题,此时用 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

4. robotparser-分析Robots协议

主要用来识别网站的robots.txt文件,然后判断哪些网站可以爬,哪些网站不可以爬,用的很少

Robots协议: 也称为 爬虫协议 ,机器人协议,全名为 网络爬虫排除标准,用来告诉引擎哪些页面可以抓取,哪些不可以。

它通常是一个叫做robots.txt的文本文件, 一般放在网站的根目录下

搜素爬虫在访问一个站点的时候, 首先会检查这个站点的根目录下是否存在robots.txt 文件, 如果存在,就会根据其中定义的爬取范围来爬取,如果没有,则会爬取所有可直接访问的页面

robots.txt文件示例:

User-agent: *

Disallow: / 

Allow: /public/

这里限定了所有搜素爬虫只能爬取public目录。

User-agent: 描述了搜索爬虫的名称, 设置为* 代表所有爬虫,也可以指定名称

例如: User-agent: Baiduspider 

这代表设置规则对百度爬虫有效,可以设置多个,但最少要有一条

Disallow: 指定了不允许爬取的目录, 设置为 / 代表不允许爬取所有页面

Allow: 一般不会单独使用, 会和 Disallow 一起用, 用来排除某些限制。上面设置为 /public/ , 结合 Disallow 的设置, 表示所有页面都不允许爬取, 但可以爬取 public 目录

robots.txt 也可以是空文件

一些爬虫名称

BaiduSpider百度
Googlebot谷歌
360Spider 360搜素

robotparser

了解了 Robots协议之后, 就可以使用 robotparser 模块来解析 robots.txt文件了。该模块为我们提供了一个类: RobotFileParser,  它可以根据一个网站的robots.txt文件来判断一个爬取爬虫是否由权限爬取这个网页

这个方法只需要在构造方法中传入robots.txt 文件的链接即可

urllib.robotparser.RobotFileParser(url='')

也可以不在声明中传入链接, 就让其默认为空, 最后再使用 set_url() 方法设置也可以

下面列出 RobotFileParser 的几个常用方法

set_url: 用来设置 robots.txt 文件的链接。 如果再创建的时候传入了,就不需要了

read: 读取 robots.txt 文件进行分析。 注意:这个方法执行读取和分析操作, 如果不调用这个方法,那么接下来的操作都会是 False,这个方法不返回任何内容, 但执行了读取操作

parse: 用来解析 robots.txt 文件, 传入其中的参数是 robots.txt 某些行的内容,它会按照 robots.txt 的语法规则来分析这些内容

can_fetch: 该方法由两个参数,第一个是User-Agent, 第二个是要抓取的URL。返回的结果是True或False 表示搜素引擎是否可以抓取这个URL

mtime: 返回上次抓取和分析 robots.txt 文件的时间

modified: 设置当前时间为上次抓取的时间

示例:

from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url('https://www.baidu.com/robots.txt')
rp.read()
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/')) 

传递URL的方法也可以为:

 rp = RobotFileParser('https://www.baidu.com/robots.txt')

输出:

True
True
False

由此可以看出,百度限制了 谷歌对 homepage的抓取,而自己却是可以抓取的

这里同样可以使用 parse 方法执行对 robots.txt 的读取和分析

示例:

from urllib.robotparser import RobotFileParser
from urllib.request import urlopen

rp = RobotFileParser()
rp.parse(urlopen('https://www.baidu.com/robots.txt').read().decode('utf-8').split('\n'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/')) 

输出:

True
True
False

可以看出,结果是一样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值