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

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

起因

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

如何使用

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

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

实现功能

功能实现分为三步:

  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端口,默认是没开启的。然后要关闭防火墙。

  1. 一开始想直接接入微信公众号的消息接口,直到全部接入完后才发现很难得到数据,才发现需要5s内返回消息给微信接口,否则需要使用客服接口异步返回数据,但是是个人的公众号不能接入客服,于是放弃。改为网页形式。

5.音乐数目过多比如1000-2000条,通常情况下重合率是相对更低的,想从算法上提高一些,但是暂时没有想到什么好的算法。

使用

填入用户名

返回结果

个人对比多次,发现10%左右就比较高了,而且歌单里音乐数目越多,一般这个重合率都偏低。

微信公众号:BrainZou
欢迎关注,一起学习。
回复“资料”,有本人精心收集的Python,Java,Android,小程序,后端,算法等等近1T的网盘资源免费分享给你。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值