如何科學(xué)的搶紅包:寫個程序搶紅包
背景大家都懂的,要過年了,正是紅包滿天飛的日子。正巧前兩天學(xué)會了Python,比較亢奮,就順便研究了研究微博紅包的爬取,為什么是微博紅包而不是支付寶紅包呢,因為我只懂Web,如果有精力的話之后可能也會研究研究打地鼠算法吧。
因為本人是初學(xué)Python,這個程序也是學(xué)了Python后寫的第三個程序,所以代碼中有啥坑爹的地方請不要當(dāng)面戳穿,重點是思路,嗯,如果思路中有啥坑爹的的地方也請不要當(dāng)面戳穿,你看IE都有臉設(shè)置自己為默認(rèn)瀏覽器,我寫篇渣文得瑟得瑟也是可以接受的對吧……
我用的是Python 2.7,據(jù)說Python 2和Python 3差別挺大的,比我還菜的小伙伴請注意。
0×01 思路整理
懶得文字?jǐn)⑹隽耍嬃藦埐輬D,大家應(yīng)該可以看懂。
首先老規(guī)矩,先引入一坨不知道有啥用但又不能沒有的庫:
- import re
- import urllib
- import urllib2
- import cookielib
- import base64
- import binascii
- import os
- import json
- import sys
- import cPickle as p
- import rsa
然后順便聲明一些其它變量,以后需要用到:
- reload(sys)
- sys.setdefaultencoding('utf-8&') #將字符編碼置為utf-8
- luckyList=[] #紅包列表
- lowest=10 #能忍受紅包領(lǐng)獎記錄最低為多少
這里用到了一個rsa庫,Python默認(rèn)是不自帶的,需要安裝一下:https://pypi.python.org/pypi/rsa/
下載下來后運(yùn)行setpy.py install安裝,然后就可以開始我們的開發(fā)步驟了。
0×02 微博登陸
搶紅包的動作一定要登陸后才可以進(jìn)行的,所以一定要有登錄的功能,登錄不是關(guān)鍵,關(guān)鍵是cookie的保存,這里需要cookielib的配合。
- cj = cookielib.CookieJar()
- opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
- urllib2.install_opener(opener)
這樣凡是使用opener進(jìn)行的網(wǎng)絡(luò)操作都會對處理cookie的狀態(tài),雖然我也不太懂但是感覺好神奇的樣子。
接下來需要封裝兩個模塊,一個是獲取數(shù)據(jù)模塊,用來單純地GET數(shù)據(jù),另一個用來POST數(shù)據(jù),其實只是多了幾個參數(shù),完全可以合并成一個函數(shù),但是我又懶又笨,不想也不會改代碼。
- def getData(url) :
- try:
- req = urllib2.Request(url)
- result = opener.open(req)
- text = result.read()
- text=text.decode("utf-8").encode("gbk",'ignore')
- return text
- except Exception, e:
- print u'請求異常,url:'+url
- print e
- def postData(url,data,header) :
- try:
- data = urllib.urlencode(data)
- req = urllib2.Request(url,data,header)
- result = opener.open(req)
- text = result.read()
- return text
- except Exception, e:
- print u'請求異常,url:'+url
有了這兩個模塊我們就可以GET和POST數(shù)據(jù)了,其中g(shù)etData中之所以decode然后又encode啥啥的,是因為在Win7下我調(diào)試輸出的時候總亂碼,所以加了些編碼處理,這些都不是重點,下面的login函數(shù)才是微博登陸的核心。
- def login(nick , pwd) :
- print u"----------登錄中----------"
- print "----------......----------"
- prelogin_url = 'http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su=%s&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.15)&_=1400822309846' % nick
- preLogin = getData(prelogin_url)
- servertime = re.findall('"servertime":(.+?),' , preLogin)[0]
- pubkey = re.findall('"pubkey":"(.+?)",' , preLogin)[0]
- rsakv = re.findall('"rsakv":"(.+?)",' , preLogin)[0]
- nonce = re.findall('"nonce":"(.+?)",' , preLogin)[0]
- #print bytearray('xxxx','utf-8')
- su = base64.b64encode(urllib.quote(nick))
- rsaPublickey= int(pubkey,16)
- key = rsa.PublicKey(rsaPublickey,65537)
- message = str(servertime) +'\t' + str(nonce) + '\n' + str(pwd)
- sp = binascii.b2a_hex(rsa.encrypt(message,key))
- header = {'User-Agent' : 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'}
- param = {
- 'entry': 'weibo',
- 'gateway': '1',
- 'from': '',
- 'savestate': '7',
- 'userticket': '1',
- 'ssosimplelogin': '1',
- 'vsnf': '1',
- 'vsnval': '',
- 'su': su,
- 'service': 'miniblog',
- 'servertime': servertime,
- 'nonce': nonce,
- 'pwencode': 'rsa2',
- 'sp': sp,
- 'encoding': 'UTF-8',
- 'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
- 'returntype': 'META',
- 'rsakv' : rsakv,
- }
- s = postData('http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.15)',param,header)
- try:
- urll = re.findall("location.replace\(\'(.+?)\'\);" , s)[0]
- login=getData(urll)
- print u"---------登錄成功!-------"
- print "----------......----------"
- except Exception, e:
- print u"---------登錄失??!-------"
- print "----------......----------"
- exit(0)
這里面的參數(shù)啊加密算法啊都是從網(wǎng)上抄的,我也不是很懂,大概就是先請求個時間戳和公鑰再rsa加密一下最后處理處理提交到新浪登陸接口,從新浪登錄成功之后會返回一個微博的地址,需要請求一下,才能讓登錄狀態(tài)徹底生效,登錄成功后,后面的請求就會帶上當(dāng)前用戶的cookie。
成功登錄微博后,我已迫不及待地想找個紅包先試一下子,當(dāng)然首先是要在瀏覽器里試的。點啊點啊點啊點的,終于找到了一個帶搶紅包按鈕的頁面了,F(xiàn)12召喚出調(diào)試器,看看數(shù)據(jù)包是咋請求的。
可以看到請求的地址是http://huodong.weibo.com/aj_hongbao/getlucky,主要參數(shù)有兩個,一個是ouid,就是紅包id,在URL中可以看到,另一個share參數(shù)決定是否分享到微博,還有個_t不知道是干啥用的。
好,現(xiàn)在理論上向這個url提交者三個參數(shù),就可以完成一次紅包的抽取,但是,當(dāng)你真正提交參數(shù)的時候,就會發(fā)現(xiàn)服務(wù)器會很神奇地給你返回這么個串:
- 1
- {"code":303403,"msg":"抱歉,你沒有權(quán)限訪問此頁面","data":[]}
這個時候不要驚慌,根據(jù)我多年Web開發(fā)經(jīng)驗,對方的程序員應(yīng)該是判斷referer了,很簡單,把請求過去的header全給抄過去。
- def getLucky(id): #抽獎程序
- print u"---抽紅包中:"+str(id)+"---"
- print "----------......----------"
- if checkValue(id)==False: #不符合條件,這個是后面的函數(shù)
- return
- luckyUrl="http://huodong.weibo.com/aj_hongbao/getlucky"
- param={
- 'ouid':id,
- 'share':0,
- '_t':0
- }
- header= {
- 'Cache-Control':'no-cache',
- 'Content-Type':'application/x-www-form-urlencoded',
- 'Origin':'http://huodong.weibo.com',
- 'Pragma':'no-cache',
- 'Referer':'http://huodong.weibo.com/hongbao/'+str(id),
- 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 BIDUBrowser/6.x Safari/537.36',
- 'X-Requested-With':'XMLHttpRequest'
- }
- res = postData(luckyUrl,param,header)
這樣的話理論上就沒啥問題了,事實上其實也沒啥問題。抽獎動作完成后我們是需要判斷狀態(tài)的,返回的res是一個json串,其中code為100000時為成功,為90114時是今天抽獎達(dá)到上限,其他值同樣是失敗,所以:
- hbRes=json.loads(res)
- if hbRes["code"]=='901114': #今天紅包已經(jīng)搶完
- print u"---------已達(dá)上限---------"
- print "----------......----------"
- log('lucky',str(id)+'---'+str(hbRes["code"])+'---'+hbRes["data"]["title"])
- exit(0)
- elif hbRes["code"]=='100000':#成功
- print u"---------恭喜發(fā)財---------"
- print "----------......----------"
- log('success',str(id)+'---'+res)
- exit(0)
- if hbRes["data"] and hbRes["data"]["title"]:
- print hbRes["data"]["title"]
- print "----------......----------"
- log('lucky',str(id)+'---'+str(hbRes["code"])+'---'+hbRes["data"]["title"])
- else:
- print u"---------請求錯誤---------"
- print "----------......----------"
- log('lucky',str(id)+'---'+res)
其中l(wèi)og也是我自定義的一個函數(shù),用來記錄日志用的:
- def log(type,text):
- fp = open(type+'.txt','a')
- fp.write(text)
- fp.write('\r\n')
- fp.close()
0×04 爬取紅包列表
單個紅包領(lǐng)取動作測試成功后,就是我們程序的核心大招模塊了——爬取紅包列表,爬取紅包列表的方法和入口應(yīng)該有不少,比如各種微博搜索關(guān)鍵字啥啥的,不過我這里用最簡單的方法:爬取紅包榜單。
在紅包活動的首頁(http://huodong.weibo.com/hongbao)通過各種點更多,全部可以觀察到,雖然列表連接很多,但可以歸納為兩類(最有錢紅包榜除外):主題和排行榜。
繼續(xù)召喚F12,分析這兩種頁面的格式,首先是主題形式的列表,比如:http://huodong.weibo.com/hongbao/special_quyu
可以看到紅包的信息都是在一個類名為info_wrap的div中,那么我們只要活動這個頁面的源碼,然后把infowrap全抓出來,再簡單處理下就可以得到這個頁面的紅包列表了,這里需要用到一些正則:
- def getThemeList(url,p):#主題紅包
- print u"---------第"+str(p)+"頁---------"
- print "----------......----------"
- html=getData(url+'?p='+str(p))
- pWrap=re.compile(r'(.+?)',re.DOTALL) #h獲取所有info_wrap的正則
- pInfo=re.compile(r'.+(.+).+(.+).+(.+).+href="(.+)" class="btn"',re.DOTALL) #獲取紅包信息
- List=pWrap.findall(html,re.DOTALL)
- n=len(List)
- if n==0:
- return
- for i in range(n): #遍歷所有info_wrap的div
- s=pInfo.match(List[i]) #取得紅包信息
- info=list(s.groups(0))
- info[0]=float(info[0].replace('\xcd\xf2','0000')) #現(xiàn)金,萬->0000
- try:
- info[1]=float(info[1].replace('\xcd\xf2','0000')) #禮品價值
- except Exception, e:
- info[1]=float(info[1].replace('\xd2\xda','00000000')) #禮品價值
- info[2]=float(info[2].replace('\xcd\xf2','0000')) #已發(fā)送
- if info[2]==0:
- info[2]=1 #防止除數(shù)為0
- if info[1]==0:
- info[1]=1 #防止除數(shù)為0
- info.append(info[0]/(info[2]+info[1])) #紅包價值,現(xiàn)金/(領(lǐng)取人數(shù)+獎品價值)
- # if info[0]/(info[2]+info[1])>100:
- # print url
- luckyList.append(info)
- if 'class="page"' in html:#存在下一頁
- p=p+1
- getThemeList(url,p) #遞歸調(diào)用自己爬取下一頁
話說正則好難,學(xué)了好久才寫出來這么兩句。還有這里的info中append進(jìn)去了一個info[4],是我想的一個大概判斷紅包價值的算法,為什么要這么做呢,因為紅包很多但是我們只能抽四次啊,在茫茫包海中,我們必須要找到最有價值的紅包然后抽丫的,這里有三個數(shù)據(jù)可供參考:現(xiàn)金價值、禮品價值和領(lǐng)取人數(shù),很顯然如果現(xiàn)金很少領(lǐng)取人數(shù)很多或者獎品價值超高(有的甚至喪心病狂以億為單位),那么就是不值得去搶的,所以我憋了半天終于憋出來一個衡量紅包權(quán)重的算法:紅包價值=現(xiàn)金/(領(lǐng)取人數(shù)+獎品價值)。
排行榜頁面原理一樣,找到關(guān)鍵的標(biāo)簽,正則匹配出來。
- def getTopList(url,daily,p):#排行榜紅包
- print u"---------第"+str(p)+"頁---------"
- print "----------......----------"
- html=getData(url+'?daily='+str(daily)+'&p='+str(p))
- pWrap=re.compile(r'(.+?)',re.DOTALL) #h獲取所有l(wèi)ist_info的正則
- pInfo=re.compile(r'.+(.+).+(.+).+(.+).+href="(.+)" class="btn rob_btn"',re.DOTALL) #獲取紅包信息
- List=pWrap.findall(html,re.DOTALL)
- n=len(List)
- if n==0:
- return
- for i in range(n): #遍歷所有info_wrap的div
- s=pInfo.match(List[i]) #取得紅包信息
- topinfo=list(s.groups(0))
- info=list(topinfo)
- info[0]=topinfo[1].replace('\xd4\xaa','') #元->''
- info[0]=float(info[0].replace('\xcd\xf2','0000')) #現(xiàn)金,萬->0000
- info[1]=topinfo[2].replace('\xd4\xaa','') #元->''
- try:
- info[1]=float(info[1].replace('\xcd\xf2','0000')) #禮品價值
- except Exception, e:
- info[1]=float(info[1].replace('\xd2\xda','00000000')) #禮品價值
- info[2]=topinfo[0].replace('\xb8\xf6','') #個->''
- info[2]=float(info[2].replace('\xcd\xf2','0000')) #已發(fā)送
- if info[2]==0:
- info[2]=1 #防止除數(shù)為0
- if info[1]==0:
- info[1]=1 #防止除數(shù)為0
- info.append(info[0]/(info[2]+info[1])) #紅包價值,現(xiàn)金/(領(lǐng)取人數(shù)+禮品價值)
- # if info[0]/(info[2]+info[1])>100:
- # print url
- luckyList.append(info)
- if 'class="page"' in html:#存在下一頁
- p=p+1
- getTopList(url,daily,p) #遞歸調(diào)用自己爬取下一頁
好,現(xiàn)在兩中專題頁的列表我們都可以順利爬取了,接下來就是要得到列表的列表,也就是所有這些列表地址的集合,然后挨個去抓:
- def getList():
- print u"---------查找目標(biāo)---------"
- print "----------......----------"
- themeUrl={ #主題列表
- 'theme':'http://huodong.weibo.com/hongbao/theme',
- 'pinpai':'http://huodong.weibo.com/hongbao/special_pinpai',
- 'daka':'http://huodong.weibo.com/hongbao/special_daka',
- 'youxuan':'http://huodong.weibo.com/hongbao/special_youxuan',
- 'qiye':'http://huodong.weibo.com/hongbao/special_qiye',
- 'quyu':'http://huodong.weibo.com/hongbao/special_quyu',
- 'meiti':'http://huodong.weibo.com/hongbao/special_meiti',
- 'hezuo':'http://huodong.weibo.com/hongbao/special_hezuo'
- }
- topUrl={ #排行榜列表
- 'mostmoney':'http://huodong.weibo.com/hongbao/top_mostmoney',
- 'mostsend':'http://huodong.weibo.com/hongbao/top_mostsend',
- 'mostsenddaka':'http://huodong.weibo.com/hongbao/top_mostsenddaka',
- 'mostsendpartner':'http://huodong.weibo.com/hongbao/top_mostsendpartner',
- 'cate':'http://huodong.weibo.com/hongbao/cate?type=',
- 'clothes':'http://huodong.weibo.com/hongbao/cate?type=clothes',
- 'beauty':'http://huodong.weibo.com/hongbao/cate?type=beauty',
- 'fast':'http://huodong.weibo.com/hongbao/cate?type=fast',
- 'life':'http://huodong.weibo.com/hongbao/cate?type=life',
- 'digital':'http://huodong.weibo.com/hongbao/cate?type=digital',
- 'other':'http://huodong.weibo.com/hongbao/cate?type=other'
- }
- for (theme,url) in themeUrl.items():
- print "----------"+theme+"----------"
- print url
- print "----------......----------"
- getThemeList(url,1)
- for (top,url) in topUrl.items():
- print "----------"+top+"----------"
- print url
- print "----------......----------"
- getTopList(url,0,1)
- getTopList(url,1,1)
0×05 判斷紅包可用性
這個是比較簡單的,首先在源碼里搜一下關(guān)鍵字看看有沒有搶紅包按鈕,然后再到領(lǐng)取排行里面看看最高紀(jì)錄是多少,要是最多的才領(lǐng)那么幾塊錢的話就再見吧……
其中查看領(lǐng)取記錄的地址為http://huodong.weibo.com/aj_hongbao/detailmore?page=1&type=2&_t=0&__rnd=1423744829265&uid=紅包id
- def checkValue(id):
- infoUrl='http://huodong.weibo.com/hongbao/'+str(id)
- html=getData(infoUrl)
- if 'action-type="lottery"' in html or True: #存在搶紅包按鈕
- logUrl="http://huodong.weibo.com/aj_hongbao/detailmore?page=1&type=2&_t=0&__rnd=1423744829265&uid="+id #查看排行榜數(shù)據(jù)
- param={}
- header= {
- 'Cache-Control':'no-cache',
- 'Content-Type':'application/x-www-form-urlencoded',
- 'Pragma':'no-cache',
- 'Referer':'http://huodong.weibo.com/hongbao/detail?uid='+str(id),
- 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 BIDUBrowser/6.x Safari/537.36',
- 'X-Requested-With':'XMLHttpRequest'
- }
- res = postData(logUrl,param,header)
- pMoney=re.compile(r'< span class="money">(\d+?.+?)\xd4\xaa< /span>',re.DOTALL) #h獲取所有l(wèi)ist_info的正則
- luckyLog=pMoney.findall(html,re.DOTALL)
- if len(luckyLog)==0:
- maxMoney=0
- else:
- maxMoney=float(luckyLog[0])
- if maxMoney< lowest: #記錄中最大紅包小于設(shè)定值
- return False
- else:
- print u"---------手慢一步---------"
- print "----------......----------"
- return False
- return True
0×06 收尾工作
主要的模塊都已經(jīng)搞定,現(xiàn)在需要將所有的步驟串聯(lián)起來:
- def start(username,password,low,fromFile):
- gl=False
- lowest=low
- login(username , password)
- if fromfile=='y':
- if os.path.exists('luckyList.txt'):
- try:
- f = file('luckyList.txt')
- newList = []
- newList = p.load(f)
- print u'---------裝載列表---------'
- print "----------......----------"
- except Exception, e:
- print u'解析本地列表失敗,抓取在線頁面。'
- print "----------......----------"
- gl=True
- else:
- print u'本地不存在luckyList.txt,抓取在線頁面。'
- print "----------......----------"
- gl=True
- if gl==True:
- getList()
- from operator import itemgetter
- newList=sorted(luckyList, key=itemgetter(4),reverse=True)
- f = file('luckyList.txt', 'w')
- p.dump(newList, f) #把抓到的列表存到文件里,下次就不用再抓了
- f.close()
- for lucky in newList:
- if not 'http://huodong.weibo.com' in lucky[3]: #不是紅包
- continue
- print lucky[3]
- id=re.findall(r'(\w*[0-9]+)\w*',lucky[3])
- getLucky(id[0])
因為每次測試的時候都要重復(fù)爬取紅包列表,很麻煩,所以加了段將完整列表dump到文件的代碼,這樣以后就可以讀本地列表然后搶紅包了,構(gòu)造完start模塊后,寫一個入口程序把微博賬號傳過去就OK了:
- if __name__ == "__main__":
- print u"------------------微博紅包助手------------------"
- print "---------------------v0.0.1---------------------"
- print u"-------------by @無所不能的魂大人----------------"
- print "-------------------------------------------------"
- try:
- uname=raw_input(u"請輸入微博賬號: ".decode('utf-8').encode('gbk'))
- pwd=raw_input(u"請輸入微博密碼: ".decode('utf-8').encode('gbk'))
- low=int(raw_input(u"紅包領(lǐng)取最高現(xiàn)金大于n時參與: ".decode('utf-8').encode('gbk')))
- fromfile=raw_input(u"是否使用luckyList.txt中紅包列表:(y/n) ".decode('utf-8').encode('gbk'))
- except Exception, e:
- print u"參數(shù)錯誤"
- print "----------......----------"
- print e
- exit(0)
- print u"---------程序開始---------"
- print "----------......----------"
- start(uname,pwd,low,fromfile)
- print u"---------程序結(jié)束---------"
- print "----------......----------"
- os.system('pause')
0×07 走你!
基本的爬蟲骨架已經(jīng)基本可以完成了,其實這個爬蟲的很多細(xì)節(jié)上還是有很大發(fā)揮空間的,比如改裝成支持批量登錄的,比如優(yōu)化下紅包價值算法,代碼本身應(yīng)該也有很多地方可以優(yōu)化的,不過以我的能力估計也就能搞到這了。
最后程序的結(jié)果大家都看到了,我寫了幾百行代碼,幾千字的文章,辛辛苦苦換來的只是一組雙色球,尼瑪坑爹啊,怎么會是雙色球呢!?。。ㄅ园祝鹤髡咴秸f越激動,居然哭了起來,周圍人紛紛勸說:兄弟,不至于的,不就是個微博紅包么,昨天手都擼酸了也沒搖出個微信紅包。)
唉,其實我不是哭這個,我難過的是我已經(jīng)二十多歲了,還在做寫程序抓微博紅包這么無聊的事情,這根本不是我想要的人生啊!