首页 > 后端开发 > Python教程 > python实现douban.fm简易客户端

python实现douban.fm简易客户端

高洛峰
发布: 2016-10-18 14:28:34
原创
1381 人浏览过

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


关于网站身份验证

http协议被设计为无连接协议,但现实中,很多网站需要对用户进行身份识别,cookie就是为此而诞生的。当我们用浏览器浏览网站时,浏览器会帮我们透明的处理cookie。而我们现在要第三方登录网站,这就必须对cookie的工作流程有一定的了解。


另外,很多网站为了防止程序自动登录而使用了验证码机制,验证码的介入会使登录过程变得麻烦,但也还不算太难处理。


实际中douban.fm的登录流程

为了模拟一个干净(不使用已有cookie)的登录流程,我使用chromium的隐身模式。

1.jpg

观察请求和响应头,可以看到,第一次请求的请求头是没有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,那个痛苦啊。

代码如下:

1

2

3

4

5

6

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构造一个表单。

1

2

3

4

5

6

7

8

9

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就完成的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

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:])

登录后复制

1.png

相关标签:
来源:php.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板