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

原文地址:http://www.csuldw.com/2016/11/10/2016-11-10-simulate-sina-login/

 

上一篇文章 小试牛刀:使用Python模拟登录知乎 介绍了如何模拟知乎登录,虽然用到了验证码信息,但请求的参数都是原封不动的传递,刚开始接触的时候,觉得难度适中,回头再看的时候,反而感觉挺容易的。在这篇文章,将继续介绍模拟登录。与之前不一样的是,这次选择的对象是新浪微博,难度稍微提升了点,好在以往的许多码友们都留有许多经验贴,经过几番斟酌,微博的模拟登录算是实现了。这两天还在研究如何高性能地爬取微博数据,业余之际乘着还有点记忆,索性将先前的小实验加工成文,算是一份小结吧。下面来看看整个实验过程。

开发工具

一如既往,笔者使用的还是之前的工具,如下:

  • Windows 7 + Python 2.75
  • Chrome + Fiddler

微博登录请求过程分析

新浪微博的登录有多个URL链接,笔者在实验的时候试了两个,这两个都是新浪通行证登录页面,都是不需要验证码的。一个是 【 http://login.sina.com.cn 】,另一个是 【 https://login.sina.com.cn/signup/signin.php?entry=sso 】。两个URL虽然很大部分相同,登录过程中仅仅是传递参数不一样。第一个URL传递的过程对password进行了加密,而第二个没有加密,所以如果使用第二个URL进行模拟登录,就简单多了。在这里,笔者决定选择使用第一种方式进行分析,下面来看详细过程。

请求登录过程主要分三部分

  1. 请求登录login.php页面前的参数获取
  2. 请求登录login.php页面时的参数分析
  3. 提交POST请求时的参数

Step 1:GET方式请求prelogin.php页面

在模拟登录之前,先观察浏览器登录过程中Fiddler抓到的包,在 /sso/login.php打开之前会先使用 GET 方式请求 /sso/prelogin.php ,请求的URL为:【 https://login.sina.com.cn/sso/prelogin.php?entry=account&callback=sinaSSOController.preloginCallBack&su=bGl1ZGl3ZWkxOCU0MHNpbmEuY29t&rsakt=mod&client=ssologin.js(v1.4.15) 】,可以看看下面这张图:

在Fiddler中,可以点击 Preview 查看具体详情,也可以直接将Request URL复制到浏览器上查看,效果图如下:

可以看出,这是一个json数据,并且携带了几个参数,我们关心的有以下四个:

  • servertime
  • nonce
  • pubkey
  • rsakv

说明一下,之所以认为这几个参数比较重要,那是因为后面对 password 的加密需要用到,对其他参数没有提及的原因是在提交POST时其它的参数并没有用到。好了,为了进行进一步探索,我们从Fiddler的结果可以看出,接下来到了 /sso/login.php 。

Step 2:POST方式请求login.php页面

从这里开始,就进行 login.php 页面的请求了(详细的Request URL:【 https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.15) 】,后面的时间戳可省略)。点击查看详情,结果图如下:

可以发现 /sso/login.php 页面有如下参数(From Data):

cdult: 3
domain: sina.com.cn
encoding: UTF-8
entry: account from: gateway: 1 nonce: AFE3O9 pagerefer: http://login.sina.com.cn/sso/logout.php prelt: 41 pwencode: rsa2 returntype: TEXT rsakv: 1330428213 savestate: 30 servertime: 1478568922 service: sso sp: password sr: 1366*768 su: username useticket: 0 vsnf: 1

到了这里,我们大概可以知道我们需要哪些参数了。在From Data 参数列表中,需要我们指定的参数有下面几个:

  • servertime
  • nonce
  • rsakv
  • sp:加密后的密码
  • su:加密后的用户名

对于参数 nonce 、 servertime 、 rsakv ,都可以从第一步中的 prelogin.php中直接获取,而 sp 和 su 则是经过加密后的字符串值,至于具体的加密规则,我们下面通过查看源码分析得出。

Step 3:探索加密规则

首先看看请求 /sso/prelogin.php 的具体情况,看到 client 为 ssologin.js ,

