requests模块
通常我们利用Python写一些WEB程序、webAPI部署在服务端,让客户端request,我们作为服务器端response数据;
但也可以反主为客利用Python的requests模块模拟浏览器行为,向其他站点发送request,让其他站点response数据给我们;
私信小编001即可获取大量Python学习资料!
requests可以模拟浏览器的请求,比起之前用到的urllib,requests模块的api更加便捷(其本质就是封装了urllib3),
特点:requests库发送请求将网页内容下载下来以后,并不会执行js代码,这需要我们自己分析目标站点然后发起新的request请求
官网链接:http://docs.python-requests.org/en/master/
1、安装requests模块
pip3 install requests
2、requests模块支持的请求方式
常用的就是requests.get()和requests.post(),建议在正式学习requests前,先熟悉下HTTP协议;http://www.cnblogs.com/linhaifeng/p/6266327.html
>>> import requests>>> r = requests.get('https://api.github.com/events') >>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})>>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})>>> r = requests.delete('http://httpbin.org/delete')>>> r = requests.head('http://httpbin.org/get')>>> r = requests.options('http://httpbin.org/get')
1、基本get请求
response查看response编码
respose.encoding:查看返回网页数据默认编码
import requestsurl='https://www.baidu.com/'respose=requests.get( url=url, headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1;Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36' })print(respose.encoding)#查看网页编码respose.encoding='utf-8' #设置网页编码print(respose.status_code)with open('a.html','w',encoding='utf-8') as f: f.write(respose.text)
2、带参数的GET请求
url编码
#带参数的url,+url编码from urllib.parse import urlencodeimport requestsk=input('输入关键字: ').strip()res=urlencode({'wd':k},encoding='utf-8') #url编码respose=requests.get('https://www.baidu.com/s?%s'% res, headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36' }, # params={'wd':k} )with open('a.html','w',encoding='utf-8') as f: f.write(respose.text)
headers设置请求头
respose=requests.get('https://www.baidu.com/s?%s'% res, headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36' },
params 请求参数设置(自动处理URL后参数编码)
k=input('输入关键字: ').strip()# res=urlencode({'wd':k},encoding='utf-8') #url编码respose=requests.get('https://www.baidu.com/s?', headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36' }, params={'wd':k} )with open('a.html','w',encoding='utf-8') as f: f.write(respose.text)
Cookies 请求携带cookie信息
respose=requests.get('https://www.baidu.com/s?', headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36' }, params={'wd':k}, Cookies={'user_session':'wGMHFJKgDcmRIVvcA14_Wrt_3xaUyJNsBnPbYzEL6L0bHcfc'}, )
allow_redirects=False 禁止根据resposes的响应头的location做页面跳转,默认是true跳转;
设置为flase可以停留在本次请求(request),获取本次响应(responses)响应头,让跳转的loction地址;否则跳转了获取的就是跳转之后页面的响应内容了!
r3=session.get('https://passport.lagou.com/grantServiceTicket/grant.html', headers={ 'Referer':'//passport.lagou.com/login/login.html', 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', 'Host':'passport.lagou.com', }, allow_redirects = False # 禁止授权完成之后,禁止做页面跳转 , )
小结:
1、get请求和post请求的区别
GET请求:HTPP默认的请求方式是GET;
GETt请求的特点:
*没有请求体,携带数据保存在URL后面
*GET请求携带的参数必须在1k之内
*GET请求的携带的数据由于封装在URL后面,所以会暴露在浏览器地址栏中
POST请求:用户先server端提交上传数据一般会使用POST请求
POST请求的特点:
*有请求体,数据保存在请求体中
*上传提交的数据无上限
*请求体中如果存在中文,会使用URL编码!
小结:
requests.post()用法与requests.get()完全一致,特殊的是requests.post()有一个data参数,用来存放请求体数据,也就是POST请求的请求体;
2、发送post请求,模拟浏览器的登录github
requests.post(url='xxxxxxxx', data={'xxx':'yyy'}) #没有指定请求头,#默认的请求头:application/x-www-form-urlencoed#如果需要向server端传说json数据,必须设置 content-ype:application/json,并且用data传值, 否则服务端取不到值requests.post(url='', data={'':1,}, headers={ 'content-type':'application/json' })、
3、使用request.post() 之 content-type
respose=requests.get('http://www.cnblogs.com/sss4/')print(respose.text) #显示文本内容print(respose.content) #显示二进制内容(比如爬 图片 或视频需要)print(respose.status_code) #返回的状态码print(respose.headers) #获取响应头print(respose.cookies) #获取服务端响应的cokies信息print(respose.cookies.get_dict()) #获取字典形式的cokies信息print(respose.cookies.items()) #获取列表类型的cookis信息print(respose.url) #获取请求的URLhttp://www.cnblogs.com/sss4/print(respose.history)#获取跳转前的urlprint(respose.json()) #获取json数据respose.encoding='gbk'#设置 requests模块的编码
当我们使用requets模块,发送了正确GET/POST请求之后,服务端势必会给我们一个response(响应内容)
1、response属性
respose=requests.get('http://www.cnblogs.com/sss4/')print(respose.text) #显示文本内容print(respose.content) #显示二进制内容(比如爬 图片 或视频需要)print(respose.status_code) #返回的状态码print(respose.headers) #获取响应头print(respose.cookies) #获取服务端响应的cokies信息print(respose.cookies.get_dict()) #获取字典形式的cokies信息print(respose.cookies.items()) #获取列表类型的cookis信息print(respose.url) #获取请求的URLhttp://www.cnblogs.com/sss4/print(respose.history)#获取跳转前的urlprint(respose.json()) #获取json数据respose.encoding='gbk'#设置 requests模块的编码
1、SSL Cert Verification(验证证书)
大家平时访问某网站的时候,URL是以https开头的,这是为什么呢?https是http+ssl协议:基于证书校验的http协议
世界上有一个专门负责为浏览器颁发证书的CA机构
某些网站会去CA中心买1个数字证书,这样浏览器每次去访问该网站都会去访问权威CA机构,获取该证书携带该证书过去访问该网站;
还有一类网站不愿意花钱去CA购买权威的证书,自己搭建了一个颁发证书的CA,这些CA中心是不被浏览器认可的,所以每次访问这些网站的时候,浏览器会去私有证书颁发机构获取证书,浏览器会提示用户这是一个不安全的链接,让用户选择处理;
我们在做爬虫的时候如何绕过证书验证环节呢?
情况1:不再证书验证
不做证书验证的情况,在某些情况下是行不通的的;
除非某些网站购买的是权威的CA证书,已经和浏览器和操作系统做了合作下载浏览器时把证书自带下载好了;(提升了用户体验,也提升了安全性。)
另外一种情况是 虽然该网站做了证书验证,但是不使用https协议也能正常登录;(用户体验为上)
verify=False 代表不做证书验证
#证书验证(大部分网站都是https)import requestsrespone=requests.get('https://www.12306.cn') #如果是ssl请求,首先检查证书是否合法,不合法则报错,程序终端
去掉报错,并且去掉警报信息
import requestsfrom requests.packages import urllib3urllib3.disable_warnings() #关闭警告respone=requests.get('https://www.12306.cn',verify=False)print(respone.status_code)
情况2:必须做用户证书验证的网站
但是一些网站必须硬性要求浏览器携带证书,比如12306这种刚需网站,如何破?(安全至上)
import requestsrespone=requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key'))print(respone.status_code)
2、使用爬虫代理
如果你使用爬某网站的频率过高,IP会被该网站封掉,如何破?找一个代理使用别人的IP地址去访问
#官网链接: http://docs.python-requests.org/en/master/user/advanced/#proxies#代理设置:先发送请求给代理,然后由代理帮忙发送(封ip是常见的事情)import requestsproxies={ 'http':'http://egon:123@localhost:9743',#带用户名密码的代理,@符号前是用户名与密码 'http':'http://localhost:9743', 'https':'https://localhost:9743',}respone=requests.get('https://www.12306.cn', proxies=proxies)print(respone.status_code)#支持socks代理,安装:pip install requests[socks]import requestsproxies = { 'http': 'socks5://user:pass@host:port', 'https': 'socks5://user:pass@host:port'}respone=requests.get('https://www.12306.cn', proxies=proxies)print(respone.status_code)
3、超时设置
import requestsresult=requests.get('https://www.baidu.com/',timeout=0.0001 ) #timeout=0.0001 代表 请求+接收服务端数据的总时间;#如果想明确控制 连接 和 等待接收服务端数据的时间timeout=(1,2))result2=requests.get('https://www.baidu.com/',timeout=(1,2)) #timeout=(0.1,0.2)#0.1代表链接超时时间 0.2代表接收数据的超时时间
4、 认证设置
爬取公司内网需要输入用户名和密码的 内网 例如:监控系统、乐视人(线上报销)
#官网链接:http://docs.python-requests.org/en/master/user/authentication/#认证设置:登陆网站是,弹出一个框,要求你输入用户名密码(与alter很类似),此时是无法获取html的# 但本质原理是拼接成请求头发送# r.headers['Authorization'] = _basic_auth_str(self.username, self.password)# 一般的网站都不用默认的加密方式,都是自己写# 那么我们就需要按照网站的加密方式,自己写一个类似于_basic_auth_str的方法# 得到加密字符串后添加到请求头# r.headers['Authorization'] =func('.....')#看一看默认的加密方式吧,通常网站都不会用默认的加密设置import requestsfrom requests.auth import HTTPBasicAuthr=requests.get('xxx',auth=HTTPBasicAuth('user','password'))print(r.status_code)#HTTPBasicAuth可以简写为如下格式import requestsr=requests.get('xxx',auth=('user','password'))print(r.status_code)
5、requests模块自带异常处理
#异常处理import requestsfrom requests.exceptions import * #可以查看requests.exceptions获取异常类型try: r=requests.get('http://www.baidu.com',timeout=0.00001)except ReadTimeout: print('===:')# except ConnectionError: #网络不通# print('-----')# except Timeout:# print('aaaaa')except RequestException: print('Error')
6、使用requests模块上传文件
import requestsfiles={'file':open('a.jpg','rb')}respone=requests.post('http://httpbin.org/post',files=files)print(respone.status_code)
每次写爬虫都要在响应头中获取cokies信息,然后在把获取的cokies信息加在请求头,太繁琐了;
如果有了 requests.session()对象,就可以自动处理cokies问题了;
session= requests.session() #相当于设置了 一个会话相关的容器,把所有会话相关的cookie都存放起来(自动保存cookie问题)r1=session.get('https://github.com/login/', headers={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'}, )authenticity_token=re.findall(r'name="authenticity_token".*?value="(.*?)"',r1.text,re.S)[0]
瞎驴博客:
http://www.cnblogs.com/linhaifeng/articles/7785043.html
由于requests模块是一个不完全模拟浏览器行为的模块,只能爬取到网页的HTML文档信息,无法解析和执行CSS、JavaScript代码,因此需要我们做人为判断;
selenium模块本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器;由于selenium解析执行了CSS、JavaScript所以相对requests它的性能是低下的;
1.安装selenium
pip install selenium
2.下载chromdriver.exe放置python安装路径/scripts目录下
下载chromdriver.exe放到python安装路径的scripts目录中即可,注意最新版本是2.29,并非2.9国内镜像网站地址:http://npm.taobao.org/mirrors/chromedriver/2.29/最新的版本去官网找:https://sites.google.com/a/chromium.org/chromedriver/downloads
3.测试是否安装成功
from selenium import webdriverbrowser=webdriver.Chrome() #实例化1个谷歌浏览器对象browser.get('https://www.baidu.com/') #开始
import timefrom selenium import webdriver#驱动浏览器from selenium.webdriver import ActionChains #滑动from selenium.webdriver.common.by import By #选择器from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTORfrom selenium.webdriver.common.keys import Keys #键盘按键操作from selenium.webdriver.support import expected_conditions as EC #等待所有标签加载完毕from selenium.webdriver.support.wait import WebDriverWait #等待页面加载完毕 寻找某些元素browser=webdriver.Chrome() #调用Chrome 驱动,生成浏览器对象wait=WebDriverWait(browser,10) #设置selenium等待浏览器加载完毕的最大等待时间try: browser.get('https://www.baidu.com/') baidu_input_tag=browser.find_element_by_id("kw") #寻找到百度页面的id='kw'的标签 key=baidu_input_tag.send_keys('张根') #在标签中输入'张根' baidu_button_tag=browser.find_element_by_id('su') #寻找到百度页面id='su'的标签 baidu_button_tag.click() #点击 wait.until(EC.presence_of_element_located((By.ID,'4'))) #等待百度页面 ID='4'的标签完毕,最大等待10秒 ''' 请求相关: browser.get('url')
响应相关: print(browser.page_source) #显示网页源码 print(browser.current_url) #获取当前url print(browser.get_cookies()) #获取当前网页cokies '''finally: time.sleep(5) browser.close() #关闭浏览器
模拟浏览器无非请求---->显示页面----->寻找标签 ------>点击标签的事件,所以selenium的关键是怎么找到页面中的标签,进而触发标签事件;
1.通过标签id属性进行定位
browser.find_element(By.ID,'kw').send_keys("美女")browser.find_element_by_id('kw').send_keys('性感')
2.通过标签name属性进行定位
browser.find_element_by_name("wd").send_keys("Linux")browser.find_element(By.NAME,'wd').send_keys("美女")
3.通过标签名进行定位
browser.find_element_by_tag_name("input").send_keys("selenium") browser.find_element(By.TAG_NAME, 'input').send_keys('aaa')
4.通过CSS查找方式进行定位
browser.find_element(By.CSS_SELECTOR, '#kw').send_keys('python django')browser.find_element(By.CSS_SELECTOR, '#kw').send_keys('python 之美')
5.通过xphan方式定位
browser.find_element_by_xpath('//*[@id="kw"]').send_keys("python Tornado")browser.find_element(By.XPATH, '//*[@id="kw"]').send_keys('吐槽大会')
6.通过搜索 页面中 链接进行定位
有时候不是一个输入框也不是一个按钮,而是一个文字链接,我们可以通过link
browser.find_element_by_link_text("新闻").click()
7.通过搜索 页面中 链接进行定位 ,可以支持模糊匹配
browser.find_element_by_partial_link_text("新").click()browser.find_element_by_partial_link_text("闻").click()
8.小结
上述均可以改写成find_element(By.ID,‘kw’)的形式
find_elements_by_xxx的形式是查找到多个元素,结果为列表
import timefrom selenium import webdriver#驱动浏览器from selenium.webdriver import ActionChains #滑动from selenium.webdriver.common.by import By #选择器from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTORfrom selenium.webdriver.common.keys import Keys #键盘按键操作from selenium.webdriver.support import expected_conditions as EC #等待所有标签加载完毕from selenium.webdriver.support.wait import WebDriverWait #等待页面加载完毕 寻找某些元素browser=webdriver.Chrome() #调用Chrome 驱动,生成浏览器对象wait=WebDriverWait(browser,10) #设置selenium等待浏览器加载完毕的最大等待时间try: browser.get('https://www.baidu.com/') #通过标签id属性进行定位 # browser.find_element(By.ID,'kw').send_keys("美女") # browser.find_element_by_id('kw').send_keys('性感') #通过标签name属性进行定位 # browser.find_element_by_name("wd").send_keys("Linux") # browser.find_element(By.NAME,'wd').send_keys("美女") #通过标签名称进行定位 # browser.find_element_by_tag_name("input").send_keys("selenium") # browser.find_element(By.TAG_NAME, 'input').send_keys('aaa') # 通过CSS查找方式进行定位 # browser.find_element(By.CSS_SELECTOR, '#kw').send_keys('python django') # browser.find_element(By.CSS_SELECTOR, '#kw').send_keys('泰山') # 通过xphan方式定位 # browser.find_element_by_xpath('//*[@id="kw"]').send_keys("python Tornado") # browser.find_element(By.XPATH, '//*[@id="kw"]').send_keys('吐槽大会') # 通过搜索 页面中 链接进行定位 # browser.find_element_by_link_text("新闻").click() #通过搜索 页面中 链接进行定位 ,可以支持模糊匹配 # browser.find_element_by_partial_link_text("新").click() # browser.find_element_by_partial_link_text("闻").click()finally: browser.find_element_by_id("su").click() time.time(3) browser.close() # 关闭浏览器browser.quit()
#1、selenium只是模拟浏览器的行为,而浏览器解析页面是需要时间的(执行css,js),一些元素可能需要过一段时间才能加载出来,为了保证能查找到元素,必须等待#2、等待的方式分两种:
wait=WebDriverWait(browser,10) #显式等待wait1=browser.implicitly_wait(10) #隐式等待wait.until(EC.presence_of_element_located((By.CLASS_NAME,'tH0')))'''显式等待:指定等待某个标签加载完毕隐式等待:等待所有标签加载完毕'''
0.ActionChains(动作链)
用selenium做自动化,有时候会遇到需要模拟鼠标操作才能进行的情况(如:iframe标签),比如单击、双击、点击鼠标右键、拖拽(滑动验证)等等。而selenium给我们提供了一个类来处理这类事件——ActionChains;
#iframe标签切换# 如果网页页面嵌套frame标签,子页面访问不到父页面的内容,父页面也访问不到子页面的内容所以需要切换;from selenium import webdriverfrom selenium.webdriver import ActionChains #鼠标键盘动作链from selenium.webdriver.common.by import By # 按照什么方式查找,By.ID,By.CSS_SELECTORfrom selenium.webdriver.common.keys import Keys # 键盘按键操作from selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWait # 等待页面加载某些元素import timebrowser=webdriver.Chrome()wait1=WebDriverWait(browser,10)browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')browser.switch_to.frame('iframeResult')#切换到id='iframewrapper'的iframe子标签action_chains=ActionChains(browser) #实例化1个动作链队列source=browser.find_element_by_id('draggable') #找到拖拽的源标签target=browser.find_element_by_id('droppable')#找到拖拽的目标标签action_chains.drag_and_drop(source,target).perform()#把动作放到动作链中,perform()准备串行执行;'''click(on_element=None) ——单击鼠标左键click_and_hold(on_element=None) ——点击鼠标左键,不松开context_click(on_element=None) ——点击鼠标右键double_click(on_element=None) ——双击鼠标左键drag_and_drop(source, target) ——拖拽到某个元素然后松开drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开key_down(value, element=None) ——按下某个键盘上的键key_up(value, element=None) ——松开某个键move_by_offset(xoffset, yoffset) ——鼠标从当前位置移动到某个坐标move_to_element(to_element) ——鼠标移动到某个元素move_to_element_with_offset(to_element, xoffset, yoffset) ——移动到距某个元素(左上角坐标)多少距离的位置perform() ——执行链中的所有动作release(on_element=None) ——在某个元素位置松开鼠标左键send_keys(*keys_to_send) ——发送某个键到当前焦点的元素send_keys_to_element(element, *keys_to_send) ——发送某个键到指定元素'''time.sleep(5)browser.quit()
1.点击,清空
import timefrom selenium import webdriver#驱动浏览器from selenium.webdriver import ActionChains #滑动from selenium.webdriver.common.by import By #选择器from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTORfrom selenium.webdriver.common.keys import Keys #键盘按键操作from selenium.webdriver.support import expected_conditions as EC #等待所有标签加载完毕from selenium.webdriver.support.wait import WebDriverWait #等待页面加载完毕 寻找某些元素browser=webdriver.Chrome() #调用Chrome 驱动,生成浏览器对象wait=WebDriverWait(browser,10) #设置selenium等待浏览器加载完毕的最大等待时间browser.get('https://www.baidu.com/')browser.find_element(By.ID,'kw').send_keys("美女")browser.find_element_by_id("su").click()#点击按钮time.sleep(4)browser.find_element(By.ID,'kw').clear()#清空input标签中的内容,让重新输入browser.find_element_by_id('kw').send_keys('性感')browser.find_element_by_id("su").click() #点击按钮
2.前进和后退
[图片上传失败…(image-487271-1640685150034)]
import timefrom selenium import webdriver#驱动浏览器from selenium.webdriver import ActionChains #滑动from selenium.webdriver.common.by import By #选择器from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTORfrom selenium.webdriver.common.keys import Keys #键盘按键操作from selenium.webdriver.support import expected_conditions as EC #等待所有标签加载完毕from selenium.webdriver.support.wait import WebDriverWait #等待页面加载完毕 寻找某些元素import timefrom selenium import webdriverbrowser=webdriver.Chrome()browser.get('http://www.cnblogs.com/sss4/')browser.find_element_by_partial_link_text( 'PythonWEB框架之Flask').click()time.sleep(3)browser.back() #后退time.sleep(3)browser.forward() #前进time.sleep(5)browser.close()
3.cokies相关
import timefrom selenium import webdriver#驱动浏览器from selenium.webdriver import ActionChains #滑动from selenium.webdriver.common.by import By #选择器from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTORfrom selenium.webdriver.common.keys import Keys #键盘按键操作from selenium.webdriver.support import expected_conditions as EC #等待所有标签加载完毕from selenium.webdriver.support.wait import WebDriverWait #等待页面加载完毕 寻找某些元素from selenium import webdriverbrowser=webdriver.Chrome()browser.get('https://www.zhihu.com/explore')print(browser.get_cookies()) #获取cokies信息browser.add_cookie({'k1':'xxx','k2':'yyy'}) #添加cokies信息print(browser.get_cookies())browser.delete_all_cookies() #删除cokies信息,慎用
4.选项卡管理
[图片上传失败…(image-b4308c-1640685150004)]
from selenium import webdriverbrowser=webdriver.Chrome()browser.execute_script('window.open()') #打开选项卡browser.execute_script('window.open()')print(browser.window_handles) #获取所有的选项卡browser.switch_to_window(browser.window_handles[0]) #切换至选项卡0browser.get('https://www.taobao.com')browser.switch_to_window(browser.window_handles[1]) #切换至选项卡1browser.get('https://www.baidu.com')browser.switch_to_window(browser.window_handles[2]) #切换至选项卡2browser.get('https://v.qq.com/')
5.selenium异常处理
from selenium import webdriverfrom selenium.common.exceptions import TimeoutException,NoSuchElementException,NoSuchFrameExceptiontry: browser=webdriver.Chrome() browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') browser.switch_to.frame('iframssseResult')except TimeoutException as e: print(e)except NoSuchFrameException as e: print(e)finally: browser.close()
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/MC_XY/article/details/122199178
内容来源于网络,如有侵权,请联系作者删除!