本工具能够查看你和她在网易云上喜欢音乐的重合率,以及哪些歌是大家都喜欢的。css
在某首歌的评论里看到说想要网易云提供一个这种功能?仔细一想,其实获取到歌单后作一个简单的计算重合率的应该仍是挺简单的。一方面想试试简单的爬取两个界面,另一方面最重要的是想利用下本身的服务器。通过几天时间,虽然说初步实现了,可是……后面会详细说遇到的问题。html
能够直接关注我公众号:python.com 或者 扫描下方的二维码关注python
公众号界面底部菜单有个“小工具”菜单 > “网易云歌单重合率” 子菜单 nginx
功能实现分为三步:web
得根据歌单id获取到歌单内的歌名列表,即哪些歌。算法
根据用户名获取到每一个用户都有的那个喜欢的歌单,再经过第一步获取到歌名,即哪一个用户。小程序
部署到网络,用户本身输入用户名,自动返回结果。后端
爬取好比发姐的歌单: http://music.163.com/playlist?id=17281445。注意比网页显示的少了一个#号。
api
#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端口。
PhantomJS用完没有关闭,致使后面不少不可描述的问题。
编码问题。能够再详细的上去看下,有不少地方,从get post,到set返回。
甚至最后后台运行main.py都须要先指明utf-8,而直接python main.py 8081却不用( 由于Python 2 的默认编码就是 ASCII,在正常状况下,Python 2 在 print unicode 时用来转换的编码并非 Python 的默认编码sys.getdefaultencoding(),而是 sys.stdout.encoding 所设的编码)。
服务器(个人是在腾讯)上须要开放8081端口,默认是没开启的。而后要关闭防火墙。
一开始想直接接入微信公众号的消息接口,直到所有接入完后才发现很可贵到数据,才发现须要5s内返回消息给微信接口,不然须要使用客服接口异步返回数据,可是是我的的公众号不能接入客服,因而放弃。改成网页形式。
音乐数目过多好比1000-2000条,一般状况下重合率是相对更低的,想从算法上提升一些,可是暂时没有想到什么好的算法。
我的对比屡次,发现10%左右就比较高了,并且歌单里音乐数目越多,通常这个重合率都偏低。
微信公众号:BrainZou欢迎关注,一块儿学习。回复“资料”,有本人精心收集的Python,Java,Android,小程序,后端,算法等等近1T的网盘资源免费分享给你。