自己編寫一個簡單的微博爬蟲
前言
很多做社交媒體數據分析的同學需要采集一些新浪微博上的數據,新浪微博雖然有提供api,但免費的api對獲取的數據項和獲取的頻率都有很大的限制,商業版api據說限制較少,但是作為屌絲學生黨拿來那么多錢買買商業版的api?!!!用類似於火車頭采集器這種工具又很難解決問題,因此我們往往需要自己編寫微博爬蟲。下面我簡單介紹一下我在編寫微博爬蟲期間遇到的問題和我的解決思路。
微博登陸問題
爬蟲需先登錄到新浪微博,否則微博一直返回登錄頁面不給數據。目前的解決辦法有:
- 先手動用瀏覽器登錄,然后導出cookie ,再寫腳本加載cookie到爬蟲的http協議里,這樣就獲取了session 和cookie,解決了身份認證的問題。
- 就是本文要介紹的模擬登陸。
注:以下模擬登陸部分是參考文章:python模擬新浪微博登陸功能(新浪微博爬蟲) 所寫,我在該文章的基礎上做了一些小改動。
第一種方案操作較為繁瑣,尤其是想要用多個微博馬甲輪詢,降低馬甲被封概率的話。第二種方案就可以實現批量馬甲登錄,但是有的賬號登錄的時候可能需要輸入驗證碼,驗證碼識別起來比較困難,目前我還沒有解決這個問題。
下面是我的模擬登陸代碼
WeiboLogin.py:等一了登錄類 WeiboLogin.最后調用WeiboLogin.Login()方法會返回一個帶有當前馬甲會話cookie的opener。多個馬甲登錄模擬登陸的話,可以實例化多個WeiboLogin,調用它們的Login()方法返回不同的opener 帶有各自的會話cookie,降低賬號被封的風險。
import urllib2,traceback,cookielib import WeiboEncode import WeiboSearch class WeiboLogin: def __init__(self, user, pwd, enableProxy=False): "初始化WeiboLogin,Proxy默認關閉" print "Initializing WeiboLogin..." self.userName = user self.passWord = pwd self.enableProxy = enableProxy self.cookiejar = cookielib.LWPCookieJar()#建立cookie self.serverUrl = "http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su=&rsakt=mod&client=ssologin.js(v1.4.11)&_=1379834957683" self.loginUrl = "http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.11)" self.postHeader = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:24.0) Gecko/20100101 Firefox/24.0'} def Login(self): #"登陸程序" ERROR_COUNT =0 opener = self.EnableCookie() while True: if ERROR_COUNT>3: print 'login error!' return False try: url = "http://weibo.com/" req = urllib2.Request(url, None, self.postHeader) opener.open(req) serverTime, nonce, pubkey, rsakv = self.GetServerTime(opener)#登陸的第一步 postData = WeiboEncode.PostEncode(self.userName, self.passWord, serverTime, nonce, pubkey, rsakv)#加密用戶和密碼 print "Post data length:\n", len(postData) req = urllib2.Request(self.loginUrl, postData, self.postHeader) print "Posting request..." result = opener.open(req)#登陸的第二步——解析新浪微博的登錄過程中3 text = result.read() loginUrl = WeiboSearch.sRedirectData(text)#解析重定位結果 req = urllib2.Request(loginUrl, None, self.postHeader) temp = opener.open(loginUrl) except: print traceback.format_exc() print 'retrying......' ERROR_COUNT+=1 continue if WeiboSearch.sCheckLoginResult(temp.read()):#檢查登錄返回信息 print 'Login sucess!' return opener else : print 'login error' return False def EnableCookie(self):#"Enable cookie & proxy (if needed)." cookie_support = urllib2.HTTPCookieProcessor(self.cookiejar) if self.enableProxy: proxy_support = urllib2.ProxyHandler({'http':'http://xxxxx.pac'})#使用代理 opener = urllib2.build_opener(proxy_support, cookie_support, urllib2.HTTPHandler) print "Proxy enabled" else: opener = urllib2.build_opener(cookie_support, urllib2.HTTPHandler) return opener def GetServerTime(self,opener): "Get server time and nonce, which are used to encode the password" print "Getting server time and nonce..." req = urllib2.Request(self.serverUrl, None, self.postHeader) serverData = opener.open(req).read()#得到網頁內容 print serverData try: serverTime, nonce, pubkey, rsakv = WeiboSearch.sServerData(serverData)#解析得到serverTime,nonce等 return serverTime, nonce, pubkey, rsakv except: print 'Get server time & nonce error!' return None
WeiboSearch.py:主要是WebLogin.py需要的一些 檢索、分析、檢查函數
import re import json def sServerData(serverData): "Search the server time & nonce from server data" p = re.compile('\((.*)\)') jsonData = p.search(serverData).group(1) data = json.loads(jsonData) serverTime = str(data['servertime']) nonce = data['nonce'] pubkey = data['pubkey']# rsakv = data['rsakv']# print "Server time is:", serverTime print "Nonce is:", nonce return serverTime, nonce, pubkey, rsakv def sRedirectData(text): p = re.compile('location\.replace\([\'"](.*?)[\'"]\)') loginUrl = p.search(text).group(1) print 'loginUrl:',loginUrl return loginUrl def sCheckLoginResult(text): p = re.compile('parent\.sinaSSOController\.feedBackUrlCallBack\(\{"result":true,"userinfo":\{"uniqueid":') if p.search(text): return True else : return False
WeiboEncode.py:用戶名密碼加密操作
import urllib import base64 import rsa import binascii def PostEncode(userName, passWord, serverTime, nonce, pubkey, rsakv): "Used to generate POST data" encodedUserName = GetUserName(userName)#用戶名使用base64加密 encodedPassWord = get_pwd(passWord, serverTime, nonce, pubkey)#目前密碼采用rsa加密 postPara = { 'entry': 'weibo', 'gateway': '1', 'from': '', 'savestate': '7', 'userticket': '1', 'ssosimplelogin': '1', 'vsnf': '1', 'vsnval': '', 'su': encodedUserName, 'service': 'miniblog', 'servertime': serverTime, 'nonce': nonce, 'pwencode': 'rsa2', 'sp': encodedPassWord, 'encoding': 'UTF-8', 'prelt': '115', 'rsakv': rsakv, 'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack', 'returntype': 'META' } postData = urllib.urlencode(postPara)#網絡編碼 return postData def GetUserName(userName): "Used to encode user name" userNameTemp = urllib.quote(userName) userNameEncoded = base64.encodestring(userNameTemp)[:-1] return userNameEncoded def get_pwd(password, servertime, nonce, pubkey): rsaPublickey = int(pubkey, 16) key = rsa.PublicKey(rsaPublickey, 65537) #創建公鑰 message = str(servertime) + '\t' + str(nonce) + '\n' + str(password) #拼接明文js加密文件中得到 passwd = rsa.encrypt(message, key) #加密 passwd = binascii.b2a_hex(passwd) #將加密信息轉換為16進制。 return passwd
使用方法:
from WeiboLogin import WeiboLogin user_name ='abc@sina.com' passwd ='1234' opener = WeiboLogin(user_name, passwd).Login()
ip請求過頻問題:
即使模擬登陸成功,寫好爬蟲程序,你會發現爬蟲爬一小會后又不返回數據了。這是因為微博服務器監測到該ip請求數據的頻率異常,一般采取的策略是加代理ip(一般的http代理即可,最好是高匿的)。而且經測試,每次更換代理的ip的時候不用重新登錄,直接用當前生成的opener添加更換的ip代理handler即可:
proxy_handler = urllib2.ProxyHandler({ "http" : proxyServer, "https" : proxyServer }) opener.add_handler(proxy_handler)
一開始我發現網上有很多免費的http代理,也搜集了不少,結果測試一遍發現很少有能用的,即使勉強能用也極不穩定,代理有效時間也很短。后來發現有小伙伴自己掃描SOCKS4 / SOCKS5代理 或者http代理的,因為我采集的數據量很少,不准備投入那么多,所以簡單租用了一下別人提供的代理(租金對學生黨來說還能接受)。
爬蟲的健壯性問題:
一方面考慮到爬蟲腳本要長時間運行,中間可能會遇到網絡不穩定、斷電等不可抗力因素,因此建議及時的存儲爬取結果,有條件的可以采用分布式采集,我的策略是依托數據庫做了一個簡單的"斷點”機制,同時在代碼的網絡請求和文件IO的地方做了異常處理和失敗重試操作。
另一方面由於微博有時會改變版面,導致原來的代碼里的解析正則表達式或者BeautifulSoup 定位失效了。我的解決方案是,盡量將自己的采集任務分類,做成不同的模塊,各個模塊之間做到松耦合,這樣更容易維護。
個人感想:如果采集少量數據,僅僅用來寫個大作業或者發篇論文的話,不用過於在乎這部分結構,浪費精力不說,很可能會本末倒置。新浪微博的反爬意識也比較高,會時不時出一些新的反爬策略,辛辛苦苦設計的“優美的結構”在新的反爬策略面前可能成為雞肋,白費工服。