req、js

requests库中,可不写为headers内各键值对的部分参数:
 
s=requests.Session()  #或.session()
s.allow_redirects=False #禁止重定向
s.proxies={'http':'http://3.7.2.1:80'}    #代理
s.timeout=5 #服务器5秒内无应答就引发个except
s.adapters.DEFAULT_RETRIES=3  #请求失败最多重试3次
s.cookies={'k1':'v1','k2':'v2',}  #或headers内str型的'Cookie':'k1=v1;k2=v2…'

r=
requests.get(*),r.text有乱码:请求之下加句r.encoding=r.apparent_encoding。
requests.get(url).text,看text的源码,本是方法.text(),但是其上却加了个@property,就摇身一变成了属性。而.json()上面并没加@property。
 
有些url的请求头中的Accept-Encoding是"gzip,deflate,br",而requests的压缩编解码方式不支持br,若添加此头则删掉",br"即可。
 
蜘网、旅游网等,在首页点翻页只能看前几页,而按后代类目如省市县的县来检索翻页,却能看全:并非防爬,而是缺钱或技术不强的公司,在SQL检索排序全量数据的压力超负荷时,未免挂掉就会截断;而县级搜某个县的总数据量少得多,SQL能轻松检索排序到末页。
*********************分割线*********************
实现随机User-Agent的俩库:
 
from fake_useragent import UserAgent as ua
headers={'User-Agent':ua().random}
 
from requests_toolbelt import user_agent as ua
browser={'Chrome','Firefox'}
ua1=ua(browser.pop(),f'{random.uniform(56,64):0.1f}') .split(' ')[0]
ua2=ua(browser.pop(),f'{random.uniform(56,64):0.1f}') .split(' ')[0]
headers={'User-Agent':ua1+' '+ua2}
*********************分割线*********************
转化:url或postData的参数,html的英文标点:
 
import html
html中的and若干字母分号转为正常标点=html.unescape('x < "abc"')
 
from urllib.parse import quote,parse_qs,urlencode
url或postData的1个中文转为3组百分号俩字母=quote('世界')
'世界'.encode('gbk'),1个中文是2组x..,而utf8是3组x..。故txt小说若无特殊字符,存为gbk≈存为utf8的大小的2/3。
 
url或postData的str型参数→字典{'k1':'v1','k2':'v2',}:parse_qsl可转为列表即[(k1,v1),(k2,v2),]
postData='q=1&w=2&e=3&r=4&t=5'
字串转字典={k:v[0] for k,v in parse_qs(postData).items()}
字典转字串=urlencode(字串转字典)
print(字串转字典,字典转字串,sep=' ')
*********************分割线*********************
requests模块报错无属性get,PyCharm无自动弹出等怪事:当前的脚本名与某个Python库重名了,如requests.py、flask.py,或用了test等莫名意义的词。
 
控制台装了个库,提示success,但PyCharm的Project Interpreter中却没它:装了anaconda,新库装到它集成的Python里了。接收新库的优先级,anaconda或高于PyCharm。
 
F12:①抓包时勾上Preserve log和Disable cache,Network下的标题栏点击Size以降序,目标数据若抓到,往往在大号size的那几个网页。②chrome下的F12的内容乱码,或selenium在上了软件锁的站点被阻:换几个浏览器试试,或在免安装版浏览器加载别人已造好的含自定义设置的插件、油堠脚本。
*********************分割线*********************
磁力链接:
import os   #http://bt2.bt87.cc;pip install torrent-cli
#-p:单行显示;-s:降序,0为按时间,1为按大小;-n:数量;-o:输出到csv或json
os.system('torrent-cli -k "柯南 纯黑的噩梦" -p -s 1 -n 19 -o movie.csv')
 
分割为多个.ts视频片段(苹果开发的,含1个M3U(8)目录url、1个key加密串url、若干F12或没再去捕获的TS媒体分片):F12点击手机图标→F5后在Network找出url中?前是.m3u8的目录文件→3种方法①用M3U8 Downloader软件;②命令行ffmpeg -i '此url';③PotPlayer打开此链接→视频录制。
 
