Python 调用新浪微博认证登陆第三方网站

Python 调用新浪微博认证登陆第三方网站

有些第三方网站为了方便用户,可以通过新浪微博、QQ 等方式进行登陆。为了爬取这些网站上的内容,需要先用 python 进行登陆操作,成功后带上 cookies 再爬取内容。这里记录了通过新浪认证的方法。

首先打开 chrome 浏览器,通过调试工具抓取登陆过程(这里设置为移动端的访问)。

20170721_sinaapi_1.png

通过查找 Request Headers 可以获得 user-agent 等信息。在 python 中进行以下设置。

user_agent = ('Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) '
          'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36')
headers = {
    'User-Agent': user_agent,
    'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Encoding':'gzip, deflate, sdch',
    'Accept-Language':'zh-CN,zh;q=0.8',
}

同时可以看到 Request URL,并且这一步获得的响应码是 301,即跳转的到 Response 中的 Location 网页,这里只需要利用 python 中 requests库中的Session 函数,即可自动处理这种跳转。

index_url = "http://m.lixinger.com"
s = requests.Session()
response = s.get(index_url, headers=headers, timeout=10)

当我们点击“使用微博登陆”后,会产生新的链接如下图所示。这个网址是我们直接请求的网址,但是又被 302 重定向了。可以看到 Location 中的网址包含了 client_id,这个 id 以后会用到,虽然每次请求这个 id 不变,但为了防止以后变化,还是把它提取出来。

20170721_sinaapi_2.png

lxr_redirect_url ='https://m.lixinger.com/auth/wb/login?next=https://m.lixinger.com'
response = s.get(lxr_redirect_url, headers=headers, timeout=10)
# response = s.get(sinaapi_url, headers=headers, timeout=10) # 上一步网页会自动跳转到这一步的网页
client_id = urlparse(response.url).query.split('&')[0].split('=')[1]

sinaapi_url = ('https://api.weibo.com/oauth2/authorize?client_id='+ client_id + '&response_type=code&scope=email&redirect_uri=https://www.lixinger.com/auth-callback?login_method=wb&next=https://m.lixinger.com')

下一步就要到新浪认证阶段了。我在网上找到了新浪微博的登陆方法,新浪微博在本地对密码加密后再上传,假面方法我也不太懂,能用就好。

def get_su(username):
    """
    对 email 地址和手机号码 先使用 javascript 中 encodeURIComponent
    对应 Python 3 中的是 urllib.parse.quote_plus 然后在 base64 加密后decode
    """
    username_quote = quote_plus(username)
    username_base64 = base64.b64encode(username_quote.encode("utf-8"))
    return username_base64.decode("utf-8")

def get_password(password, servertime, nonce, pubkey):
    rsaPublickey = int(pubkey, 16)
    key = rsa.PublicKey(rsaPublickey, 65537)  # 创建公钥
    message = str(servertime) + '\t' + str(nonce) + '\n' + str(password)  # 拼接明文js加密文件中得到
    message = message.encode("utf-8")
    passwd = rsa.encrypt(message, key)  # 加密
    passwd = binascii.b2a_hex(passwd)  # 将加密信息转换为16进制。
    return passwd

def get_server_data(su):
    pre_url = "https://login.sina.com.cn/sso/prelogin.php?entry=openapi&callback=sinaSSOController.preloginCallBack&su="
    pre_url = pre_url + su + "&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.18)&_="+str(int(time.time()*1000))
    # pre_data_res = s.get(pre_url, headers=headers,timeout=10)
    pre_data_res = requests.get(pre_url, headers=headers,timeout=10)
    sever_data = eval(pre_data_res.content.decode("utf-8").replace("sinaSSOController.preloginCallBack", ''))
    return sever_data

get_su 函数是编码用户名,get_password 是加密用户密码。get_server_data 是需要在登陆前预连接新浪服务器获取参数,登录时需要将获得的参数一并提交到服务器。当我们输入完用户名后,后台会立即提交一次。如图所示。返回的参数在 Response 标签里面。

20170721_sinaapi_3.png

我们获得了这些参数后,整理成合适的格式就可以准备提交了。

su = get_su(username)
sever_data = get_server_data(su)
servertime = sever_data["servertime"]
nonce = sever_data['nonce']
rsakv = sever_data["rsakv"]
pubkey = sever_data["pubkey"]
showpin = sever_data["showpin"]
password_secret = get_password(password, servertime, nonce, pubkey)