然后我们到登录页面 https://login.sina.com.cn 中查看源码【 view-source:https://login.sina.com.cn/ 】并搜索“ssllogin.js”,接着点击进入 ssologin.js ,搜索“username”字符串,找到与“username”相应的加密部分(这里需仔细查看+揣测),然后搜索“password”,找到“password”的加密部分,最后分析出“username”和“password”的加密规则。加密部分如下:

加密用户名的代码:

request.su = sinaSSOEncoder.base64.encode(urlencode(username));

加密密码的代码:

if ((me.loginType & rsa) && me.servertime && sinaSSOEncoder && sinaSSOEncoder.RSAKey) {
  request.servertime = me.servertime; request.nonce = me.nonce; request.pwencode = "rsa2"; request.rsakv = me.rsakv; var RSAKey = new sinaSSOEncoder.RSAKey(); RSAKey.setPublic(me.rsaPubkey, "10001"); password = RSAKey.encrypt([me.servertime, me.nonce].join("\t") + "\n" + password) } else { if ((me.loginType & wsse) && me.servertime && sinaSSOEncoder && sinaSSOEncoder.hex_sha1) { request.servertime = me.servertime; request.nonce = me.nonce; request.pwencode = "wsse"; password = sinaSSOEncoder.hex_sha1("" + sinaSSOEncoder.hex_sha1(sinaSSOEncoder.hex_sha1(password)) + me.servertime + me.nonce) } } 

微博对于 username 的加密规则比较单一,使用的是 Base64 加密算法,而对 password 的加密规则比较复杂,虽然使用的是 RSA2 (python中需要使用 pip install rsa 安装rsa模块),但加密的逻辑比较多。根据上面的代码,可以看出 password 加密是这样的一个过程:首先创建一个 rsa 公钥,公钥的两个参数都是固定值,第一个参数是登录过程中 prelogin.php 中的 pubkey ,第二个参数是加密的 js 文件中指定的”10001”(这两个值需要先从16进制转换成10进制,把“10001”转成十进制为“65537”)。最后再加入 servertime 和 nonce 进行进一步加密。

经过上面的分析之后,发起 POST 请求时的 post_data 基本上已经全部可以得到了,接下来就跟模拟登录其它网站类似了,可以使用 request ,也可以使用 urllib2。下面来看详细代码部分。

源码实现

Github源码链接: https://github.com/csuldw/WSpider/tree/master/SinaLogin,源码包括下列文件:

  • dataEncode.py:用于对提交POST请求的数据进行编码处理
  • Logger.py:用于打印log
  • SinaSpider.py:用于爬取sina微博数据的文件(主文件)

为了方便扩展,笔者将代码进行了封装,所以看起来代码量比较多,不过个人觉得可读性还是比较良好,算是凑合吧。

1. dataEncode.py

