申明:本文純屬原創(chuàng),有參考的地方都會(huì)在文中給出鏈接。如有轉(zhuǎn)載,需征求本人同意。
一、目標(biāo)網(wǎng)站介紹
網(wǎng)易云音樂(lè)是一款由網(wǎng)易開發(fā)的音樂(lè)產(chǎn)品,是網(wǎng)易杭州研究院的成果,依托專業(yè)音樂(lè)人、DJ、好友推薦及社交功能,在線音樂(lè)服務(wù)主打歌單、社交、大牌推薦和音樂(lè)指紋,以歌單、DJ節(jié)目、社交、地理位置為核心要素,主打發(fā)現(xiàn)和分享。
2017年11月17日,網(wǎng)易云用戶突破4億。知乎上有這樣一個(gè)問(wèn)題:你為什么用網(wǎng)易云。其中,有一條是這樣說(shuō)的:
成年人的生活里有太多無(wú)奈。
我之前把情緒和秘密寫在QQ留言板里,被朋友們發(fā)現(xiàn)。
后來(lái)寫在人人網(wǎng)上,被朋友們發(fā)現(xiàn)。
后來(lái)寫在微博里,被朋友們發(fā)現(xiàn)。
后來(lái)寫在知乎里,被朋友們發(fā)現(xiàn)。
后來(lái)淹沒(méi)在網(wǎng)易云強(qiáng)大的評(píng)論區(qū)里。
我需要一個(gè)地方,一個(gè)可以光明正大寫出來(lái)的地方。
不用擔(dān)心被任何人看到,不需要任何解釋。
網(wǎng)易云可以把所有的悲傷,變成段子。
出于大眾對(duì)網(wǎng)易云音樂(lè)的喜愛(ài),這次文本挖掘我放在了這里,希望能發(fā)現(xiàn)些有趣的東西。
二、所需工具
- Anaconda 3.5.0
- Pycharm
- Node.js
- Mongodb
- Studio 3T
- RStudio
三、數(shù)據(jù)爬取
3.1 環(huán)境搭建
本文基于Scrapy框架爬取數(shù)據(jù)。使用pip install 來(lái)安裝scrapy需要安裝大量的依賴庫(kù):
- wheel
pip install wheel
pip install D:\Downloads\Scrapy\lxml-4.3.0-cp36-cp36m-win_amd64.whl
pip install D:\Downloads\Scrapy\pyOpenSSL-18.0.0-py2.py3-none-any.whl
pip install D:\Downloads\Scrapy\Twisted-18.9.0-cp36-cp36m-win_amd64.whl
-
Pywin32
可執(zhí)行文件,挑選與Python對(duì)應(yīng)版本安裝就好。 - Scrapy
pip install scrapy
這里我使用了Anaconda來(lái)安裝scrapy,安裝時(shí)只需要一條語(yǔ)句:
conda install scrapy
3.2 網(wǎng)站分析
網(wǎng)易云音樂(lè)首頁(yè):

爬取思路有兩種:
1.基于網(wǎng)頁(yè)原代碼,利用正則表達(dá)式、XPath等獲取數(shù)據(jù);
2.基于每次請(qǐng)求的API,直接獲取所需數(shù)據(jù);
本文采用第二種,針對(duì)電視劇《旋風(fēng)少女》插曲的評(píng)論做簡(jiǎn)單說(shuō)明:

查看NetWork,發(fā)起請(qǐng)求,我們可以看到,數(shù)據(jù)保存在這里:

請(qǐng)求地址為:https://music.163.com/weapi/v1/resource/comments/R_SO_4_28936510?csrf_token=

但是Request HEaders里的Cookie值、FromData里的params、encSecKey都是加密過(guò)的。開始解密:

然后,我從一眾解密大佬的海談闊論中發(fā)現(xiàn)了這個(gè)神奇的存在,哈哈,眾多泥石流里的一股清泉啊。沒(méi)錯(cuò),就是這個(gè)鏈接:
http://music.163.com/api/v1/resource/comments/R_SO_4_28936510

