Python爬虫与数据分析
- 目的
爬取网易云音乐歌曲热评,分析热评特征。 - 思路
(1)爬取华语歌单中所有歌单url
(2)从每篇歌单地址中爬取每首歌的url
(3)从每首歌的首页爬取热评 - 代码
(1) 爬取华语歌单中所有歌单url,从歌单url获取歌单中每首歌的歌名和id,并保存到文件music_163_02.csv。
import logging
import requests
from pyquery import PyQuery as pq
import pandas as pd
import random
import time
# headers需要填上,否则无法正常爬取
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36'}
# 设置日志的格式、输出级别
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
def scrape_index(url):
response = requests.get(url,headers = headers )
logging.info('scrape index %s...',url) #不需要再url前加%,而是,
try:
if response.status_code == 200:
return parse_index(response.text) # 传到parse_index 方法中获取歌单url列表
else :
logging.error('invaild status is %s while scraping url %s', response.status_code, url)
except Exception:
logging.error('error occurred while scraping %s', url, exc_info=True) # exc_info=True:会将异常异常信息添加到日志消息中
def parse_index(html):
doc = pq(html) # 用pyquery进行解析
a = doc('#m-pl-container .dec .s-fc0') # #对应div .对应class
a1 = a.items() # 对于返回值是多个元素,然后对每个元素做处理,需要调用items方法,返回的generator类型,可以通过for 循环去取值
return a1
def scrape_detail(url):
response = requests.get(url,headers = headers )
logging.info('scraping detail %s...',url)
try:
if response.status_code == 200:
logging.info('detail url is succeed ')
return parse_detail(response.json()) # API获取的内容返回的是json格式
else:
logging.error('invaild status is %s while scraping url %s', response.status_code, url)
except Exception:
logging.error('error occurred while scraping %s', url, exc_info=True)
'''
热评获取API:http://music.163.com/api/v1/resource/comments/R_SO_4_{歌曲ID}?limit=20&offset=0
所以获取歌曲的ID就可以得到热评
'''
def parse_detail(html):
list_02 = []
jobs = html['result']['tracks']
for j in jobs:
dic ={}
dic['name'] = j['name'] # 创建 字典
dic['id'] = j['id']
list_02.append(dic)
return list_02
def get_list():
list_01 = []
url = 'https://music.163.com/discover/playlist/?order=hot&cat=%E5%8D%8E%E8%AF%AD&limit=35&offset={page}'
for page in range(0,35,35): # 跑一页试试,如果跑全部,改为 range(0,1295,35)
url1 = url.format(page = page)
list = []
for i in scrape_index(url1): # generator 遍历之后的i的类型仍然是qyquery类型
i_url = i.attr('href') # attr 方法来获取属性
'''
获取歌单和评论均用了网易云音乐get请求的API,快速高效!
网易云歌单API
https://music.163.com/api/playlist/detail?id={歌单ID}
热评获取API
http://music.163.com/api/v1/resource/comments/R_SO_4_{歌曲ID}?limit=20&offset=0
'''
detail_url = f'https://music.163.com/api{i_url.replace("?","/detail?")}' #获取的url还需要替换一下符合API要求的格式
list.append(detail_url)
list_01.extend(list) # extend 对列表合并
time.sleep(5+random.random()) # 文明爬虫
return list_01
df = pd.DataFrame(columns = ('name','id'))
def save_date(list):
df1 = pd.DataFrame(list)
df2 = pd.concat([df, df1])
df3 = df2.drop_duplicates(subset=None, keep='first',inplace = False)
df3.to_csv('G:/work/01_project/05_Data/music_163_02.csv',index_label="index_label", encoding='utf-8-sig') # index_label索引列的列标签
def main():
detail_list = []
url_01 = get_list()
for l in url_01:
logging.info('detail url is %s',l)
detail_list_part = scrape_detail(l)
detail_list.extend(detail_list_part) # 列表合并,得到最后的完整歌单信息列表
time.sleep(5+random.random())
save_date(detail_list)
if __name__ == '__main__':
main()
(2)获取每首歌的url,从首页得到热评信息
import pandas as pd
import requests
import logging
import time
import random
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36'}
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
def scrape_comment(url, name):
logging.info('scraping comments %s',url)
try:
response = requests.get(url, headers = headers)
if response.status_code == 200:
return parse_comment(response.json(), name ) # 网易云热评API返回的是json格式
else :
logging.error('invaild status_code %s while scraping %s',response.status_code, url)
except Exception:
logging.error('can not scraping %s', url)
def parse_comment(html, name ):
data = []
jobs = html['hotComments']
for job in jobs:
dic = {}
dic['nickname'] = job['user']['nickname']
dic['userid'] = job['user']['userId']
dic['content'] = job['content'].replace('\n', '') # 对换行符进行替换
dic['likecount'] = job['likedCount']
dic['time'] = stampToTime(job['time']) # 时间戳的转换
dic['name'] = name
data.append(dic)
return data
def stampToTime(stamp):
'''
获得是13位的时间戳,需要转化成时间字符串
将ms(毫秒)转成s(秒) stamp/1000
将10位的符合python的时间戳转化成时间元组,localtime():北京时间
将时间元组用strftime转化成时间字符串
'''
timeStamp = float(stamp/1000)
timeArray = time.localtime(timeStamp)
date = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
return date
def main():
df = pd.read_csv('G:/work/01_project/05_Data/music_163_02.csv',header = 0) # 对上个保存的歌曲的ID的csv的内容提取,header= 0:第一行作为column
data_comment = []
for index,row in df.iterrows(): # 数据框中的行 进行迭代的一个生成器,它返回每行的索引及一个包含行本身的对象。
name = row['name']
url = f'http://music.163.com/api/v1/resource/comments/R_SO_4_{row["id"]}?limit=20&offset=0' # 本文只爬取首页(第一页)的热评
data1 = scrape_comment(url, name)
data_comment.extend(data1)
df1 = pd.DataFrame(data_comment)
df1.to_csv('G:/work/01_project/05_Data/hotComments_06p.csv',encoding = 'utf-8-sig') # 是utf-8-sig 而不是utf-8
logging.info('scraping id %s',index)
time.sleep(random.random()) # 文明爬虫
if __name__ == '__main__':
main()
代码思路总结
爬虫实际是模拟人对网页信息进行获取,而代码则替代人工,快速且可暴力循环的,解析并爬取互联网的大量信息。(个人简单理解)从思路中分析,提出以下问题:(1)如何爬取华语歌单中所有歌单url,并从每篇歌单地址中爬取每首歌的url?(代码块1实现,Python包是工具包)
步骤如下:
a、进入华语歌单url,循环获取每一页歌单里的每个歌单url ,歌单url用pyquery进行解析html进行获取,获取的url需要替换成符合API要求的格式,将每一页的API列表进行合并。
b、从歌单API列表中用requests得到响应的json,解析json获取想要得到的所有歌曲key和value,将这些key和value放入字典,将这些字典放入列表中,得到的列表是一个歌单里的歌曲列表,将每个歌单的歌曲列表合并。
c、得到歌曲列表的歌名和url的id值,保存数据到文件music_163_02.csv。
(2)如何从每首歌的首页爬取热评?(代码块2实现,Python包是工具包)
步骤如下:
a、读取文件music_163_02.csv获取歌曲id和名字,热评API中加入歌曲id,只爬取第一页的热评。用requests得到响应的json,从json中获取具体的评论,评论列表合并。
- 数据清洗、数据获取与数据分析
(1)数据清洗与具体数据获取
import matplotlib.pyplot as plt
import pandas as pd
import jieba
import jieba.analyse
import numpy as np
from PIL import Image
from wordcloud import WordCloud
'''
发现也是因为csv文件中单个item内有\r,即回车符
解决方法:lineterminator=”\n”:让\n作为换行符即可
'''
df3 = pd.read_csv('G:/work/01_project/05_Data/hotComments_06p.csv',index_col = 0,lineterminator='\n')
# 用strip()方法去除开头或则结尾的空格
df3['content1'] = df3['content'].apply(lambda x:x.strip())
# 有些句子中有\r,因为我们以\n作为换行符,所以这些\r不属于文本,需要去掉
df3['content1'] = df3['content'].apply(lambda x:x.replace('\r', ''))
df4 = df3.drop(['content'],axis=1)
df4.rename(columns = {'content1':'content'},inplace = True)
df4['name1'] = df4['name\r'].apply(lambda x:x.replace('\r', ''))
df5 = df4.drop(['name\r'], axis=1)
df5.rename(columns = {'name1':'name'}, inplace=True)
df5
# 对点赞数排序
df6 = df5.sort_values(by = 'likecount', ascending=False)
#df6.head(10)['content']
df6.head(10).to_csv('G:/work/01_project/05_Data/strat_TOP10.csv', index=False, encoding = 'utf-8-sig')
df6

# 对重复的句子次数排序
df7 = df5.groupby('content').size().sort_values(ascending=False).reset_index(name='count')
df7

(2)数据分析
#热评最多的ID都有那些特征
df8 = df5.groupby('userid').count().sort_values(by='content',ascending=False)
df8
# 热评最多的Id 是 626970848
from matplotlib.font_manager import FontProperties
font_set = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=15)
df9 = df5[df5['userid'] == 626970848]
plt.hist(df9['likecount'], bins = 200, density = True )
plt.xlim((0,70000))
plt.title('用户的点赞分布', fontproperties=font_set)
plt.show()
df9


df10 = df9['content'].map(len) # map函数进行求取每一单元格个长度
plt.hist(df10, bins = 20, density = True)
plt.title('626970848用户的评论长度分布', fontproperties=font_set)
plt.show()
df10

热评最多的用户点赞数集中分布在10000以下,几千几百的最多,侧面说明了这些歌曲是比较小众的,可能在新歌区广撒网评论。评论长度集中在60字以下,侧面说明短评获取关注度更多。
热评的频次较多的词语有哪些?
'''
jieba库中基于 TextRank 算法的关键词抽取
详情见官方文档:https://github.com/fxsjy/jieba
'''
segments = []
for index,row in df4.iterrows():
content = row[5]
words = jieba.analyse.textrank(content,topK=3, withWeight=False,allowPOS=('ns', 'n', 'vn', 'v'))
for w in words: # 对分词好后的words进行提取,并且关联一个1,方便进行计数
segments.append({'word':w,'counts':1})
df_w = pd.DataFrame(segments)
df_w.to_csv('G:/work/01_project/05_Data/jieba_01.csv',index = False,encoding = 'utf-8-sig')
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
text = ' '.join(df_w['word'])
mask_cir = np.array(Image.open('G:/work/01_project/05_Data/2222.png'))
wordc =WordCloud(
background_color='white',
mask = mask_cir,
font_path = 'G:/work/01_project/05_Data/simhei.ttf', # 中文显示的方法,baidu载一个SimHei.ttf字体包即可让云词显示中文
max_words=1000
).generate(text)
plt.imshow(wordc)
plt.axis('off')
plt.show()

将所有热评绘制成词云,发现“喜欢”与“首歌”这两个词汇被最多提及。还有其他“感觉”、“大哭”、“女孩”等,也许大部分人都经历过的两件事,就是年少懵懂的倾慕与深夜崩溃的瞬间。
本文学习知乎链接实战(其代码有部分缺少以及错误):https://zhuanlan.zhihu.com/p/143188410?utm_source=wechat_session