Scrapy爬取网易云音乐和评论---转

开发环境:WIN7+Anaconda+py2.7+scrapy
数据库:MongoDB
文章的顺序:
1、先分析思路;
2、再分析scrapy框架每个模块的作用;
3、最后写代码和分析API,以及评论

Scrapy爬取网易云音乐和评论(一、思路分析)

一、我们先分析歌手,有两种方法:

方法一:遍历

优点:有个别歌手有主页,但是没有申请音乐人,所以不存在歌单列表页,用第二种方法也获取不到。
缺点:不好测试它到底有多少,大概十一二万的样子,大多id是相隔不远的。有些id之间相隔了几位数,本来挺稳定的,想着往4位数遍历就行,却发现还有7位数的,这样遍历的跨度有些大,(id从1872开始),要做些处理,还有判断这个页面存在与否。

方法二:从歌手分类爬取所有歌手的id

歌手分类页http://music.163.com/#/discover/artist
这里要说一下,网易云的所有网址,要去掉中间那个#号才是真正的url,带#的查看源代码是获取不到真正的信息的。
所以其实是:http://music.163.com/discover/artist

优点:方便,不需要考虑遍历的数量,不需要对页面是否存在做处理
缺点:可能会漏掉一些有主页但未注册的歌手。

我们主要以方法二入手,分析如下:

我们看这个页面左侧栏:


 
图一

2、因为当时我写的时候,参考到前面提到的那篇GitHub上的代码,
这个group_ids里的就是左侧每个项对应所有的页面了(不包括最上方的推荐歌手和入驻歌手,因为包含在其他里面了)

 
图二 左栏里的id

3、我们按F12或右键检查,如图,每个对应的url是:http://music.163.com/discover/artist/cat?id=xxx
这里的id就是上面group_ids里的数字了。

 
图三

4、然后我们再点进去,如图四,url的id就是上面这个group_ids里的元素了,而后面的initial是首字母的意思,你看下面我们选中的是A,然后它是65,是不是想到ASCII码?在ASCII码中A就是从65开始的,Z是90,后面以此类推,最后有个其他,代替的是0:

 
图四

我们将这两个分别存储为一个列表或元组:

#  左侧栏所有:男女、国家分类id
group_ids = (1001, 1002, 1003, 2001, 2002, 2003, 6001, 6002, 6003, 7001, 7002, 7003, 4001, 4002, 4003)
# 歌手姓名首字母id
initials = [i for i in range(65,91)] + [0]

二、歌手页

1、点进来之后我们来到歌手页,http://music.163.com/#/artist?id=6452,同样,查看源代码的时候去掉url里的#

2、我们获取的这个歌手页的url对应的是热门50首,在对应网页里我们会发现下面有好几个块:热门50首专辑MV歌手介绍

 
图五

3、因为受框架的限制,以上四个信息的内容不在一个传递链里,
以下两种顺序的特点都是后者传入的参数都是由前者返回的,而这四个之间属于相同的id,他们并不需要由前者返回,不构成一个传递链:

1)、歌手 ——>专辑列表——>歌曲列表——>歌曲信息——>第5
2)、歌手 ——>热门50首的歌曲列表——>歌曲信息——>第4

4、如果如果你只需要热门歌曲你可以获取它所有链接,这个代码被我分为两块:
1)、第一块是包含热门50首的url,也只有url,在id名为'song-list-pre-cache'的div标签里,div->ul->li->a->href
2)、而第二块textarea里是json,是这50首歌的比较完整的信息,只不过,这些信息通过lxml.etree或者BeautifulSoup用text的方式获取下来会是字符串,我们需要用json将它格式化。
如果你只需要歌曲的话,选择第一条就好了,直接跳到第四篇讲API的),用歌曲的API即可。

 
图六 热门50首歌曲的所有链接或者json

5、我们要获取所有歌手的歌曲,就得从歌手的专辑下手,获取专辑里所有的歌手才行。我们在专辑页会发现,有些是有很多页的,我最开始用的是scrapy的xpath解析页面,后来搜的时候发现了API,所以接下来的东西,我们就不通过页面的方式了,API我是通过这个网站发现的:http://moonlib.com/606.html(最近发现网站挂了,请看我的第四篇讲API的,有其他类似API的文章链接)。