很好,狂喜。但是很不巧的是網(wǎng)易云設(shè)置了反爬蟲,根本下不不了手,爬蟲時(shí)會(huì)出現(xiàn)以下錯(cuò)誤:
{"code":-460,"msg":"Cheating"}
這是網(wǎng)上存在的解決辦法:
更換動(dòng)態(tài)IP的:
復(fù)制請(qǐng)求頭的:
說(shuō)明:上面兩種方法在現(xiàn)在是行不通的,網(wǎng)易云加強(qiáng)了反爬蟲機(jī)制,對(duì)請(qǐng)求頭中的Cookie值進(jìn)行了加密,所以有了下面這些對(duì)請(qǐng)求頭中的Cookie值進(jìn)行解密的:
但是,這種解密方法繁瑣復(fù)雜,本文采用@Binaryify團(tuán)隊(duì)開發(fā)的[NeteaseCloudMusicApi]獲取請(qǐng)求,這是一個(gè)相當(dāng)便利、好用的API,感謝Binaryify。
3.3 功能特性
此次爬蟲,下面的所有功能,都可以實(shí)現(xiàn),是的,都可以!只要你想要的,在網(wǎng)易云上面有的,都可以爬,隨便爬?。?!就是這么任性和囂張!奈我何?!哈哈哈!
獲取用戶信息 , 歌單,收藏,mv, dj 數(shù)量
獲取用戶歌單
獲取用戶電臺(tái)
獲取用戶關(guān)注列表
獲取用戶粉絲列表
獲取用戶動(dòng)態(tài)
獲取用戶播放記錄
獲取精品歌單
獲取歌單詳情
搜索
搜索建議
獲取歌詞
歌曲評(píng)論
收藏單曲到歌單
專輯評(píng)論
歌單評(píng)論
mv 評(píng)論
電臺(tái)節(jié)目評(píng)論
banner
獲取歌曲詳情
獲取專輯內(nèi)容
獲取歌手單曲
獲取歌手 mv
獲取歌手專輯
獲取歌手描述
獲取相似歌手
獲取相似歌單
相似 mv
獲取相似音樂(lè)
獲取最近 5 個(gè)聽(tīng)了這首歌的用戶
獲取每日推薦歌單
獲取每日推薦歌曲
私人 FM
簽到
喜歡音樂(lè)
垃圾桶
歌單 ( 網(wǎng)友精選碟 )
新碟上架
熱門歌手
最新 mv
推薦 mv
推薦歌單
推薦新音樂(lè)
推薦電臺(tái)
推薦節(jié)目
獨(dú)家放送
mv 排行
獲取 mv 數(shù)據(jù)
播放 mv/視頻
排行榜
歌手榜
云盤
電臺(tái) - 推薦
電臺(tái) - 分類
電臺(tái) - 分類推薦
電臺(tái) - 訂閱
電臺(tái) - 詳情
電臺(tái) - 節(jié)目
給評(píng)論點(diǎn)贊
獲取動(dòng)態(tài)
獲取熱搜
發(fā)送私信
發(fā)送私信歌單
新建歌單
收藏/取消收藏歌單
歌單分類
收藏的歌手列表
訂閱的電臺(tái)列表
相關(guān)歌單推薦
付費(fèi)精選接口
音樂(lè)是否可用檢查接口
登錄狀態(tài)
獲取視頻數(shù)據(jù)
發(fā)送/刪除評(píng)論
熱門評(píng)論
視頻評(píng)論
退出登錄
所有榜單
所有榜單內(nèi)容摘要
收藏視頻
收藏 MV
視頻詳情
相關(guān)視頻
關(guān)注用戶
新歌速遞
喜歡音樂(lè)列表(無(wú)序)
收藏的 MV 列表
本文以其中幾個(gè)為例,進(jìn)行數(shù)據(jù)爬取和文本挖掘:
3.3 歌單
我們找到歌單頁(yè)面為下:

可以看到:
語(yǔ)種有:華語(yǔ)、歐美、日語(yǔ)、韓語(yǔ)、小語(yǔ)種;
風(fēng)格有:流行、搖滾、民謠、電子、舞曲、說(shuō)唱、輕音樂(lè)、說(shuō)唱、爵士、鄉(xiāng)村、古典、民族、英倫、金屬、朋克、藍(lán)調(diào)、雷鬼、世界音樂(lè)、拉丁、古風(fēng)等;還有對(duì)歌單的場(chǎng)景、情感、主題分類。
我們不對(duì)其進(jìn)行帥選,爬取所有種類的歌單。當(dāng)每頁(yè)為35個(gè)歌單的時(shí)候,一共有38頁(yè);當(dāng)每頁(yè)為20個(gè)歌單的時(shí)候,一共有66頁(yè),我們把66頁(yè)一共1306個(gè)的歌單信息爬取下來(lái):
class MenuSpider(scrapy.Spider):
name = 'menu'
allowed_domains = ['localhost:3000']
start_urls = ['http://localhost:3000/']
allplaylist_url = 'http://localhost:3000/top/playlist?order=hot&cat=%E5%85%A8%E9%83%A8&limit=20&offset={offset}'
def start_requests(self):
for i in range(0, 66):
yield Request(self.allplaylist_url.format(offset=i * 20), callback=self.parse_allplaylist)
def parse_allplaylist(self, response):
result = json.loads(response.text)
item = MusicmenuItem()
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
3.3 歌曲

我們選擇爬取歌曲評(píng)論信息的時(shí)候一并把歌曲信息爬下來(lái)。
3.5 歌曲評(píng)論
我們的目的是獲取K個(gè)歌單下面的M首歌曲的前N頁(yè)評(píng)論信息。

