zoukankan      html  css  js  c++  java
  • 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
      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:])

    以下是本人使用自己的帐号登录并播放红心兆赫:

    接下来,我会继续完善这个python douban.fm客户端程序,添加频道搜索和查看,全局按键控制等功能。

  • 相关阅读:
    Socket 的网络编程
    《Python 3.5从零开始学》笔记-第8章 面向对象编程
    Python 的8个关键要素
    分布式发布订阅模型网络的实现有哪些
    MongoDB知识整理
    C++模板类与Qt信号槽混用
    C++中 =default,=delete用法
    QT知识整理
    Python题整理
    STL库的应用
  • 原文地址:https://www.cnblogs.com/7c00/p/2860505.html
Copyright © 2011-2022 走看看