如何科學的搶紅包:寫個程序搶紅包

jopen 9年前發布 | 36K 次閱讀 紅包

如何科學的搶紅包:寫個程序搶紅包

0×00 背景

大家好,我是來自IDF實驗室的@無所不能的魂大人

紅包紛紛何所似?兄子胡兒曰:“撒錢空中差可擬。”兄女道韞曰:“未若姨媽因風起。”

背景大家都懂的,要過年了,正是紅包滿天飛的日子。正巧前兩天學會了Python,比較亢奮,就順便研究了研究微博紅包的爬取,為什么是微博紅包而不是支付寶紅包呢,因為我只懂Web,如果有精力的話之后可能也會研究研究打地鼠算法吧。

因為本人是初學Python,這個程序也是學了Python后寫的第三個程序,所以代碼中有啥坑爹的地方請不要當面戳穿,重點是思路,嗯,如果思路中有啥坑爹的的地方也請不要當面戳穿,你看IE都有臉設置自己為默認瀏覽器,我寫篇渣文得瑟得瑟也是可以接受的對吧……

我用的是Python 2.7,據說Python 2和Python 3差別挺大的,比我還菜的小伙伴請注意。

0×01 思路整理

懶得文字敘述了,畫了張草圖,大家應該可以看懂。

如何科學的搶紅包:寫個程序搶紅包

首先老規矩,先引入一坨不知道有啥用但又不能沒有的庫:

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 #能忍受紅包領獎記錄最低為多少

這里用到了一個rsa庫,Python默認是不自帶的,需要安裝一下:https://pypi.python.org/pypi/rsa/

下載下來后運行setpy.py install安裝,然后就可以開始我們的開發步驟了。

0×02 微博登陸

搶紅包的動作一定要登陸后才可以進行的,所以一定要有登錄的功能,登錄不是關鍵,關鍵是cookie的保存,這里需要cookielib的配合。

cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)

這樣凡是使用opener進行的網絡操作都會對處理cookie的狀態,雖然我也不太懂但是感覺好神奇的樣子。

接下來需要封裝兩個模塊,一個是獲取數據模塊,用來單純地GET數據,另一個用來POST數據,其實只是多了幾個參數,完全可以合并成一個函數,但是我又懶又笨,不想也不會改代碼。

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數據了,其中getData中之所以decode然后又encode啥啥的,是因為在Win7下我調試輸出的時候總亂碼,所以加了些編碼處理,這些都不是重點,下面的login函數才是微博登陸的核心。

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)

這里面的參數啊加密算法啊都是從網上抄的,我也不是很懂,大概就是先請求個時間戳和公鑰再rsa加密一下最后處理處理提交到新浪登陸接口,從新浪登錄成功之后會返回一個微博的地址,需要請求一下,才能讓登錄狀態徹底生效,登錄成功后,后面的請求就會帶上當前用戶的cookie。

0×03 指定紅包抽取

如何科學的搶紅包:寫個程序搶紅包

成功登錄微博后,我已迫不及待地想找個紅包先試一下子,當然首先是要在瀏覽器里試的。點啊點啊點啊點的,終于找到了一個帶搶紅包按鈕的頁面了,F12召喚出調試器,看看數據包是咋請求的。

可以看到請求的地址是http://huodong.weibo.com/aj_hongbao/getlucky,主要參數有兩個,一個是ouid,就是紅包id,在URL中可以看到,另一個share參數決定是否分享到微博,還有個_t不知道是干啥用的。

好,現在理論上向這個url提交者三個參數,就可以完成一次紅包的抽取,但是,當你真正提交參數的時候,就會發現服務器會很神奇地給你返回這么個串:

{"code":303403,"msg":"抱歉,你沒有權限訪問此頁面","data":[]}

這個時候不要驚慌,根據我多年Web開發經驗,對方的程序員應該是判斷referer了,很簡單,把請求過去的header全給抄過去。

def getLucky(id): #抽獎程序
        print u"---抽紅包中:"+str(id)+"---"
        print  "----------......----------"
 
        if checkValue(id)==False: #不符合條件,這個是后面的函數
                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)

這樣的話理論上就沒啥問題了,事實上其實也沒啥問題。抽獎動作完成后我們是需要判斷狀態的,返回的res是一個json串,其中code為100000時為成功,為90114時是今天抽獎達到上限,其他值同樣是失敗,所以:

hbRes=json.loads(res)
if hbRes["code"]=='901114': #今天紅包已經搶完
        print u"---------已達上限---------"
        print  "----------......----------"
        log('lucky',str(id)+'---'+str(hbRes["code"])+'---'+hbRes["data"]["title"])
        exit(0)
elif hbRes["code"]=='100000':#成功
        print u"---------恭喜發財---------"
        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)

其中log也是我自定義的一個函數,用來記錄日志用的:

def log(type,text):
        fp = open(type+'.txt','a')
        fp.write(text)
        fp.write('\r\n')
        fp.close()

0×04 爬取紅包列表