you-get下载或播放多媒体:
切到视频保存目录,先查看资源:you-get -i http://v.yku.com/v_show/id_*.html
按提示选择下哪个:you-get --format=flvhd http://v.yku.com/v_show/id_*.html
url不要套引号,否则就变为自动古鸽搜索并匹配最佳结果来下载了;-o '路径' -O 文件名,可以套引号。
用本地播放器在线观看无广告的视频:切到PotPlayer的安装目录→you-get -p PotPlayerMini64.exe --format=flvhd 该视频的url。但许多时间长高清晰的视频是分段呈送的,PotPlayer只接收第1个片段,而VLC、MPlayer却能载入you-get解析出的所有片段并依次播放。
*********************分割线*********************
curl:
解压curl-7.57.0.cab,把【…curl-7.57.0I386】加入系统path,控制台执行各种命令:curl …
 
-o输出网页内容:curl -o "F:/导出.html" "http://www.example.com"
#-O下载可不给文件名,用URL里的,如批下载7张图:curl -O "http://example.com/test[1-7].jpg"
 
-A是UA,-e是referer,-H自定义头,-b是Cookie,-d是postData(中文用--data-urlencode),-x代理,-L自动重定向,-F上传文件:
curl -A "Chrome/61" -e "http://www.example.com" -H "Content-Type:text/html" -b "csrf=xx" -d "d1=x&d2=y" --data-urlencode "name=张 李" -x "http://12.3.456.7:88" -L -F uploadfile=@test.txt -F title=上传 "http://example.com/upload"
****************************************分割线****************************************
Egの拉钩网深圳的Python工作:
 
import requests,time,random
from openpyxl import Workbook
from fake_useragent import UserAgent
 
info=[]
s=requests.session()
s.get('https://www.lagou.com/jobs/list_Python?fromSearch=true')
s.cookies['LGUID']=s.cookies['user_trace_token']    #反爬の行为分析1:访问4页之后添加个LGUID
 
h={'User-Agent':UserAgent().random,    #随机取一个UA的类实例
         'Referer':'https://www.lagou.com/jobs/list_Python?fromSearch=true'}
url='https://www.lagou.com/jobs/positionAjax.json'
for page in range(1,10):
    print(f'begin to handle page of {page}')
    data=dict(city='深圳',kd='Python',pn=f'{page}')
    time.sleep(random.random())    #反爬の行为分析2:无间断爬取,会被识别为在用程序访问
    response=s.post(url,data=data,headers=h).json()
    jobs=response['content']['positionResult']['result']
    for job in jobs:
        workplace=job['city']
        salary=job['salary']
        positionName=job['positionName']
        industryField=job['industryField']
        companySize = job['companySize']
        shortName=job['companyShortName']
        fullName=job['companyFullName']
        benefits=','.join(job['companyLabelList'])    #或json.dumps(job['…List'],ensure_ascii=False)
        info.append([workplace,salary,positionName,industryField,
                     companySize,shortName,fullName,benefits])
 
wb = Workbook()
ws = wb.active
ws.append(['城市','薪资','职位','领域','规模','简称','全称','福利'])
for x in info:
    ws.append(x)
wb.save('E:拉钩网.xlsx')
****************************************分割线****************************************
Egの百度下拉框:
 
import requests, json, string, pandas, time, random
from fake_useragent import UserAgent
 
t = set()
letter = string.ascii_lowercase
 
