设为首页 收藏本站
查看: 1349|回复: 0

[经验分享] python实现的douban.fm客户端,添加登录功能

[复制链接]

尚未签到

发表于 2015-4-19 07:56:04 | 显示全部楼层 |阅读模式
  一个月前心血来潮用python实现了一个简单的douban.fm客户端,计划是陆续将其完善成为Ubuntu下可替代web版本的douban.fm客户端。但后来因为事多,被一直搁着,没有再继续完善。就在昨天,一位园友在评论中提到了登录的实现,虽然最近依然事多,但突然很想实现这个功能。正好,前几天因为一些需要,曾用python实现过网站登录,约摸估计这douban.fm的登录不会差太多。

关于网站身份验证
  http协议被设计为无连接协议,但现实中,很多网站需要对用户进行身份识别,cookie就是为此而诞生的。当我们用浏览器浏览网站时,浏览器会帮我们透明的处理cookie。而我们现在要第三方登录网站,这就必须对cookie的工作流程有一定的了解。
  另外,很多网站为了防止程序自动登录而使用了验证码机制,验证码的介入会使登录过程变得麻烦,但也还不算太难处理。

实际中douban.fm的登录流程
  为了模拟一个干净(不使用已有cookie)的登录流程,我使用chromium的隐身模式。
DSC0000.png
  观察请求和响应头,可以看到,第一次请求的请求头是没有Cookie字段的,而服务器的响应头中包含着Set-Cookie字段,这告诉浏览器下次请求该网站时需要携带Cookie。
  这里我注意到了一个有意思的现象,访问douban.fm,实际中经过了3次重定向。当然,一般来说我们并不需要关注这些细节,浏览器和高级的httplib会透明的处理重定向,但如果使用底层的C Socket,就必须小心的处理这些重定向。
  点击登录按钮,浏览器发起几个新的请求,其中有几个至关重要的请求,这几个请求是我们第三方登录douban.fm的关键所在。
  首先,有一条请求的URL是http://douban.fm/j/new_captcha,请求该URL,服务器会返回一个随机字符串,这有什么用呢?
   DSC0001.png
  再看下一条请求,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()
  这段代码实现了验证码的下载。
  接着,我们填写表单,并提交。
DSC0002.png
  可以看到,登录表单的目标地址为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就完成的。


DSC0003.gif DSC0004.gif View Code


  1 #!/usr/bin/python
  2 # coding: utf-8
  3
  4 import sys
  5 import os
  6 import subprocess
  7 import getopt
  8 import time
  9 import json
10 import urllib
11 import urllib2
12 import getpass
13 import ConfigParser
14 from cookielib import CookieJar
15
16 # 保存到文件
17 def save(filename, content):
18     file = open(filename, 'wb')
19     file.write(content)
20     file.close()
21
22
23 # 获取播放列表
24 def getPlayList(channel='0', opener=None):
25     url = 'http://douban.fm/j/mine/playlist?type=n&channel=' + channel
26     if opener == None:
27         return json.loads(urllib.urlopen(url).read())
28     else:
29         return json.loads(opener.open(urllib2.Request(url)).read())
30
31
32 # 发送桌面通知
33 def notifySend(picture, title, content):
34     subprocess.call([
35         'notify-send',
36         '-i',
37         os.getcwd() + '/' + picture,
38         title,
39         content])
40
41
42 # 登录douban.fm
43 def login(username, password):
44     opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(CookieJar()))
45     while True:
46         print '正在获取验证码……'
47         captcha_id = opener.open(urllib2.Request(
48             'http://douban.fm/j/new_captcha')).read().strip('"')
49         save(
50             '验证码.jpg',
51             opener.open(urllib2.Request(
52                 'http://douban.fm/misc/captcha?size=m&id=' + captcha_id
53             )).read())
54         captcha = raw_input('验证码: ')
55         print '正在登录……'
56         response = json.loads(opener.open(
57             urllib2.Request('http://douban.fm/j/login'),
58             urllib.urlencode({
59                 'source': 'radio',
60                 'alias': username,
61                 'form_password': password,
62                 'captcha_solution': captcha,
63                 'captcha_id': captcha_id,
64                 'task': 'sync_channel_list'})).read())
65         if 'err_msg' in response.keys():
66             print response['err_msg']
67         else:
68             print '登录成功'
69             return opener
70
71
72 # 播放douban.fm
73 def play(channel='0', opener=None):
74     while True:
75         if opener == None:
76             playlist = getPlayList(channel)
77         else:
78             playlist = getPlayList(channel, opener)
79         
80         if playlist['song'] == []:
81             print '获取播放列表失败'
82             break
83                 picture,
84
85         for song in playlist['song']:
86             picture = 'picture/' + song['picture'].split('/')[-1]
87
88             # 下载专辑封面
89             save(
90                 picture,
91                 urllib.urlopen(song['picture']).read())
92
93             # 发送桌面通知
94             notifySend(
95                 picture,
96                 song['title'],
97                 song['artist'] + '\n' + song['albumtitle'])
98
99             # 播放
100             player = subprocess.Popen(['mplayer', song['url']])
101             time.sleep(song['length'])
102             player.kill()
103
104
105 def main(argv):
106     # 默认参数
107     channel = '0'
108     user = ''
109     password = ''
110
111     # 获取、解析命令行参数
112     try:  
113         opts, args = getopt.getopt(
114             argv, 'u:p:c:', ['user=', 'password=', 'channel='])  
115     except getopt.GetoptError as error:
116         print str(error)
117         sys.exit(1)
118
119     # 命令行参数处理
120     for opt, arg in opts:
121         if opt in ('-u', '--user='):
122             user = arg
123         elif opt in ('-p', '--password='):
124             password = arg
125         elif opt in ('-c', '--channel='):
126             channel = arg
127
128     if user == '':
129         play(channel)
130     else:
131         if password == '':
132             password = getpass.getpass('密码:')
133         opener = login(user, password)
134         play(channel, opener)
135
136
137 if __name__ == '__main__':
138     main(sys.argv[1:])
  以下是本人使用自己的帐号登录并播放红心兆赫:
DSC0005.png
DSC0006.png
  接下来,我会继续完善这个python douban.fm客户端程序,添加频道搜索和查看,全局按键控制等功能。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-58551-1-1.html 上篇帖子: 使用python编写svn钩子 下篇帖子: WHY PYTHON Rocks
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表