我们用到的是2到6(不包括5,没用到歌单),第7条接口是MV的,不过不幸没有发现像专辑一样的列表页信息,它只有单曲的MV的API。不过这里我们用不上。后面第四篇会专门分析API。

 
图七

6、接下来就是每个专辑的所有歌曲还有专辑、歌手的一些信息,另外专辑下也有评论,且评论数的获取方式有些不同,因此评论有两种处理。

 
图八 专辑信息、歌曲列表

7、最后从图八里的歌曲链接点进去的就是歌曲页了,如图九:

 
图九

二、Scrapy框架每个模块的作用

关于如何建立一个scrapy程序,可以参考这两篇文章:

1、http://cuiqingcai.com/3472.html(创建的时候推荐)
2、http://www.cnblogs.com/wuxl360/p/5567631.html

关于使用mongodb,可以参考:

1、http://www.jianshu.com/p/30408d8ad1c0

一、建立的命令:

scrapy startproject + 你的项目名        

第一篇文章有提到两个比较特别且有用的地方:

1、
 
entrypoint.py

解释一下:execute里面的三个字符串连起来它其实就是最后执行scrapy程序的命令。这个文件的好处是,倘若你在使用编辑器,比如sublime,是可以在配置后直接执行的,而不用打开DOS窗口然后执行,如果你在sublime里直接执行scrapy本身的任何一个文件,它都不会执行成功,而只能执行这个entrypoint.py,名字应该随意吧,无所谓。

另一点请参考以下的第三部分

二、框架结构

现在整个框架的结构是这样的:


 
项目结构

当然,这个spiders文件夹下的WangYiYun.py并不是自动生成的,这个需要我们自己建立,这个文件就是主爬虫程序。

另外,这个脚本的名字建议不要取和项目名同名,否则后面可能会踩坑。以下简称WYY.py,省得出错,我因为已经生成了,改了别的地方又会出错,解决办法是在代码的最前面,编码注释的后面加上这么一句(参考链接找不到了,但是参考GitHub的链接代码里也有):

from __future__ import absolute_import
  • entrypoint.py:执行程序,就想象是run/python xx.py
  • items.py:如果你学过orm的话,会很好的理解它,它相当于数据库的字段。
  • middlewares.py:这个是个中间件,我也不知道它是做什么的,因为并没有用到。且很多例子上显示的结构没有它,基本上我们也不会改到它。
  • pipelines.py:这个就是定义存储的文件,比如连接,使用哪个数据库存储。
  • settings.py:一看就知道,是配置文件。

三、关于setting配置:

1、关于调试

上面的原因和配置解释的很清楚,


 
来自http://cuiqingcai.com/3472.html

2、关于spidername和robots.txt

 
spidername and robots.txt

BOT_NAME很重要,在WYY.py文件里写脚本的时候,继承自scrapy.Spider的这个类,它需要有一个name,而这两者必须同名。

 
spidername

最下面那行的ROBOTSTXT_OBEY,大家知道爬虫绕不开robots.txt这个文件,每个网站都会有这个网站,是必须遵守的一个守则吧,就是有些不让你爬,有些又允许你爬。默认是True,如果失败了,可以尝试将其注释,然后复制一行,改为False。

settings.py文件里大多都是写好的,你只要将它复制,取消注释,然后修改即可,最好不要不复制直接在原文上改,万一改到了什么出了错,还能有个参照物。

3、关于headers

 
Headers

重要的一般就是Referer、User-Agent(这个必须要有)、Accept(可选,但是涉及到xhr,即json文件,就要修改了)。
这里将它注释,改成自己的,你也可以写在主爬虫WYY.py文件里另写,比较自由,写在这里算是一个基本配置吧。

4、关于ITEM_PIPELINES

 
ITEM_PIPELINES

这个是启用一个Item Pipeline组件,数字代表优先级,越小越优先,没有注释的那行是我的,而下面还有一行,是我之前在网上看过的一种写法,但是并不能成功,它应当是一个字典,列表不行

5、关于mongodb配置
随便写在哪,我们就写在刚刚ITEM_PIPELINES的后面

 
mongodb

这里顺便建议,常量都用大写。
HOST是本地,PORT是端口,DBNAME是数据库,WYY。
接下来四个是集合了,相当于table,这个顺序是倒序。