def suggestion(searchWord):
    url = f'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?
    sugmode=2&json=1&wd={searchWord}'
    h = {'User-Agent': UserAgent().random}
    #有的词如上海bd,所返建议有'等标点,百度源码在其前加了个转义符,json库解析不了
    response = requests.get(url, headers=h).text[17:-2].replace("\", '')
    return json.loads(response)['s']
 
def moreSuggestion(searchWord):
    for x in letter:
        for y in letter:
            print(x + ':' + y)
            time.sleep(random.random()/5)
            t.update(suggestion(searchWord + x + y))
    pandas.DataFrame(list(t)).to_csv('百度下拉框.csv',index=False,mode='a+',encoding='gbk')
 
moreSuggestion('上海')
****************************************分割线****************************************
jsの把阳灮电影的ftp网址转为某雷格式:
ThunderEncode(形参)调用了俩函数,并且这俩也调用了新函数。则把各函数块(恰好都在1个js文件内)打包写入1个立即函数,并在其尾加一句;return ThunderEncode('当前ftp')。
 
import requests,re,execjs
from fake_useragent import UserAgent
 
def spider(url):
    headers={'User-Agent':UserAgent().random}
    response=requests.get(url,headers=headers)
    response.encoding=response.apparent_encoding
    return response.text
 
urlJS='http://www.dyt删t8.net/js/base64.js'
urlMovie='http://www.ygd删y8.com/html/gndy/dyzz/20171201/55673.html'
 
fileJS=spider(urlJS)
urlFtp=re.findall('(ftp://.+?)"',spider(urlMovie))[0]
funcJS='function(){'+fileJS+';return ThunderEncode("'+urlFtp+'")}()'
thunderUrl=execjs.eval(funcJS)
print(thunderUrl)
*********************分割线*********************
jsの囿道番译:
 
反复请求目标url,发现本例除了请求头偏多外,另获知PostData的几个不可缺参数中salt和sign并不固定。在Sessions包搜sign,目标js文件内发现了3处sign,美化下js代码以便观察,选和PD内容最相似的那处为目标区。对比PD的各键值对,猜sign所在{}中,核心参数的没给出明文的值(如本例的i:e.i在PD中对应i:单词,从而得出e.i为所查单词,然后依js原文赋值给var t,最后让t去参与sign的计算)。参数salt和sign按周围的相关js算式解出。
 
import time,random,hashlib,requests
from fake_useragent import UserAgent
 
def fanyi(word):
    salt=str(int(time.time()*1000)+int(random.random()*10))
    h={'User-Agent':UserAgent().random,'Referer':url.rpartition('/')[0]}
    #sign算式的前3段近来没变,仅第4段几天一改,若短期内高频使用则手动给出s4
    #s4=requests.get(jsUrl,headers=h).text.split('t+i+"')[1].split('"')[0]
    sign='fanyideskweb'+word+salt+'aNPG!!u6sesA>hBAW1@(-'
    sign=hashlib.md5(sign.encode()).hexdigest()
    data=dict(client='fanyideskweb',version='2.1',keyfrom='fanyi.web',
        i=word,salt=salt,sign=sign)
    h.update({'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
        'Content-Length':'127','Cookie':'OUTFOX_SEARCH_USER_ID=-@127.0.0.1'})
    response=requests.post(url,data,headers=h).json()['translateResult'][0][0]
    print(response['src']+':'+response['tgt'])
 
#jsUrl='http://shared.ydstatic.com/fanyi/newweb/v1.0.6/scripts/newweb/fanyi.min.js'
url='http://fanyi.yo删除udao.com/translate_o'
fanyi('世界你好')
fanyi('beautiful soup')
*********************分割线*********************
jsの瓜子二手车:
eval(function(形参){改装形参}(包含anti等大堆function的各实参))过后的function anti,才是最后去计算value的anti。而初现于实参的function anti尚未改装,拿它去计算的value是假的。
 
除了给Cookie设置antipas的203起始url,ua可随意,访问其他url时ua都要一字不差。
 
import os,re,execjs,requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
 
s=requests.Session()
s.verify=False
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36'}
indexUrl='https://www.guazi.com/'
 
def addAntipasToCookie():
    jsFile='D:瓜子.js'   #请求1次存下js,不必费流量次次请求,本地js过期后删掉再换个
    if not os.path.isfile(jsFile):
        response=s.get(indexUrl,headers={'User-Agent':'Mozilla/5.0 Chrome/64'})
        response.encoding=response.apparent_encoding
        js=re.findall('eval.+?value=anti.+?;',response.text,re.S)[0]
        with open(jsFile,'w',encoding='utf8') as f:
            f.write(js)
    with open(jsFile,encoding='utf8') as f:
        js=f.read()
    s.cookies['antipas']=execjs.compile(js).eval('value')
 
def main():
    response=s.get(indexUrl+'sz/',headers=headers)
    response.encoding=response.apparent_encoding
    print(response.text)
 
if __name__ == '__main__':
    addAntipasToCookie()
    main()
*********************分割线*********************
jsの央妈的门规:
美化js后观察到有函数块及代码块含window.、document.等dom对象,而execjs、pyv8等只是纯粹的js运行环境,并无dom结构,碰到dom对象会报错ReferenceError:xxx is not defined。幸好本例的各处dom对象都非必需,只提取其余代码块也能运算出cookies。
若碰到dom对象location且不可或缺,可声明为location='当前url'?
 
import requests,re,jsbeautifier,execjs
from fake_useragent import UserAgent as ua
 
def spider(url):
    s.headers['User-Agent']=ua().random
    response=s.get(url)
    response.encoding=response.apparent_encoding
    return response.text
 
def parseJS(js):
    js=jsbeautifier.beautify(js)
    secondUrl=host+re.findall('/.+?==',js)[0]
    js=re.findall('(.+?^}).+?(function QWE.+?^}).+?(KTK.+? + ";).+?(KTK.+?) ',js,re.S+re.M)[0]
    js=f'{js[0]} {js[1]} return {js[2]}"+'+js[3].replace('confirm','QWERTASDFGXYSF()')
    js='(function(){'+js+'})()'
    cookies=execjs.eval(js)
    return secondUrl,cookies
 
def secondRequest(secondUrl,cookies):
    s.cookies['wzwstemplate'],s.cookies['wzwschallenge']=cookies.split(';')
    response=spider(secondUrl)
    if '设为首页' in response:
        print('成功突破')
 
if __name__ == '__main__':
    s=requests.Session()
    host='http://www.pb删c.go除v.cn'
    response=spider(host+'/tiaofasi/144941/144957/index.html')
    js=re.findall('javascript">s*(.*?)s*</script>',response,re.S)[0]
    secondUrl,cookies=parseJS(js)
    secondRequest(secondUrl,cookies)
*********************分割线*********************
jsの企鹅好友:
空间的权限设置改为企鹅好友能看我的空间,点击上方的链接…n个企鹅好友…
 
js算式,Cookie中的skey比url中的g_tk复杂得多,前者靠浏览器取,后者计算js。
 
url中的g_tk:抓包Sessions下搜g_tk,找到其来源——*/qzfl_v8_2.1.*.js。在此js文件内找到7处g_tk,发现其值都=QZFL.pluginsDefine.getACSRFToken(实参),于是搜此function块:其自身的形参基本没参与,而是return arguments.callee._DJB(skey)。即本匿函数名._DJB(skey),实参skey则是来自cookie中的某键:若域名含qzone则为值随意的p_skey否则为2h有效期的skey,键为空时的采用顺序p_skey>skey>rv2>''。再看QZFL.pluginsDefine.getACSRFToken._DJB(形参),这回没再调用新函数,可用js库运算其'''function块'''了。
 
from requestium import Session,Keys
options={'arguments':['disable-infobars',],
    'experimental_options':{'prefs':{'profile.managed_default_content_settings.images':2}},
    'binary_location': 'D:/Program Files/Browser/CentBrowser/Application/chrome.exe'}
 
def get_skey():
    driver=Session('C:/Program Files/Python36/chromedriver','chrome',7,options).driver
    driver.get(f'https://user.qz删one.q除q.com/{myQQ}/main')  #主页登入会重定向到game页
    css=driver.ensure_element_by_css_selector
    frame=css('#login_frame')   #javascript:void(0);,模拟不了按钮等,故frame内来回按Tab
    frame.send_keys(Keys.TAB,Keys.ENTER,myQQ,Keys.TAB,pwd,Keys.ENTER)
    try:css('.icon-logout')
    except:input('浏览器端成功处理验证码后,在本句句尾任敲一字母:')
    skey=driver.get_cookie('skey')['value']
    driver.quit()
    return skey
 
from fake_useragent import UserAgent
import requests,json,execjs
 
def get_g_tk():    #两种js算法:execjs直接处理js算式;把js算式改装为Python语句
    js='''DJB=function(e){var t=5381;for(var n=0,r=e.length;n<r;++n){t+=
    (t<<5)+e.charCodeAt(n)};return t&2147483647}'''
    return execjs.compile(js).call('DJB',myQQ)  #Cookie的p_skey(实参的出处)选用的QQ
    # t=5381    #算法②
    # for s in myQQ:
    #     t+=(t<<5)+ord(s)  #ord(s)对应js的e.charCodeAt(n),字符的10进制Unicode
    # return t&2147483647
 
def qqFriends():
    global skey    #skey每过2h左右的有效期,就需要全局更新1次
    url=f'https://h5.qz删one.q除q.com/proxy/domain/base.qzone.qq.com/
cgi-bin/right/get_entryright.cgi?ver=1&uin={myQQ}&g_tk={get_g_tk()}'
    h={'User-Agent':UserAgent().random,'Cookie':f'uin=o{myQQ};p_skey={myQQ};skey={skey}'}
    response=requests.get(url,headers=h).text
    if '请先登录' in response:
        skey=get_skey()
        h['Cookie']=f'uin=o{myQQ};p_skey={myQQ};skey={skey}'
        response=requests.get(url,headers=h).text
    d = {}
    if 'friendlist_end":1' in response:
        friends=json.loads(response.split('friendlist":')[1].split('],')[0]+']')
        for friend in friends:
            d.update({friend['data']:friend['label']})
        print(d)
 
if __name__ == '__main__':
    myQQ='904477955'
    pwd='密码'
    skey=get_skey() #skey也可取自登录后的Cookie(值如@ZN33nvgYZ)手敲在此,期效2h
    qqFriends()
*********************分割线*********************
jsの网意云愔乐的评论:
 
1、未加密可直接get的api(知呼肖飞的发现):
https://music.163.com/api/v1/resource/comments/R_SO_4_818820?limit=20&offset=60
 
2、分析post的俩参被加了密的weapi:
打开Dev刷新网页,Network下全局任搜1条歌评,至/weapi/v1/resource/comments/R_SO_4_*。点Headers请求头左侧的x,看到postdata的加密参数Initiator自core.js。转至core.js并右键它点Open in Sources Panel,点{}将其格式化:
 
格式化的js页内搜params和encSecKey,为函数window.asrsea(参1,参2,参3,参4)所返回的{},而window.asrsea又来自前文的函数d。在d尾的}处打个断点,拉动DEV界面调整Scope窗口至方便观察d函数各实参的右下部:
 
