關于網站身份驗證
http協議被設計為無連接協議,但現實中,很多網站需要對用戶進行身份識別,cookie就是為此而誕生的。當我們用瀏覽器瀏覽網站時,瀏覽器會幫我們透明的處理cookie。而我們現在要第三方登錄網站,這就必須對cookie的工作流程有一定的了解。
另外,很多網站為了防止程序自動登錄而使用了驗證碼機制,驗證碼的介入會使登錄過程變得麻煩,但也還不算太難處理。
實際中douban.fm的登錄流程
為了模擬一個干凈(不使用已有cookie)的登錄流程,我使用chromium的隱身模式。
觀察請求和響應頭,可以看到,第一次請求的請求頭是沒有Cookie字段的,而服務器的響應頭中包含著Set-Cookie字段,這告訴瀏覽器下次請求該網站時需要攜帶Cookie。
這里我注意到了一個有意思的現象,訪問douban.fm,實際中經過了3次重定向。當然,一般來說我們并不需要關注這些細節,瀏覽器和高級的httplib會透明的處理重定向,但如果使用底層的C Socket,就必須小心的處理這些重定向。
點擊登錄按鈕,瀏覽器發起幾個新的請求,其中有幾個至關重要的請求,這幾個請求是我們第三方登錄douban.fm的關鍵所在。
首先,有一條請求的URL是http://douban.fm/j/new_captcha,請求該URL,服務器會返回一個隨機字符串,這有什么用呢?(其實是個驗證碼)
再看下一條請求,http://douban.fm/misc/captcha?size=m&id=0iPlm837LsnSsJTMJrf5TZ7e,這條請求會返回驗證碼。原來如此,請求http://douban.fm/j/new_captcha,將服務器返回的字符串作為下一條請求的id參數值。
我們可以寫一段python代碼來驗證我們的想法。
值得注意的是python提供了3個http庫,httplib、urllib和urllib2,能透明處理cookie的是urllib2,想我之前用httplib手動處理cookie,那個痛苦啊。
代碼如下:
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(CookieJar())) captcha_id = opener.open(urllib2.Request('http://douban.fm/j/new_captcha')).read().strip('"') captcha = opener.open(urllib2.Request('http://douban.fm/misc/captcha?size=m&id=' + captcha_id)).read()) file = open('captcha.jpg', 'wb') file = write(captcha) file.close()
這段代碼實現了驗證碼的下載。
接著,我們填寫表單,并提交。
可以看到,登錄表單的目標地址為http://douban.fm/j/login,參數有:
source: radio
alias: 用戶名
form_password: 密碼
captcha_solution: 驗證碼
captcha_id: 驗證碼ID
task: sync_channel_list
接下來要做的是用python構造一個表單。
opener.open( urllib2.Request('http://douban.fm/j/login'), urllib.urlencode({ 'source': 'radio', 'alias': username, 'form_password': password, 'captcha_solution': captcha, 'captcha_id': captcha_id, 'task': 'sync_channel_list'}))
服務器返回的數據格式是json,具體格式這里不贅訴了,大家可以自己測試。
我們怎么知道登錄是否起作用了呢?是了,之前的文章提到過channel=-3為紅心兆赫,是用戶的收藏列表,沒有登錄是獲取不到該頻道的播放列表的。請求http://douban.fm/j/mine/playlist?type=n&channel=-3,如果返回你自己收藏過的音樂列表,那么就說明登錄起作用了。
代碼整理
結合之前的版本和新增的登錄功能,再加上命令行參數處理、頻道選擇,一個稍稍完善的douban.fm就完成的
View Code #!/usr/bin/python # coding: utf-8 import sys import os import subprocess import getopt import time import json import urllib import urllib2 import getpass import ConfigParser from cookielib import CookieJar # 保存到文件 def save(filename, content): file = open(filename, 'wb') file.write(content) file.close() # 獲取播放列表 def getPlayList(channel='0', opener=None): url = 'http://douban.fm/j/mine/playlist?type=n&channel=' + channel if opener == None: return json.loads(urllib.urlopen(url).read()) else: return json.loads(opener.open(urllib2.Request(url)).read()) # 發送桌面通知 def notifySend(picture, title, content): subprocess.call([ 'notify-send', '-i', os.getcwd() + '/' + picture, title, content]) # 登錄douban.fm def login(username, password): opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(CookieJar())) while True: print '正在獲取驗證碼……' captcha_id = opener.open(urllib2.Request( 'http://douban.fm/j/new_captcha')).read().strip('"') save( '驗證碼.jpg', opener.open(urllib2.Request( 'http://douban.fm/misc/captcha?size=m&id=' + captcha_id )).read()) captcha = raw_input('驗證碼: ') print '正在登錄……' response = json.loads(opener.open( urllib2.Request('http://douban.fm/j/login'), urllib.urlencode({ 'source': 'radio', 'alias': username, 'form_password': password, 'captcha_solution': captcha, 'captcha_id': captcha_id, 'task': 'sync_channel_list'})).read()) if 'err_msg' in response.keys(): print response['err_msg'] else: print '登錄成功' return opener # 播放douban.fm def play(channel='0', opener=None): while True: if opener == None: playlist = getPlayList(channel) else: playlist = getPlayList(channel, opener) if playlist['song'] == []: print '獲取播放列表失敗' break picture, for song in playlist['song']: picture = 'picture/' + song['picture'].split('/')[-1] # 下載專輯封面 save( picture, urllib.urlopen(song['picture']).read()) # 發送桌面通知 notifySend( picture, song['title'], song['artist'] + ' ' + song['albumtitle']) # 播放 player = subprocess.Popen(['mplayer', song['url']]) time.sleep(song['length']) player.kill() def main(argv): # 默認參數 channel = '0' user = '' password = '' # 獲取、解析命令行參數 try: opts, args = getopt.getopt( argv, 'u:p:c:', ['user=', 'password=', 'channel=']) except getopt.GetoptError as error: print str(error) sys.exit(1) # 命令行參數處理 for opt, arg in opts: if opt in ('-u', '--user='): user = arg elif opt in ('-p', '--password='): password = arg elif opt in ('-c', '--channel='): channel = arg if user == '': play(channel) else: if password == '': password = getpass.getpass('密碼:') opener = login(user, password) play(channel, opener) if __name__ == '__main__': main(sys.argv[1:])
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com