想知道你和她在網易雲喜歡的音樂的重合率?

本工具能夠查看你和她在網易雲上喜歡音樂的重合率,以及哪些歌是大家都喜歡的。css

原由

在某首歌的評論裏看到說想要網易雲提供一個這種功能?仔細一想,其實獲取到歌單後作一個簡單的計算重合率的應該仍是挺簡單的。一方面想試試簡單的爬取兩個界面,另一方面最重要的是想利用下本身的服務器。通過幾天時間,雖然說初步實現了,可是……後面會詳細說遇到的問題。html

如何使用

能夠直接關注我公衆號:python.com 或者 掃描下方的二維碼關注python

公衆號界面底部菜單有個「小工具」菜單 > 「網易雲歌單重合率」 子菜單 nginx

實現功能

功能實現分爲三步:web

  1. 得根據歌單id獲取到歌單內的歌名列表,即哪些歌。算法

  2. 根據用戶名獲取到每一個用戶都有的那個喜歡的歌單,再經過第一步獲取到歌名,即哪一個用戶。小程序

  3. 部署到網絡,用戶本身輸入用戶名,自動返回結果。後端

獲取歌名列表

爬取好比髮姐的歌單: http://music.163.com/playlist?id=17281445。注意比網頁顯示的少了一個#號。
api


用BeautifulSoup處理,先獲得Class名叫‘f-hide’的ul,再在ul下找到因此a標籤的文本。獲得這部分歌名存儲在列表裏,部分代碼以下:

#link1是連接,header是構造的
s1 = requests.session()
s1 = BeautifulSoup(s1.get(link1,headers=headers).content,'lxml')
main = s1.find('ul', {'class': 'f-hide'})
for music in main.find_all('a'):
lists1.append(music.text)
複製代碼

照這個方法,再獲取到另一個歌名列表,再來處理,計算重合率。相關代碼以下:服務器

#用到了正則,是用來替換叼Unicode前的U替換爲<br>一是爲了轉換編碼顯示,二是爲了後面換行顯示歌名。
#decode('unicode-escape')也是爲了顯示,將unicode編碼解碼。
myset1 = set(lists1)
myset2 = set(lists2)
pattern = re.compile('\Wu\'')
intersectionset = re.sub(pattern,'<br>\'',str(myset1 & myset2))
length = len(myset1 | myset2)
print intersectionset
return(u"大家的歌單重合率爲:%f%%<br><br>重複歌曲共%d首
以下:%s"
%(len(myset1 & myset2)*100/length,len(myset1&myset2),intersectionset.decode('unicode-escape')))
複製代碼

根據用戶名獲取到歌單連接

先提下歌單是有一個id對應的,用戶也有一個userid對應。
前面咱們看到http://music.163.com/playlist?id=17281445歌單就是帶惟一id,前面都是固定的,那麼這個如何獲取?能夠先經過爬網易雲的搜索界面獲取到該用戶id,及主頁。爬主頁便可獲得這個歌單的鏈接了。
發現是js加載的,沒找到合適的方法,因此用的是PhantomJS和selenium加載。
注意下構造的搜索網頁。s是搜索的內容,type=1002表示搜索用戶。

def get_playlist_by_name(username):
#指定contentFrame 獲取"ttc"class,再獲取"a"tag,最後獲取到用戶主頁連接,圖見搜索界面圖。
#quote轉碼中文
try:
driver = webdriver.PhantomJS(executable_path="/usr/local/phantomjs/bin/phantomjs")
driver.get('http://music.163.com/#/search/m/?s={}&type=1002'.format(quote(username.encode('utf8'))))
#WebDriverWait(driver, 5, 0.3).until(EC.presence_of_element_located(locatorttc))
driver.switch_to.frame("contentFrame")
sleep(1)
tr = driver.find_element_by_class_name('ttc')
user = tr.find_element_by_tag_name('a')
#加載用戶主頁 獲取到私人最喜歡的歌單的連接並返回,圖見下方的用戶主頁圖。
driver.get(user.get_attribute('href'))
#WebDriverWait(driver, 5, 0.3).until(EC.presence_of_element_located(locatordec))
driver.switch_to.frame("contentFrame")
sleep(1)
dec = driver.find_element_by_class_name('dec')
#print(dec.page_source)
playlist = dec.find_element_by_tag_name('a')
return playlist.get_attribute('href')
except Exception as e:
print e
return ""
finally:
driver.close()
複製代碼
搜索界面
搜索界面
用戶主頁
用戶主頁