2018年12月30日下午3點(diǎn)27分:
元旦公休,周日,CS下雪,CSU美不勝收。
從前晚、昨天、昨晚01:00點(diǎn),我一直在一個(gè)問(wèn)題上掙扎,那就是爬取前4首歌的前3頁(yè)評(píng)論時(shí),為什么爬取的時(shí)候只會(huì)爬到7條記錄:第一首第一頁(yè),第二首第一頁(yè),第三首第一頁(yè),第四首一、二、三頁(yè),現(xiàn)在這個(gè)問(wèn)題終于解決。
其中,有包括:Scrapy的調(diào)試:pycharm下打開、執(zhí)行并調(diào)試scrapy爬蟲程序。
- Crawer 1.0
# -*- coding: utf-8 -*-
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #改變標(biāo)準(zhǔn)輸出的默認(rèn)編碼
class MusiccommentsSpider(scrapy.Spider):
name = 'musiccomments'
allowed_domains = ['localhost:3000']
start_urls = ['http://localhost:3000/comment/music?id=296883/']
comment_url = 'http://localhost:3000/comment/music?id={id}&offset={offset}&limit=20'
playlist_url = 'http://localhost:3000/playlist/detail?id=2571516884'
num_comment = 0
song_id=0
def start_requests(self):
yield Request(self.playlist_url, callback=self.parse_playlist)
#yield Request(self.comment_url.format(offset=self.num_comment), callback=self.parse_comment,dont_filter=True)
def parse_playlist(self, response):
result = json.loads(response.text)
item = WangyiyunPlaylistItem()
#print(result.get('playlist').get('tracks').get('name'))
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
#global song_id
for i in range(0, 4):
#for i in range(0,(result.get('playlist').get('trackCount'))-1):
self.num_comment=0
self.song_id = result.get('playlist').get('tracks')[i].get('id')
yield Request(self.comment_url.format(id=self.song_id,offset=self.num_comment), callback=self.parse_comment,dont_filter=True)
def parse_comment(self, response):
result = json.loads(response.text)
item = WangyiyunCommentItem()
#print(response.text.encode('utf-8','ignore'))
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
self.num_comment = self.num_comment + 1
if self.num_comment <= 2:
yield Request(self.comment_url.format(id=self.song_id, offset=self.num_comment * 20),
callback=self.parse_comment, dont_filter=True)
究其原因,是yield的緣故,傳送門:python中yield的用法詳解——最簡(jiǎn)單,最清晰的解釋。一定要明白語(yǔ)句的執(zhí)行順序和yield的特殊用法,雙重循環(huán)省事更多,何必非要搞函數(shù)遞歸?
- Crawer 2.0
# -*- coding: utf-8 -*-
import time
import scrapy
from scrapy import Spider,Request
import io
import sys
import json
from wangyiyun.items import WangyiyunCommentItem
from wangyiyun.items import WangyiyunPlaylistItem
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #改變標(biāo)準(zhǔn)輸出的默認(rèn)編碼
class MusiccommentsSpider(scrapy.Spider):
name = 'musiccomments'
allowed_domains = ['localhost:3000']
start_urls = ['http://localhost:3000/comment/music?id=296883/']
comment_url = 'http://localhost:3000/comment/music?id={id}&offset={offset}&limit=20'
playlist_url = 'http://localhost:3000/playlist/detail?id=2571516884'
num_comment = 0
song_id=0
def start_requests(self):
yield Request(self.playlist_url, callback=self.parse_playlist)
#yield Request(self.comment_url.format(offset=self.num_comment), callback=self.parse_comment,dont_filter=True)
def parse_playlist(self, response):
result = json.loads(response.text)
item = WangyiyunPlaylistItem()
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
for i in range(0, 4):
for j in range(0,3):
yield Request(
self.comment_url.format(id=result.get('playlist').get('tracks')[i].get('id'), offset=j * 20),
callback=self.parse_comment, dont_filter=True)
def parse_comment(self, response):
result = json.loads(response.text)
item = WangyiyunCommentItem()
#print(response.text.encode('utf-8','ignore'))
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
是不是簡(jiǎn)潔了很多?
接下來(lái),連接數(shù)據(jù)庫(kù),提取上面所爬歌單的id,根據(jù)id值獲得每個(gè)歌單下面的歌曲id,再根據(jù)歌曲id值獲得每首歌下面的評(píng)論信息。執(zhí)行翻頁(yè)操作,從而最終獲取1306個(gè)歌單下面的前30首歌曲的前10頁(yè)評(píng)論:
- Crawer 3.0
# -*- coding: utf-8 -*-
import time
import scrapy
from scrapy import Spider,Request
import io
import sys
import json
import pandas as pd
import pymongo
from wangyiyun.items import WangyiyunCommentItem
from wangyiyun.items import WangyiyunPlaylistItem
from wangyiyun.items import WangyiyunAllPlaylistItem
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #改變標(biāo)準(zhǔn)輸出的默認(rèn)編碼
class MusiccommentsSpider(scrapy.Spider):
name = 'musiccomments'
allowed_domains = ['localhost:3000']
start_urls = ['http://localhost:3000/comment/music?id=296883/']
allplaylist_url = 'http://localhost:3000/top/playlist?order=hot&cat=%E5%85%A8%E9%83%A8&limit=20&offset={offset}'
playlist_url = 'http://localhost:3000/playlist/detail?id={id}'
comment_url = 'http://localhost:3000/comment/music?id={id}&offset={offset}&limit=20'
num_comment = 0
num_page = 0
song_id = 0
results = ''
def start_requests(self):
client = pymongo.MongoClient(host='localhost', port=27017)
db = client['music']
collection = db['menu']
# 將數(shù)據(jù)庫(kù)數(shù)據(jù)轉(zhuǎn)為dataFrame
menu = pd.DataFrame(list(collection.find()))
num = menu['playlists']
result = pd.DataFrame(num.iloc[0])
for i in range(1, 66):
data2 = pd.DataFrame(num.iloc[i])
result = pd.concat([result, data2], ignore_index=True)
print(result.shape)
id = result['id']
for i in range(0, 1000):
yield Request(self.playlist_url.format(id=id.iloc[i]), callback=self.parse_playlist)
def parse_playlist(self, response):
result = json.loads(response.text)
item = WangyiyunPlaylistItem()
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
for j in range(0, 30):
for k in range(0,10):
yield Request(
self.comment_url.format(id=result.get('playlist').get('tracks')[j].get('id'), offset=k * 20),
callback=self.parse_comment, dont_filter=True)
def parse_comment(self, response):
result = json.loads(response.text)
item = WangyiyunCommentItem()
#print(response.text.encode('utf-8','ignore'))
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
獲取了13943個(gè)文檔。由于爬取時(shí)歌曲信息和評(píng)論信息保存到了一張表里,去掉其中的歌曲信息,評(píng)論信息約有12000*20=24萬(wàn)條。

說(shuō)明:由于爬取的時(shí)候IP被封,這24萬(wàn)條評(píng)論并非所有,這一問(wèn)題在后文中處理。

并且,IP被封還導(dǎo)致我網(wǎng)易云音樂(lè)的評(píng)論是看不了的,加載不出來(lái)的。。。網(wǎng)易云音樂(lè)做得真夠絕!一直都是這樣子。。。。

3.6 網(wǎng)易云音樂(lè)用戶
爬取思路:我們從網(wǎng)易云音樂(lè)的大V云音樂(lè)小秘書開始,爬取其關(guān)注和粉絲,再爬取其關(guān)注者的關(guān)注和粉絲,粉絲的關(guān)注和粉絲,以此類推,不斷遞歸,雪球會(huì)越滾越大,涉及到的用戶會(huì)越來(lái)越多,怎么樣?是不是感覺(jué)很爽很刺激?
代碼奉上:
# -*- coding: utf-8 -*-
import time
import scrapy
import json
from scrapy import Spider, Request
from WangyiyunUser.items import WangyiyunuserItem
class UserSpider(scrapy.Spider):
name = 'user'
allowed_domains = ['localhost:3000']
start_urls = ['http://localhost:3000/']
user_url = 'http://localhost:3000/user/detail?uid={uid}'
follows_url = 'http://localhost:3000/user/follows?uid={uid}&offset={offset}&limit={limit}'
followers_url = 'http://localhost:3000/user/followeds?uid={uid}&offset={offset}&limit={limit}'
start_uid = '9003'
follows_next_page=0
followers_next_page=0
def start_requests(self):
yield Request(self.user_url.format(uid=self.start_uid), self.parse_user,dont_filter = True)
yield Request(self.follows_url.format(uid=self.start_uid, limit=30, offset=0),
self.parse_follows,dont_filter = True)
yield Request(self.followers_url.format(uid=self.start_uid, limit=30, offset=0),self.parse_followers,dont_filter = True)
def parse_user(self, response):
result = json.loads(response.text)
item = WangyiyunuserItem()
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
#print(result.get('profile').get('userId'))
yield Request(
self.follows_url.format(uid=result.get('profile').get('userId'), limit=30, offset=0),
self.parse_follows,dont_filter = True)
yield Request(
self.followers_url.format(uid=result.get('profile').get('userId'), limit=30, offset=0),
self.parse_followers,dont_filter = True)
def parse_follows(self, response):
results = json.loads(response.text)
print('正在判斷關(guān)注者:')
if 'follow' in results.keys():
for result in results.get('follow'):
yield Request(self.user_url.format(uid=result.get('userId')),
self.parse_user,dont_filter = True)
if results.get('more') == True:
self.follows_next_page = self.follows_next_page+1
yield Request(self.follows_url.format(uid=self.start_uid, limit=30, offset=self.follows_next_page*30),
self.parse_follows,dont_filter = True)
def parse_followers(self, response):
results = json.loads(response.text)
if 'followeds' in results.keys():
for result in results.get('followeds'):
yield Request(self.user_url.format(uid=result.get('userId')),
self.parse_user,dont_filter = True)
if results.get('more') == True:
self.followers_next_page=self.followers_next_page+1
yield Request(self.followers_url.format(uid=self.start_uid, limit=30, offset=self.followers_next_page*30),
self.parse_followers,dont_filter = True)
最終在封IP之前獲得了14974條用戶詳情。

