【代码】网易云音乐歌手专辑歌曲评论的爬取

基于网易云音乐(周杰伦-晴天)评论的爬取爬取某首歌的评论
实现爬取某个歌手的所有专辑的所有歌曲的所有评论。要改进的有很多,先这样吧。。。

import random
import time
from math import ceil
import sys
from threading import Thread

import requests
import pymongo
from lxml import etree
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.wait import WebDriverWait


class GetSinger:
    def __init__(self, url):
        replace = random.randint(1, 9)
        self.headers = {
            'Host': 'music.163.com',
            # 'Referer': 'https://music.163.com/song?id=482999668',
            'Origin': 'https://music.163.com',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) '
                          'AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/71.0.3578.98 Safari/537.36',
            # 加上cookies 否则{'code': -460, 'msg': 'Cheating'}
            'Cookie': '_ntes_nnid=3{}533f97b25070a32c249f59513ad20c,1{}92582485123; _ntes_nuid=3{}533f97b25070a32c249f59513ad20c;......'.format(replace, replace, replace)
        }
        self.proxies = {
            'https' : '123.163.117.246:33915',
            'https' : '180.125.17.139:49562',
            }    # 这些ip大概是无效的
        requests.adapters.DEFAULT_RETRIES = 5  # 增加重连次数
        self.session = requests.session()
        self.session.keep_alive = False  # 关闭多余连接
        self.base_url = 'https://music.163.com'
        self.url = url.replace('/#', '')
        self.album_url = self.url.replace('artist', 'artist/album') + '&limit=10000'  # 一页内展示所有专辑
        self.client = pymongo.MongoClient(host='localhost', port=27017)

    def get_albums(self):  # 获取专辑url
        html = self.session.get(self.album_url, headers=self.headers)
        text = etree.HTML(html.text)
        albums = text.xpath("//li/div/a[@class='msk']/@href")
        for album in albums:
            yield album  # 多次用yield,避免内存消耗,可能并非必须

    def get_songs(self):  # 获取某专辑下所有歌曲的地址
        for album in self.get_albums():
            html = self.session.get(self.base_url+album, headers=self.headers)
            # print(html.text)
            text = etree.HTML(html.text)
            # div和a之间有很多节点,通过'寻找所有这些节点下的a节点'定位失败,
            # 看到一些人定位的是div[@class="f-hide"],
            # 在这个节点的父节的兄弟节点div[@class='n-songtb'定位成功
            songs = text.xpath("//div[@class='n-songtb']//a/@href")
            yield album, songs  # 专辑,以及其中的歌曲地址列表

    def get_song(self):  # 获取某歌曲地址,附带所属的专辑地址
        for album, songs in self.get_songs():
            for song in songs:
                yield album, song

    
    def get_comment_num(self):     # 获取某歌曲评论最大页数
        for album, song in self.get_song():
            while True:
                try:
                    chrome_options = Options()
                    chrome_options.add_argument('--headless')
                    driver = webdriver.Chrome(chrome_options=chrome_options)
                    driver.get(self.base_url + song)
                    frame = WebDriverWait(driver, 10).until(lambda x: x.find_elements_by_id('g_iframe')[0])
                    driver.switch_to.frame(frame)
                    cmmt_num = WebDriverWait(driver, 10).until(
                        lambda x: x.find_elements_by_xpath("//span[@class='sub s-fc3']/span")[0].text)
                    driver.close()
                except Exception as e:
                    print(e)  # 如果获取不到 '评论条数' 元素,这里会报错list index out of range,那么继续去获取
                    		  # 但是这里如果评论数不为0而加载出来为0,这个问题还没解决!有时候直接加载出来和点击最后一页页码按钮加载出来的,略微不一致
                    continue
                else:
                    yield album, song, int(cmmt_num)
                    break

    def get_comment(self):   # 开始爬取某歌曲下的所有评论,存到数据库
        for album, song, cmmt_num in self.get_comment_num():
            pages_num = ceil(cmmt_num/20) # 分为两部分处理,防止重复
            pages_num_a = int(pages_num/10)*10
            pages_num_b = pages_num - pages_num_a
            print(pages_num, pages_num_a, pages_num_b)
            collections = self.url.replace('https://music.163.com/artist?id=', 'artist')+album.replace('/album?id=', 'a') + song.replace('/song?id=', 's')
            #  处理10的倍数页数的评论
            for i in range(0, pages_num_a, 10):
                ps = []
                # time.sleep(0.2)        # 每次创建线程间睡2-3秒左右
                for j in range(10):  # 测试设置为1, 实际是10
                    p = HandleChunkedEncodingError(target=self.save, name='thd'+str(i+j), args=(collections, song, i+j))
                    ps.append(p)
                for p in ps:
                    p.start()
                for p in ps:
                    p.join()
                info = 'page ' + str(i + 10) + 'done'  # 每10页爬取,保证能检测到问题
                print(info)
                # sys.stdout.write("\r{0}".format(info))
                # sys.stdout.flush()
		    #  处理剩下的10页以内的评论
            ps = []
            for i in range(pages_num_a, int(pages_num)):
                p = HandleChunkedEncodingError(target=self.save, name='thd'+str(i), args=(collections, song, i))
                ps.append(p)
            for p in ps:
                p.start()
            for p in ps:
                p.join()
            info = 'page left' + str(pages_num_b) + 'done'
            print(info)
            # sys.stdout.write("\r{0}".format(info))
            # sys.stdout.flush()

    def save(self, collection, song, page):
        for comment in self.get_content(song, page):
            user = comment.get('user')
            img_url = user.get('avatarUrl')
            name = user.get('nickname')
            uid = user.get('userId')
            cid = comment.get("commentId")
            ctime = comment.get("time")
            content = comment.get('content')
            item = {
                'name': name,
                'uid': uid,
                'img_url': img_url,
                'cid': cid,
                'ctime': ctime,
                'content': content
            }

            while True:
                try:
                    self.insert2db(collection, item)
                except Exception as e:
                    # print('inset error:', e)
                    # time.sleep(0.5)
                    pass
                else:
                    break

    def get_content(self, song, page):
        offset = str(page * 20)
        req_url = self.base_url+'/api/v1/resource/comments/R_SO_4_'+song.replace('/song?id=', '')+'?limit=20&offset=' + offset
        print(req_url)
        # req_url = 'https://music.163.com/api/v1/resource/comments/R_SO_4_1325711115?limit=20&offset=0'
        while True:  # get方式吧,这个api不需要表单数据,代理失效了,注释下
            response = self.session.get(url=req_url, headers=self.headers)#, proxies=self.proxies)
            if response.status_code != 200 and response.status_code != 500:  # 避免服务器内部错误刷屏
                # print(response.status_code)  # 如果请求不成功则睡2-3秒,再继续post
                time.sleep(2)
                continue

            try:
                result = response.json()
                comments = result.get("comments")
                if comments is None:  # 此处存疑
                    continue
                else:
                    return comments
            except Exception as e:
                # print('get error:', e)                # 显示异常
                continue

    def insert2db(self, collection, item):
        db = self.client.singer  # 创建sing库
        collection = db[collection]  # 歌手+专辑+歌名动态创建集合
        collection.insert(item)  # 插入评论
        time.sleep(1000)  # 阻塞,测试用


class HandleChunkedEncodingError(Thread):
    def __init__(self, target, name, args):
        Thread.__init__(self)
        self.name = name
        self.args = args
        self.target = target

    def run(self):
        while True:
            try:
                self.target(*self.args) # 运行线程
            except Exception as e:
                # print('thread', self.name, 'running error: ', e)
                time.sleep(5)  # 创建新线程
                rethd = HandleChunkedEncodingError(target=self.target, name=self.name, args=self.args)
                rethd.start()
                rethd.join()
            else:
                break


if __name__ == '__main__':
    url = 'https://music.163.com/#/artist?id=7217'
    fan = GetSinger(url)
    fan.get_comment()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值