1、MONGODB_COL_ARTIST - > ArtistInfo -> 所有的歌手列表
2、MONGODB_COL_ALBUMLIST - >AlbumListInfo - > 每个歌手的所有专辑列表
3、MONGODB_COL_ALBUM - >AlbumInfo - > 每张专辑内的所有歌曲列表
4、MONGODB_COL_SONG - > SongInfo -> 每首歌曲的信息

四、关于items.py

它就相当于SQL/MySQL里的字段,它没有什么特别的字段类型,反正所有都是scrapy.Field()就可以了,另外三个集合同样,每个单独写个类,依照你们自己的需求定字段即可。

 
items.py

五、关于pinelines.py

 
pinelines.py

切记,要记得导入items里的那几个你定义的字段的类,我之前忘了导入,然后一切程序正常,就死活存不进去,也不报错,差点掉坑里走不出来

然后这个WangyiyunPipeline基本就两块,一个初始化init(),一个process_item(),前者是用来连接的,后者是用来存储的。

可以看到我init里有一些注释,这里说明一下,因为涉及到多个集合存储,一开始真不知道怎么弄,一开始我以为把每个都扔init就成了,然后通过self调用,后来发现不行,在init定义一个集合就可以了。process_item()还是参考刚刚那个GitHub那个项目,才知道通过isinstance判断。

 
pinelines.py -> process_item()

isinstance大家知道什么意思吧,然后每个item对应的什么在注释我也写了。另外,我下面还有一些被注释掉的代码部分,这里就是我在最开头说的,想要跳过一些重复的地方,但是跳过之后不知道做什么处理。

在不用框架的时候,我们存Mongodb。是先定义一个空字典,然后赋值,最后insert_many/insert_one,这里也是一样的,只不过,我们是将传入的item给dict化。

而后面,在不使用默认的集合时,重新赋一个取代之前的artist即可。

接下来我们开始正式写代码了。

三、爬取歌手

前面有提到,spiders目录下的文件最好不要取和项目相同的名字,如果取了也没关系,有办法,在导入模块的最前面加上这句:

from __future__ import absolute_import

因为参考的文章太多了,我也找不到出处的链接了抱歉。

一、导入:

 
import

仍然提醒,要记得导入items的那几个模块、

二、最基本的代码结构是这样的:

class WangYiYunCrawl(scrapy.Spider):
    name = 'WangYiYun'
    allowed_domains = ['music.163.com']
    # start_urls = 'http://music.163.com/discover/artist/cat?id={gid}&initial={initial}'
    group_ids = (1001, 1002, 1003, 2001, 2002, 2003, 6001, 6002, 6003, 7001, 7002, 7003, 4001, 4002, 4003)
    initials = [i for i in range(65,91)] + [0]
    headers = {
            "Referer":"http://music.163.com",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3067.6 Safari/537.36",
        }

    def start_requests(self):
        pass

   def parse(self,response):
        pass

最前面的那一大段前面都有说过,就不再提,这里的headers是自己写的,所以后面会调用到self.headers,只在settings.py文件里配置的这里可以省略,后面也不用用。

还剩allowed_domains。


 
http://cuiqingcai.com/3472.html

首先讲一下我之前一直困惑的地方:start_urls 和start_requests()可以同时存在,也可以只要一个即可。
如果你写的是start_urls,那start_requests()这个函数可以省掉,直接在parse里对它进行处理,parse这个函数,就是爬虫的主程序,平常怎么写就怎么写。

然后这个response,我们先来看代码:


 
start_requests()和parse()

start_requests()这个函数在返回的时候,(对了,这个scrapy里返回用的都不是return,而是yield,迭代的意思),使用Request,可以看到它大多是和requests这个库很像,它的作用也是一样,返回是一个response,它特别的在于它最后一个参数,callback的值接的是回调函数,即你要把返回的response作为参数传递给哪个函数,这个函数后面不需要括号,所以一开始我也没搞懂它是个什么。

另外,这里调用headers是因为我将headers定义在了这个class里,如果是定义在settings.py里,这里可省略。
之后的函数都是这样,如果你要将什么参数穿到下一个函数,都可以用这个,而在回调函数里必须传入这个response参数。

关于parse函数:

 
来自http://cuiqingcai.com/3472.html

parse这个函数的名称无所谓,但是最好带上parse(许多scrapy类型的文章都这么用,一眼看上去就知道是什么),并且保证传递的回调函数参数和这个函数名称一致即可。