postdata = {
        'entry': 'openapi',
        'gateway': '1',
        'from': '',
        'savestate': '0',
        'useticket': '1',
        'pagerefer': "https://m.lixinger.com/login",
        'ct':'1800',
        's':'1',
        'vsnf': '1',
        'vsnval': '',
        'door':'',
        'appkey':'fECuW',
        'su': su,
        'service': 'miniblog',
        'servertime': servertime,
        'nonce': nonce,
        'pwencode': 'rsa2',
        'rsakv': rsakv,
        'sp': password_secret,
        'sr': '1366*768',
        'encoding': 'UTF-8',
        'cdult':'2',
        'domain':'weibo.com',
        'prelt': '465',
        'returntype': 'TEXT'
        }


sina_login_url = ('https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.18)&_='+
    str(int(time.time()*1000))+'&openapilogin=qrcode')

login_page = s.post(sina_login_url, data=postdata, headers=headers)
login_loop = (login_page.content.decode("unicode_escape"))

注意提交时的网址上含有时间戳,同时使用 post 请求。登陆成功后会获得 cookies。接下来第三条 ,向 https://api.weibo.com/oauth2/authorize 发送请求。请求包括上一次会话返回的数据。

redirect_uri='https://www.lixinger.com/auth-callback?login_method=wb&next=https://m.lixinger.com'
callback_url = ('https://api.weibo.com/2/oauth2/authorize?client_id' + client_id + '&response_type=code&display=default&redirect_uri='+redirect_uri+'&from=&with_cookie=')

postdata2 ={
    'display':'mobile',
    'action':'login',
    'ticket':eval(login_loop)['ticket'],
    'scope':'email',
    'isLoginSina':'',
    'withOfficalFlag':'0',
    'quick_auth':'false',
    'withOfficalAccount':'',
    'response_type':'code',
    'regCallback':callback_url,
    'redirect_uri':redirect_uri,
    'client_id':'159681110',
    'appkey62':'fECuW',
    'state':'',
    'from':'',
    'officialMobile':'null',
    'verifyToken':'null',
    'version':'',
    'sso_type':'',
}

authorize_url = 'https://api.weibo.com/oauth2/authorize'
ref_headers = {
    "User-agent": user_agent,
    "Referer": sinaapi_url,
    "Content-Type": "application/x-www-form-urlencoded"
    }

authorize_page = s.post(authorize_url, data=postdata2, headers=ref_headers)

20170721_sinaapi_4.png

这里提交的表单内容是在 Form Data 里出现的。在 General 中的 Request Method 显示了提交方法 Post。多提交几次就会发现有些内容是不变的,所以只需要写死就好了。需要注意的是这里的 header 一定要加上 Referer,否则会显示登陆成功但不跳转。如果一切顺利的话现在已经登陆成功了。我们可以保存 cookies 到本地,方便下次使用。

def save_cookies(requests_cookiejar, filename):
    with open(filename, 'wb') as f:
        pickle.dump(requests_cookiejar, f)

def load_cookies(filename):
    with open(filename, 'rb') as f:
        return pickle.load(f)

我们可以获取特殊页面,尝试检测特征值(如你的用户名)来判断是否登录成功。

lxr_page = s.get('https://m.lixinger.com/api/users/current', headers=headers)
lxr_json = json.loads(lxr_page.content.decode('utf-8'))
if lxr_json['name'] == '你的用户名':
    print('Login lixinger success...')
    save_cookies(s.cookies, 'lxr_cookies')
    print('save lxr_cookies...')
else:
    print('Login lixinger error...')

从本地加载 cookies 后可以使用赋值或 update 方法将 cookies 添加到 Session 中,这样就不用在每次连接中都显示添加 cookies。

s = requests.Session()
lxr_cookies = load_cookies('lxr_cookies')
s.cookies.update(lxr_cookies)
# s.cookies=lxr_cookies # 两种方法都可以

以上就是 python 调取新浪微博认证的全部过程。

参考资料:

  1. 超详细的Python实现新浪微博模拟登陆
  2. “史上最详细”的Python模拟登录新浪微博流程
  3. python模拟新浪微博登陆之获取cookies
  4. python模拟登录新浪微博自动获得调用新浪api所需的code
  5. How to save requests (python) cookies to a file?
  6. python requests库添加自定义cookie的方法
  7. Requests设置和保存Cookies的方法
  8. 模拟登录一些知名的网站,为了方便爬取需要登录的网站