四、數(shù)據(jù)分析
4.1 歌單
4.1.1 播放數(shù)量最多的TOP20歌單
導(dǎo)入包,設(shè)置中文字體支持
import pymongo
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
#設(shè)置圖片顯示字體對(duì)漢字的支持
mpl.rcParams['font.sans-serif'] = ['SimHei']
client = pymongo.MongoClient(host='localhost', port=27017)
從數(shù)據(jù)庫(kù)導(dǎo)入數(shù)據(jù)
db = client['music']
collection = db['menu']
# 將數(shù)據(jù)庫(kù)數(shù)據(jù)轉(zhuǎn)為dataFrame
data = pd.DataFrame(list(collection.find()))
查看數(shù)據(jù)維度
print(data.shape)
每頁(yè)的歌單保存為一個(gè)文檔,66頁(yè)的歌單,一共有66個(gè)文檔。
取其中一頁(yè)歌單,查看內(nèi)容
num=data['playlists']
print(num.iloc[0])
為方便觀察,對(duì)其做json解析。限于篇幅,取其中一個(gè)歌單的內(nèi)容顯示如下:
[{
'name': '你是個(gè)成熟的成年人了,你該戒掉情緒了。',
'id': 2468145627,
'trackNumberUpdateTime': 1540176152147,
'status': 0,
'userId': 49341371,
'createTime': 1539449979814,
'updateTime': 1540176152147,
'subscribedCount': 58406,
'trackCount': 43,
'cloudTrackCount': 0,
'coverImgUrl': 'http://p2.music.126.net/qXN1QIV_mGUV1BusdRX1CA==/109951163602371733.jpg',
'coverImgId': 109951163602371730,
'description': '-\n\n你要做一個(gè)不動(dòng)聲色的大人了。不準(zhǔn)情緒化,不準(zhǔn)偷偷想念,不準(zhǔn)回頭看。去過(guò)自己另外的生活。\n\n以前的你,哭著哭著就笑了。\n\n現(xiàn)在的你,笑著笑著就哭了。\n\n到了一定的年紀(jì),眼淚越來(lái)越少,因?yàn)樯磉呍僖矝](méi)有一個(gè)能幫你擦眼淚的人。\n\n在成年人的世界里,最讓人想哭的三個(gè)字是:不要哭。\n\n-\n\n人生還有眼淚也沖刷不干凈的巨大悲傷,還有難忘的痛苦讓你們即使想哭也不能流淚。\n\n懷揣著痛苦和悲傷,即使如此也要帶上它們笑著前行。\n\n-\n\n如果可以,往后請(qǐng)讓笑容比眼淚多。就算要哭,每一滴眼淚的名字也應(yīng)該是——喜極而泣。\n\n-\n\n封面:站酷插畫師Y_jianjian',
'tags': ['華語(yǔ)', '流行', '治愈'],
'playCount': 4370831,
'trackUpdateTime': 1546744947197,
'specialType': 0,
'totalDuration': 0,
'creator': {
'defaultAvatar': False,
'province': 440000,
'authStatus': 0,
'followed': False,
'avatarUrl': 'http://p1.music.126.net/NbMIANWbmL6PVBmduaDzqA==/109951163776645958.jpg',
'accountStatus': 0,
'gender': 1,
'city': 441500,
'birthday': 845481600000,
'userId': 49341371,
'userType': 200,
'nickname': '一點(diǎn)波瀾-',
'signature': '激不起一點(diǎn)波瀾。',
'description': '',
'detailDescription': '',
'avatarImgId': 109951163776645950,
'backgroundImgId': 109951163373553600,
'backgroundUrl': 'http://p1.music.126.net/O3aDHMNl_VW7GgK2VnGz9Q==/109951163373553592.jpg',
'authority': 0,
'mutual': False,
'expertTags': ['華語(yǔ)', '流行', '歐美'],
'experts': None,
'djStatus': 10,
'vipType': 11,
'remarkName': None,
'avatarImgIdStr': '109951163776645958',
'backgroundImgIdStr': '109951163373553592',
'avatarImgId_str': '109951163776645958'
},
'tracks': None,
'subscribers': [{
'defaultAvatar': False,
'province': 440000,
'authStatus': 0,
'followed': False,
'avatarUrl': 'http://p1.music.126.net/_XE9wV7-4JlWUPf51pnM_w==/109951163772509594.jpg',
'accountStatus': 0,
'gender': 2,
'city': 440700,
'birthday': -2209017600000,
'userId': 515582083,
'userType': 0,
'nickname': '小心我錘爆你',
'signature': '',
'description': '',
'detailDescription': '',
'avatarImgId': 109951163772509600,
'backgroundImgId': 109951163772503820,
'backgroundUrl': 'http://p1.music.126.net/DQiWNBKXye4i_pbaSzUi9A==/109951163772503826.jpg',
'authority': 0,
'mutual': False,
'expertTags': None,
'experts': None,
'djStatus': 0,
'vipType': 0,
'remarkName': None,
'avatarImgIdStr': '109951163772509594',
'backgroundImgIdStr': '109951163772503826',
'avatarImgId_str': '109951163772509594'
}],
'subscribed': None,
'commentThreadId': 'A_PL_0_2468145627',
'newImported': False,
'adType': 0,
'highQuality': False,
'privacy': 0,
'ordered': True,
'anonimous': False,
'shareCount': 520,
'coverImgId_str': '109951163602371733',
'commentCount': 403,
'alg': 'alg_sq_topn_lr'
},
我們將第一個(gè)文檔,即第一頁(yè)歌單數(shù)據(jù)轉(zhuǎn)化為數(shù)據(jù)框,并查看其數(shù)據(jù)維度:
print(pd.DataFrame(num.iloc[0]).shape)
結(jié)果:
(20, 33)
一共有20行,33列。即第一頁(yè)的20個(gè)歌單中,每個(gè)歌單都有33個(gè)字段。
將66頁(yè)的所有歌單合并到一起,查看數(shù)據(jù)維度
result=pd.DataFrame(num.iloc[0])
for i in range(1,66):
data2=pd.DataFrame(num.iloc[i])
result=pd.concat([result,data2],ignore_index=True)
print(result.shape)
結(jié)果:
(1306, 33)
所有全部歌單一共有1306個(gè),每個(gè)歌單33個(gè)字段。
查看行名:
print(result.columns.values.tolist())
33個(gè)字段為下所示:
['adType', 'alg', 'anonimous', 'cloudTrackCount', 'commentCount', 'commentThreadId', 'coverImgId', 'coverImgId_str', 'coverImgUrl', 'createTime', 'creator', 'description', 'highQuality', 'id', 'name', 'newImported', 'ordered', 'playCount', 'privacy', 'shareCount', 'specialType', 'status', 'subscribed', 'subscribedCount', 'subscribers', 'tags', 'totalDuration', 'trackCount', 'trackNumberUpdateTime', 'trackUpdateTime', 'tracks', 'updateTime', 'userId']
播放數(shù)量最多的TOP20歌單
result1=result.sort_values(by=['playCount'],ascending = False)
result1['playCount']
result2=pd.concat([result1['name'],result1['playCount']],axis=1,ignore_index=False)
print((pd.DataFrame(result2)).shape)
print((pd.DataFrame(result2)).head)
data=result2
data.index=data['name']
colors = '#6D6D6D' # 設(shè)置標(biāo)題顏色為灰色
color_line = '#CC2824'
fontsize_title = 20
from IPython.core.pylabtools import figsize # import figsize
figsize(12.5, 4) # 設(shè)置 figsize
plt.rcParams['savefig.dpi'] = 200 #圖片像素
#plt.rcParams['figure.dpi'] = 200 #分辨率
# 默認(rèn)的像素:[6.0,4.0],分辨率為100,圖片尺寸為 600&400
# 指定dpi=200,圖片尺寸為 1200*800
# 指定dpi=300,圖片尺寸為 1800*1200
# 設(shè)置figsize可以在不改變分辨率情況下改變比例
# 我們使用R語(yǔ)言中g(shù)gplot的風(fēng)格
plt.style.use('ggplot')
data[1:21].plot(kind='barh',color=color_line).invert_yaxis()
#使用 pd.Series把dataframe轉(zhuǎn)成Series
#data = pd.Series(data['playCount'].values)
#for y,x in enumerate(list(data['playCount'].values[1:21])):
for y,x in enumerate(list(data.iloc[:,1][1:21].values)):
plt.text(x+1600000,y+0.3,'%s' %round(x,1),ha='center',color=colors)
#plt.text(x-20,y+0.3,'%s' %x,color=colors)
plt.xlabel('播放數(shù)量')
plt.ylabel('歌單名稱')
plt.title('播放數(shù)量最多的TOP20歌單', color = colors, fontsize=fontsize_title)
plt.tight_layout()
plt.savefig('播放數(shù)量最多的TOP20歌單.png',dpi=200)
plt.show()
結(jié)果:

顯示為文本:
name
聽(tīng)說(shuō)你也在找好聽(tīng)的華語(yǔ)歌 41094772
那些喜歡到循環(huán)播放的歌 37185064
失戀必聽(tīng)歌單 | 因?yàn)槟阃蝗宦?tīng)懂了很多歌 31393852
2018年度最熱新歌TOP100 30460302
別急,甜甜的戀愛(ài)馬上就輪到你了 27107300
溫柔暴擊 | 沉溺于男友音的甜蜜鄉(xiāng) 18519378
最是粵語(yǔ)最為情深 也唯獨(dú)你最難忘懷 17338788
提神醒腦 瘋狂抖腿魔性搖頭.GIF 15403695
Hip-hop | 少女心狙擊手 12368797
你最想對(duì)暗戀的人 說(shuō)的一句話是什么 11561604
風(fēng)月無(wú)憾 | 你是我有關(guān)青春 最美的句讀 9827816
新的一年,希望你喜歡的人也喜歡你 8841251
【2018年度電影精選| Ready Story 】 8747021
"若是心懷舊夢(mèng) 就別再無(wú)疾而終" 8706438
“長(zhǎng)大”這兩個(gè)字,孤獨(dú)得連偏旁都沒(méi)有 8587671
如何用手機(jī)鈴聲驚艷四座? 日語(yǔ)篇 8059432
再見(jiàn)大俠:武俠小說(shuō)泰斗金庸逝世 7993862
KTV必點(diǎn):有沒(méi)有一首歌,唱著唱著就淚奔 7890135
你放棄過(guò)一個(gè)愛(ài)了很久的人嗎? 7818393
歐美·耳朵懷孕 | 盤點(diǎn)那些流行歌手 7497257
播放量第一的為《聽(tīng)說(shuō)你也在找好聽(tīng)的華語(yǔ)歌 》,播放數(shù)高達(dá)4109萬(wàn)多。第二為《那些喜歡到循環(huán)播放的歌》,播放量為3718萬(wàn)多。前20個(gè)歌單的播放量最少的也在749萬(wàn)以上。究其原因,有以下幾點(diǎn):
- 歌單名本身很具誘惑力,很適合沒(méi)有固定聽(tīng)歌意向的群體,較為大眾化,是絕大多數(shù)人的首選。像《別急,甜甜的戀愛(ài)馬上就輪到你了》《溫柔暴擊 | 沉溺于男友音的甜蜜鄉(xiāng)》《你最想對(duì)暗戀的人 說(shuō)的一句話是什么》《風(fēng)月無(wú)憾 | 你是我有關(guān)青春 最美的句讀》等,光聽(tīng)名字就很吸引人。
- 創(chuàng)建人鹿白川,有網(wǎng)易云達(dá)人認(rèn)證,粉絲數(shù)也高達(dá)9萬(wàn)多,實(shí)打?qū)嵉拇骎。標(biāo)簽:音樂(lè)(華語(yǔ)、流行、歐美)、資訊(生活);個(gè)人介紹:話少慢熱不喜交際 / 不推廣 勿擾;請(qǐng)問(wèn)都這個(gè)水平了還需要推廣么???
其創(chuàng)建的歌單播放量動(dòng)輒上百萬(wàn),上千萬(wàn),看來(lái)屬于網(wǎng)易云音樂(lè)中的主力人物。他的主頁(yè):
image.png - 歌單下的歌曲本身質(zhì)量就很高,這也是最重要的一點(diǎn)。
相反,查看播放量最少的20首:
中島美嘉 Special Live 2019 廣州/上海 85
霉霉丨泰勒斯威·夫特的Style 82
C95東方專輯收錄(一專一首) 80
CLUB MIAMI HOHHOT 百大DJ-DENIZ KOYU 78
戰(zhàn)歌舞曲 76
vlog ? 74
*我們自雪鄉(xiāng)去往霧夏*(貳) 73
伴讀喵自習(xí)室 71
細(xì)膩莫扎特 69
【開業(yè)歌曲】新店開業(yè)喜慶歌曲大全100首 67
鄧麗君1982年香港伊麗莎白體育館演唱會(huì) 65
?? 2019年精選電音 待制作 敬請(qǐng)期待 62
一周日語(yǔ)新歌(12/29~01/04) 60
祺鑫時(shí)光軸2018年下 58
【純音樂(lè)】你是否聽(tīng)過(guò),那屬于夏日的美好 49
方燦的Spotify歌單 42
冷門純音| 輕叩心扉,靜享愜意好時(shí)光 31
【我愛(ài)學(xué)習(xí)】學(xué)習(xí)解壓輕音樂(lè) 29
【輕音樂(lè)】如何優(yōu)雅地睡覺(jué) 24
思念的解藥 即是歸鄉(xiāng) 19
恰恰和上面三點(diǎn)條件都相反,也難怪不太受歡迎。
4.2 網(wǎng)易云用戶
4.2.1 數(shù)據(jù)庫(kù)簡(jiǎn)單查詢分析
篩選同在湖南的網(wǎng)易云小伙伴:
select * from users2 where profile.city = 650100
從數(shù)據(jù)庫(kù)里得到了315條記錄:

