很久之前就了解过模拟登录的过程,最近对python用的比较多,想来练练手,就想实现一下新浪微博登录,首先随便一搜,网上有大量的前辈们都做过了,我也仔细看了一下,并且参考之后发现无法登录,而且还有很多细节没有说得太清楚。同时网上最新的也是很久之前的,对于最新的版本也有一些改动,因此将我接近两天时间的研究全过程记录一下。

已有实现的简要过程

网上已有实现可以见http://www.douban.com/note/201767245/以及http://www.jb51.net/article/44779.htm。当然还有很多其他例子,都是比较早前的实现,总体来说大致过程都分为如下三步:

1 .预登陆

通过请求http://login.sina.com.cn/sso/prelogin.php来获取客户端对用户密码进行加密的参数。主要列举如下:

  • pubkey :客户端使用RSA加密的公钥
  • servertime:服务器时间,用来与用户密码一起扰乱加密
  • nonce:服务器随机字符串,用来与用户密码一起扰乱加密

2 .登录

通过http://login.sina.com.cn/sso/login.php?client=ssologin.js使用POST对用户名密码进行处理,以及其他相关参数的处理后,得到这个请求返回的cookie和正文html内容。
主要涉及到的是用户名先使用urlencode加密然后base64加密,密码使用如下方式扰乱:

servertime + '\t' + nonce + '\n' + password

然后对扰乱后的字符串使用RSA加密。

3 .跳转到ajaxlogin

将第二部中得到的正文html内容中的一段JavaScript代码中的“location.replace()”中的地址用正则取出,然后对这个地址发起访问就结束了。
上述三个步骤就是目前已有的模拟登录的简要概述,但是目前微博已经做了一些改动,有很多细节需要注意,详细记录如下。所有实现封装在一个类中,详细步骤的代码片段都在这个环境下。

prelogin请求

这个请求在用户输入微博名称之后,选中用户密码输入框时,会使用ajax方式请求一遍,获取用户输入的微博登录名实时对应的随机信息。请求参数如下:

entry:weibo
callback:sinaSSOController.preloginCallBack
su:base64.encode(urlencode(username))
rsakt:mod
checkpin:1
client:ssologin.js(v1.4.18)
_:1437133246747

其中可以看出ssologin.js的版本已经是1.4.18了,比之前列出的版本都更新很多版本了。最后一个参数是客户端的时间,单位是毫秒。以上通过fiddler获取。上述python实现如下:

def __mtime(self):'''Return the current time by milli-second'''return long('%.0f' % (time.time() * 1000))
    b64username = base64.b64encode(urllib.quote(self.username))preReqHeader = {'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','Accept-Encoding':'gzip, deflate','Accept-Language':'zh-CN,zh;q=0.8','Cache-Control':'max-age=0','Connection':'keep-alive','User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36','Host':'login.sina.com.cn','Origin':'http://weibo.com','Referer':'http://weibo.com/',}plt = self.__mtime()payload = {'entry':'weibo','callback':'sinaSSOController.preloginCallBack','su': b64username,'rsakt':'mod','checkpin':'1','client':'ssologin.js(v1.4.18)','_': plt,}

上述请求返回了一段JavaScript代码:

sinaSSOController.preloginCallBack({"retcode":0,"servertime":1437133178,"pcid":"xd-b0fc6894be2638ae76e6399c104101c9d433","nonce":"SVRB9M","pubkey":"EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245A87AC253062882729293E5506350508E7F9AA3BB77F4333231490F915F6D63C55FE2F08A49B353F444AD3993CACC02DB784ABBB8E42A9B1BBFFFB38BE18D78E87A0E41B9B8F73A928EE0CCEE1F6739884B9777E4FE9E88A1BBE495927AC4A799B3181D6442443","rsakv":"1330428213","showpin":0,"exectime":9})

