Python模拟登陆新浪微博

作者:@houkai
本文为作者原创,转载请注明出处:https://www.cnblogs.com/houkai/p/3488468.html


上篇介绍了新浪微博的登陆过程,这节使用 Python 编写一个模拟登陆的程序。讲解与程序如下:

1、主函数(WeiboMain.py):

import urllib2
import cookielib

import WeiboEncode
import WeiboSearch

if name == 'main':
weiboLogin
= WeiboLogin('×××@gmail.com', '××××')#邮箱(账号)、密码
if weiboLogin.Login() == True:
print "登陆成功!"

前两个 import 是加载 Python 的网络编程模块,后面的 import 是加载另两个文件 WeiboEncode.py 和 Weiboseach.py(稍后介绍)。主函数新建登陆对象,然后登陆。

2、WeiboLogin 类(WeiboMain.py):

class WeiboLogin:
    def __init__(self, user, pwd, enableProxy = False):
        "初始化 WeiboLogin,enableProxy 表示是否使用代理服务器,默认关闭"
    <span style="color: rgba(0, 0, 255, 1)">print</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Initializing WeiboLogin...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
    self.userName </span>=<span style="color: rgba(0, 0, 0, 1)"> user
    self.passWord </span>=<span style="color: rgba(0, 0, 0, 1)"> pwd
    self.enableProxy </span>=<span style="color: rgba(0, 0, 0, 1)"> enableProxy
    
    self.serverUrl </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://login.sina.com.cn/sso/prelogin.php?entry=weibo&amp;callback=sinaSSOController.preloginCallBack&amp;su=&amp;rsakt=mod&amp;client=ssologin.js(v1.4.11)&amp;_=1379834957683</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
    self.loginUrl </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.11)</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
    self.postHeader </span>= {<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">User-Agent</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Mozilla/5.0 (Windows NT 6.1; rv:24.0) Gecko/20100101 Firefox/24.0</span><span style="color: rgba(128, 0, 0, 1)">'</span>}</pre>

初始化函数,定义了两个关键的 url 成员:self.serverUrl 用于登陆的第一步(获取 servertime、nonce 等),这里的第一步实质包含了解析新浪微博的登录过程的 1 和 2;self.loginUrl 用于第二步(加密用户和密码后,POST 给该 URL,self.postHeader 是 POST 的头信息),这一步对应于解析新浪微博的登录过程的 3。类内函数还有 3 个:

def Login(self):
        "登陆程序"  
        self.EnableCookie(self.enableProxy)#cookie 或代理服务器配置
        
        serverTime, nonce, pubkey, rsakv = self.GetServerTime()#登陆的第一步
        postData = WeiboEncode.PostEncode(self.userName, self.passWord, serverTime, nonce, pubkey, rsakv)#加密用户和密码
        print "Post data length:\n", len(postData)
    req </span>=<span style="color: rgba(0, 0, 0, 1)"> urllib2.Request(self.loginUrl, postData, self.postHeader)
    </span><span style="color: rgba(0, 0, 255, 1)">print</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Posting request...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
    result </span>= urllib2.urlopen(req)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">登陆的第二步——解析新浪微博的登录过程中3</span>
    text =<span style="color: rgba(0, 0, 0, 1)"> result.read()
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">:
        loginUrl </span>= WeiboSearch.sRedirectData(text)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">解析重定位结果</span>

urllib2.urlopen(loginUrl)
except:
print 'Login error!'
return False

    </span><span style="color: rgba(0, 0, 255, 1)">print</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Login sucess!</span><span style="color: rgba(128, 0, 0, 1)">'</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> True</pre>

self.EnableCookie 用于设置 cookie 及代理服务器,网络上有很多免费的代理服务器,为防止新浪封 IP,可以使用。然后使登陆的第一步,访问新浪服务器得到 serverTime 等信息,然后利用这些信息加密用户名和密码,构建 POST 请求;执行第二步,向 self.loginUrl 发送用户和密码,得到重定位信息后,解析得到最终跳转到的 URL,打开该 URL 后,服务器自动将用户登陆信息写入 cookie,登陆成功。

def EnableCookie(self, enableProxy):
    "Enable cookie & proxy (if needed)."
cookiejar </span>= cookielib.LWPCookieJar()<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">建立cookie</span>
cookie_support =<span style="color: rgba(0, 0, 0, 1)"> urllib2.HTTPCookieProcessor(cookiejar)

</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> enableProxy:
    proxy_support </span>= urllib2.ProxyHandler({<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http</span><span style="color: rgba(128, 0, 0, 1)">'</span>:<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://xxxxx.pac</span><span style="color: rgba(128, 0, 0, 1)">'</span>})<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">使用代理</span>
     opener =<span style="color: rgba(0, 0, 0, 1)"> urllib2.build_opener(proxy_support, cookie_support, urllib2.HTTPHandler)
    </span><span style="color: rgba(0, 0, 255, 1)">print</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Proxy enabled</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">:
    opener </span>=<span style="color: rgba(0, 0, 0, 1)"> urllib2.build_opener(cookie_support, urllib2.HTTPHandler)

urllib2.install_opener(opener)</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">构建cookie对应的opener</span></pre>

EnableCookie 函数比较简单,可以参见cookie——小甜品

def GetServerTime(self):
    "Get server time and nonce, which are used to encode the password"
<span style="color: rgba(0, 0, 255, 1)">print</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Getting server time and nonce...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
serverData </span>= urllib2.urlopen(self.serverUrl).read()<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">得到网页内容</span>
 <span style="color: rgba(0, 0, 255, 1)">print</span><span style="color: rgba(0, 0, 0, 1)"> serverData

</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">:
    serverTime, nonce, pubkey, rsakv </span>= WeiboSearch.sServerData(serverData)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">解析得到serverTime,nonce等</span>
     <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> serverTime, nonce, pubkey, rsakv
</span><span style="color: rgba(0, 0, 255, 1)">except</span><span style="color: rgba(0, 0, 0, 1)">:
    </span><span style="color: rgba(0, 0, 255, 1)">print</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Get server time &amp; nonce error!</span><span style="color: rgba(128, 0, 0, 1)">'</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span> None</pre>

WeiboSearch 文件中的函数主要用于解析从服务器得到的数据,比较简单。

3、sServerData 函数(WeiboSearch.py):

import re
import json

def sServerData(serverData):
"Search the server time & nonce from server data"

p </span>= re.compile(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">\((.*)\)</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
jsonData </span>= p.search(serverData).group(1<span style="color: rgba(0, 0, 0, 1)">)
data </span>=<span style="color: rgba(0, 0, 0, 1)"> json.loads(jsonData)
serverTime </span>= str(data[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">servertime</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">])
nonce </span>= data[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">nonce</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
pubkey </span>= data[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pubkey</span><span style="color: rgba(128, 0, 0, 1)">'</span>]<span style="color: rgba(0, 128, 0, 1)">#

rsakv = data['rsakv']#
print "Server time is:", serverTime
print "Nonce is:", nonce
return serverTime, nonce, pubkey, rsakv

解析过程主要使用了正则表达式和 JSON,这部分比较容易理解。另外 Login 中解析重定位结果部分函数也在这个文件中如下:

def sRedirectData(text):
    p = re.compile('location\.replace\([\'"](.*?)[\'"]\)')
    loginUrl = p.search(text).group(1)
    print 'loginUrl:',loginUrl
    return loginUrl

4、从第一步到第二步要对用户和密码进行加密,编码操作(WeiboEncode.py)

import urllib
import base64
import rsa
import binascii

def PostEncode(userName, passWord, serverTime, nonce, pubkey, rsakv):
"Used to generate POST data"

encodedUserName </span>= GetUserName(userName)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">用户名使用base64加密</span>
 encodedPassWord = get_pwd(passWord, serverTime, nonce, pubkey)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">目前密码采用rsa加密</span>
 postPara =<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">entry</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">weibo</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">gateway</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">from</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">savestate</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">7</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">userticket</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ssosimplelogin</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">vsnf</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">vsnval</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">su</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: encodedUserName,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">service</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">miniblog</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">servertime</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: serverTime,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">nonce</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: nonce,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pwencode</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">rsa2</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sp</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: encodedPassWord,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">encoding</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">UTF-8</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">prelt</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">115</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">rsakv</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: rsakv,     
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">url</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://weibo.com/ajaxlogin.php?framelogin=1&amp;callback=parent.sinaSSOController.feedBackUrlCallBack</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">returntype</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">META</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
postData </span>= urllib.urlencode(postPara)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">网络编码</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> postData</pre>

PostEncode 函数构建 POST 的消息体,要求构建得到内容与真正登陆所需的信息相同。难点在用户名和密码的加密方式:

def GetUserName(userName):
    "Used to encode user name"
userNameTemp </span>=<span style="color: rgba(0, 0, 0, 1)"> urllib.quote(userName)
userNameEncoded </span>= base64.encodestring(userNameTemp)[:-1<span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> 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

新浪登录过程,密码的加密方式原来是 SHA1,现在变为了 RSA,以后可能还会变化,但是各种加密算法在 Python 中都有对应的实现,只要发现它的加密方式(破碎的心),程序比较易于实现。

到这里,Python 模拟登陆新浪微博就成功了,运行输出:

loginUrl: http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack&ssosavestate=1390390056&ticket=ST-MzQ4NzQ5NTYyMA==-1387798056-xd-284624BFC19FE242BBAE2C39FB3A8CA8&retcode=0
Login sucess!

如果需要爬取微博中的信息,接下来只需要在 Main 函数后添加爬取、解析模块就可以了,比如读取某微博网页的内容:

htmlContent = urllib2.urlopen(myurl).read()#得到 myurl 网页的所有内容 (html)

大家可以根据不同的需求设计不同的爬虫模块了,模拟登陆的代码放在这里