网络爬虫-进阶

一.多线程爬虫

threading模块

该模块是python中专门提供用来做多线程的模块 threading模块最常用的类Thread

  1. 传统方式
    import threading
    import time
    def coding():
	for x in range(3):
		print('正在编写代码%s' %x)
		time.sleep(1)
    def drawing():
	for i in range(3):
		print('正在编译%s' %i)
		time.sleep(1)
    def main():
	coding()
	drawing()
if __name__ == '__main__':
	main()
**多线程方式**
ex:
    import threading
    import time
    def coding():
	for x in range(3):
		print('正在编写代码%s' %x)
		time.sleep(1)
    def drawing():
	for i in range(3):
		print('正在编译%s' %i)
		time.sleep(1)
    def main():
	t1 = threading.Thread(target=coding)
	t2 = threading.Thread(target=drawing)
	t1.start()
	t2.start()
if __name__ == '__main__':
	main()
  1. 使用Thread类创建多线程:

    • 查看线程数: 使用threading.enumerate()函数可以看到当前线程的数量

    • 查看当前线程的名字: 使用threading.current_thread()可以看到当前线程的信息

    • 继承自threading.Thread类:为了让线程代码更好的封装 可以使用threading模块下的Thread类 继承自这个类
      然后实现run方法 线程就会自动运行ran方法中的代码

          import threading
          import time 
          class CodingThread(threadimg.Thread):
      	    def run(self):
      		for x in range(3):
      			print('正在编写代码%s' %threading.current_thread())
      			time.sleep(1)
          class DrawingThread(threading.Thread):
      	    def run(self):
      		for i in range(3):
      			print('正在编译%s' %threading.Thread())
      			time.sleep(1)
          def main():
      	t1 = DrawingThread()
      	t2 = CodingThread()
      	t1.start()
      	t2.start()
      if __name__ == '__main__':
      	main()
      
  2. 多线程共享全局变量以及锁机制:多线程都是在同一个进程中进行的 因此进程中的全局变量所用线程都是可共享的
    又因为线程的执行顺序是无序的 有可能造成数据错误

        import threading
        VALUSE = 0
        def add_value():
    	global VALUE
    	for i in range(100):
    		VALUE +=1
    	print('value: %d'%VALUE)
        def main():
    	for x in range(2):
    		t = threading.Thread(target=add_value)
    		t.start()
    if __name__ == '__main__'	
        main()
    

解决方法:


    import threading
    VALUSE = 0
    #创建一个锁
    glock = threading.Lock()
    def add_value():
	global VALUE
	#加锁
	glock.acquire()
	for i in range(100):
		VALUE +=1
       #释放锁
	glock.release()
	print('value: %d'%VALUE)
    def main():
	for x in range(2):
		t = threading.Thread(target=add_value)
		t.start()
