Ich habe vor einem Monat aus einer Laune heraus einen einfachen douban.fm-Client mit Python implementiert. Der Plan ist, ihn schrittweise zu einem douban.fm-Client zu verbessern, der die Webversion unter Ubuntu ersetzen kann. Aber später wurde es auf Eis gelegt und nicht weiter verbessert, weil zu viel zu tun war. Erst gestern erwähnte ein Gartenfreund in einem Kommentar die Implementierung des Logins. Obwohl es in letzter Zeit noch viel zu tun gab, wollte er diese Funktion plötzlich implementieren. Zufälligerweise habe ich vor ein paar Tagen aufgrund einiger Anforderungen Python verwendet, um die Website-Anmeldung zu implementieren. Ich schätze, dass die Anmeldung von douban.fm nicht allzu unterschiedlich sein wird.
Über Website-Authentifizierung
Das http-Protokoll ist als verbindungsloses Protokoll konzipiert, aber in Wirklichkeit müssen viele Websites Benutzer identifizieren, und dafür werden Cookies verwendet Daraus geboren. Wenn wir einen Browser zum Durchsuchen einer Website verwenden, verarbeitet der Browser Cookies transparent für uns. Und jetzt möchten wir, dass sich ein Dritter auf der Website anmeldet, was ein gewisses Verständnis des Cookie-Workflows erfordert.
Darüber hinaus verwenden viele Websites einen Bestätigungscode-Mechanismus, um zu verhindern, dass sich das Programm automatisch anmeldet. Das Eingreifen des Bestätigungscodes macht den Anmeldevorgang mühsam, ist es aber nicht allzu schwierig damit umzugehen.
Der eigentliche Anmeldevorgang von douban.fm
Um einen sauberen Anmeldevorgang (ohne Verwendung vorhandener Cookies) zu simulieren, verwende ich den Inkognito-Modus von Chromium.
Wenn Sie die Anforderungs- und Antwortheader beobachten, können Sie sehen, dass der Anforderungsheader der ersten Anforderung kein Cookie-Feld enthält und der Antwortheader des Servers das Set-Cookie enthält Feld, das den Browser anweist, das Cookie bei der nächsten Anforderung der Website zu übertragen.
Als ich douban.fm besuchte, habe ich tatsächlich drei Weiterleitungen durchlaufen. Natürlich müssen wir im Allgemeinen nicht auf diese Details achten. Browser und erweiterte httplib behandeln Umleitungen transparent, aber wenn Sie den zugrunde liegenden C-Socket verwenden, müssen Sie mit diesen Umleitungen vorsichtig umgehen.
Klicken Sie auf die Schaltfläche „Anmelden“, und der Browser initiiert mehrere neue Anfragen, darunter mehrere wichtige Anfragen. Diese Anfragen sind der Schlüssel zu unserem Drittanbieter-Login.
Zunächst gibt es eine angeforderte URL: http://douban.fm/j/new_captcha. Bei der Anforderung dieser URL gibt der Server eine zufällige Zeichenfolge zurück. Was ist der Sinn? Was soll ich verwenden? (Eigentlich handelt es sich um einen Bestätigungscode)
Sehen Sie sich die nächste Anfrage an, http://douban.fm/misc/captcha?size=m&id=0iPlm837LsnSsJTMJrf5TZ7e. Diese Anfrage gibt den Bestätigungscode zurück. Es stellt sich heraus, dass Sie http://douban.fm/j/new_captcha anfordern und die vom Server zurückgegebene Zeichenfolge als ID-Parameterwert der nächsten Anforderung verwenden.
Wir können einen Python-Code schreiben, um unsere Idee zu überprüfen.
Es ist erwähnenswert, dass Python drei http-Bibliotheken bereitstellt: httplib, urllib2 und urllib2, die Cookies transparent verarbeiten können. Ich glaube, ich habe httplib zur manuellen Verarbeitung verwendet Kekse. Dieser Schmerz.
Der Code lautet wie folgt:
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()
Dieser Code implementiert den Download des Verifizierungscodes.
Als nächstes füllen wir das Formular aus und senden es ab.
Wie Sie sehen können, lautet die Zieladresse des Anmeldeformulars http://douban.fm/j/login und die Parameter sind:
Quelle: Radio
Alias: Benutzername
form_password: Passwort
captcha_solution: Bestätigungscode
captcha_id: Bestätigungscode-ID
Aufgabe: sync_channel_list
Was Als nächstes wird Python verwendet, um ein Formular zu erstellen.
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'}))
Das vom Server zurückgegebene Datenformat ist json. Das spezifische Format wird hier nicht beschrieben. Sie können es selbst testen.
Woher wissen wir, ob die Anmeldung funktioniert? Ja, im vorherigen Artikel wurde erwähnt, dass Kanal = -3 das Herz-Megahertz ist, also die Favoritenliste des Benutzers. Sie können die Wiedergabeliste dieses Kanals nicht abrufen, ohne sich anzumelden. Anfrage http://douban.fm/j/mine/playlist?type=n&channel=-3 Wenn eine Liste Ihrer eigenen Lieblingsmusik zurückgegeben wird, funktioniert die Anmeldung.
Code-Organisation
Durch die Kombination der vorherigen Version und der neuen Anmeldefunktion sowie der Verarbeitung von Befehlszeilenparametern und der Kanalauswahl ist ein leicht verbessertes Douban .fm fertiggestellt
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:])