Skip to content

Instantly share code, notes, and snippets.

@seyrenus
Forked from PeterDing/xiami_vip_linux.py
Created August 9, 2013 07:57
Show Gist options
  • Save seyrenus/6191888 to your computer and use it in GitHub Desktop.
Save seyrenus/6191888 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python2
# vim: set fileencoding=utf8
import re, sys, os, urllib2, random, time, datetime
from mutagen.id3 import ID3,TRCK,TIT2,TALB,TPE1,APIC
from HTMLParser import HTMLParser
email = ''
password = ''
#############################################################
# from https://gist.github.com/lepture/1014329
#############################################################
# {{{
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2011, lepture.com
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of the author nor the names of its contributors
# may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import urllib
import httplib
from contextlib import closing
from Cookie import SimpleCookie
ua = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
checkin_headers = {
'User-Agent': ua,
'Content-Length': '0',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Host': 'www.xiami.com',
'Origin': 'http://www.xiami.com/',
'Referer': 'http://www.xiami.com/',
'Content-Length': '0',
}
class Xiami(object):
def __init__(self, email, password):
self.email = email
self.password = password
self._auth = None
def login(self):
print 'login ....'
_form = {
'email': self.email,
'password': self.password,
'LoginButton': '登录',
}
data = urllib.urlencode(_form)
headers = {'User-Agent': ua}
headers['Referer'] = 'http://www.xiami.com/web/login'
headers['Content-Type'] = 'application/x-www-form-urlencoded'
with closing(httplib.HTTPConnection('www.xiami.com')) as conn:
conn.request('POST', '/web/login', data, headers)
res = conn.getresponse()
cookie = res.getheader('Set-Cookie')
self._auth = SimpleCookie(cookie)['member_auth'].value
print 'login success'
return self._auth
def checkin(self):
if not self._auth:
self.login()
headers = checkin_headers
headers['Cookie'] = 'member_auth=%s; t_sign_auth=1' % self._auth
with closing(httplib.HTTPConnection('www.xiami.com')) as conn:
conn.request('POST', '/task/signin', None, headers)
res = conn.getresponse()
return res.read()
# }}}
########################################################
def init(email, password):
home = os.path.expanduser('~')
cookie_dir = os.path.join(home, '.Xiami.cookie')
xm = Xiami(email, password)
if os.path.exists(cookie_dir):
f = open(cookie_dir).read().split('\n')
tm_s = (int(datetime.datetime.now().strftime('%s')) - int(f[0])) > 18000
xm._auth = f[1]
member_auth = xm._auth
auth = xm.checkin()
if auth != '0' or tm_s:
member_auth = xm.login()
if member_auth != f[1]: print ' --> member_auth is new one.'
tm = datetime.datetime.now().strftime('%s')
open(cookie_dir, 'w').write(tm + '\n' + member_auth)
return member_auth
else:
return member_auth
else:
member_auth = xm.login()
tm = datetime.datetime.now().strftime('%s')
open(cookie_dir, 'w').write(tm + '\n' + member_auth)
return member_auth
cookie = 'member_auth=%s' % init(email, password)
opener = urllib2.build_opener()
opener.addheaders = [('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'), ('User-Agent', ua), ('Cookie', cookie)]
s = '\x1b[1;%dm%s\x1b[0m'
re_id = re.compile(r'song_id>(\d+)<')
re_title = re.compile(r'title><!\[CDATA\[(.+?)\]\]')
re_artist = re.compile(r'artist><!\[CDATA\[(.+?)\]\]')
re_album_name = re.compile(r'album_name><!\[CDATA\[(.+?)\]\]')
re_pic = re.compile(r'pic>(.+?)<')
re_song = re.compile(r'"location":"(.+?)"')
parser = HTMLParser()
template_info = 'http://www.xiami.com/song/playlist/id/%s/type/%s'
template_parse = 'http://www.xiami.com/song/gethqsong/sid/%s'
template_wgets = 'wget -nv -c -U "' + ua + '" -O "%s" %s'
def decry(row, encryed_url):
url = encryed_url
urllen = len(url)
rows = int(row)
cols_base = urllen / rows # basic column count
rows_ex = urllen % rows # count of rows that have 1 more column
matrix = []
for r in xrange(rows):
length = cols_base + 1 if r < rows_ex else cols_base
matrix.append(url[:length])
url = url[length:]
url = ''
for i in xrange(urllen):
url += matrix[i % rows][i / rows]
return urllib.unquote(url).replace('^', '0')
def song_infos(code, tp):
infos = []
api_xml = opener.open(template_info % (code, tp)).read()
for i in api_xml.split('<track>\n')[1:]:
t = {}
t['id_'] = re_id.findall(i)[0].strip()
t['title'] = re_title.findall(i)[0].strip()
t['artist'] = re_artist.findall(i)[0].strip()
t['album_name'] = re_album_name.findall(i)[0].strip()
tt = re_pic.findall(i)[0].strip()
t['pic'] = re.sub(r'_\d\.jpg$', '.jpg', tt)
infos.append(t)
return infos
def modified_sname(sname):
sname = parser.unescape(sname)
sname = sname.replace('/', ' - ')
sname = sname.replace('\\', '')
sname = sname.replace('"', '\\"')
try:
sname = sname.encode('utf8')
except UnicodeDecodeError:
pass
if len(sname) >= 250:
return sname[:243] + '...mp3'
else:
return sname
def modified_id3(sname, info, track):
id3 = ID3()
id3.add(TRCK(encoding=3, text=track))
id3.add(TIT2(encoding=3, text=info['title'].decode('utf8')))
id3.add(TALB(encoding=3, text=info['album_name'].decode('utf8')))
id3.add(TPE1(encoding=3, text=info['artist'].decode('utf8')))
id3.add(APIC(encoding=3, mime='->', type=3, desc=u'Cover', data=info['pic']))
id3.save(sname)
def download(code, tp):
infos = song_infos(code, tp)
size = len(infos)
z = 0
if size <= 9:
z = 1
elif size >= 10 and size <= 99:
z = 2
elif size >= 100 and size <= 999:
z = 3
else:
z = 1
ii = 1
for i in infos:
if i['id_'] != '':
sname = str(ii).zfill(z) + '.' + i['title'] + ' - ' + i['artist'] + '.mp3'
sname = modified_sname(sname)
j = opener.open(template_parse % i['id_']).read()
t = re_song.search(j)
t = t.group(1)
row = t[0]
encryed_url = t[1:]
durl = decry(row, encryed_url)
num = random.randint(0,100) % 7
col = s % (num + 90, sname)
print '\n ++ 正在下载: %s' % col
wget = template_wgets % (sname, durl)
status = os.system(wget)
time.sleep(10)
if status not in [0, 2048]: # other http-errors, such as 302.
print '\n\n ----### ERROR ==> %d ###--- \n\n' % status
print ' ===> ', wget
break
modified_id3(sname, i, str(ii))
ii += 1
def main(url):
if 'showcollect/' in url:
code = re.search(r'showcollect/id/(\d+)', url).group(1)
tp = '3'
download(code, tp)
elif 'album/' in url:
code = re.search(r'album/(\d+)', url).group(1)
tp = '1'
download(code, tp)
elif 'artist/' in url:
code = re.search(r'artist/(\d+)', url).group(1)
tp = '2'
download(code, tp) # 下载该歌手最火的前20首歌
else:
code = re.search(r'song/(\d+)', url).group(1)
tp = '0'
download(code, tp)
if __name__ == '__main__':
argv = sys.argv
if argv[1] == '-a' and argv[2]:
main(argv[2])
else:
print 'Usage:\n -a url download any list of xiami.com'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment