前言
今天这篇文章是我曾经做过的一个数据挖掘的项目,主要内容是运用python爬取QQ音乐用户歌单并进行数据的可视化以及歌曲推荐,本项目完整的包含了数据的爬取、数据的分析以及对爬取结果的可视化过程。文章内容很多,我本人大多时候亦难以耐住性子阅读长篇技术类博文。但是由于涵盖的内容较多,我虽已尽可能的用简明梗概的语言来描述这整个过程,篇幅也还是较长,欢迎相互交流一下。
摘要
现如今商品推荐算法已经应用到了生活中的方方面面,淘宝购物、浏览新闻、歌曲推荐等功能其核心内容都是推荐算法。此篇文章就是将内容推荐算法及其简单的优化之后的算法应用到QQ音乐上的一个具体的实例,即根据用户喜欢的歌曲来推荐新的用户可能喜欢的歌曲。
关于数据的获取,本文通过QQ音乐的官网运用网络爬虫(相关代码见末尾给出的代码链接)获取到了歌曲《晴天》下的前2500条发表评论的用户的个人信息以及他们喜欢的歌曲里面的前150首歌曲信息((若喜欢的歌曲不足150则爬取全部的歌曲信息)。由于存在同一名用户多次评论以及部分用户空间上锁无法访问的原因,最终得到的歌单可访问的用户数目为1629。对此1629名用户的歌单数目进行分析,歌单歌曲数目大于等于150首的仅有16首,数目大于120首的用户数目为473,数目大于100的数目为531。由于歌单歌曲数目大于150及大于120的用户数目过少,最终决定对歌单数目大于100的531个用户进行分析。
首先,本文通过比较这531名用户歌单,得到其平均重合数为8.14,即任意两个用户之间有大约8首歌是重复的。对于歌单数目介于100-150首之间的用户来说,随机两用户重复度介于6%-8%之间,由此看来用内容推荐算法进行歌曲的推荐是合理的。随后,本文应用python实现的内容推荐法对这部分进行了实现。
其次,本文应用了基于关联规则的改进算法通过比较歌曲重合度的方法为用户进行了歌曲的推荐。此方法相比传统的关联规则的好处是一定可以得到一个输出。
随后,本文仿照向量的相似性算法来进行歌曲推荐算法的设计。在将用户的歌单信息映射成为代表用户的歌曲喜好的特征向量时,由于直接使用歌单内歌曲信息难以确定映射规则同时数据维度过高(至少大于等于100),因此我对此算法做出了相应的修改,即统计用户歌单里的所有歌曲的种类及各种类的歌曲数目,将其作为此用户的特征向量。此算法相比原来算法,一方面将向量的长度降低到了5位,另一方面数目即可直接构成向量,不必考虑映射规则。运用以上两种算法都一定能得到较为准确的输出结果,但是两种算法都只会求出契合度最高的用户的歌单。因为在爬取时每个用户歌单里的歌曲数目介于100到150之间,去除重复的歌曲之后,能输出的推荐的歌曲数目介于100到130之间。为了提高输出的推荐歌曲数目,本文采用了k-means聚类算法,将以上的特征向量进行聚类并将与待推荐用户在同一类的用户的歌曲信息作为输出。运用此方法平均可得到700到1000首歌曲推荐结果。
最后,由于以上所有的算法为用户推荐歌曲的基础都是假设用户已经喜欢了一定量的歌曲然后通过其喜欢的内容来推荐新的歌曲。但是需要考虑的是每天都有大量的新用户出现,因此此部分用户的歌曲推荐也是需要考虑的一个重要部分。本文通过统计531名用户的歌单里歌曲的频数来确定热门歌曲,即频数越大,越热门,对新用户的推荐算法即将热门歌曲对新用户进行推送。同时,本文也将统计结果生成了词云图进行可视化并对结果进行了简单地分析。
一、用python对数据进行爬取
1.1、爬取数据之前的准备
首先,因为待分析用户的id是从歌曲评论信息中获得的,所以从越热门的歌曲下获取用户信息那么分析的结果就越客观(如果歌曲为小众歌曲的话最后获取到的待分析用户的特征就会具有明显的偏向性,不能代表大多数的用户)。又因为在目前,全网最热门的歌曲为周杰伦的《晴天》(参考文献:全网最热门歌曲),所以我最终决定爬取的用户为歌曲《晴天》下的评论用户。
其次,由于评论人数太多,故这里只选择爬取前2500名评论用户作为分析目标,并且每个用户歌单中的歌曲数目只爬取前150首(不足150首的则全部爬取)。
1.2、数据的正式爬取
经过分析网站发现其数据由js动态加载。因此,为了获取到所需要的信息,根据其相关请求的headers总结出各个keys的意义再拼成url发起请求即可。
首先,进入到歌曲《晴天》的页面,找到其加载评论信息的js地址以及返回内容如下所示
图一的js就是获取用户在晴天这首歌曲下的所有用户的评论信息,由图二返回的结果我们可以看到其返回了一个commentlist,里面包含了所有评论人的个人信息,例如昵称,个性签名等。不过这里面最重要的字段信息当属uin字段,经过我的分析发现它实际上就是评论用户的QQ号码,而且通过此uin我们可以拼出url进入到其个人主页(如图三,注意看其顶部url红色圈中的部分,就是一个QQ号。同理,将此QQ号替换成你的就会显示你的歌单信息)进而获取到其喜欢的歌单信息。
此时,我们用得到的uin进行拼接,便可访问所有用户的主页。在得到用户主页的url之后,我们找到了其获取喜欢的歌的js地址,如图4所示
进而可以得到用户喜欢的歌曲信息,如图5所示
此时我发现,想要拿到用户喜欢的歌单信息,需要的url字段(也就是图4中的url)里面有一个disstid字段还没有获得。根据其字面意思猜想其为歌单的编号(事实证明确实是歌单编号),随后我在加载用户homepage页面的js里找到了此js地址,其返回结果里的mymusic里名为“我喜欢”的title对应的id就为所要得到的disstid,js地址内容及返回结果如下图6、图7所示。
通过此js地址便可拿到用户的喜欢的歌单的 disstid 从而可以访问其歌单信息,进而爬取歌单。至此,爬虫的设计完成,此部分Python代码实现部分见下方链接。
最终,本文通过QQ音乐的官网运用网络爬虫(相关代码见附录)获取到了歌曲《晴天》下的前2500条发表评论的用户的个人信息以及他们喜欢的歌曲里面的前150首歌曲信息((若喜欢的歌曲不足150则爬取全部的歌曲信息)。
下方即为用户歌单的爬取结果(为方便后面数据的分析此时已经整理为了json格式,每首歌曲前面的阿拉伯数字代表歌曲的流派)
最后,下面给出本次的歌单爬取结果以及爬取部分的全部代码(由于代码比较常规,所以注释较少,不懂之处可在评论里提出)。
完整爬取结果
数据爬取部分代码
二、对爬取数据进行以及进行歌曲的推荐
2.1、数据的预处理
在获取到数据之后,对数据首先进行预处理。在用爬虫爬取数据时,我一共爬取了100页的评论,因此理论上得到的可访问歌单的用户数目为2500。但是由于某些特殊的情况,例如同一个用户在此歌曲下多次评论、部分用户的空间加锁等,实际得到的歌单信息不为空的用户数目为1629。
对于此1629名歌单里歌曲数目不为0的用户,本文先做了以下用户歌单歌曲数目预分析,如图9所示。
首先,在爬取歌单内歌曲时,我设定的最大值就是 150 ,但是由于 1629 个用户中,仅有16人歌单数目达到了150首,因此,需要适当的降低歌单里歌曲数目的阈值来使得更多的用户可作为分析样本。但是歌曲数目越多必然分析的结果越具有客观性,因此,两相权衡,针对此结果,最终确定将歌单里歌曲数目大于等于 100 的 531 名用户作为分析样本。
其次,对于上文选取的531名用户,在进行常规的内容推荐算法(基于内容的重合度进行推荐,此处的内容重合度也就是歌单内歌曲的重合数)之前,先对歌曲能否应用此算法进行了简单的探究。于是将这531名用户歌单里的歌曲两两相比较,每两个用户重复的歌曲数目为i,成对比较的组数为k定义平均重合度为λ,由此得到下式
用Python按上述思路实现可得其平均重合度为8.14,即任意两个用户之间有大约8首歌是重复的。对于歌单数目介于100-150首之间的用户来说,随机两用户重复度介于6%-8%之间,占比接近百分之十。另外,本文又统计了在所有的组数中,重合数大于平均重合度的组的占比。我们得到在 140715 组中,有 50373 组的重合数目大于了平均重合度,占比 35.8% 。综合以上两点,我们可以认为常规的内容推荐算法是可以运用到此歌曲推荐算法中的。
2.2、根据关联规则进行歌曲推荐的算法及实现
2.2.1算法思路
基于上述获取到的531组用户数据,一个最基本的推荐算法就是根据关联规则来进行歌曲的推荐。简单来说就是将这531名用户的歌单进行两两对比,假设一个用户的歌单内歌曲数目为 M ,另一个用户的歌单内歌曲数目为 N ,当他们重合的歌曲数目占歌单总歌曲数目的比例大于某个给定的阈值 T 时,他们的相关性 a 置为1,即认为这两组用户具有相关性;反之, a 置为0,即认为不相关。公式如下:
进而在为用户推荐歌曲时,只需将与此用户具有相关度的用户的歌曲推荐给他即可。
但是这种算法在阈值 T 的选取上没有一个准确的约定,对于这个实例来说,根据前面求得的的平均重合度 ( λ=8.14 ) ,我们指定的阈值应该为8%,但是经过分析8%这个重合度大约只有1/3的组数能达到,所以按照此类方法有可能得不到任何的输出结果。倘若为了得到输出而调低阈值,就可能得到庞大的歌曲推荐结果,导致结果不再具有实际的意义。因此,再对以上的算法进行改进,改进后算法为:将这531名用户的歌单进行两两对比,仅将与待推荐用户歌单重合度最高的用户的歌单中的歌曲作为输出结果。
2.2.2算法实现
因为在前面将爬取的数据结果写入到文件中时,我对数据进行了简单的处理,将其写为了嵌套字典的格式,类似如下结果(以下仅为字典中的一个键值对)
{'范佩西':{'夕阳醉了': '1', '回望': '1', 'The Hit Nation': '1', '暗号': '1', '纸短情长': '2', '其实都没有': '1', '回到过去': '1', 'Polaris': '1', '等你下课(with 杨瑞代)': '1', '唇语': '1', '空空如也': '1', 'Call Of The Ambulance': '1', "DJ's International": '2', 'Summertime Sadness': '2', '佛系少女': '1', '甜到爆表': '1', '不仅仅是喜欢': '1', '慢慢喜欢你': '1', '放过自己': '1', '再见只是陌生人': '1', '爱你没差': '1', '好可惜': '1', '我落泪情绪零碎': '1', '冰雨': '1', '知足': '2', '手写的从前': '2', '龙卷风': '2', '夜曲': '2', '遇见': '2', '周杰伦歌曲串烧': '2', '安静': '2', '让我取暖': '1', '天空': '3', '那年夏天宁静的海': '1', '一首简单的歌': '1', '专属情歌': '1', '如果的事': '1', '一场游戏一场梦': '1', '遗失的美好': '1', '暧昧': '1', '那些花儿': '3', '他一定很爱你': '1', '约定': '1', '黄昏': '1', '比我幸福': '1', '断点': '1', '一生有你': '1', '情非得已': '1', '一个人的北京': '1', '你,好不好?': '1', '哭砂': '1', '排行榜': '1', '心疼·笔记本': '1', '背叛': '1', '寻水的鱼': '1', '虎口脱险': '1', '想你就写信': '1', '发如雪': '1', '那片海': '1', '娃娃脸': '1', '下一站天后': '1', '给我一个理由忘记': '1', '董小姐': '2', '老男孩': '1', '爱很美': '1', '不再联系': '1', '爱夏': '1', '怎样': '1', '斑马,斑马': '2', '我的歌声里': '1', '最初的梦想': '1', '第一次': '1', '我们的爱': '1', '大城小爱': '1', '愿得一人心': '1', '青花': '1', '平凡之路': '1', '拯救': '1', '寂寞沙洲冷': '1', '青春纪念册': '1', '你快回来': '1', '不想让你哭': '1', '只要你过得比我好': '1', '就是爱你': '1', '无情的雨无情的你': '1', '我终于失去了你': '1', '蓝色土耳其': '1', '我难过': '1', '男人好难': '1', '风吹麦浪': '2', '向往': '1', '当爱已成往事': '1', '让我欢喜让我忧': '1', '花心': '1', '练习': '1', '风往北吹': '1', '两个人的烟火': '3', '到不了': '1', '白色恋人': '1', '我们的纪念': '1', 'Only Love': '3', '达尔文': '1', '心肝宝贝': '1','BINGBIAN病变': '1', 'PLANET': '1', '孤单北半球': '1', '相见不如怀念': '1', '距离': '1', '说好的童话': '1', '下个,路口,见': '1', '下个路口见': '1', '一笑而过': '1', '红玫瑰': '1', '不将就': '1', '我的地盘': '3', '美人鱼': '1', '岁月神偷': '1', '请跟我联络': '1', '倒带': '1', '一笑而过 + 你的背包': '1', '一口气全念对': '1', '我要夏天': '1', '派伟俊': '3', '罗曼蒂克的爱情': '1', '断了的弦': '1', '不要对他说': '1', '미워': '1', '习惯一个人': '1', '一人一城': '1', '春风十里': '1', '滂沱大雨里': '3', '你还要我怎样': '1', '那个男人': '1', '北极星的眼泪': '1', '第三者的第三者': '1', 'Liekkas': '2', '心墙': '1', '下雨天': '1', '眼泪知道': '1'}}
外层字典的键为用户昵称,值为歌单信息。同时,歌单信息也为一个字典,这个字典的键为歌曲名称,值为歌曲种类。至于在此写入歌曲种类的原因则是为了接下来的优化算法的实现。首先,将文件按行读取并将读取结果写入到一个字典中(此时字典中的每一项都为一个字典,格式同上),再将歌单长度大于等于100的用户昵称写入到一个列表。于是,通过遍历此列表中的每一个元素就能得到用户的昵称,前面构建的字典通过此昵称就能得到用户的歌曲信息,最后,只需将取出的歌曲运用set进行比较即可。输出结果格式如下图
2.3、基于向量相似度的优化算法
2.3.1、算法思路
向量相似度算法在此不再赘述。具体内容可通过此链接(向量的相似性)自行了解。在此,仿照向量相似度的计算,以歌曲名称作为向量的各个分量信息,将这 531 名用户的歌单进行两两对比,假设第f用户的歌单内歌曲构成的向量为 ,第s用户的歌单内歌曲构成的向量为,相似度为,公式如下:
将这531名用户的歌单进行两两对比,仅将与待推荐用户歌单相似度最高的用户的歌单中的歌曲作为输出结果即可。但是算法目前涉及到三个问题:
1、这里作为分析对象的 531 名用户的歌单长度只是保证了大于等于100,即只保证了向量的长度大于100,并不能保证向量的长度相同,因此无法做向量的点乘运算。
2、歌曲名称作为向量基本元素需要先将歌曲名称映射为具体的数据,此映射规则难以制定。
3、哪怕映射出了一个向量,此向量的长度也大于100,计算要求过高,应进行数据的降维。
因此做以下处理,统计用户歌单里的所有歌曲的种类及各种类的歌曲数目,将其作为此用户的特征向量。在简单的分析之后,发现大部分用户的歌单里的歌曲种类分别为1(pop),2(folk),3(R&B),5(Alternative)(此处的1,2,3,5代表的就是实际的歌曲风格(括号内为流派),在js里的获取到的歌曲种类就是此种数字形式),考虑到可能还存在用户喜欢了一些冷门的歌曲,种类不属于以上四种,因此将不属于此四类歌曲风格的歌曲类型统一记为4,至此,这个由歌曲种类所构成的长度为5的向量就是用户的特征向量。将这531名用户的歌单进行两两对比,仅将与待推荐用户歌单相似度最高的用户的歌单中的歌曲作为输出结果即可。因此对已经爬取到的用户数据进行整理,统计每个用户歌单中五类各区各自的数目如下图所示
2.3.2、算法实现
数据的读入以及预处理与根据内容进行歌曲推荐的算法部分相同,算法的主要部分是将歌曲信息转换为歌曲种类的特征向量然后带入计算的过程,具体代码见附录。输出结果格式如下图
2.4、基于k-means聚类分析的优化算法
2.4.1、算法思路
k-means聚类分析在此亦不再赘述。具体内容可通过此链接(k-means聚类分析)自行了解。在此处,我并非将用户的歌单信息直接聚类,原因还是因为将歌曲名称作为向量基本元素需要先将歌曲名称映射为具体的数据,此映射规则难以制定。而且,哪怕映射出了一个向量,此向量的长度也大于100,计算要求过高,应进行数据的降维。所以本文采用了在基于相似度中的优化算法中得到的特征向来作为聚类的标准。将此531名用户的特征向量进行聚类分析,将与待推荐用户处在同一类中用户的歌单中的歌曲作为推荐结果即可。
2.4.1、算法实现
数据的读入以及预处理与根据内容进行歌曲推荐的算法部分相同,算法的主要部分是进行聚类分析。此处的聚类分析并非我自己编写(感觉麻烦),就调用了scipy里的库来进行。因此除去代码编写的问题后此部分的核心问题就变成了应该聚成多少个类。首先我们容易知道,分的类越多,同类之间的特征相似度就越高,分类效果就越好(通过查看不同分类的聚类损失度我也验证了这个观点),但是类里面的点的数目就越少。对于此实例,因为共有531名用户,每个用户歌单里最少具有100首歌曲,倘若要求结果至少能为用户推荐500首歌曲,那么就意味着一个类大约有5个用户即可,因此此处将聚类的类数给设置为100。以下为推荐结果
2.5、新用户的歌曲推荐算法
2.5.1算法思路
以上所有的算法为用户推荐歌曲的基础都是假设用户已经喜欢了很多的歌曲然后通过其喜欢的内容来推荐新的歌曲。但是如何为一个新用户推荐歌曲呢?答案是显而易见的,即为他推送热门歌曲。因此,本文将此531名用户的喜欢的歌曲进行了频数统计以得到热门歌曲从而进行推送。顺便地,本文也将统计结果生成了词云图进行可视化,字体越大,代表歌曲越热门。
2.5.2算法实现
数据的读入以及预处理与根据内容进行歌曲推荐的算法部分相同。整理数据之后,用最外层字典根据用户列表里的元素来逐个获取用户的歌单信息放入列表中,再通过文件读写的方式将其转为text格式用wordcloud处理即可。
最后,给出以上数据分析的代码。
数据分析
三、数据的可视化
在上方对新用户的歌曲推荐算法中我们对这531名用户的歌单中的歌曲出现的频数进行了统计,此处为统计结果。下图即为热门歌曲的可视化结果
我们可以明显看出的是热门的歌曲中周杰伦的歌曲的数目明显是最多的,一方面是因为周杰伦在当今的华语乐坛确实是最热门的歌手之一;另一方面可能是因为此部分用户都是来源于周杰伦的《晴天》评论区,是周杰伦的粉丝的可能性较为大。