python爬虫笔记——Selenium的初级使用

一、动态渲染页面爬取

1、背景问题
  • 对于访问Web时直接响应的数据(就是response内容可见(不是ajax加载或者被渲染过的数据)),我们使用urllib、requests或Scrapy框架爬取。
  • 对于一般的JavaScript动态渲染的页面信息(Ajax加载),我们可以通过抓包分析Ajax请求地址来抓取信息。
    • Ajax = 异步 JavaScript 和 XML(标准通用标记语言的子集)。
    • Ajax 是一种用于创建快速动态网页的技术。
    • Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。比如:京东指定商品信息的评论信息。
  • 即使通过Ajax获取数据,但还有会部分加密参数,后期经过JavaScript计算生成内容,导致我们难以直接找到规律,如淘宝页面。
2、 解决办法
  • 方法原理:为了解决这些问题,我们可以直接使用模拟浏览器运行的方式来实现信息获取。
  • 在Python中有许多模拟浏览器运行库,如:Selenium、Splash、PyV8、Ghost等。

二、Selenium的介绍

1、Selenium的功能及简介
  • Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击,下拉,拖拽、挪动滚动条等操作。(Selenium功能强大,webDriver只是该模块的一个分支)
  • Selenium可以获取浏览器当前呈现的页面源代码,做到可见既可爬,对应JavaScript动态渲染的信息爬取非常有效。
  • Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还支持无界面浏览器(需要安装PhantomJS驱动)。
  • 官方网址:http://www.seleniumhq.org
  • 官方文档:http://selenium-python.readthedocs.io
  • 中文文档:http://selenium-python-zh.readthedocs.io
2、Selenium的安装

2.1、模块安装

pip install selenium

2.2、驱动安装(包括驱动下载,以及将驱动放到指定位置):

2.2.1、驱动下载:

2.2.2、驱动安装:

  • Windows安装:将解压的文件:chromedriver.exe 放置到Python的Scripts目录下。
  • Mac/Linux安装:将解压的文件:chromedriver 放置到**/usr/local/bin/**目录下;
    在这里插入图片描述
    在这里插入图片描述
3、短处

使用Selenium模拟浏览器运行,需要等网页执行完毕才渲染到数据,会消耗很长时间。

三、Selenium的使用

1、声明浏览器对象
from selenium import webdriver

driver = webdriver.Chrome()  #谷歌 需:ChromeDriver驱动
driver = webdriver.FireFox() #火狐 需:GeckoDriver驱动
driver = webdriver.Edge()  
driver = webdriver.Safari()  
driver = webdriver.PhantomJS() #无界面浏览器

创建谷歌浏览器对象,启用Chrome的Headless 无界面模式

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
driver = webdriver.Chrome(chrome_options=chrome_options)
2、访问页面
from selenium import webdriver

driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()
driver.get("http://www.taobao.com")	#访问指定网址
print(driver.page_source)
#driver.close()(结束之前需要进行驱动关闭,但运行此代码时会关闭浏览器,导致程序无法继续运行)
3、 查找节点:
3.1、使用selenium内置函数获取节点

获取单个节点的方法:

find_element_by_id()
find_element_by_name()
find_element_by_xpath()
find_element_by_link_text()
find_element_by_partial_link_text()
find_element_by_tag_name()
find_element_by_class_name()
find_element_by_css_seletor()
from selenium import webdriver
from selenium.webdriver.common.by import By

#创建浏览器对象
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()
driver.get("http://www.taobao.com")
#下面都是获取id属性值为q的节点对象
input = driver.find_element_by_id("q")
print(input)

input = driver.find_element_by_css_selector("#q")
print(input)

input = driver.find_element_by_xpath("//*[@id='q']")
print(input)

#效果同上
input = driver.find_element(By.ID,"q")
print(input)

#driver.close()

获取多个节点的方法:

find_elements_by_id()
find_elements_by_name()
find_elements_by_xpath()
find_elements_by_link_text()
find_elements_by_partial_link_text()
find_elements_by_tag_name()
find_elements_by_class_name()
find_elements_by_css_seletor()
3.2、使用selenium.webdriver.common.by模块中的By来找节点
  • 引入模块:from selenium.webdriver.common.by import By
  • By是selenium中内置的一个class,在这个class中有各种方法来定位元素
  • By所支持的定位器的分类:

1、id属性定位:

find_element(By.ID,"id")

2、name属性定位

find_element(By.NAME,"name")

3、classname属性定位

find_element(By.CLASS_NAME,"claname")

4、a标签文本属性定位

find_element(By.LINK_TEXT,"text")

5、a标签部分文本属性定位

find_element(By.PARTIAL_LINK_TEXT,"partailtext")

6、标签名定位

find_elemnt(By.TAG_NAME,"input")

7、xpath路径定位

find_element(By.XPATH,"//div[@name='name']")

8、css选择器定位

find_element(By.CSS_SELECTOR,"#id")
4 、节点交互:

input.clear()——清空输入框(先获取输入框节点)
input.send_keys(’***’)——模拟键盘输入指定内容(先获取输入框节点)
botton.click()——触发点击动作(先获取按钮节点)

from selenium import webdriver
import time

#创建浏览器对象
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()
driver.get("http://www.taobao.com")
#下面都是获取id属性值为q的节点对象
input = driver.find_element_by_id("q")
#模拟键盘输入iphone
input.send_keys('iphone')
time.sleep(3)
#清空输入框
input.clear()
#模拟键盘输入iPad
input.send_keys('iPad')
#获取搜索按钮节点
botton = driver.find_element_by_class_name("btn-search")
#触发点击动作
botton.click()

#driver.close()
5、 动态链:

需导入ActionChains模块:

from selenium.webdriver import ActionChains
  • ActionChains是一种自动化低级别交互的方法(模拟鼠标各种操作),如鼠标移动,鼠标按钮操作,按键操作和上下文菜单交互。
  • 这对于执行更复杂的操作(如悬停和拖放)很有用.
    • move_to_element(to_element )-- 将鼠标移到元素的中间(悬停)
    • move_by_offset(xoffset,yoffset )-- 将鼠标移至当前鼠标位置的偏移量
    • drag_and_drop(源,目标)-- 然后移动到目标元素并释放鼠标按钮。
    • pause(秒)-- 以秒为单位暂停指定持续时间的所有输入
    • perform()-- 执行所有存储的操作。
    • release(on_element = None )释放元素上的一个持有鼠标按钮。
    • reset_actions()-- 清除已存储在远程端的操作。
    • send_keys(* keys_to_send )-- 将键发送到当前的焦点元素。
    • send_keys_to_element(element,* keys_to_send )-- 将键发送到一个元素。

拖拽案例:

from selenium import webdriver
from selenium.webdriver import ActionChains
import time

#创建浏览器对象
driver = webdriver.Chrome()
#加载指定url地址
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
driver.get(url)

# 切换Frame窗口    
driver.switch_to.frame('iframeResult')
#获取两个div节点对象
source = driver.find_element_by_css_selector("#draggable")
target = driver.find_element_by_css_selector("#droppable")
#创建一个动作链对象
actions = ActionChains(driver)
#将一个拖拽操作添加到动作链队列中
actions.drag_and_drop(source,target)
time.sleep(3)
#执行所有存储的操作(顺序被触发)
actions.perform()
#driver.close()
6、 执行JavaScript:

Selenium API 并没有提供实现某些操作的方法,比如,下拉进度条。但它可以直接模拟运行 JavaScript,此时使用 execute_script 方法即可实现;
语法:driver.execute_script('window.open()')——执行javascript命令
滑动滚动条

from selenium import webdriver

#创建浏览器对象
driver = webdriver.Chrome()
#加载指定url地址
driver.get("https://www.zhihu.com/explore")
#执行javascript程序将页面滚动移至底部
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
#执行javascript实现一个弹框操作
driver.execute_script('window.alert("Hello Selenium!")')

#driver.close()
7、 获取节点信息:

前面说过,通过 page_source 属性可以获取网页的源代码,接着就可以使用解析库(如正则表达式、Beautiful Soup、pyquery 等)来提取信息了。

不过,既然 Selenium 已经提供了选择节点的方法,并且返回的是 WebElement 类型,那么它也有相关的方法和属性来直接提取节点信息,如属性、文本等。这样的话,我们就可以不用通过解析源代码来提取信息了,非常方便。

WebElement 节点属性:
获取属性:get_attribute —— 获取节点的属性,但是前提是得先选中这个节点;
获取文本值:text——每个 WebElement 节点都有 text 属性,直接调用 “text” 属性就可以得到节点内部的文本信息,这相当于 pyquery 的 text 方法;
获取 ID: id
位置:location
标签名:tag_name
大小:size

from selenium import webdriver
from selenium.webdriver import ActionChains

#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
#获取id属性值为zh-top-link-logo的节点(logo)
logo = driver.find_element_by_id("zh-top-link-logo")
print(logo) #输出节点对象
print(logo.get_attribute('class')) #节点的class属性值
#获取id属性值为zu-top-add-question节点(提问按钮)
input = driver.find_element_by_id("zu-top-add-question")
print(input.text) #获取节点间内容
print(input.id)  #获取id属性值
print(input.location) #节点在页面中的相对位置
print(input.tag_name) #节点标签名称
print(input.size)     #获取节点的大小
print(driver.page_source)	#得到页面代码
#driver.close()
8、切换Frame:
  • 网页中有一种节点叫做iframe,也就是子Frame,他可以将一个页面分成多个子父界面。相当于页面的子页面,它的结构和外部网页的结构完全一致。
  • Selenium 打开页面后,默认是在父级 Frame 里面操作,而此时如果页面中还有子 Frame,Selenium 是不能获取到子 Frame 里面的节点的。这时就需要使用 switch_to.frame 方法来切换 Frame。实例详见第⑥的动态链案例
9、 延迟等待:
  • 浏览器加载网页是需要时间的,Selenium也不例外,若要获取完整网页内容,就要延时等待。
  • 在Selenium中延迟等待方式有两种:一种是隐式等待,一种是显式等待(推荐)。
9.1、隐式等待(固定时间):driver.implicitly_wait(2)
from selenium import webdriver

#创建浏览器对象
driver = webdriver.Chrome()
#使用隐式等待(固定时间)
driver.implicitly_wait(2) 
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
#获取节点    
input = driver.find_element_by_id("zu-top-add-question")
print(input.text) #获取节点间内容

#driver.close()
9.2、显式等待
  • 什么时候用?——当你的操作,引起了页面的变化,而你接下来要操作变化 的元素的时候,就一定要加等待。
  • 使用方法:
    使用之前,引入相关的库
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    
    1、先确定元素的定位表达式
    web_ locator = ‘XXXX’
    2、调用webdriverWait类设置等待总时长、轮询周期。并调用期until、until_ not方法。
    WebDriverWait(driver,等待时长,轮循周期).until(这个条件成立,才向下执行)/until_ not() 
    
    3、使用expected_ conditions对 应的方法来生成判断条件。
    EC.类名((定位方式、定位表达式))
    
    例: EC.presence_ of_ element_ located((By.CSS_ SELECTOR,web locator)))
    在这里插入图片描述
    在这里插入图片描述
  • 显性等待的等待判断条件(常结合EC模块使用)
    • 等待元素可见
    • 等待元素可用
    • 等待新窗口出现
    • 等待 url地址=xxx的时候
    • 等待 的上限是多少秒,每隔0.5秒去查看条件是否成立
  • 显示等待的作用本质:
    显示等待会明确等到某个条件满足之后,再去执行下一步操作。程序每隔xx秒看一眼,如果条件成立了,则执行下一步,否则继续等待,直到等待的元素可见;超过设置的最长时间,然后抛出TimeoutException。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
#显式等待,最长10秒
wait = WebDriverWait(driver,10)
#等待条件:10秒内必须有个id属性值为zu-top-add-question的节点加载出来,否则抛异常。
input = wait.until(EC.presence_of_element_located((By.ID,'zu-top-add-question')))
print(input.text) #获取节点间内容
#driver.close()
10、expected_conditions EC 模块
  • 导入模块:from selenium.webdriver.support import expected_conditions as EC
  • 简介:selenium的expected_conditions模块收集了一系列的场景判断方法,比如:判断一个元素是否存在,如何判断alert弹窗出来了,如何判断动态的元素等等。
  • 建议:可以搭配wait模块使用来判断页面跳转等
  • 部分方法函数:
    1、title_is: 判断当前页面的title是否完全等于(==)预期字符串,返回布尔值;
    from selenium import webdriver
    from selenium.webdriver.support import expected_conditions as EC
    
    driver = webdriver.Chrome()  # 启动chrome
    driver.get('http://www.baidu.com')
    title = EC.title_is('百度一下,你就知道')(driver)
    print(title)
    
    2、title_contains : 判断当前页面的title是否包含预期字符串,返回布尔值
    from selenium import webdriver
    from selenium.webdriver.support import expected_conditions as EC
    
    driver = webdriver.Chrome()  # 启动chrome
    driver.get('http://www.baidu.com')
    title = EC.title_contains('百度')(driver)
    print(title)
    
    3、presence_of_element_located : 判断某个元素是否被加到了dom树里,并不代表该元素一定可见;
    4、visibility_of_element_located : 判断某个元素是否可见. 可见代表元素非隐藏,并且元素的宽和高都不等于0;
    5、visibility_of : 跟上面的方法做一样的事情,只是上面的方法要传入locator,这个方法直接传定位到的element就好了;
    6、presence_of_all_elements_located : 判断是否至少有1个元素存在于dom树中。举个例子,如果页面上有n个元素的class都是‘column-md-3‘,那么只要有1个元素存在,这个方法就返回True;
    7、text_to_be_present_in_element(节点对象,str) : 判断某个元素节点对象中的text是否 包含 了预期的字符串;
    8、text_to_be_present_in_element_value : 判断某个元素中的value属性是否 包含 了预期的字符串;
    9、frame_to_be_available_and_switch_to_it : 判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去,否则返回False;
    10、invisibility_of_element_located : 判断某个元素中是否不存在于dom树或不可见;
    11、element_to_be_clickable : 判断某个元素中是否可见并且是enable的,这样的话才叫clickable;
    12、staleness_of : 等某个元素从dom树中移除,注意,这个方法也是返回True或False;
    13、element_selection_state_to_be : 判断某个元素的选中状态是否符合预期;
    14、element_located_selection_state_to_be : 跟上面的方法作用一样,只是上面的方法传入定位到的element,而这个方法传入locator;
    15、alert_is_present : 判断页面上是否存在alert。
11、前进和后退:

driver.back() ——后退
driver.forward() ——前进

from selenium import webdriver
import time

#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.baidu.com")
driver.get("https://www.taobao.com")
driver.get("https://www.jd.com")
time.sleep(2)
driver.back() #后退
time.sleep(2) 
driver.forward() #前进
#driver.close()
12、Cookies:

driver.get_cookies()——获取当前cookies信息
driver.add_cookie({'name':'namne','domain':'www.zhihu.com'})——添加cookies信息

from selenium import webdriver
from selenium.webdriver import ActionChains

#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
print(driver.get_cookies())
driver.add_cookie({'name':'namne','domain':'www.zhihu.com','value':'zhangsan'})
print(driver.get_cookies())
driver.delete_all_cookies()
print(driver.get_cookies())
#driver.close()
13、选项卡(窗口)管理:

driver.window_handles[1]——选项卡名称(按序号排名)
driver.switch_to_window(driver.window_handles[0])——切换到指定选项卡

from selenium import webdriver
import time

#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.baidu.com")
#使用JavaScript开启一个新的选型卡
driver.execute_script('window.open()')
print(driver.window_handles)
#切换到第二个选项卡,并打开url地址
driver.switch_to_window(driver.window_handles[1])
driver.get("https://www.taobao.com")
time.sleep(2)
#切换到第一个选项卡,并打开url地址
driver.switch_to_window(driver.window_handles[0])
driver.get("https://www.jd.com")
#driver.close()
14、异常处理:

超时异常:

from selenium import webdriver
from selenium.common.exceptions import TimeoutException,NoSuchElementException

#创建浏览器对象
driver = webdriver.Chrome()
try:
    #加载请求指定url地址
    driver.get("https://www.baidu.com")
except TimeoutException:
    print('Time Out')

网页加载完成找不到指定元素异常

try:
    #加载请求指定url地址
    driver.find_element_by_id("demo")
except NoSuchElementException:
    print('No Element')
finally:
    #driver.close()
    pass

关于更多的异常类,可以参考官方文档

15、小案例

模拟谷歌浏览器访问百度首页,并输入python关键字搜索

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

#初始化一个浏览器(如:谷歌,使用Chrome需安装chromedriver)
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS() #无界面浏览器
try:
    #请求网页
    driver.get("https://www.baidu.com")
    #查找id值为kw的节点对象(搜索输入框)
    input = driver.find_element_by_id("kw")
    #模拟键盘输入字串内容
    input.send_keys("python")
    #模拟键盘点击回车键
    input.send_keys(Keys.ENTER)
    #显式等待,最长10秒
    wait = WebDriverWait(driver,10)
    #等待条件:10秒内必须有个id属性值为content_left的节点加载出来,否则抛异常。
    wait.until(EC.presence_of_element_located((By.ID,'content_left')))
    # 输出响应信息
    print(driver.current_url)
    print(driver.get_cookies())
    print(driver.page_source)
finally:
    #关闭浏览器
    #driver.close()
    pass
16、反屏蔽

现在很多网站都加上了对 Selenium 的检测,来防止一些爬虫的恶意爬取。即如果检测到有人在使用 Selenium 打开浏览器,那就直接屏蔽。

其大多数情况下,检测基本原理是检测当前浏览器窗口下的 window.navigator 对象是否包含 webdriver 这个属性。因为在正常使用浏览器的情况下,这个属性是 undefined,然而一旦我们使用了 Selenium,Selenium 会给 window.navigator 设置 webdriver 属性。很多网站就通过 JavaScript 判断如果 webdriver 属性存在,那就直接屏蔽。

想法一:
可以使用 JavaScript 直接把这个 webdriver 属性置空,比如通过调用 execute_script 方法来执行如下代码:

Object.defineProperty(navigator, "webdriver", {get: () => undefined})

这行 JavaScript 的确是可以把 webdriver 属性置空,但是 execute_script 调用这行 JavaScript 语句实际上是在页面加载完毕之后才执行的,执行太晚了,网站早在最初页面渲染之前就已经对 webdriver 属性进行了检测,所以用上述方法并不能达到效果。

解决方法:
在 Selenium 中,我们可以使用 CDP(即 Chrome Devtools-Protocol,Chrome 开发工具协议)来解决这个问题,通过 CDP 我们可以实现在每个页面刚加载的时候执行 JavaScript 代码,执行的 CDP 方法叫作 Page.addScriptToEvaluateOnNewDocument,然后传入上文的 JavaScript 代码即可,这样我们就可以在每次页面加载之前将 webdriver 属性置空了。另外我们还可以加入几个选项来隐藏 WebDriver 提示条和自动化扩展信息,代码实现如下:

from selenium import webdriver
from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_experimental_option('useAutomationExtension', False)
browser = webdriver.Chrome(options=option)
browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
   'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'
})
browser.get('https://antispider1.scrape.cuiqingcai.com/')

这样整个页面就能被加载出来了。

对于大多数的情况,以上的方法均可以实现 Selenium 反屏蔽。但对于一些特殊的网站,如果其有更多的 WebDriver 特征检测,可能需要具体排查。

三、Selenium的淘宝爬取实战

1、前言

淘宝使用大量反爬措施,可以使用selenium调出网页结合手机扫码登录使用。

2、案例要求

使用Selenium爬取淘宝商品,指定关键字和指定页码信息来进行爬取

3、案例分析:

url地址:https://s.taobao.com/search?q=ipad

4、具体代码实现
'''通过关键字爬取淘宝网站的信息数据'''
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from pyquery import PyQuery as pq
from urllib.parse import quote
import json

KEYWORD = "ipad"
MAX_PAGE = 10

# browser = webdriver.Chrome()
# browser = webdriver.PhantomJS()
#创建谷歌浏览器对象,启用Chrome的Headless无界面模式
#chrome_options = webdriver.ChromeOptions()
#chrome_options.add_argument('--headless')
#browser = webdriver.Chrome(chrome_options=chrome_options)
#在此处需要有界面来进行手机扫码登录
browser = webdriver.Chrome()
#显式等待:
wait = WebDriverWait(browser, 10)

def index_page(page):
    '''抓取索引页 :param page: 页码'''
    print('正在爬取第', page, '页')
    try:
        url = 'https://s.taobao.com/search?q=' + quote(KEYWORD)
        browser.get(url)
        if page > 1:
            input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))
            submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))
            input.clear()
            input.send_keys(page)
            submit.click()
        #等待条件:显示当前页号,显式商品的节点对象出现
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))
        wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
        html = browser.page_source
        return html
    except TimeoutException:
        index_page(page)
        
def get_products(html):
    '''提取商品数据'''
    doc = pq(html)
    items = doc('#mainsrp-itemlist .items .item').items()
    for item in items:
        yield {
            'image': item.find('.pic .img').attr('data-src'),
            'price': item.find('.price').text(),
            'deal': item.find('.deal-cnt').text(),
            'title': item.find('.title').text(),
            'shop': item.find('.shop').text(),
            'location': item.find('.location').text()
        }

def save_data(result):
    '''保存数据'''
	with open("./taobao.txt","a",encoding="utf-8") as f1:
		f1.write(json.dumps(result,ensure_ascii=False)+"\n")

def main():
    '''遍历每一页'''
    for i in range(1, MAX_PAGE + 1):
        html = index_page(i)
        result = get_products(html)
		for i in content:
			save_data(i)
    browser.close()

# 主程序入口
if __name__ == '__main__':
    main()
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值