花了4个晚上终于把模拟登录新浪微博学习完了,相对于知乎迷你登录,微博登录的过程确实难度大了很多,好多知识点都不懂,所以虽然把代码都码了一遍,但很多都是照猫画虎,其实还有很多地方不是十分清楚。代码中写了很多注解(对向我一样的初学者来说应该很有必要),所以看以来不是很简洁,其实源码查不到150行。
希望本文能对向我一样的初学者能起到一点借鉴作用
写以下我这几天自学的过程:
一、第一天直接看xchaoinfo的源码,完全看不懂,好不好。看了15分钟直接放弃,转百度看图文教程,还好网上的教程不算少(其实方法都一样,代码也基本没多少差别,应该起初都出自同一作者),
网上从抓包介绍,好吧又不会,那就学吧~~~
这样一天就过去了,感觉什么都没学到
二、第二天继续抓包,还是没什么进展,期间换了各种抓包工具,都不太会用,感觉网上介绍的看方法用这些抓包工具都没发抓到,这样一天快要过去的时候,想想还是用浏览器的F12吧,这样就用了火狐浏览器(因为之前一致用chrome的F12看源码,也抓不到js文件),下载了firebug,用起来还是一头雾水,晚上想想还是睡觉吧,睡觉前又百度了firebug的用法,本来也没抱希望有什么用,可是突然奇迹发生了,多次尝试了之后找到了教程中的js文件,太晚了,睡觉!!!
自学真的很痛苦,可能很简单的问题会难住好几天。
三、第三天,继续看js文件,抓post的各种属性,看着教程试着理解密码加密的方法,差不多的时候继续回去啃源码,中间碰到不会的模块google和自己用idle一点点测试,理解个大概继续往下啃,啃到一般就睡觉了。
四、第四天的继续下面的代码,后面的表单登录相对简单,因为之前有相关的学习经验,进过两个小时,终于测试成功了。
下面把源码贴出来给自己留个纪念,也给需要的书友提供一点思路。
"""
新浪微博模拟登录练习,本教程参考了很多网上的教程,很多教程的源码都差不多,本文的代码主要修改自author : "xchaoinfo" github的源码
新浪微博的模拟登录对新手来说比较困难(我就是初学者,以前没有任何编程基础,因为对现在的工作状态不满意,2017年开始自学python)
author : xcaojianhong
qq:1254798548
date:2017.02.21
"""
import time
import base64 #加密模块
import rsa #加密模块
import binascii #二进制模块
import requests #是的,本次练习还是用的requests库,本意是学习scrapy爬虫,可是找不到模拟登录的详细的教程,只能先搁置了
import re #正则模块,不太会用
import random #我不是太理解为甚要rondom模块
import http.cookiejar
from PIL import Image #这个只用到简单的方法,高级的本人也不会
from urllib.parse import quote_plus #涉及编码问题,需要用到该模块
'''
如果没有开启登录保护,不用输入验证码就可以登录
如果开启登录保护,需要输入验证码,PIL库就是用了打开验证码的图片用的,就只是打开,还是需要手动输入的,不是完成自动验证
'''
# 构造 Request headers,这个不解释了,很多基础教程中都会有的,一般网站都有反爬虫的机制,最常见的就是识别是不是浏览器访问的,这个就是用来模拟浏览器的
user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
headers = {
'User-Agent': user_agent
}
session = requests.session() #实例化一个session类,session类能够自动处理cookies
session.cookies = http.cookiejar.LWPCookieJar(filename = 'cookies') #用于保存cookies,对爬取后需爬去有用
index_url = "http://weibo.com/login.php" #这个是微博登录的原始的url
# 访问初始页面带上 cookies
try:
session.get(index_url,headers=headers,timeout=2) #为什么用延时参数不太清楚,我自己写的话就只写excpet后面的一句
except:
session.get(index_url,headers=headers)
try:
input = raw_input #这个自行百度两个input的差异,因为我也不太清楚
except:
pass
# 下面开始困难了,我光抓包就花了两个晚上才看懂网上讲的方法是怎么回事,期间换了很多抓包的工具,最后还是觉得firebug好用
def get_su(username):
"""
1.输入账号,会先在js中通过encodeURIComponent进行编码,对应python的urllib.parse.quote_plus方法,问我怎么知道的,是因为源码中的注释说的
密码的加密方法在ssologin.js文件中,为我为什么知道,网上教程说的,自己分析的话我估计2天都不一定能找到。
在js里面有这样的代码:
username = sinaSSOEncoder.base64.encode(urlencode(username))
urlencode函数中用了encodeURIComponent编码,具体代码就不贴了,自己仔细看能找到。这里能看到用户名用了base64进行加密
2.其实我也不知道怎么解释,为什么要先对username_quote进行utf-8编码,然后才base64加密,照着写就行了,我在python中测试不编码加密会报错,具体原因不清楚
"""
username_quote = quote_plus(username) #详细解释看1
username_base64 = base64.b64encode(username_quote.encode('utf-8')) #详细解释看2
# print(username_base64.decode('utf-8')) 测试用
return username_base64.decode('utf-8') #解码成utf-8格式字符串
# 预登陆获得 servertime, nonce, pubkey, rsakv 这些属性在后面密码加密过程中需要用到
def server_data(su):
"""
3.通过抓包分析我们看到,https://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack
&su=ODg4ODg4OA==&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.18)&_=1487602221124这个url的response是一个json格式的文本,其中包含
servertime, nonce, pubkey, rsakv这些属性,这正是我们需要的,&su=ODg4ODg4OA==,这个su并不是get_su的返回值,但是相近,直接用get_su的返回值不影响
另外这里用到了time模块,用了生成时间戳,1487602221124这个就是时间戳
"""
#get_su()解释了那么多,就是为了获得su,虽然在这里su不那么重要(因为实际预登录url中su并不是完全正确的),但是最后的post表单中需要用到su
pre_url = "http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su="\
+ su + "&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.18)&_=" + str(int(time.time() * 1000)) #为什么是这个url,详细请看3
pre_data_res = session.get(pre_url, headers=headers)
sever_data = eval(pre_data_res.content.decode("utf-8").replace("sinaSSOController.preloginCallBack", '')) #这里用repalce替换
# print(sever_data) 测试用
return sever_data
# 这个是本次登录中最难的地方,需要用到上面的参数
def get_password(password,servertime,nonce,pubkey):
"""
具体为什么需要这些参数,还是要仔细分析之前提到的js文件,里面有密码加密的过程
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"); #rsaPubkey就是pubkey,js的代码就不贴了
password = RSAKey.encrypt([me.servertime, me.nonce].join("\t") + "\n" + password)
"""
rsapublickey = int(pubkey,16) #如果是16进制,则转化为10进制
key = rsa.PublicKey(rsapublickey,65537) # '10001'转化为10进制就是65537
message = str(servertime) + '\t' + str(nonce) + '\n' + str(password) # 仔细对着js代码慢慢看
message = message.encode("utf-8")
#print(message) 测试用
passwd = rsa.encrypt(message, key) # 加密,为什么要用rsa,我也不知道,源代码就用的,我也不懂这个方法的具体的用途,反正看着和js的代码相似的,当成是python版本的翻译看
#print(passwd) 测试用
passwd = binascii.b2a_hex(passwd) # 将加密信息转换为16进制,post需要16进制ps,猜测的,对编码不太了解。
# print(passwd) 测试用
return passwd
# 获得验证码,并下载后用PIL模块自动打开,没有安装的话手动去爬虫文件的目录中手动打开图片
def get_cha(pic):
"""
我没有详细分析需要验证码的收获,因为我登录新浪微博基本没有碰到需要验证码的时候
下面用到了随机数,我不是太理解,既然能用随机数,为什么不能用固定的数字,反正都是我们自己构造的一个数字
"""
cha_url = "http://login.sina.com.cn/cgi/pin.php?r="
cha_url = cha_url + str(int(random.random() * 100000000)) + "&s=0&p="
cha_url = cha_url + pcid
cha_page = session.get(cha_url, headers=headers)
with open("cha.jpg", 'wb') as f:
f.write(cha_page.content)
f.close()
try:
im = Image.open("cha.jpg")
im.show()
im.close()
except:
print(u"请到当前目录下,找到验证码后输入")
def login(username,password):
"""
激动人心的时候快要到了,上面作的一些准备工作就是为了获得post的属性
"""
su = get_su(username) #获得加密的su
sever_data = server_data(su) #获得server_data函数返回的字典
servertime = sever_data['servertime']
nonce = sever_data['nonce']
rsakv = sever_data["rsakv"]
pubkey = sever_data["pubkey"]
showpin = sever_data["showpin"] #这个参数的值关系到是否需要输入验证码,0表示不需要,1表示需要
password_secret = get_password(password,servertime,nonce,pubkey) #获得加密的sp
# su,sp,servertime,nonce,rsakv,sp属性是变化的,其他的都可以写死
postdata = {
'entry':'weibo',
'gateway':'1',
'from':'',
'savestate':'7',
'useticket':'1',
'pagerefer':"http://login.sina.com.cn/sso/logout.php?entry=miniblog&r=http%3A%2F%2Fweibo.com%2Flogout.php%3Fbackurl",
'wsseretry':'servertime_error',
'vsnf':'1',
'su':su,
'service':'miniblog',
'servertime':servertime,
'nonce':nonce,
'pwencode':'rsa2',
'rsakv':rsakv,
'sp':password_secret,
'sr':'1536*864',
'encoding':'UTF-8',
'prelt':'105',
'url':'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
'returntype':'META'
}
login_url = 'http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.18)' #用于登录的url,psot表单提交的url
if showpin == 0:
login_page = session.post(login_url, data=postdata, headers=headers)
else:
pcid = sever_data["pcid"]
get_cha(pcid)
postdata['door'] = input(u"请输入验证码")
login_page = session.post(login_url, data=postdata, headers=headers)
login_loop = (login_page.content.decode("GBK")) #看网络抓包,知道是gbk编码
#print(login_loop)
pa = r'location\.replace\([\'"](.*?)[\'"]\)' #这里的正则查相关的教程
loop_url = re.findall(pa, login_loop)[0]
#print(loop_url)
# 正常访问login_page就结束了,但是微博丧心病狂的还需要一部跳转,请继续往下看,我自己写的都累了,需要进一步访问login_loop这个url
login_index = session.get(loop_url, headers=headers)
uuid = login_index.text
print(login_index.status_code) #测试登录是否成功
session.cookies.save() #保存cookies
# ↑↑↑↑↑↑到上面其实已经完成了登录了
response = session.get('http://weibo.com/',headers=headers)
# print(response.text) #测试用,可以打印出来看看是否与正常登录看到的微博首页的源码一样了
# 以下是原文作者用来登录微博个人首页获得使用者微博账号,并打印出来的代码,我没有详细解析
uuid_pa = r'"uniqueid":"(.*?)"'
uuid_res = re.findall(uuid_pa, uuid)[0]
web_weibo_url = "http://weibo.com/%s/profile?topnav=1&wvr=6&is_all=1" % uuid_res
weibo_page = session.get(web_weibo_url, headers=headers)
weibo_pa = r'<title>(.*?)</title>'
# print(weibo_page.content.decode("utf-8"))
userID = re.findall(weibo_pa, weibo_page.content.decode("utf-8", 'ignore'), re.S)[0] #不加re.S参数好像没什么不同,上文我没加
print(u"欢迎你 %s, 你在正在使用 xcaojianhong 写的爬虫模拟登录微博" % userID)
print(input(u'输入任何键继续'))
print(u'好吧其实大部分代码都是参考xchaoinfo的文章')
if __name__ == "__main__":
username = input(u'用户名:')
password = input(u'密码:')
login(username, password)
测试结果: