Python3爬蟲抓取《曾經我也想過一了百了》熱評-因為像你這樣的人生於這世上,我稍稍喜歡這個世界了。
作為初學者,懷著緊張的心情來分享我的第一個爬蟲小程式。看到很多網易雲音樂熱評的爬蟲,我也來試試。我這次要爬取的是《曾經我也想過一了百了》中島美嘉的這首歌。
首先進行抓包分析
首先用瀏覽器開啟網易雲音樂的網頁版,進入歌曲頁面,可以看到下面有評論。接著 F12 進入開發者控制檯,接下來就要做的是,找到歌曲評論對應的 URL“R_SO_4_26830207?csrf_token=”,並分析驗證其資料跟網頁現實的資料是否吻合。
在Preview的介面,可以看到全部評論是comments,熱門評論是hotComments。在Headers頁面,可以看到請求方式是POST,以及請求所需要的各種引數。發現表單中需要填兩個資料,名稱為 params 和 encSecKey。後面緊跟的是一大串字元,換幾首歌會發現,每首歌的 params 和 encSecKey 都是不一樣的,因此,這兩個資料可能經過一個特定的演算法進行加密過的。
在此感謝大佬解析過網易雲音樂的加密原理,直接拿來用就可以啦。
def createSecretKey(self,size): return (''.join(map(lambda xx: (hex(ord(xx))[2:]), str(os.urandom(size)))))[0:16] # 進行aes加密 def aesEncrypt(self,text, secKey): pad = 16 - len(text) % 16 #print("leix") #print(type(text)) #print(type(pad * chr(pad))) if isinstance(text,bytes): #print("type(text)=='bytes'") text=text.decode('utf-8') text = text + str(pad * chr(pad)) encryptor = AES.new(secKey, AES.MODE_CBC, '0102030405060708') ciphertext = encryptor.encrypt(text) ciphertext = base64.b64encode(ciphertext) return ciphertext # 進行rsa加密 def rsaEncrypt(self,text, pubKey, modulus): text = text[::-1] #rs = int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16) rs = int(codecs.encode(text.encode('utf-8'),'hex_codec'), 16) ** int(pubKey, 16) % int(modulus, 16) return format(rs, 'x').zfill(256) # 將明文text進行兩次aes加密獲得密文encText def encrypted_request(self,text): modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' nonce = '0CoJUm6Qyw8W8jud' pubKey = '010001' text = json.dumps(text) secKey = self.createSecretKey(16) encText = self.aesEncrypt(self.aesEncrypt(text, nonce), secKey) encSecKey = self.rsaEncrypt(secKey, pubKey, modulus) data = { 'params': encText, 'encSecKey': encSecKey } return data
還有一個offset偏移量的引數,來限制顯示不同頁的評論。
def get_offset(self, offset):
if offset == 0:
text = {'rid': '', 'offset': '0', 'total': 'true', 'limit': '20', 'csrf_token': ''}
else:
text = {'rid': '', 'offset': '%s' % offset, 'total': 'false', 'limit': '20', 'csrf_token': ''}
return text
爬取資料
接著我們可以向網頁發出request請求,然後開始爬取資料啦。根據要求,需要爬取歌曲的熱評以及全部評論,且熱評只在第一頁出現。因此寫了兩個函式來分別爬取熱評以及全部評論,且由於後續需要對資料進行視覺化分析,因此根據需要生成了txt檔案以及csv檔案。
爬取熱門評論
def get_hot_comment(self):
hot_comments_list = []
hot_comments_list.append(u"使用者ID,使用者暱稱,使用者頭像地址,評論時間,點贊總數,評論內容\n")
req = self.get_json_data(self.comment_url, offset=0)
print("已連線熱評")
hot_comments = req['hotComments'] # 熱門評論
print("共有%d條熱門評論!\n" % len(hot_comments))
for item in hot_comments:
comment = item['content'] # 評論內容
comments = comment.replace('\n','').replace('\t','').replace(' ','')
print(comments)
likedCount = int(item['likedCount']) # 點贊總數
comment_time = item['time']# 評論時間(時間戳)
comment_times = comment_time/1000
publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times))
# print(publicTime)
userID = item['user']['userId'] # 評論者id
nickname = item['user']['nickname'] # 暱稱
avatarUrl = item['user']['avatarUrl'] # 頭像地址
comment_info = str(userID) + u"," + nickname + u"," + avatarUrl + u"," + str(
publicTime) + u"," + str(likedCount) + u"," + comments + u"\n"
# print(comment_info)
hot_comments_list.append(comment_info)
with codecs.open("%s_hotcomments.csv"%(song_id), 'a', encoding='utf-8') as f:
f.writelines(hot_comments_list)
爬取全部評論
def get_song_comment(self):
'''某首歌下全部評論 '''
all_comments_list = [] # 存放所有評論
# all_comments_list.append(u"使用者ID,使用者暱稱,使用者頭像地址,評論時間,點贊總數,評論內容\n") # 頭部資訊
comment_info = []
data = self.get_json_data(self.comment_url, offset=0)
comment_count = data['total']
if comment_count < 20:
comments = data['comments']
for item in comments:
comment = item['content'] # 評論內容
comments = comment.replace('\n', '').replace('\t', '').replace(' ', '')
likedCount = int(item['likedCount']) # 點贊總數
comment_time = item['time'] # 評論時間(時間戳)13位 要除以1000
comment_times = comment_time / 1000
publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times))
# print(type(comment_time)) int
userID = item['user']['userId'] # 評論者id
nickname = item['user']['nickname'] # 暱稱
avatarUrl = item['user']['avatarUrl'] # 頭像地址
comment_info = str(userID) + u"," + nickname + u"," + avatarUrl + u"," + str(
publicTime) + u"," + str(likedCount) + u"," + comments + u"\n"
all_comments_list.append(comment_info)
with codecs.open("%s_allcomments.csv"%(song_id), 'a', encoding='utf-8') as f:
f.writelines(all_comments_list)
if comment_count > 20:
for offset in range(20, int(comment_count), 20):
print("開始爬取第{}頁".format(offset/20))
comment = self.get_json_data(self.comment_url, offset=offset)
_comments = comment['comments']
for item in _comments:
comment = item['content'] # 評論內容
comments = comment.replace('\n', '').replace('\t', '').replace(' ', '')#處理換行符以及空格
likedCount = int(item['likedCount']) # 點贊總數
comment_time = item['time'] # 評論時間(時間戳)
comment_times = comment_time / 1000
publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times))
userID = item['user']['userId'] # 評論者id
nickname = item['user']['nickname'] # 暱稱
avatarUrl = item['user']['avatarUrl'] # 頭像地址
all_comments_list = str(userID) + u"," + nickname + u"," + avatarUrl + u"," + str(
publicTime) + u"," + str(likedCount) + u"," + comments + u"\n"
# all_comments_list.append(comment_info)
with codecs.open("%s_allcomments.csv"%(song_id), 'a', encoding='utf-8') as f:
f.writelines(all_comments_list)
return all_comments_list
時間戳轉換
爬取出的資料中,時間是以13位的時間戳呈現,因此要進行轉換,通過網上的方法,13位的時間戳在轉換時要先除以1000。
comment_time = item['time'] # 評論時間(時間戳)
comment_times = comment_time / 1000
publicTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_times))
結巴中文分詞
為了單獨對評論內容進行分析,因此,對評論內容進行了單獨的爬取。
中文文字的預處理過程可以分為分詞->去停用詞(包含空格、回車、標點符號等都算作停用詞)->詞頻統計。
全模式會對已經分詞出來的詞再進行分詞,一般情況下使用精確模式。如果對分詞的結果不滿意,可以新增自定義詞典。使用停用詞時,採用的是哈工大的停用詞表。
from collections import Counter
import jieba
import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
song_id = input('please input song_id:')
# 新增需要自定以的分詞
jieba.add_word("wifi")
jieba.add_word("小眾")
jieba.add_word("天冷")
jieba.add_word("這首歌")
# 建立停用詞list
def stopwordslist(filepath):
stopwords = [line.strip() for line in open(filepath, 'r').readlines()]
return stopwords
# 對句子進行分詞
def seg_sentence(sentence):
sentence_seged = jieba.cut(sentence.strip())
stopwords = stopwordslist('stopwords.txt') # 這裡載入停用詞的路徑
outstr = ''
for word in sentence_seged:
if word not in stopwords:
if len(word) > 1 and word != '\r\n':
outstr += word
outstr += " "
return outstr
inputs = open('%s_allcomments_content.txt'%(song_id), 'r',encoding='utf-8') # 載入要處理的檔案的路徑
outputs = open('jieba_%s.txt'%(song_id), 'w',encoding='utf-8') # 載入處理後的檔案路徑
for line in inputs:
line_seg = seg_sentence(line) # 這裡的返回值是字串
outputs.write(line_seg)
outputs.close()
inputs.close()
# WordCount
with open('jieba_%s.txt'%(song_id), 'r',encoding='utf-8') as fr: # 讀入已經去除停用詞的檔案
data = jieba.cut(fr.read())
data = Counter(data)
with open('%s_jieba.txt'%(song_id), 'w',encoding='utf-8') as fw: # 讀入儲存wordcount的檔案路徑
for k, v in data.most_common(500):
fw.write('%s,%d\n' % (k, v))
視覺化
詞雲圖採用的是wordcloud包。
由於wordcloud本身的分配的顏色不太美觀,因此我選擇了對其新增背景圖,根據背景圖的顏色展示詞雲圖:使用numpy包開啟背景圖並轉化為數字矩陣,通過ImageColorGenerator從背景圖片生成顏色值,從而使詞雲圖顏色變為背景圖顏色。
也可以使用tableau進行資料視覺化。下面是我對評論時間以及熱門評論的分析,以及對全部評論進行結巴分詞後的詞雲圖展示。
最後
曾經有人說過,如果你也曾在深夜哭泣第二天照常打起精神上班上課,如果你也曾被陰霾重重包圍卻對生活留了一絲縫隙讓陽光照進,如果你曾迷惘,如果你曾受挫,如果你曾也大罵去他的生活,如果你曾也與看不見的敵人戰鬥過,如果你曾也想過一了百了,推薦中島美嘉的《曾經我也想過一了百了》。
從詞雲圖中,可以看到,出現最多的詞語就是“加油”、“好好活著”。可見評論者更多的是從這首歌中得到力量。熱評第一的是“我曾想死是因為,被說成是冷酷的人。想要被愛而哭泣,是因為嚐到了人的溫暖。我曾想死是因為,你美麗的笑了。一味想著死的事,一定是因為太過認真地活。我曾想死是因為,還未和你相遇。因為像你這樣的人生於這世上,我稍稍喜歡這個世界了。” 新的和年輕的,總是美好的,哪怕充滿未知。人能越活越年輕越活越自如想想真是概率太低且太難了。不過既然以後只有越來越老越來越沉重的份兒,可能真的要更珍惜現在吧。