阅读 720

想知道你和她在网易云喜欢的音乐的重合率?

本工具可以查看你和她在网易云上喜欢音乐的重合率,以及哪些歌是你们都喜欢的。

起因

在某首歌的评论里看到说想要网易云提供一个这种功能?仔细一想,其实获取到歌单后做一个简单的计算重合率的应该还是挺简单的。一方面想试试简单的爬取两个界面,另外一方面最重要的是想利用下自己的服务器。经过几天时间,虽说初步实现了,但是……后面会详细说遇到的问题。

如何使用

可以直接关注我公众号:python.com 或者 扫描下方的二维码关注

公众号界面底部菜单有个“小工具”菜单 > “网易云歌单重合率” 子菜单

实现功能

功能实现分为三步:

  1. 得根据歌单id获取到歌单内的歌名列表,即哪些歌。

  2. 根据用户名获取到每个用户都有的那个喜欢的歌单,再通过第一步获取到歌名,即哪个用户。

  3. 部署到网络,用户自己输入用户名,自动返回结果。

获取歌名列表

爬取比如发姐的歌单: http://music.163.com/playlist?id=17281445。注意比网页显示的少了一个#号。


用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的网盘资源免费分享给你。

关注下面的标签,发现更多相似文章
评论