單個紅包領取動作測試成功后,就是我們程序的核心大招模塊了——爬取紅包列表,爬取紅包列表的方法和入口應該有不少,比如各種微博搜索關鍵字啥啥的,不過我這里用最簡單的方法:爬取紅包榜單。

在紅包活動的首頁(http://huodong.weibo.com/hongbao)通過各種點更多,全部可以觀察到,雖然列表連接很多,但可以歸納為兩類(最有錢紅包榜除外):主題和排行榜。

繼續召喚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')) #現金,萬->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')) #已發送
                if info[2]==0:
                        info[2]=1 #防止除數為0
                if info[1]==0:
                        info[1]=1 #防止除數為0
                info.append(info[0]/(info[2]+info[1])) #紅包價值,現金/(領取人數+獎品價值)
                # if info[0]/(info[2]+info[1])>100:
                #  print url
                luckyList.append(info)
        if 'class="page"' in html:#存在下一頁
                p=p+1
                getThemeList(url,p) #遞歸調用自己爬取下一頁

話說正則好難,學了好久才寫出來這么兩句。還有這里的info中append進去了一個info[4],是我想的一個大概判斷紅包價值的算法,為什么要這么做呢,因為紅包很多但是我們只能抽四次啊,在茫茫包海中,我們必須要找到最有價值的紅包然后抽丫的,這里有三個數據可供參考:現金價值、禮品價值和領取人數,很顯然如果現金很少領取人數很多或者獎品價值超高(有的甚至喪心病狂以億為單位),那么就是不值得去搶的,所以我憋了半天終于憋出來一個衡量紅包權重的算法:紅包價值=現金/(領取人數+獎品價值)。

排行榜頁面原理一樣,找到關鍵的標簽,正則匹配出來。

如何科學的搶紅包:寫個程序搶紅包

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獲取所有list_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')) #現金,萬->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')) #已發送
                if info[2]==0:
                        info[2]=1 #防止除數為0
                if info[1]==0:
                        info[1]=1 #防止除數為0
                info.append(info[0]/(info[2]+info[1])) #紅包價值,現金/(領取人數+禮品價值)
                # 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) #遞歸調用自己爬取下一頁

好,現在兩中專題頁的列表我們都可以順利爬取了,接下來就是要得到列表的列表,也就是所有這些列表地址的集合,然后挨個去抓:

def getList():
        print u"---------查找目標---------"
        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 判斷紅包可用性

這個是比較簡單的,首先在源碼里搜一下關鍵字看看有沒有搶紅包按鈕,然后再到領取排行里面看看最高紀錄是多少,要是最多的才領那么幾塊錢的話就再見吧……

其中查看領取記錄的地址為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 #查看排行榜數據
                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獲取所有list_info的正則
                luckyLog=pMoney.findall(html,re.DOTALL)
 
                if len(luckyLog)==0:
                        maxMoney=0
                else:
                        maxMoney=float(luckyLog[0])
 
                if maxMoney< lowest: #記錄中最大紅包小于設定值                         return False         else:                 print u"---------手慢一步---------"                 print  "----------......----------"                 return False         return True

0×06 收尾工作

主要的模塊都已經搞定,現在需要將所有的步驟串聯起來:

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])

因為每次測試的時候都要重復爬取紅包列表,很麻煩,所以加了段將完整列表dump到文件的代碼,這樣以后就可以讀本地列表然后搶紅包了,構造完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"紅包領取最高現金大于n時參與: ".decode('utf-8').encode('gbk')))
                fromfile=raw_input(u"是否使用luckyList.txt中紅包列表:(y/n) ".decode('utf-8').encode('gbk'))
        except Exception, e:
                print u"參數錯誤"
                print  "----------......----------"
                print e
                exit(0)
 
        print u"---------程序開始---------"
        print  "----------......----------"
        start(uname,pwd,low,fromfile)
        print u"---------程序結束---------"
        print  "----------......----------"
        os.system('pause')

0×07 走你!

如何科學的搶紅包:寫個程序搶紅包

如何科學的搶紅包:寫個程序搶紅包

基本的爬蟲骨架已經基本可以完成了,其實這個爬蟲的很多細節上還是有很大發揮空間的,比如改裝成支持批量登錄的,比如優化下紅包價值算法,代碼本身應該也有很多地方可以優化的,不過以我的能力估計也就能搞到這了。

最后程序的結果大家都看到了,我寫了幾百行代碼,幾千字的文章,辛辛苦苦換來的只是一組雙色球,尼瑪坑爹啊,怎么會是雙色球呢!!!(旁白:作者越說越激動,居然哭了起來,周圍人紛紛勸說:兄弟,不至于的,不就是個微博紅包么,昨天手都擼酸了也沒搖出個微信紅包。)

唉,其實我不是哭這個,我難過的是我已經二十多歲了,還在做寫程序抓微博紅包這么無聊的事情,這根本不是我想要的人生啊!

源碼下載:

weibo_hb.rar

作者/idf實驗室(企業帳號)

來源:freebuf.com/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!