这里使用正则方式提取了preloginCallBack函数中的对象,并转换为python字典对象。完整请求封装为一个函数:

    def preLogin(self, header, payload):pre = requests.get(self.__class__.Url['preLogin'],headers = header,params  = payload)#Parse the preLogin textif pre.status_code != 200:raise base.LoginErrortext    = pre.textdictObj = {}try:jsonStr = re.search(r'({[^{]+?})', text).group(1)dictObj = eval(jsonStr)except:raise base.LoginErrorreturn dictObj, pre.headers

另外,通过实际登录会发现,浏览器中的ssologin.js文件是登录处理的核心文件,这个文件是经过先加密后压缩了的,我进行解压缩之后还是可以发现很多处理方式。从而找到了很多细节的答案。
首先可以找到prelogin的处理:

this.prelogin = function(a, b) {var c = location.protocol == "https:" ? ssoPreLoginUrl.replace(/^http:/, "https:") : ssoPreLoginUrl,d = a.username || "";d = sinaSSOEncoder.base64.encode(urlencode(d));delete a.username;var e = {entry: me.entry,callback: me.name + ".preloginCallBack",su: d,rsakt: "mod"};c = makeURL(c, objMerge(e, a));me.preloginCallBack = function(a) {if (a && a.retcode == 0) {me.setServerTime(a.servertime);me.nonce = a.nonce;me.rsaPubkey = a.pubkey;me.rsakv = a.rsakv;pcid = a.pcid;preloginTime = (new Date).getTime() - preloginTimeStart - (parseInt(a.exectime, 10) || 0)}typeof b == "function" && b(a)};preloginTimeStart = (new Date).getTime();excuteScript(me.scriptId, c)};

变量e就是构造的请求参数,preloginCallBack就是回掉函数,将返回的参数进行了处理:servertime、nonce、pubkey、rsakv都是直接赋值,供下次调用,pcid这个参数是为了进行验证码的操作,这里可以忽略。

preloginTime参数

另外一个preloginTime是一个时间间隔,可以看出是客户端的一个计时,同时还减去了服务器返回的exectime这个时间值,另外通过查阅,发现最后preloginTime这个值是下一个login请求的prelt这个参数的值,这里我使用python也进行了计时,模拟了这个参数

      preLoginDict, preRespHeaders = self.preLogin(preReqHeader, payload)endPre = self.__mtime()prelt = endPre - plt - long(preLoginDict['exectime'])

login请求

参数构造

首先是构造login请求的POST参数,这里比较重要的就是rsa加密扰乱过的密码字符串,这个部分在之前的前辈们都很突兀地就提到了是如何加密的(还看到有人提问是不是新浪内部的人员),我经过解压缩ssologin.js这个文件,找到了这个部分的出处。
首先在一个login函数中通过loginByConfig这个函数判断登录方式,最终根据默认方式,使用了loginByIframe且请求方式是POST:

loginByConfig = function() {if (!me.feedBackUrl && loginByXMLHttpRequest(a, b, c)) return !0;if (me.useIframe && (me.setDomain || me.feedBackUrl)) {if (me.setDomain) {document.domain = me.domain;!me.feedBackUrl && me.domain != "sina.com.cn" && (me.feedBackUrl = makeURL(me.appLoginURL[me.domain], {domain: 1}))}loginMethod = "post";var d = loginByIframe(a, b, c);.....

然后在loginByIframe函数中调用了makeRequest函数,这个函数构造了这个POST请求的参数:

makeRequest = function(a, b, c) {var d = {entry: me.getEntry(),gateway: 1,from: me.from,savestate: c,useticket: me.useTicket ? 1 : 0};me.failRedirect && (me.loginExtraQuery.frd = 1);d = objMerge(d, {pagerefer: document.referrer || ""});d = objMerge(d, me.loginExtraFlag);d = objMerge(d, me.loginExtraQuery);d.su = sinaSSOEncoder.base64.encode(urlencode(a));me.service && (d.service = me.service);if (me.loginType & rsa && me.servertime && sinaSSOEncoder && sinaSSOEncoder.RSAKey) {d.servertime = me.servertime;d.nonce = me.nonce;d.pwencode = "rsa2";d.rsakv = me.rsakv;var e = new sinaSSOEncoder.RSAKey;e.setPublic(me.rsaPubkey, "10001");b = e.encrypt([me.servertime, me.nonce].join("\t") + "\n" + b)} else if (me.loginType & wsse && me.servertime && sinaSSOEncoder && sinaSSOEncoder.hex_sha1) {d.servertime = me.servertime;d.nonce = me.nonce;d.pwencode = "wsse";b = sinaSSOEncoder.hex_sha1("" + sinaSSOEncoder.hex_sha1(sinaSSOEncoder.hex_sha1(b)) + me.servertime + me.nonce)}d.sp = b;try {d.sr = window.screen.width + "*" + window.screen.height} catch (f) {}return d}

这个函数里面就很明显地用if判断了loginType,对于目前的rsa加密方式,有如下代码:

var e = new sinaSSOEncoder.RSAKey;
e.setPublic(me.rsaPubkey, "10001");
b = e.encrypt([me.servertime, me.nonce].join("\t") + "\n" + b)

另外也有之前版本的两次sha1加密的方式,不过目前好像都是使用rsa方式。

b = sinaSSOEncoder.hex_sha1("" + sinaSSOEncoder.hex_sha1(sinaSSOEncoder.hex_sha1(b)) + me.servertime + me.nonce)

python实现如下,参数preObj是前一步prelogin请求返回的内容里面的函数调用参数对象转换为了python的字典对象。

    def encryptPassword(self, pw, preObj):import rsa, binasciiif not isinstance(pw, types.StringType):return Nonen = int(preObj['pubkey'], 16)  #Convert the 16 string n to numbere = int('10001', 16) #Convert the 16 string e to numbermessage = str(preObj['servertime']) + '\t' + \str(preObj['nonce']) + '\n' + str(pw)key = rsa.PublicKey(n, e)sp  = rsa.encrypt(message, key)return binascii.b2a_hex(sp)

除此之外,还有prelt参数在前面提到过,可以直接添加。su参数是加密后的用户名。nonce、rsakv、servertime都直接添加。其余都固定不变即可。

请求头的构造

请求头里面需要设置Content-Type为“application/x-www-form-urlencoded”,另外还有Content-Length也需要设置,这个需要手动计算一下,特别重要的是,通过测试,发现进行login请求的Cookie中需要设置,而且这个是固定值就就可以,直接将fiddler得到的请求的Cookie加入,都是全局值标识用户ip和固定信息所用。

    sp = self.encryptPassword(self.password, preLoginDict)info('Get encrpyt password sucess:')info('password = ' + sp)#print sp#;exit()loginData = {'entry'     : 'weibo','gateway'   : '1','from'      : '','savestate' : '0','useticket' : '1','pagerefer' : 'http://login.sina.com.cn/sso/logout.php?entry=miniblog&r=http%3A%2F%2Fweibo.com%2Flogout.php%3Fbackurl%3D%252F','vsnf'      : '1','su'        : b64username,'service'   : 'miniblog','servertime': preLoginDict['servertime'] + (self.__mtime() - endPre),'nonce'     : preLoginDict['nonce'],'pwencode'  : 'rsa2','rsakv'     : preLoginDict['rsakv'],'sp'        : sp,'sr'        : '1600*900','encoding'  : 'UTF-8','prelt'     : prelt,'url'       : 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack','returntype': 'META',}loginReqHeaders = preReqHeader.copy()conLen = len(urllib.urlencode(loginData))loginReqHeaders['Content-Length'] = conLenloginReqHeaders['Content-Type'] = 'application/x-www-form-urlencoded'loginReqHeaders['Cookie'] = '固定cookie值'login = requests.post(self.__class__.Url['login'],headers = loginReqHeaders,data = loginData,)

这样请求之后得到的返回信息头部要将Cookie保存下来,并且与发送的请求Cookie合并到一起,下次请求时需要。
另外,得到的html内容如下:

<html>
<head>
<title>新浪通行证</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK" /><script charset="utf-8" src="http://i.sso.sina.com.cn/js/ssologin.js"></script>
</head>
<body>
正在登录 ...
<script>try{sinaSSOController.setCrossDomainUrlList({"retcode":0,"arrURL":["http:\/\/crosdom.weicaifu.com\/sso\/crosdom?action=login","http:\/\/passport.97973.com\/sso\/crossdomain?action=login","http:\/\/passport.weibo.cn\/sso\/crossdomain?action=login"]});}catch(e){}try{sinaSSOController.crossDomainAction('login',function(){location.replace('http://passport.weibo.com/wbsso/login?url=http%3A%2F%2Fweibo.com%2Fajaxlogin.php%3Fframelogin%3D1%26callback%3Dparent.sinaSSOController.feedBackUrlCallBack%26sudaref%3Dweibo.com&ticket=ST-NTE1MTU5MzUwMA==-1437130678-xd-9EB20B3EB2CB305249A978593E222D95&retcode=0');});}catch(e){}</script>
</body>
</html>

passport登录请求

从上面得到的html内容可以看到,一段JavaScript代码中,使用的location.replace调用的是passport下的wbsso/login文件,和以前实现的版本并不一样,并不是直接进行ajaxlogin请求。
因此使用正则获取到这个跳转地址,发起请求。

login_sid_t的获取

这里比较重要的是,发现fiddler获取的实际请求中cookie包含了一个”login_ sid_t”项,此时必须要加上才行,因此去回溯所有请求,发现,这个参数是在第一次请求weibo.com时生成的,而且每次都不一样,因此又需要获取一次。python实现如下:

        weiboHeaders = {'Host' : 'weibo.com','User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36','Cookie':'TC-Ugrow-G0=0149286e34b004ccf8a0b99657f15013; SUB=_2AkMi9DzDdcNhrAFXmvEXyWjia4xRnk2l5Z-gbhmfSH1UXH4SjVcLhkcF2RF-Xtyj2Ea64VJJRk99qu8X-IjCokugCM12sNUAQM9eIag.; SUBP=0033WrSXqPxfM72wWs9jqgMF55529P9D9WFAlHfAQ6dPVKycqD2L8_sC5JpV8Jxfqgp4qg4rMcvV9XWrdg8DdF4odcXt',}try:weibo =requests.get(self.__class__.Url['weibo'], headers = weiboHeaders)except: passloginSidT = ''if weibo.status_code == 200:info('Get "login_sid_t" success:')loginSidT = weibo.headers['set-cookie']loginSidT = loginSidT[0: loginSidT.find(';')]

这里请求头的Cookie内容也是直接套用了fiddler中的值,都是固定值直接写死没有影响。

请求头构建

这里有一个非常重要的地方就是,一定要设置好Host和Referer两个请求头,否则会返回无权限。我就是在这里设置错了耽误了很久的时间。
另外需要构建的就是Cookie,这里非常重要的就是那个login_sid_t的设置,以及myuid和un的设置。其余的参数直接使用前面的头部信息就可以。myuid是一个固定值,直接写死。但是un是当前用户名,uid确是微博用户的id,这些信息保存在login请求中的cookie中的SUP中,SUP使用了urlencode之后保存,需要从中解密出uid和name信息:

    def __getInfo(self, ck):ck = urllib.unquote(ck)sup = re.search(r'SUP=([^;]+);', ck).group(1)suplist = sup.split('&')suplist = filter(lambda x: x.startswith('uid') or x.startswith('name'), suplist)sup = {s[0:s.find('=')] : urllib.unquote(s[s.find('=')+1:]) for s in suplist}return sup

url获取并发起请求

请求的url需要从location.replace里面提取,使用正则提取,然后就加上之前构造的请求头,直接发送请求即可:

url = re.search('location.replace\(\'([^\)]+)\'\)', login.content)
url = url.group(1)
info('Get ajax url: ' + url)
con = requests.get(url, headers = ajaxReqHeaders)

此时,返回的内容是一段html:

<html><head><script language='javascript'>parent.sinaSSOController.feedBackUrlCallBack({"result":true,"userinfo":{"uniqueid":"5151593500","userid":null,"displayname":null,"userdomain":"?wvr=5&lf=reg"},"redirect":"http:\/\/d.weibo.com\/?from=signin"});</script></head><body></body></html>

可以看到,回掉函数中的参数result项是true,代表登录成功。

最终登录

上述返回代码在浏览器端是直接跳转到给定的redirect地址即可,但是通过分析fiddler,发现redirect地址请求之后又进行了跳转,最终跳转了”weibo.com/u/uid/home?userdomain“,其中uid是前面从Cookie获取的uid,userdomain就是这里返回的参数中的userinfo中userdomain。
因此再次使用正则提取上述参数,并构造出最终的请求地址:

    ajaxFeedBack = re.search(r'feedBackUrlCallBack\(([^\)]+)\)', con.content)ajaxFeedBack = ajaxFeedBack.group(1)ajaxFeedBack = ajaxFeedBack.replace('true', 'True').replace('null', 'None')ajaxFeedBack = eval(ajaxFeedBack)if not ajaxFeedBack['result']:info('Ajax login failed!')sys.exit(2)userdomain = ajaxFeedBack['userinfo']['userdomain']info('Get userdomain sucess:' + userdomain)

至于请求头,就将前面所有请求头中的Cookie进行合并,同时删除不用的请求头,改好Host、Referer等信息,发起最终请求即可获取到登录后的微博首页内容。

    info('Try to get main content...')mainReqHeaders = ajaxReqHeaders.copy()mainReqHeaders['Host'] = 'weibo.com'mainReqHeaders['Referer'] = 'http://weibo.com/'mainUrl = self.__class__.Url['weibo'] + \'/u/' + userinfo['uid'] + '/home' + userdomainmain = requests.get(mainUrl, headers = mainReqHeaders)if main.status_code == 200:info('Login success.')info(main.content)return mainReqHeadersinfo('Login failed!')

至此,模拟登录就算实现了,而且是返回了微博登录后的首页内容。有一个要说明的地方是,虽然可以使用python标准库中管理cookie的cookielib等模块,但我这里还是当做字符串进行处理的,我的考虑是可以按自己需要每次请求只构造必需的Cookie,当然这个前提是需要多次进行试验,另外一个方面是可以锻炼一下考虑是否全面的思维,特别是处理Cookie提取中expires的问题等等。
欢迎交流和指正~_~

全程模拟新浪微博登录(2015)相关推荐

  1. php模拟关注微博,PHP基于laravel框架获取微博数据之一 模拟新浪微博登录

    参考资料: http://www.csuldw.com/2016/11/10/2016-11-10-simulate-sina-login/ http://blog.csdn.net/fly_leop ...

  2. php rsa2 微博,PHP 基于laravel框架获取微博数据之一 模拟新浪微博登录

    模拟新浪微博登录是抓取新浪数据的基础,网上的参考资料大多介绍的是用Python开发,有一篇使用php模拟登录的资料还是在phpcms中实现的,也没有太深入分析. PS:网上资料来源比较乱,不知道php ...

  3. rsa加密算法python_模拟新浪微博登录(Python+RSA加密算法)

    声明: 由于本人使用用的是Python语言,以下内容就在该语言下进行解释说明.有使用Java语言的可以参考IT男杂记(http://marspring.mobi/http-client-weibo/) ...

  4. spirngmvc如何实现直接输入网页重定向到登录_Python 模拟新浪微博登录

    点击上方"小猿学 Python",选择"置顶公众号" 作者:北岛知寒 链接:https://www.cnblogs.com/crazyacking/p/5232 ...

  5. [置顶]定向爬虫 - Python模拟新浪微博登录

    当我们试图从新浪微博抓取数据时,我们会发现网页上提示未登录,无法查看其他用户的信息. 模拟登录是定向爬虫制作中一个必须克服的问题,只有这样才能爬取到更多的内容. 实现微博登录的方法有很多,一般我们在模 ...

  6. 微博登录记录pythonurllib_定向爬虫 - Python模拟新浪微博登录

    当我们试图从新浪微博抓取数据时,我们会发现网页上提示未登录,无法查看其他用户的信息. 模拟登录是定向爬虫制作中一个必须克服的问题,只有这样才能爬取到更多的内容. 实现微博登录的方法有很多,一般我们在模 ...

  7. 模拟新浪微博登录:从原理分析到实现

    上一篇文章小试牛刀:使用Python模拟登录知乎介绍了如何模拟知乎登录,虽然用到了验证码信息,但请求的参数都是原封不动的传递,刚开始接触的时候,觉得难度适中,回头再看的时候,反而感觉挺容易的.在这篇文 ...

  8. 模拟新浪微博登录-原理分析到实现

    原文地址:http://www.csuldw.com/2016/11/10/2016-11-10-simulate-sina-login/ 上一篇文章 小试牛刀:使用Python模拟登录知乎 介绍了如 ...

  9. 模拟新浪微博登录发送微博

    2015-10-14 应该是新浪微博更新,导致项目中之前开发的发送未必代码不能用了,结合网上的案例,修改了点代码重新整合下.代码看附件. 注意点: 模拟登陆时 postMethod2.addParam ...

最新文章

  1. AD上删除了Exchange容器,再重装时报'找不到企业组织容器
  2. 史上最全的SpringCloud入门学习教程
  3. c++关联容器的成员函数find的一个例子
  4. markdown的流程图实现和代码语法着色
  5. android 展示星期方式,Android显示从一周到另一周的日期(星期四至星期四)
  6. googleearthpro打开没有地球_嫦娥五号成功着陆地球!为何嫦娥五号返回时会燃烧,升空却不会?...
  7. pacman 查询_掌握pacman包管理工具,玩转Arch Linux
  8. 泛型使用思想,记一次java泛型使用经历
  9. 表单从gb2312的页面提交到utf-8页面,或者表单从utf-8的页面提交到gb2312页面的解决办法...
  10. DHCP报文分析(三级网络技术)
  11. LaTeX2021 公式编写、图文安装、详细教程、一文读懂
  12. 海威计算机网络,海威分布式大屏幕显示控制系统的主要优点
  13. 对代码规范性的一点切实感受
  14. 待业一年多,我终于找到工作啦,月薪1.5万,双休不加班
  15. 67.输入若干行文本,每行以回车结束,以 ctrl+z 作为结束符,统计其行数
  16. 一位老农带着猫、狗、鱼过河,河边有一条船,每次老农只能带一只动物过河。当老农不和猫狗鱼在一起时,狗会咬猫,猫会吃鱼,当老农和猫狗鱼在一起时,则不会发生这种问题。编程解决猫狗鱼过河问题。
  17. Hibernate 返回类型转Integer
  18. 【日拱一卒进击大厂系列】如何写好一份技术简历
  19. 计算机基础知识(2)
  20. can总线分析仪与stm32的收发操作

热门文章

  1. 高数_证明_极限的唯一性
  2. 未来生活照进现实——7大热门智慧城市项目和世界8大顶级智慧城市盘点
  3. android 11.0 12.0控制屏幕亮屏和灭屏操作
  4. 敏捷 橄榄球运动_澳大利亚橄榄球迷的研究声称南非裁判的偏见被证明是错误的
  5. 解决lazarus 多线程报错问题
  6. ibm服务器内存型号_【IBM联想 46W0821 46W0823 16GB DDR4-2666 8GB, 服务器内存】价格_厂家 - 中国供应商...
  7. python 囚徒困境_40行Python代码实现“旅行者困境“演化过程
  8. 电脑花屏不一定是显卡问题
  9. Python爬虫违法吗?如何判断爬虫采集内容是否违法?
  10. 主板开机电路故障的维修实例