Python爬虫与数据分析

Python爬虫与数据分析

  1. 目的
    爬取网易云音乐歌曲热评,分析热评特征。
  2. 思路
    (1)爬取华语歌单中所有歌单url
    (2)从每篇歌单地址中爬取每首歌的url
    (3)从每首歌的首页爬取热评
  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. 代码思路总结
    爬虫实际是模拟人对网页信息进行获取,而代码则替代人工,快速且可暴力循环的,解析并爬取互联网的大量信息。(个人简单理解)

    从思路中分析,提出以下问题:
    

    (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. 数据清洗、数据获取与数据分析
    (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


版权声明:本文为Showing_abc原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。