更换几首歌曲id观察各自评论的第1页,按F8会多次到访断点,期间d的首参经历了4次改变,第2变才是window.asrsea带上4个实参调回头对d的调用,因为只有它含歌曲id和似乎代表翻页的offset参数;另外同首歌后面各页的评论,d的首参降至2种变化了,第1变和首页评论的第2变样式相同。
d的后仨实参始终固定,仅首参json中的前仨键值在随歌曲id或评论页而变:"{"rid":"R_SO_4_818820","offset":"60","total":"false","limit":"20","csrf_token":""}"。
 
函数d用到了它所有的兄长函数a、b、c,而b和c又用到了前文的一大堆依赖函数。多番尝试发现,以b的依赖函数CryptoJS起始,至abcd的母函数的前方,均为必需,此为要存至本地的新js的第1部分。第2部分自定义:function result(a1,a2,a3,a4){abcd四个函数块;return d(a1,a2,a3,a4)}。
 
import requests,re,os,json,time,execjs
from fake_useragent import UserAgent as ua
from pandas import DataFrame as df
from threading import Thread
 
def getJS():
    jsUrl='https://s3.music.126.net/web/s/core.js?d12301143ef572fae5078cd4a5f62862'
    js=requests.get(jsUrl,headers={'User-Agent':'Mozilla/5 Chrome/64'}).text.split(' ')[85:88]
    js[-1]='function result(a1,a2,a3,a4)'+re.findall('{f.+h}',js[-1])[0]+'return d(a1,a2,a3,a4)}'
    js=' '.join(js)
    with open('D:/网易云歌评论之core.txt','w',encoding='utf8') as f:
        f.write(js)
 
