多线程爬取网易云歌曲评论

之前用爬虫爬取了拉钩网的岗位信息,而那个效率比较低,现在略作升级,做成多线程,目标也换成了网易云。

首先在浏览器上打开网易云音乐,找到想要爬取的歌曲,我选择的是《一直很安静》,打开开发者工具,找到网络,在html的响应中并未找到歌曲评论,再到xhr中找,很容易就能找到一个名为R_OS开头的文件包含了我们想要的东西

多线程爬取网易云歌曲评论_第1张图片

接着我们再跳到参数选项,

多线程爬取网易云歌曲评论_第2张图片

赫然显示两个非常长的字符串,很明显,这是加密过的,而具体破解方法网上有很多,我就不再赘述,这里贴一个链接:https://www.zhihu.com/question/36081767

我用的是里面一个取巧的办法

多线程爬取网易云歌曲评论_第3张图片

这位大哥说是猜出来了这么一个api,不过我更愿意相信他是找规律推理出来的,具体的接口是http://music.163.com/api/v1/resource/comments/R_SO_4_516997458?limit=1&offset=1

limit为一页的评论数量,最大值为100,offset为偏移量,偏移量是指评论的偏移量,而不是页面的偏移量。

访问该api获取的数据为json

多线程爬取网易云歌曲评论_第4张图片

通过使用json包加载数据,再通过键值对的形式获取评论,再进行存储,

我储存评论使用了一个单独的线程,通过定义一个全局的commentList,爬虫线程爬取的评论存储到commentList,而存储线程则不断循环,只要commentList不为空则将数据存储,

程序的退出则利用一个flag标志,开始时为0,达到指定数量后为2,存储文件关闭后为4,程序退出。为了保证爬取的数据都能被存储,必须保证爬虫线程先销毁,再在commentList被清空后,文件关闭,再销毁存储线程,而存储线程是不休眠的,爬虫则休眠一段时间,这就有可能达到指定数量后flag值为2,存储文件直接关闭,而爬虫则因为休眠还有数据爬取了却没保存,因此设置了一个TIME标志,爬虫线程销毁前将TIME置10,让存储线程休眠等待爬虫线程。

下面是具体的代码:

import requests
import os
import json
import time
import random
from  threading import Thread
import jieba
import re
import threading

PATH = os.getcwd() #文件存储位置
OFFSET = 10000 #爬取的偏移量,也可指定起始页面
COMMENTS = [] #存储评论的list
WORDS = [] #存储分词的list
FLAG = 0 #退出的标志
TIME = 0 #保存文件的休眠时间

#获取header
def getHeader():
    agent = ['Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0',
             'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
             'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko',
             'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0',
             'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
             'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'
             ]
    i = random.randint(0,len(agent)-1)
    header = {'User-Agent':agent[i]}
    return header

#获取URL
def getUrl():

    url = r'http://music.163.com/api/v1/resource/comments/R_SO_4_5260494?limit=100&offset='
    global OFFSET
    global FLAG

    URL = url+str(OFFSET)
    n = OFFSET
    OFFSET = OFFSET+100  #爬取一个页面偏移量加100

    if OFFSET > 100000:  #达到目标则不再爬取,返回空值
        URL = None
        if FLAG == 0:
            FLAG = 2
        print('OFFSET以达10 \n FLAG=%d'%FLAG)
    return URL, n

#爬取html
def getHtml(url,header):
    req = requests.get(url,headers = header)
    text = req.text
    return text

#获取评论
def getComment(text):
    theComments = []
    js = json.loads(text)
    coms = js['comments']
    for s in coms:
        st = (s['content']).strip()
        theComments.append(st)
    return theComments

#存储评论
def saveComment():
    global FLAG
    global LOCK
    with open(PATH + r'\comment.txt', 'a', encoding='utf-8') as commentfile:
        print('打开评论文件')
        while True:  #不断循环,将评论list中的文件保存下来
            time.sleep(TIME)
            if COMMENTS:

                st = COMMENTS.pop()
                for s in st:
                    commentfile.write(s+'\n')

            else:
                if FLAG == 2:
                    commentfile.close()
                    FLAG = 3
                    print('评论文件关闭')
                    break


#分词
def participle(comment):
    comment = re.sub('\[|\]|,|!|。|?','',comment)
    cut = jieba.cut(comment)
    word = ' '.join(cut)
    return word

#存储分词
def saveWord():
    global FLAG
    global LOCK
    with open(PATH+r'\WORDS.txt','a',encoding='utf-8') as wordFile:
        print('打开分词文件')
        while True:
            time.sleep(TIME)
            if WORDS:
                wordFile.write(WORDS.pop())

            else:
                if FLAG == 3:
                    wordFile.close()
                    print('分词文件关闭')
                    FLAG = 4
                    break


def spyder():
    global TIME
    while True:
        try:
            url, n = getUrl()
            header = getHeader()
            if url == None:
                break
            time.sleep(random.randint(5,10))  #每爬取一个网页就休眠随机时间
            html = getHtml(url, header)
            comment = getComment(html)
            word = participle(' '.join(comment))
            COMMENTS.append(comment)  #将获取的评论存储到list中
            WORDS.append(word)  #将分词存储到list中

            print('已爬取页面:',str(n))
        except BaseException:
            print('爬取页面%d失败'% n)
    TIME = 10  #爬取即将结束,让存储线程休眠时间增长,保证所有文件都进行存储
    print('%s 线程爬取结束'%threading.current_thread().name)

#多线程运行爬虫
def run():
    spyderThreads = []
    for i in range(6):
        spyderThreads.append(Thread(target=spyder))

    tc = Thread(target=saveComment)
    tw = Thread(target=saveWord)

    for t in spyderThreads:
        t.start()

    tc.start()
    tw.start()

    for t in spyderThreads:
        t.join()
    tc.join()
    tw.join()

if __name__ == '__main__':
    run()

如果有什么错误,还请大家指正

你可能感兴趣的:(python)