# -*- coding: utf-8 -*-
"""
Created on Tue Nov 08 10:14:38 2016

@author: liudiwei
"""
import base64
import rsa
import binascii import requests import json import re #使用base64对用户名进行编码 def encode_username(username): return base64.encodestring(username)[:-1] #使用rsa2对password进行编码 def encode_password(password, servertime, nonce, pubkey): rsaPubkey = int(pubkey, 16) RSAKey = rsa.PublicKey(rsaPubkey, 65537) #创建公钥 codeStr = str(servertime) + '\t' + str(nonce) + '\n' + str(password) #根据js拼接方式构造明文 pwd = rsa.encrypt(codeStr, RSAKey) #使用rsa进行加密 return binascii.b2a_hex(pwd) #将加密信息转换为16进制。 #读取preinfo.php,获取servertime, nonce, pubkey, rsakv四个参数值 def get_prelogin_info(): url = r'http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su=&rsakt=mod&client=ssologin.js(v1.4.18)' html = requests.get(url).text jsonStr = re.findall(r'\((\{.*?\})\)', html)[0] data = json.loads(jsonStr) servertime = data["servertime"] nonce = data["nonce"] pubkey = data["pubkey"] rsakv = data["rsakv"] return servertime, nonce, pubkey, rsakv #根据Fiddler抓取的数据,构造post_data def encode_post_data(username, password, servertime, nonce, pubkey, rsakv): su = encode_username(username) sp = encode_password(password, servertime, nonce, pubkey) #用于登录到 http://login.sina.com.cn post_data = { "cdult" : "3", "domain" : "sina.com.cn", "encoding" : "UTF-8", "entry" : "account", "from" : "", "gateway" : "1", "nonce" : nonce, "pagerefer" : "http://login.sina.com.cn/sso/logout.php", "prelt" : "41", "pwencode" : "rsa2", "returntype" : "TEXT", "rsakv" : rsakv, "savestate" : "30", "servertime" : servertime, "service" : "sso", "sp" : sp, "sr" : "1366*768", "su" : su, "useticket" : "0", "vsnf" : "1" } #用于登录到 http://login.sina.com.cn/signup/signin.php?entry=ss,将POST替换成下面的即可 """ post_data = { "cdult" : "3", "domain" : "sina.com.cn", "encoding" : "UTF-8", "entry" : "sso", "from" : "null", "gateway" : "1", "pagerefer" : "", "prelt" : "0", "returntype" : "TEXT", "savestate" : "30", "service" : "sso", "sp" : password, "sr" : "1366*768", "su" : su, "useticket" : "0", "vsnf" : "1" } """ return post_data 

2. Logger.py

# -*- coding: utf-8 -*-
"""
Created on Thu Nov 02 14:01:17 2016

@author: liudiwei
"""
import os
import logging  

class LogClient(object): def __init__(self): self.logger = None """#EXAMPLE logger = createLogger('mylogger', 'temp/logger.log') logger.debug('logger debug message') logger.info('logger info message') logger.warning('logger warning message') logger.error('logger error message') logger.critical('logger critical message') """ def createLogger(self, logger_name, log_file): prefix = os.path.dirname(log_file) if not os.path.exists(prefix): os.makedirs(prefix) # 创建一个logger logger = logging.getLogger(logger_name) logger.setLevel(logging.INFO) # 创建一个handler,用于写入日志文件 fh = logging.FileHandler(log_file) # 再创建一个handler,用于输出到控制台 ch = logging.StreamHandler() # 定义handler的输出格式formatter formatter = logging.Formatter('%(asctime)s | %(name)s | %(levelname)s | %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # 给logger添加handler logger.addHandler(fh) logger.addHandler(ch) self.logger = logger return self.logger 

2. SinaSpider.py