def getPostData(musicID,page):
    total='false' if page>1 else 'true'
    a1={'rid':f'R_SO_4_{musicID}','offset':f'{(page-1)*20}','total':total,'limit':'20','csrf_token':''} 
    a2='010001'
    a3='00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
    a4='0CoJUm6Qyw8W8jud'
    postdata=execjs.compile(js).call('result',json.dumps(a1),a2,a3,a4)
    postdata['params']=postdata.pop('encText')  #出自core.js的window.asrsea(各实参)后
    return postdata
 
def getComments(musicID,data):
    info=[]
    url=f'https://music.163.com/weapi/v1/resource/comments/R_SO_4_{musicID}'
    h={'User-Agent':ua().random,'Content-Type':'application/x-www-form-urlencoded'}
    comments=requests.post(url,data,headers=h).json()['comments']
    for comment in comments:
        t=time.strftime('%Y-%m-%d',time.localtime(comment['time']/1000))
        info.append((musicID,t,comment['likedCount'],comment['content'],
            comment['user']['userId'],comment['user']['nickname']))
    df(info).to_csv('D:/歌评.csv',header=False,index=False,mode='a+',encoding='utf-8-sig')
 
def main(musicID,page):
    global js
    if not os.path.isfile('D:/网易云歌评论之core.txt'):
        getJS()
    with open('D:/网易云歌评论之core.txt',encoding='utf8') as f:
        js=f.read()
    data=getPostData(musicID,page)
    getComments(musicID,data)
 