部署到網絡

80端口在個人服務器上已經被使用了,我也不想在連接上加上端口號,因此須要先在nginx進行配置,將子域名的80端口轉到服務器的8081端口。

server {
listen 80;
server_name api.brainzou.com;
location / {
proxy_pass http://xxx.xxx.xxx.xxx:8081/;
}
location /buy {
proxy_pass http://xxx.xxx.xxx.xxx:8081/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
複製代碼

而後看真正的部署網絡的部分,這裏用的是web.py,是在官方的簡單的form的例子下修改的。經過獲取到form裏的值傳遞到get_playlist_by_name方法。最後把數據返回。

# -*- coding: utf-8 -*-
# filename: main.py
import web
from web import form
import Music163RepetitiveRate
render = web.template.render('templates/')
urls = ('/music163', 'index')
app = web.application(urls, globals())
myform = form.Form(
form.Textbox("fname",form.notnull, description=u"用戶名1"),
form.Textbox("sname",form.notnull, description=u"用戶名2"))
class index:
def GET(self):
web.header('Content-Type','text/html;charset=UTF-8')
form = myform()
print(form.render())
return render.formtest(form)
def POST(self):
web.header('Content-Type','text/html;charset=UTF-8')
form = myform()
if not form.validates():
print(form.render())
return render.formtest(form)
else:
print "begin"
playlist1=Music163RepetitiveRate.get_playlist_by_name(form.d.fname)
print playlist1
playlist2=Music163RepetitiveRate.get_playlist_by_name(form.d.sname)
print playlist2
content = Music163RepetitiveRate.repetitive_rate_by_playlistlink(playlist1,playlist2)
return content
if __name__ == "__main__":
web.config.debug = False
web.internalerror = web.debugerror
app.run()
複製代碼

而後fortest.xml放置到templates下。

$def with (form)  
<div class="center">
<form name="main" method="post">
$if not form.valid: <p class="error">請重試!</p>
$:form.render()
<input class="input"type="submit" />
</form>
<a>提交後,大概須要20s來取歌單數據和分析,請耐心等待!</a>
<div>
<style>
.center {
width:500px;
height: 500px;
position: absolute;
left:50%;
top:50%;
margin-left:-100px;
margin-top:-100px;
}
.input{
width:100px;
margin-left:100px;
}
</style>
複製代碼

最後,手動指明python使用utf-8編碼。後臺運行加上指明端口8081。記得服務器開放8081端口。

遇到的問題

  1. PhantomJS用完沒有關閉,致使後面不少不可描述的問題。

  2. 編碼問題。能夠再詳細的上去看下,有不少地方,從get post,到set返回。
    甚至最後後臺運行main.py都須要先指明utf-8,而直接python main.py 8081卻不用( 由於Python 2 的默認編碼就是 ASCII,在正常狀況下,Python 2 在 print unicode 時用來轉換的編碼並非 Python 的默認編碼sys.getdefaultencoding(),而是 sys.stdout.encoding 所設的編碼)。

  3. 服務器(個人是在騰訊)上須要開放8081端口,默認是沒開啓的。而後要關閉防火牆。

  4. 一開始想直接接入微信公衆號的消息接口,直到所有接入完後才發現很可貴到數據,才發現須要5s內返回消息給微信接口,不然須要使用客服接口異步返回數據,可是是我的的公衆號不能接入客服,因而放棄。改成網頁形式。

  5. 音樂數目過多好比1000-2000條,一般狀況下重合率是相對更低的,想從算法上提升一些,可是暫時沒有想到什麼好的算法。

使用

填入用戶名
填入用戶名
返回結果
返回結果

我的對比屢次,發現10%左右就比較高了,並且歌單裏音樂數目越多,通常這個重合率都偏低。

微信公衆號:BrainZou歡迎關注,一塊兒學習。回覆「資料」,有本人精心收集的Python,Java,Android,小程序,後端,算法等等近1T的網盤資源免費分享給你。

相關文章
相關標籤/搜索