自己编写一个简单的微博爬虫
自己编写一个简单的微博爬虫
前言
很多做社交媒体数据分析的同学需要采集一些新浪微博上的数据,新浪微博虽然有提供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 定位失效了。我的解决方案是,尽量将自己的采集任务分类,做成不同的模块,各个模块之间做到松耦合,这样更容易维护。
个人感想:如果采集少量数据,仅仅用来写个大作业或者发篇论文的话,不用过于在乎这部分结构,浪费精力不说,很可能会本末倒置。新浪微博的反爬意识也比较高,会时不时出一些新的反爬策略,辛辛苦苦设计的“优美的结构”在新的反爬策略面前可能成为鸡肋,白费工服。