篩選地處長(zhǎng)沙等級(jí)在5級(jí)以上的:
select * from users2 where profile.city = 430100 and level>5
得到了69條記錄:

其中挑選了一個(gè)id為1389014,昵稱叫猜猜尋的進(jìn)行分析
4.2.2 關(guān)注者的地區(qū)分布
導(dǎo)入之前爬取到的用戶數(shù)據(jù)
import pymongo
import pandas as pd
import numpy as np
client = pymongo.MongoClient(host='localhost', port=27017)
db = client['music']
collection = db['users3']
# 將數(shù)據(jù)庫(kù)數(shù)據(jù)轉(zhuǎn)為dataFrame
user = pd.DataFrame(list(collection.find()))
查看數(shù)據(jù)維度:
print(user.shape)
有14974個(gè)用戶,每個(gè)用戶下面有13個(gè)字段信息:
(14974, 13)
查看字段名稱:
['_id', 'adValid', 'bindings', 'code', 'createDays', 'createTime', 'level', 'listenSongs', 'mobileSign', 'pcSign', 'peopleCanSeeMyPlayRecord', 'profile', 'userPoint']
我們選取其中的profile進(jìn)行分析:
profile=pd.DataFrame(user['profile'])
profile下面有41個(gè)字段,我們將14974個(gè)用戶的profile字段下面的其中兩個(gè)字段:province和userId提取出來(lái),合并成一個(gè)數(shù)據(jù)框。
result=pd.DataFrame([[9003,110000]])
for i in range(1,14974):
province=profile.iloc[i][0].get('province')
userId=profile.iloc[i][0].get('userId')
#列合并
userInfo=pd.DataFrame([[userId,province]])
#行合并
result=pd.concat([result,userInfo],ignore_index=True)
#列命名
result.columns = ['userId','province']
統(tǒng)計(jì)每個(gè)省份所包含的網(wǎng)易云音樂(lè)用戶數(shù)量。
data = result.groupby(result['province']).count()
data.to_csv("userProvinceInfo.csv", sep=',', header=True, index=False)
由于原始數(shù)據(jù)中province的值為行政區(qū)劃代碼,需要根據(jù)行政區(qū)劃代碼表還原用戶的所在省份信息。
install.packages("xlsx")
library(xlsx)
userInfo=read.csv("userProvinceInfo.csv")
code=read.table("clipboard",header = TRUE)
pro=matrix(0,nrow(userInfo),1)
for(i in 3:36){
pro[i-2,1]=as.matrix(code[which(userInfo[i,2]==code[,1]),2])[1,1]
}
pro=pro[-c(35,36,37),]
data=cbind(userInfo[-c(35,36,37),1],pro)
colnames(data)<-c("userCounts","Province")
write.xlsx(x = data, file = "data.xlsx",
sheetName = "data", row.names = FALSE)
利用得到的最終數(shù)據(jù),基于可視化工具Power Map 2016,制作演示視頻,并將其轉(zhuǎn)化為GIF(簡(jiǎn)書不能上傳視頻)。但是GIF不能超過(guò)10M,下面是壓縮后的,很模糊。原文件大小70M,高清,會(huì)看得很舒服。制作好的原演示視頻鏈接:網(wǎng)易云音樂(lè)中國(guó)分布演示視頻
是不是炫酷到爆炸,有型到噴汁???。?br> 在第二個(gè)深入鏡頭,你可以看到交通路線,有沒(méi)有百度地圖的即視感?!再深入點(diǎn),我可以找到你的位置。
五、文本相似計(jì)算原理
5.1 TF-IDF
我們?cè)谂袛嘁粋€(gè)詞的重要性的時(shí)候,不能僅僅依靠詞頻來(lái)處理,因此引入了TF-IDF值。TF-IDF是Term Frequency - Inverse Document Frequency的縮寫,即“詞頻-逆文本頻率”。它由兩部分組成,TF和IDF。
TF是詞頻,我們后面做的詞條—文檔矩陣給出了文本中各個(gè)詞的出現(xiàn)頻率統(tǒng)計(jì),并作為文本特征,這個(gè)很好理解。而IDF,即“逆文本頻率”,則反映了一個(gè)詞在所有文本中出現(xiàn)的頻率,如果一個(gè)詞在很多的文本中出現(xiàn),那么它的IDF值應(yīng)該低。反過(guò)來(lái),如果一個(gè)詞在比較少的文本中出現(xiàn),那么它的IDF值應(yīng)該高。比如一些專業(yè)的名詞如“Machine Learning”。這樣的詞IDF值應(yīng)該高。一個(gè)極端的情況,如果一個(gè)詞在所有的文本中都出現(xiàn),那么它的IDF值應(yīng)該為0。
上面是從定性上說(shuō)明的IDF的作用,那么如何對(duì)一個(gè)詞的IDF進(jìn)行定量分析呢?這里直接給出一個(gè)詞x的IDF的基本公式如下:
IDF(x)=log(N/N(x)) (1)
其中,N代表語(yǔ)料庫(kù)中文本的總數(shù),而N(x)代表語(yǔ)料庫(kù)中包含詞x的文本總數(shù)。
當(dāng)然,在一些特殊的情況IDF計(jì)算會(huì)有一些小問(wèn)題,比如某一個(gè)生僻詞在語(yǔ)料庫(kù)中沒(méi)有,這樣我們的分母為0, IDF沒(méi)有意義了。所以常用的IDF我們需要做一些平滑,使語(yǔ)料庫(kù)中沒(méi)有出現(xiàn)的詞也可以得到一個(gè)合適的IDF值。平滑的方法有很多種,最常見(jiàn)的IDF平滑后的公式之一為:
IDF(x)=log((N+1)/(N(x)+1))+1 (2)
有了IDF的定義,我們就可以計(jì)算某一個(gè)詞的TF-IDF值了:
TF?IDF(x)=TF(x)?IDF(x) (3)
其中TF(x)指詞x在當(dāng)前文本中的詞頻。
5.2 Doc2Bow模型
Word2vec和Doc2Bow同屬兩位學(xué)術(shù)大牛Quoc Le 和 Tomas Mikolov發(fā)明的。
在2014年的《Distributed Representations of Sentences and Documents》所提出文章向量(Documents vector),或者稱句向量(Sentences vector),當(dāng)然在文章中,統(tǒng)一稱這種向量為Paragraph Vector。
Word2vec涉及到很多數(shù)學(xué)模型,囊括了詞向量的理解、sigmoid函數(shù)、邏輯回歸、Bayes公式、Huffman編碼、n-gram模型、淺層神經(jīng)網(wǎng)絡(luò)、激活函數(shù)、最大似然及其梯度推導(dǎo)、隨機(jī)梯度下降法、詞向量與模型參數(shù)的更新公式、CBOW模型和 Skip-gram模型、Hierarchical Softmax算法和Negative Sampling算法等。當(dāng)然還會(huì)結(jié)合google發(fā)布的C源碼(好像才700+行),講述相關(guān)部分的實(shí)現(xiàn)細(xì)節(jié),比如Negative Sampling算法如何隨機(jī)采樣、參數(shù)更新的細(xì)節(jié)、sigmod的快速近似計(jì)算、詞典的hash存儲(chǔ)、低頻與高頻詞的處理、窗口內(nèi)的采樣方式、自適應(yīng)學(xué)習(xí)、參數(shù)初始化、w2v實(shí)際上含有兩中方法等,用C代碼僅僅700+行實(shí)現(xiàn),并加入了諸多技巧。原文可以參見(jiàn)以下鏈接:
作者:林海山波
來(lái)源:CSDN
原文:https://blog.csdn.net/lenbow/article/details/52120230
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!
5.3 計(jì)算相似度
本文使用歐氏距離度量相似度。歐幾里得度量(euclidean metric)(也稱歐式距離)是一個(gè)通常采用的距離定義,指在m維空間中兩個(gè)點(diǎn)之間的真實(shí)距離,或者向量的自然長(zhǎng)度(即該點(diǎn)到原點(diǎn)的距離)。
0ρ = sqrt( (x1-x2)^2+ (y1-y2)^2 )
similarity = 1/(op + 1)
最終的similarity就是相似度評(píng)價(jià)的值。
實(shí)現(xiàn)見(jiàn)下篇