三、parse函数

1、默认情况,scrapy推荐使用Xpath,因为response这个对象可以直接使用Xpath来解析数据,比如我代码中的,response对象下直接就可以用selector.xpath。

response.selector.xpath('//ul[@id="m-artist-box"]/li')

当然,除此之外,还有一种使用xpath的方法:

from scrapy.selector import Selector
selector = Selector(response.body)

关于Selector的用法,可以参考:
http://blog.csdn.net/liuweiyuxiang/article/details/71065004
但是这种方法并不是特别方便,所以直接使用response.selector.xpath的方法就好。

2、关于xpath的格式,参考中文官方文档吧,http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/tutorial.html。它跟lxml大同小异,但是还是有些区别,如图,这是四种基本的方法:

 
Selector()的方法

它返回的其实都是数组,xpath不用说,然后最常用的就是extract了,这个返回的列表里都是文本,而不是Selector对象

 
extract()

它获取的就是所有href的集合。
等价于BeautifulSoup这么用,只不过这个是获取单个的:

from bs4 import BeautifulSoup
soup = BeautifulSoup(response.content,'lxml')
href = soup.find('a')['href']

然后简单提两个xpath简单而常用用法:
@href:这种@后面加什么的,都是某个标签的某个属性,其他比如src也是这样用。
text():这个就是获取文本了。

3、item它就是那个对应某个爬虫所对应的数据库的字段,因为mongodb存储的格式类似json,在python里它就是个dict,当它是个dict就可以了。

item = WYYArtistItem()

4、使用scrapy.Request它可以传递的不只是url,它也可以传递整个item,使用meta,例如

yield scrapy.Request(url=url,meta={'item': item}, headers=self.headers, method='GET', callback=self.parse)

然后在parse()函数调用的时候,

def parse(self,response):
    item = response.meta['item']

但是并不建议这么用,因为很浪费资源。

另外,传递url的时候,除了用url,如果获得的url这段直接存进了item里,也可以直接用item['url']:

yield scrapy.Request(url=item['album_url'], headers=self.headers, method='GET', callback=self.parse_album_list)

最最最最重要的一点是,如果要存到数据库里,比如最后一个不用再Request了,那么一定要加上

yield item

这样才能存进数据库里,之前一直存不进去,一个就是前面忘了导入items,一个就是这里。

四、其他部分

后面基本都照这个模式来,因为我的顺序是:歌手--专辑页--专辑所有歌曲--歌曲,刚好每一个爬下来的url都可以直接传递给下一个函数,通过callback的方式。

这里最大的好处就是,比如歌手页,不用爬下来存一个列表,然后到了下一个函数,再遍历一遍这个列表,它每抓一个url,直接就能到下一个函数运行。

我运行的时候最大的一个问题就是‘yield item’那里,四个部分,我最后一个步骤才放‘yield item’,于是它只存最后一个,即歌曲部分,搞得我一脸懵逼,后来想想大概要执行完这个,然后再把前面的改成yield item,才能都存进去。这个是一个很严重的问题。

所以最好就是在parse就是第一个地方就存,yield item,存完再改成yield Request再执行下一个函数。我才知道之前参照的那个项目里为什么会有注释掉的'yield item'。

因为这份代码被我弃用了,所以还有一些有瑕疵的地方没改过来,代码就不发出来了。
我之前贴的那个GitHub的项目就还可以,我就是参照那个改的,基本上我讲清楚了,弄懂了就可以看得懂,就可以上手了。


四、关于API

前面有提到,API的参考链接,另外再放上几个,

1、http://moonlib.com/606.html(我用的这个)
2、http://blog.csdn.net/qujunjie/article/details/34422379
3、https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=neteasecloudmusicapi(这个比较官方,我也不知道是不是官方,但是很全很全很全)

我们爬取的顺序是:

1、歌手专辑
2、专辑信息(不包括评论)
3、歌曲信息(不包括评论)
4、歌词
5、专辑和歌曲评论(这个另起一章写)

我们拿一个来讲解,其他的类似:
比如,歌手专辑:

http://music.163.com/api/artist/albums/166009?id=166009&offset=0&total=true&limit=12
  • offset:偏移量,它其实算是比如歌手的专辑页每页有12首,第一页的offset就是0(一般是0开头),然后第二页offset就是12,第一页0~11刚好12位数,所以offset从12开头,以此类推。可以算作是second_offset = limit*(first_offset+1)
  • limit:一页有多少,这个可以改,但是不同的网站有不同的规律,比如豆瓣,它这个limit的上限跟当页显示给你看的不同,比如一页默认给你显示20条数据,但是你给它改成50也会给你返回50条。但是网易云不一样,它显示是12条,它的limit可以往下,但不能往上,往上它返回也是12条,它这是固定的,那超过怎么办呢?假设36条,那么3页对吧,根据前面的offset,不停的改offset,这数字一看就有规律,很容易就能想到用遍历,如图,这是我写的一个歌手专辑信息的函数:
 
image.png

我的那个get_req()函数就是对requests.get做了些处理,中途肯定会遇到各种各样的状态码对吧,这个你们自己去思考。

这里我没有用response,因为不涉及到一个完整的传递链,它只是要存进数据库的某一个字段,如图,这个才是我要进行存储的的函数,其中调用了get_artist_album_info()这个函数,它只是作为一个字段存进了item。

 
其中调用了get_artist_album_info()这个函数,它只是作为一个字段存进了item。

然后回到get_artist_album_info()函数,这里的建议就是,将固定的不变,会变的用params这个参数,requests.get它后面可以传各种参数,包括params,以及前面的headers。

这个params里有四个参数:

  • id:歌手的id,事实上,有它,albums_url里那个%s占位的地方可以不要,但是因为最开始参照的那个网站有,也是测试成功就没管了。(上面我提供的链接1和2的区别在这里,用哪个都行)
  • offset:这个首先看到我的page_count,就是指页数,比如3页,但其实网易云这个抓不到有多少页,其实应该是从第一页的json返回的专辑信息得到的总专辑数量有多少,然后进行处理的。但是因为写这个的时候我直接爬的页数,但是当时没测试,所以建议改成这样就可以了:
# album_count是一个歌手所有专辑的总数
# 获得的方法可以先爬第一页的json数据,或者别的你们自己找
for offset in range(0,album_count,12):
    params = {
        'id':singer_id,
        'offset':offset,
        'total':'true',
        'limit':12
    }
  • total:这个参数的意义除了在评论有用,这里不确定到底有什么用,最开始我以为,改成TRUE能不需要offset,会返回所有数据,后来发现没什么区别,最大上限就是一页12,放着也没关系。
  • limit:这里就是12

API

以此类推,其他到底都是这样了,这里把http://moonlib.com/606.html的API集中写一下,method都是GET:
1、歌手专辑:

# 歌手专辑:
# 三种写法,随意,推荐第三种,后面都是
1、http://music.163.com/api/artist/albums/[artist_id]/
2、http://music.163.com/api/artist/albums/166009/id=166009&offset=0&total=true&limit=5
3、url='http://music.163.com/api/artist/albums/166009'
params = {....}

2、专辑里的歌曲列表

# 专辑里的歌曲列表
http://music.163.com/api/album/2457012?ext=true&id=2457012&offset=0&total=true&limit=10

3、歌曲信息

# 歌曲信息
# 这里说明一下,%5B和%5D就是一对中括号[],最好改成[],像歌手专辑里第一种写法那个一样,因为%5B那种写法还要处理,且麻烦。
http://music.163.com/api/song/detail/?id=28377211&ids=%5B28377211%5D

4、歌词信息

# 这个跟其他都不一样,后面的lv、kv、tv是固定的,只要改id即可。
http://music.163.com/api/song/lyric?os=pc&id=93920&lv=-1&kv=-1&tv=-1

五、评论

评论的API的参考链接:

1、https://github.com/darknessomi/musicbox/wiki/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%96%B0%E7%89%88WebAPI%E5%88%86%E6%9E%90%E3%80%82(这个是从歌单下手的,里面的评论可以参考)
2、http://www.imooc.com/article/17459?block_id=tuijian_wz
3、http://blog.csdn.net/u012104691/article/details/53766045
后面这几篇都讲的比较详细,当时查资料的时候,还查到另外一种写法,就是里面有一堆命名是first_param什么的,看得头晕眼花,然后当时测试似乎也没有成功,建议用现在的这种就好了。

