python douban.fm简易客户端实现方法

对python这个高级语言感兴趣的小伙伴,下面一起跟随编程之家 jb51.cc的小编两巴掌来看看吧!

一个月前心血来潮用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@H_301_35@,能透明处理cookie的是urllib2,想我之前用httplib手动处理cookie,那个痛苦啊。

代码如下:

# @param python实现douban.fm简易客户端
# @author 编程之家 jb51.cc|www.www.jb51.cc 

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()

# End www.jb51.cc

这段代码实现了验证码的下载。

接着,我们填写表单,并提交。

可以看到,登录表单的目标地址为http://douban.fm/j/login,参数有:

source: radio

alias: 用户名

form_password: 密码

captcha_solution: 验证码

captcha_id: 验证码ID

task: sync_channel_list

接下来要做的是用python构造一个表单。

# @param python实现douban.fm简易客户端
# @author 编程之家 jb51.cc|www.www.jb51.cc 

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'}))

# End www.jb51.cc

服务器返回的数据格式是json,具体格式这里不赘诉了,大家可以自己测试。

我们怎么知道登录是否起作用了呢?是了,之前的文章提到过channel=-3为红心兆赫,是用户的收藏列表,没有登录获取不到该频道的播放列表的。请求http://douban.fm/j/mine/playlist?type=n&channel=-3,如果返回你自己收藏过的音乐列表,那么就说明登录起作用了。

代码整理

结合之前的版本和新增的登录功能,再加上命令行参数处理、频道选择,一个稍稍完善的douban.fm就完成的。

# @param python实现douban.fm简易客户端
# @author 编程之家 jb51.cc|www.www.jb51.cc 

 #!/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,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','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:])

# End www.jb51.cc

运行效果

相关文章

在这篇文章中,我们深入学习了XPath作为一种常见的网络爬虫技巧。XPath是一种用于定位和选择XML文档中特...
祝福大家龙年快乐!愿你们的生活像龙一样充满力量和勇气,愿你们在新的一年里,追逐梦想,勇往直前,不...
今天在爬虫实战中,除了正常爬取网页数据外,我们还添加了一个下载功能,主要任务是爬取小说并将其下载...
完美收官,本文是爬虫实战的最后一章了,所以尽管本文着重呈现爬虫实战,但其中有一大部分内容专注于数...
JSON是一种流行的数据传输格式,Python中有多种处理JSON的方式。官方的json库是最常用的,它提供了简单...
独立样本T检验适用于比较两组独立样本的均值差异,而配对T检验则适用于比较同一组样本在不同条件下的均...