# -*- coding: utf-8 -*-
"""
Created on Tue Nov 08 10:14:38 2016

@author: liudiwei
"""
import os
import getpass
import json import requests import cookielib import urllib import urllib2 import gzip import StringIO import time import dataEncode from Logger import LogClient class SinaClient(object): def __init__(self, username=None, password=None): #用户输入的用户名与密码 self.username = username self.password = password #从prelogin.php中获取的数据 self.servertime = None self.nonce = None self.pubkey = None self.rsakv = None #请求时提交的数据列表 self.post_data = None self.headers = {} #用于存储登录后的session self.session = None self.cookiejar = None #用于输出log信息 self.logger = None #存储登录状态,初始状态为False self.state = False #初始时调用initParams方法,初始化相关参数 self.initParams() #初始化参数 def initParams(self): self.logger = LogClient().createLogger('SinaClient', 'out/log_' + time.strftime("%Y%m%d", time.localtime()) + '.log') self.headers = dataEncode.headers return self #设置username 和 password def setAccount(self, username, password): self.username = username self.password = password return self #设置post_data def setPostData(self): self.servertime, self.nonce, self.pubkey, self.rsakv = dataEncode.get_prelogin_info() self.post_data = dataEncode.encode_post_data(self.username, self.password, self.servertime, self.nonce, self.pubkey, self.rsakv) return self #使用requests库登录到 https://login.sina.com.cn def login(self, username=None, password=None): #根据用户名和密码给默认参数赋值,并初始化post_data self.setAccount(username, password) self.setPostData() #登录时请求的url login_url = r'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.15)' session = requests.Session() response = session.post(login_url, data=self.post_data) json_text = response.content.decode('gbk') res_info = json.loads(json_text) try: if res_info["retcode"] == "0": self.logger.info("Login success!") self.state = True #把cookies添加到headers中 cookies = session.cookies.get_dict() cookies = [key + "=" + value for key, value in cookies.items()] cookies = "; ".join(cookies) session.headers["Cookie"] = cookies else: self.logger.error("Login Failed! | " + res_info["reason"]) except Exception, e: self.logger.error("Loading error --> " + e) self.session = session return session #生成Cookie,接下来的所有get和post请求都带上已经获取的cookie def enableCookie(self, enableProxy=False): self.cookiejar = cookielib.LWPCookieJar() # 建立COOKIE cookie_support = urllib2.HTTPCookieProcessor(self.cookiejar) if enableProxy: proxy_support = urllib2.ProxyHandler({'http': 'http://122.96.59.107:843'}) # 使用代理 opener = urllib2.build_opener(proxy_support, cookie_support, urllib2.HTTPHandler) self.logger.info("Proxy enable.") else: opener = urllib2.build_opener(cookie_support, urllib2.HTTPHandler) urllib2.install_opener(opener) #使用urllib2模拟登录过程 def login2(self, username=None, password=None): self.logger.info("Start to login...") #根据用户名和密码给默认参数赋值,并初始化post_data self.setAccount(username, password) self.setPostData() self.enableCookie() #登录时请求的url login_url = r'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.15)' headers = self.headers request = urllib2.Request(login_url, urllib.urlencode(self.post_data), headers) resText = urllib2.urlopen(request).read() try: jsonText = json.loads(resText) if jsonText["retcode"] == "0": self.logger.info("Login success!") self.state = True #将cookie加入到headers中 cookies = ';'.join([cookie.name + "=" + cookie.value for cookie in self.cookiejar]) headers["Cookie"] = cookies else: self.logger.error("Login Failed --> " + jsonText["reason"]) except Exception, e: print e self.headers = headers return self #打开url时携带headers,此header需携带cookies def openURL(self, url, data=None): req = urllib2.Request(url, data=data, headers=self.headers) text = urllib2.urlopen(req).read() return self.unzip(text) #功能:将文本内容输出至本地 def output(self, content, out_path, save_mode="w"): self.logger.info("Download html page to local machine. | path: " + out_path) prefix = os.path.dirname(out_path) if not os.path.exists(prefix): os.makedirs(prefix) fw = open(out_path, save_mode) fw.write(content) fw.close() return self """ 防止读取出来的HTML乱码,测试样例如下 req = urllib2.Request(url, headers=headers) text = urllib2.urlopen(req).read() unzip(text) """ def unzip(self, data): data = StringIO.StringIO(data) gz = gzip.GzipFile(fileobj=data) data = gz.read() gz.close() return data #调用login1进行登录 def testLogin(): client = SinaClient() username = raw_input("Please input username: ") password = getpass.getpass("Please input your password: ") session = client.login(username, password) follow = session.post("http://weibo.cn/1669282904/follow").text.encode("utf-8") client.output(follow, "out/follow.html") #调用login2进行登录 def testLogin2(): client = SinaClient() username = raw_input("Please input username: ") password = getpass.getpass("Please input your password: ") session = client.login2(username, password) info = session.openURL("http://weibo.com/1669282904/info") client.output(info, "out/info2.html") if __name__ == '__main__': testLogin2() 

关于源码的分析,可以参考代码中的注解,如有不理解的地方,可在评论中提出。

运行

直接在Windows控制台运行 python SinaSpider.py ,然后根据提示输入用户名和密码即可。

运行结果展示

OK,可以成功的登录到微博了,接下来想爬取什么数据就尽情的爬吧。

 

更正一个我在运行时发现的问题:

password无法输入,将testLogin1和testLogin2中输入密码的

password = getpass.getpass("Please input your password: ")

改为  password = raw_input("Please input your password: ") 

转载于:https://www.cnblogs.com/EmilySun/p/6196540.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值