if __name__ == '__main__':
    musicID='818820'
    pages=9
    [Thread(target=main,args=(musicID,page)).start() for page in range(1,pages+1)]
*********************分割线*********************
jsの判文网:
对比历次抓包的目标Session,Cookie的j_token皆异,另外js等其他文件也无此值,想必玄机在自家。原样模拟目标Session虽返回200却非目标html,逐块剖析其script标签的js后明了:+function (){document.cookie = "j" + "_" + "token=" + p(window.v);location.reload();}():
即目标Session得请求2次,先取首次response的window.v,经p函数运算后得j_token,把它写入Cookie并再次请求,才能返回真实信息。
 
用Chrome的Console逐块剖析js:发现核心vars和functions都在$那几行密文,而前面的window.n="j_token"等明文都是混淆视听的烟幕弹。$.$是Function,故从末行的两层嵌套立即函数$.$($.$(*)())();提取出函数正文*。又因*里有$.$$等多个var,故要把除了$.$构造函数语句之外的各行$所代表的vars,和*一起贴在Console下执行,最后从返回的"return"*""取出"*"再次贴在Console执行,终得与j_token相关的算式。
 
import requests,re,execjs
from fake_useragent import UserAgent
from lxml import html
from gevent import monkey;monkey.patch_all()
from gevent.pool import Pool
 
class Spider:
    def detail(self,page):
        url=f'http://openl删aw.cn/search/judgement/type?causeId=
69140e0574bd4476b3b36438044ed04d&page={page}'
        res1=requests.get(url)    #首次请求得js明文及密文,j_token的算式藏在$密文区
        cookies=requests.utils.dict_from_cookiejar(res1.cookies)
        s=re.findall('window.v="(.*)";',res1.text)[0]
#         js="function p(s){return s.substring(2,4).concat('n')
# .concat(s.substring(0,1)).concat('p').concat(s.substring(4,8)).concat('e')
# .concat(s.substring(1,2)).concat(s.substring(16)).concat(s.substring(8,16));}"
#         j_token=execjs.compile(js).call('p',s)
        j_token=s[2:4]+'n'+s[0:1]+'p'+s[4:8]+'e'+s[1:2]+s[16:]+s[8:16]
        cookies['j_token']=j_token  #再次请求同个url,要带上添加了j_token的cookies
        headers={'User-Agent':UserAgent().random}
        res2=requests.get(url,headers=headers,cookies=cookies)
        items=html.fromstring(res2.text).cssselect('.entry-title a')
        for item in items:
            print(item.text,'http://openl删aw.cn'+item.get('href'),sep=':')
 
def main():
    spider=Spider()
    p=Pool(size=4)
    [p.spawn(spider.detail,page) for page in range(1,9)]
    p.join()
 
if __name__ == '__main__':
    main()
原文地址:https://www.cnblogs.com/scrooge/p/7701078.html