if __name__ == '__main__'	
    main()
  1. Lock版生产者和消费者模式:threading.Lock锁实现的生产者与消费者模式

      import threading
        gMoney = 1000
        gLock = threading.Lock
        gTotalTimes = 10
        gTimes = 0
        class Producer(threading.Thread):
    	def run(self):
    		global gMoney
    		global gTimes
    	        #持续生产
    		while True:
    			money = random.randint(100,1000)
    			gLock.acquire()
    			if gLock >=10:
    				glock.release()
    				break
    			gMoney +=money
    			print('%s生产了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney))
    			gTimes +=1
    			gLock。release()
    			time.sleep(1)
    
        class Consumer(threading.Thread):
    	def run(self):
    	global gMoney
    		while True:
    			money = random.randint(100,1000)
    			gLock.acquire()
    			#需要做一下判断
    			if gMoney>= money
    				gMoney -=money
    				print('%消费了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney))
    			else:
    				if gTimes >=gTotalTimes:
    				gLock.release()
    				print('您的余额不足')
    			gLock。release()
    			time.sleep(1)
    	   def main():
    	        #5个生产进程
    		for x in range(5):
    			t = Product()        
    			t.start()
    		#三个消费进程
    		for x in range(3):
    			t = Consumer()
    			t.start()
    	if __name__ == '__main__()'
    		main()
    

Condition 版的生产者与消费者模式:优化版模式
Lock版本的生产者与消费者模式 可以正常运行 但是纯在一个不足 在消费者中 总是通过while True死循环并且上锁的方式去判断
钱够不够 上锁是一个很费cpu自愿的行为 相比较Condition版是一个更好的方式 用threading.Condition来实现
并且 threading.Condition可以在没有数据的时候处于阻塞等待状态 一旦有了合适的数据 还可以使用notify相关的函数来通知其他处于等待状态的线程
这样就可以不用做一些无用的上锁和解锁的操作 来提高程序的性能
5. threading.Condition相关函数的介绍:

  • acquire :上锁
  • release :解锁
  • wait :将当前线程处于等待状态 并且会释放锁 可以被其他线程使用notify 和notify_all函数唤醒 被唤醒后会继续等待上锁 上锁后继粗制星系买你的代码
  • notify :通知某个正在等待的线程 默认是第一个等待的线程
  • notify_all :通知等待所有正在等待的线程 notify 和notify_all 不会释放锁 并且需要再release 之前调用
    import threading
    import random
    import time
    gMoney = 1000
    gContion = threading.conditon
    gTotalTimes = 10
    gTimes = 0
    class Producer(threading.Thread):
	def run(self):
		global gMoney
		global gTimes
	        #持续生产
		while True:
			money = random.randint(100,1000)
			gContdiion.acquire()
			if gLock >=10:
				glock.release()
				break
			gMoney +=money
			print('%s生产了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney))
			gTimes +=1
			#唤醒线程
			gCondition.notify_all()
			gCondition.release()
			time.sleep(1)

    class Consumer(threading.Thread):
	def run(self):
	global gMoney
		while True:
			money = random.randint(100,1000)
			gContion.acquire()
			#只要金钱不足就让线程处于阻塞状态
			while gMoney <money:
				#余额不足可能是生产者生产够10次了,那么就要结束这个线程了
				if gTimes>=gTotalTimes:
				#结束线程之前要释放锁
					gCondition.release()
					#不能用break
					return
				print('您的余额不足')
				#处于阻塞状态
				gCondition.wait()
			gMoney -=money
			print('%消费了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney))
			gContdition.release()
			time.sleep(1)

   def main():
        #5个生产进程
	for x in range(5):
		t = Product()        
		t.start()
	#三个消费进程
	for x in range(3):
		t = Consumer()
		t.start()
if __name__ == '__main__()'
	main()
  1. Queue 线程安全队列:
    在线程中 访问一些全局变量 枷锁是一个经常的过程 如果你是想把一些数据存储到某个队列中 python中内置了一个线程安全的模块,叫Queue模块 该模块提供了同步的线程安全的队列类 包括FIFO(先进先出)队列Queue LIFO(后进先出)队列lifoQueue 这些队列实现了锁原语
    能够在多线程中直接使用 可以使用队列来实现进程间的同步

    相关函数:

    • 初始化Queue(maxsize) :创建一个先进先出的队列
    • qsize() :返回队列的大小
    • empty() :判断队列是否为空
    • full :判断队列是否满了
    • get() :从队列中去最后一个数据
    • put() :将一个数据放到队列中
from queue inmport Queue
import time
def set_value(q):
	index = 0 
while True:
	q.put(index)
	index +=1
	time.sleep(2)
def get_value(q):
	while True:
		print(q.get())
def main():
	q = Queue(4)
	t1 = threadingThread(target=set_value,args=[q])
	t2 = threadingThread(target=get_value,args=[q])
	t1.start()
	t2.start()
if __name__ =='__main__':
	main()**
  1. GIL解释器锁:
    python自带的解释器是cpython cpython解释其的多线程实际上并不是一个真正的多线程 (在多核CPU中 只能利用一核 )
    同一时刻只有一个线程在执行 为了保证同一时刻只有一个线程在执行 cpython解释器中有一个叫GIL的东西 除了cpython解释器还有其他解释器
    这些解释器是没有GIL锁的
    JPython : 用Java实现的解释器 不存在GIL锁
    IronPython :用.net实现的解释器 不存在GIL锁
    pypy :用pyhton实现的解释器 不存在GIL锁

二.AJAX

异步JavaScript和XML 在后台与服务器进行少量数据交换 Ajax可以使网页实现异步更新 (不需要重新加载整个网页的情况下 对网页的某部分进行更新 )传统传输数据格式方面 使用的是xml语法
其实现在在数据交互基本上都是JSON 使用AJAX加载的数据 即使使用了JS 将数据渲染到浏览器中 在网页代码中还是不能看到ajax加载的数据 只能看到使用url加载的html代码

获取ajax数据的方式:

  1. 直接使用Ajax调用的接口 然后通过这个接口去请求这个接口
    优点 :直接可以请求到数据 代码量少 性能高
    缺点 :分析接口比较复杂 特别是一些通过js混淆的接口 要有一定的j功底 容易被发现是爬虫
  2. 使用Selenium+chromedriver模拟浏览器行为获取数据
    优点: 直接模拟浏览器的行为 爬虫更稳定
    缺点:代码量多 性能低

使用Ajax接口

抓包神器Fiddler

Fiddler是一款强大Web调试工具,它能记录所有客户端和服务器的HTTP请求。 Fiddler启动的时候,默认IE的代理设为了127.0.0.1:8888,而其他浏览器是需要手动设置。
1. 工作原理
Fiddler 是以代理web服务器的形式工作的,它使用代理地址:127.0.0.1,端口:8888
在这里插入图片描述
2. Fiddler抓取HTTPS设置
启动Fiddler,打开菜单栏中的 Tools > Telerik Fiddler Options,打开“Fiddler Options”对话框。
在这里插入图片描述
3. 对Fiddler进行设置:
- 打开工具栏->Tools->Fiddler Options->HTTPS,
- 选中Capture HTTPS CONNECTs (捕捉HTTPS连接),
- 选中Decrypt HTTPS traffic(解密HTTPS通信)
- 另外我们要用Fiddler获取本机所有进程的HTTPS请求,所以中间的下拉菜单中选中…from all processes (从所有进程)
- 选中下方Ignore server certificate errors(忽略服务器证书错误)
在这里插入图片描述
4. 为 Fiddler 配置Windows信任这个根证书解决安全警告:Trust Root Certificate(受信任的根证书
在这里插入图片描述
5. Fiddler 主菜单 Tools -> Fiddler Options…-> Connections

	- 选中Allow remote computers to connect(允许远程连接)
	- Act as system proxy on startup(作为系统启动代理)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200809180334894.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pbnBlbmd5b3U=,size_16,color_FFFFFF,t_70)
6. 重启Fiddler,使配置生效(这一步很重要,必须做)。
  1. Fiddler 如何捕获Chrome的会话
    1. 安装SwitchyOmega 代理管理 Chrome 浏览器插件
      在这里插入图片描述

    2. 如图所示,设置代理服务器为127.0.0.1:8888
      在这里插入图片描述

      1. 通过浏览器插件切换为设置好的代理。
        在这里插入图片描述
  2. Fiddler界面
    1. 设置好后,本机HTTP通信都会经过127.0.0.1:8888代理,也就会被Fiddler拦截到。
    2. 请求 (Request) 部分详解
      Headers —— 显示客户端发送到服务器的 HTTP 请求的 header,显示为一个分级视图,包含了 Web 客户端信息、Cookie、传输状态等。
      Textview —— 显示 POST 请求的 body 部分为文本。
      WebForms —— 显示请求的 GET 参数 和 POST body 内容。
      HexView —— 用十六进制数据显示请求。
      Auth —— 显示响应 header 中的 Proxy-Authorization(代理身份验证) 和 Authorization(授权) 信息.
      Raw —— 将整个请求显示为纯文本。
      JSON - 显示JSON格式文件。
      XML —— 如果请求的 body 是 XML 格式,就是用分级的 XML 树来显示它。
    3. 响应 (Response) 部分详解
      Transformer —— 显示响应的编码信息。
      Headers —— 用分级视图显示响应的 header。
      TextView —— 使用文本显示相应的 body。
      ImageVies —— 如果请求是图片资源,显示响应的图片。
      HexView —— 用十六进制数据显示响应。
      WebView —— 响应在 Web 浏览器中的预览效果。
      Auth —— 显示响应 header 中的 Proxy-Authorization(代理身份验证) 和 Authorization(授权) 信息。
      Caching —— 显示此请求的缓存信息。
      Privacy —— 显示此请求的私密 (P3P) 信息。
      Raw —— 将整个响应显示为纯文本。
      JSON - 显示JSON格式文件。
      XML —— 如果响应的 body 是 XML 格式,就是用分级的 XML 树来显示它 。

Selenium+chromedriver模拟浏览器行为获取数据

Selenium+chromedriver 获取动态数据:
Selenium相当于一个机器人 可以模拟人类在浏览器的一些行为 自动处理浏览器上的一些行为 比如点击 填充数据 删除cookie等
chromedriver 是一个驱动chrome浏览器的驱动程序 使用它才可以驱动浏览器 不同浏览器有不同的driver
具体浏览器的driver安装问题 自行百度查找-*-

安装Selenium和chromedriver

1.安装Selenium selenium有很多语言的版本 有Java ruby python :pip install selenium
2.安装chrome driver 下载完成后放到不需要权限的纯英文目录下就可以了

	```python
	   from selenium import webdriver
	    driver_path = r'path(路径)\chromedriver.exe'
	    driver = webdriver.chrome(executable_path=driver_path)
	    driver.get('url')
	    不会返回数据  想要获取数据 需要用属性来获取
	    #获取网页源代码
	    print(driver.page_source)
	```

selenium常用的操作:

- **关闭页面:**
driver.close()  :关闭当前页面
driver.quit() :退出整个浏览器
- **定位元素:**
	- find_element_by_id:根据id查找元素  :
		    submittag = driver.find_element_by_id('jhin')
		    submittag1 = driver.find_element(By.ID,'jhin')
	- find_element_by_class_name:根据类名查找元素:
       		 submittag = driver.find_element_by_name('jhin')
			submittag1 = driver.find_element(By.CLASS_NAME,'jhin')
	- find_element_by_name :根据name属性的值来查找元素
		    submittag = driver.find_element_by_name('jhin')
		    submittag1 = driver.find_element(By.NAME,'jhin')
	- find_element_by_tag_name :根据标签名属性的值来查找元素
		    submittag = driver.find_element_by_tag_name('jhin')
		    submittag1 = driver.find_element(By.TAG_NAME,'jhin')
	- find_element_by_xpath:根据xpath语法来获取元素
		    submittag = driver.find_element_by_xpath('//jhin')
		    submittag1 = driver.find_element(By.XPATH,'//jhin')
	- find_element_by_css_selector :根据css选择器选择元素
		    submittag = driver.find_element_by_css_selector('//jhin')
		    submittag1 = driver.find_element(By.CSS_SELECTOR,'//jhin')
		find_element是获取第一个满足条件的元素  find_elements是获取所有满足条件的元素
- **如果只想要解析网页中的数据  推荐将网页源代码传递给lxml解析   解析效率更高**
	     html = etree.HTML(driver.page_source)
	     html.xpath('xxxx')
4. **对元素进行一些操作  比如给一个文本框输入值  或者点击某个按钮  推荐使用selenium提供的查找元素的方法**
	  - #输入值
	   inputtag = driver.find_element_by_id('kw')
	   inputtag.send_keys('python')
	- 操作表单元素:
		常见的表单元素:
		 input  type='text/password/email/number'
		 button  input[type='submit']
		 checkbox  input='checkbox'
	- 操作输入框:第一步:找到这个元素  第二步使用send_keys(value)将数据填充进去  
		    inputtag = driver.find_element_by_id('jhin')
		    inputtag.send_keys('python')
	- 使用clear方法可以清楚输入框的中的内容:

inputtag.clear()
- 操作checkbox:因为要选中checkbox标签 在网页中是通过鼠标点击的 因此想要选中
checkbox标签 那么要先选中这个标签 然后执行click事件 :比如勾选记住密码的选项 。。。
remembertag = driver.finder_element_by_name(‘jhin’)
ramembertag.click()
- 选择select select元素不能直接点击 因为点击后还需要选中元素 selenium专门为select标签提供了一个selenium.webdriver.support.ui.select
将获取到的元素当成参数传到这个类 创建这个对象 以后就可以使用这个对象进行选择了
比如网站的外链接

		```python
		  #操作select标签
		  from selenium import webdriver
		  form selenium.webdriver.support.ui import Select
		  driver_path = r'path(路径)/chromdriver.exe'
		  driver = webdriver.chrome(executable_path=driver_path)
		  driver.get('http://www.doubai.cn')
		  selectBtn = Select(driver.find_element_by_name('xxx'))
		  #选择第一个外链网站
		  selectBtn.select_by_index(1)
		  selectBtn.select_by_visible_text('杰')
		  selectBtn.select_by_value('url')
		```

		```python
		#百度收索
		   from selenium import webdriver
		   form selenium.webdriver.support.ui import Select
		   driver_path = r'path(路径)/chromdriver.exe'
		   driver = webdriver.chrome(executable_path=driver_path)
		   driver.get('http://www.baidu.cn')
		   inputtag = driver.find_element_by_id('kw')
		   inputtag.send_keys('python')
		   submittag = driver.find_element_by_id('su')
		   submittag.click()
		```

 	- selenium行为链:有的时候在页面中的操作可能要很多步   可以用鼠标行为链ActionChins来完成  
             比如现在要将鼠标移动到某个元素来执行点击事件

		```python
		from selenium import webdriver
		from selenium.webdrive.common.action_chains import Actions
		import time
		driver_path = r'path(路径)/chromdriver.exe'
		driver = webdriver.chrome(executable_path=driver_path)
		driver.get('http://www.baidu.com/')
		inputtag = driver.find_element_by_id('kw')
		submittag = driver.find_element_by_id('su')
		actions = ActionChains(driver)
		#把鼠标移动到标签上
		actions.move_to_element(inputtag)
		actions.send_keys_to_element(inputtag,'python')
		#移动到收索 然后点击
		actions.move_to_element(submittag)
		actions.click(submittag)
		#执行
		actions.perform()
		click_and_hold(element):点击点不松开鼠标
		context_click(element):右键点击
		double_click(element):双击
		``
5. **Cookie操作:**
	- 获取所有的cookie:
 for cookie in driver.get_cookies()
print(cookie)
	- 根据cookie的key获取value:
value = driver.get_cookie(key)
	- 删除所有的cookie:
dirver.delete_all_cookies()
	- 删除某个cookie:
driver.delete_cookie(key)

		```python
		from selenium import webdriver
		  from selenium.webdrive.common.action_chains import Actions
		  import time
		  driver_path = r'path(路径)/chromdriver.exe'
		  driver = webdriver.chrome(executable_path=driver_path)
		  driver.get('http://www.baidu.com/')
		  for cookie in driver.get_cookies()
		  print(cookie)
		  print(driver.get_cookies('jhin'))
		  dirver.delete_all_cookies('jhin')
		  dirver.delete_all_cookies()
		```

6. **页面等待:现在的网页越来越多的采用了Ajax技术  这样程序便不能确定何时某个元素完全加载出来**  

如果实际页面等待时间过长 导致某个要找的元素还没出来 但是你的代码去直接使用了webElement
那么就会抛出异常 因此 selenium提供了两个等待方式 隐式等待 以及显示等待
隐式等待:调用driver。implictly_wait 那么获取不同的元素之前 会等待10秒

	```python
	  from selenium import webdriver
	  driver_path = r'path(路径)/chromdriver.exe'
	  driver = webdriver.chrome(executable_path=driver_path)
	  driver.get('http://www.baidu.com/')
	  #等待20秒
	  driver.implicitly_wait(20)
	  driver.find_element_by_id('kw')
	```

7. **显式等待:显式等待是表明某个条件成立后才执行的获取元素的操作  也可以在等待的时候指定一个最大的时间**  

如果超过这个时间会抛出一个异常 显式等待应该使用 selenium.webdriver.excepted_conditions期望的条件
和selenium.webdriver.support.ui.webDriverWait来配合完成

	```python
	  from selenium import webdriver
	  from selenium.webdriver.support.ui import WebDriverWait
	  from selenium.webdriver.support import expect_condition as EC
	  from selenium.webdriver.common.by import By
	  driver_path = r'path(路径)/chromdriver.exe'
	  driver = webdriver.chrome(executable_path=driver_path)
	  driver.get('http://www.baidu.com/')
	  WebDriverWait(driver,10).until(
	    #等到元素出现时
	    EC.persence_of_element_located((By.ID,'jhin'))
	```

8. **其他一些等待条件:**

presence_of_element_located:某个元素加载完毕了
presence_of_element_all_located:网页中所有满足条件的某个元素加载完毕了
element_to_be_cliable:某个元素是可以点击了

9. **selenium 打开多窗口  和切换窗口:**
	1. 切换页面:  有时候窗口中有很多tab页面  需要进行页面的切换   selenium提供了一个叫做switch_to_window 来进行切换

具体切换到那个页面 可以从driver.window_handles中找到

	```python
	  from selenium import webdriver
	  driver_path = r'path(路径)/chromdriver.exe'
	  driver = webdriver.chrome(executable_path=driver_path)
	  driver.get('http://www.baidu.com/')
	#打开一个新的页面
	  driver.execute_script("window.open('http://wwww.douban.com')")
	  print(driver.current_url)
	#切换到这个新的页面中
	  driver.seitch_to_window(driver.window_handles[1])  #window_handles[x]  x是页面间的距离   起始页是0  
	```
	2. 虽然在窗口中切换到了新的页面  但是driver中还没有切换

如果想要在代码中切换到新的页面 并且做一些爬虫 那么应该使用driver.switch_to_window 来切换到制定的窗口
driver.window_handlers中提取具体的第几个窗口
WebElement 元素:
from selenium。webdriver.remote.webelement import WebElement 类是每个获取出来的元素的所属类:
10. 一些常用的属性:
get_attribute:这个标签的某个属性的值
screenshot:获取当前页面的截图 这个方法只能在driver上使用
driver的对象类 也是继承自WebElement
设置代理ip: 不同浏览器有不同的实现方式
以chrome浏览器为例:

	```python
	from selenium import webdriver
	driver_path = r'path(路径)/chromdriver.exe'
	
	options = webdriver.ChromeOptions()
	options.add_argument("--proxy--server=http://ip:port")
	driver = webdriver.Chrome(executable_path=driver_path,chrome_options=options)
	driver.get('http://httpbin.org/ip')
	```

三.图形验证码识别技术

开源库Tesseract

将图片翻译成文字一般称为光学文字识别 简称OCR 但是要有一定的技术(大量的数据 算法 机器学习 深度学习)
Tesseract:是一个OCR库 目前由谷歌赞助 公认的最优秀 最准确的OCR库
安装:
windowa系统: 下载可执行文件 然后放在不需要权限的目录下 https://digi.bib.uni-mannheim.de/tesseract/
linux系统 :https://github.com/tesseract.ocr/tesseract/wiki/Compling ubuntu下可以通过命令安装 sudo apt install tesseract-ocr

  1. 设置环境变量 :
    想要在命令行中使用tesseract 在widows下应该设置环境变量 而linux和mac系统在安装的时候就默认设置好了
    一共需要设置两个环境变量 第一个就是将exe文件所在的路径添加到path环境变量中去 还有一个就是需要吧训练的数据文件路径也放到环境变量中 在环境变量中 添加一个TESSDATA_PREFIX=path/tessdata
    python代码中操作tesseract 需要安装一个库 pytesseract 通过pip 可以安装
    并且需要安装第三方库PIL
    #使用pytesseract 将图片上的文字转换为文本
import pytesseract
from PIL import Image
#指定tesseract。exe所在的路径
pyttesseract.pytesseract.tesseract_cmd = r'path\tesseract.exe'
#打开图片
image = Image.open('filename')
#调用image_to_string将图片转换为文本
text = pytesseract.image_to_string(Image.open('imagepath'))
print(text)
  1. tesseract 处理网站验证码:
 import pytesseract
 from urllib import request
 from PIL import Image
 import time
 def main():
     pyttesseract.pytesseract.tesseract_cmd = r'path\tesseract.exe'
     url = '验证码url'
     while True:
	request.urlretrieve(url,'image_name.png')
	image = Image.open('image_name.png')
        text = pytesseract.image_to_string(Image.open('imagepath'))
        print(text)
        #避免请求太频繁
	time.sleep(2)
if __name__ == '__main()__'
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值