基本模式就是这样:


 
来自https://github.com/darknessomi/musicbox/wiki/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%96%B0%E7%89%88WebAPI%E5%88%86%E6%9E%90%E3%80%82

因为专辑和歌曲都有评论,所以我专门将它写成了个类,后面直接调用就可以了。

# -*-coding:utf-8-*-
import os
import re
import sys
import json
import base64
import binascii
import hashlib
import requests
from Crypto.Cipher import AES

class CommentCrawl(object):
    '''评论的API封装成一个类,直接传入评论的API,再调用函数get_song_comment()和get_album_comment()即可分别获取歌曲和专辑的评论信息
    '''

    def __init__(self,comment_url):
        self.comment_url = comment_url
        self.headers = {
            "Referer":"http://music.163.com",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3067.6 Safari/537.36",
        }

    def createSecretKey(self,size):
        '''生成长度为16的随机字符串作为密钥secKey
        '''
        return (''.join(map(lambda xx: (hex(ord(xx))[2:]), os.urandom(size))))[0:16]

    def AES_encrypt(self,text, secKey):
        '''进行AES加密
        '''
        pad = 16 - len(text) % 16
        text = text + pad * chr(pad)
        encryptor = AES.new(secKey, 2, '0102030405060708')
        encrypt_text = encryptor.encrypt(text.encode())
        encrypt_text = base64.b64encode(encrypt_text)
        return encrypt_text

    def rsaEncrypt(self, text, pubKey, modulus):
        '''进行RSA加密
        '''
        text = text[::-1]
        rs = int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16)
        return format(rs, 'x').zfill(256)

    def encrypted_request(self, text):
        '''将明文text进行两次AES加密获得密文encText,
        因为secKey是在客户端上生成的,所以还需要对其进行RSA加密再传给服务端。
        '''
        pubKey = '010001'
        modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
        nonce = '0CoJUm6Qyw8W8jud'

        text = json.dumps(text)
        secKey = self.createSecretKey(16)
        encText = self.AES_encrypt(self.AES_encrypt(text, nonce), secKey)
        encSecKey = self.rsaEncrypt(secKey, pubKey, modulus)
        data = {
            'params': encText,
            'encSecKey': encSecKey
        }
        return data   

    def get_post_req(self, url, data):
        try:
            req = requests.post(url, headers=self.headers, data=data)
        except Exception,e:
            # dosomething
            print url,e
            # return None
        return req.json()            

    def get_offset(self, offset=0):
        '''偏移量
        '''
        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

    def get_json_data(self,url,offset):
        '''json 格式的评论
        '''
        text = self.get_offset(offset)
        data = self.encrypted_request(text)
        json_text = self.get_post_req(url, data)
        return json_text

    def get_song_comment(self):
        '''某首歌下全部评论
        '''
        comment_info = []
        data = self.get_json_data(self.comment_url,offset=0)
        comment_count = data['total']
        if comment_count:
            comment_info.append(data)
            if comment_count > 20:
                for offset in range(20,int(comment_count),20):
                    comment = self.get_json_data(self.comment_url,offset=offset)
                    comment_info.append(comment)
        return comment_info

    def get_album_comment(self,comment_count):
        '''某专辑下全部评论
        '''
        album_comment_info = []
        if comment_count:
            for offset in range(0,int(comment_count),20):
                comment = self.get_json_data(self.comment_url,offset=offset)
                album_comment_info.append(comment)
        return album_comment_info

重复的地方我就不赘述了,最后两个地方我之所以分开写,是因为专辑的评论数可以从专辑信息里获取,但歌曲评论数从专辑列表信息里获取不到,只能先爬取它第一页的json数据,它里面的total就是评论总数,然后再做后面的处理。

评论的API:

# 1、专辑评论API:
comment_url = 'http://music.163.com/weapi/v1/resource/comments/R_AL_3_%s?csrf_token=' % album_id
# 2、歌曲评论API:
comment_url = 'http://music.163.com/weapi/v1/resource/comments/R_SO_4_%s?csrf_token=' % song_id

然后将comment_url 作为参数传入上面封装的那个类里即可,不同的是专辑还需先获取专辑评论的数量。

所有的分析都结束了,接下来的代码自己写吧。



转载链接:https://www.jianshu.com/p/65f29ed8b718



原文地址:https://www.cnblogs.com/jamnoble/p/10946026.html