python实现douban.fm浅易客户端
一个月前心血来潮用python实现了一个简朴的douban.fm客户端,打算是连续将其完善成为Ubuntu下可替代web版本的douban.fm客户端。但厥后因为事多,被一直搁着,没有再继承完善。就在昨天,一位园友在评论中提到了登录的实现,固然最近依然事多,但溘然很想实现这个成果。正好,前几天因为一些需要,曾用python实现过网站登录,约摸预计这douban.fm的登录不会差太多。
关于网站身份验证
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'] + '\n' + 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:])
#p#分页标题#e#
运行结果图