Python爬虫:网易云音乐评论爬取

目录

  • 0x00 写在前面
  • 0x01 iframe处理
  • 0x02 获取歌单
  • 0x03 获取歌曲
  • 0x04 获取评论
  • 0x05 完整代码
  • 0x06 总结

0x00 写在前面

这次的网易云音乐评论爬取跟ctfhub登录+签到一样,算是一次实战吧,也算是把这几天学习到的给真正拿来运用了,在这期间呢也确实又学到了新的知识,也对之前学的selenium的知识做了巩固。

0x01 iframe处理

先来看网页源码
Python爬虫:网易云音乐评论爬取_第1张图片
这里采用了iframe来构造页面,当我们单纯显示网站源码时,iframe里的东西我们时看不到的,所以我这里采用了selenium的switch_to.frame来切换到iframe页面进行元素的定位和信息的填写

	url = "https://music.163.com/#/search/m/"
    driver.get(url)
    driver.switch_to.frame('g_iframe')#切换到iframe部分
    sleep(1)
    driver.find_element_by_id('m-search-input').send_keys(user)#找到输入框并输入信息
    driver.find_element_by_id('m-search-input').send_keys(Keys.ENTER)

回到网站,我是想搜索用户并访问其主页,再获取用户自己创建的歌单列表和歌单网址的列表,所以我们先去获得用户主页的网址,还是先去看看网页
Python爬虫:网易云音乐评论爬取_第2张图片

Python爬虫:网易云音乐评论爬取_第3张图片

显然我们可以通过id搜索来确定输入框,接着我偷了个懒没有管搜索的按钮,而是直接在输入框输入enter来达到目的。最后,我们还要点击用户按钮来使得我们的搜索显示的是用户以及用户主页,最后获得table元素即可
从而,getUser函数完整代码如下:

def getUser(user, driver):#user是要搜索的用户名,driver是我们的浏览器驱动
    url = "https://music.163.com/#/search/m/"
    driver.get(url)
    driver.switch_to.frame('g_iframe')#根据iframe的id切换到iframe部分
    sleep(1)
    driver.find_element_by_id('m-search-input').send_keys(user)#向搜索框中输入用户名
    driver.find_element_by_id('m-search-input').send_keys(Keys.ENTER)#输入enter,代表开始搜索
    driver.find_element_by_xpath('//*[@class="m-tabs m-tabs-srch f-cb ztag"]/li[8]').click()#点击网页中的用户按钮,改变table的值
    sleep(1)
    tab = driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div[2]/div/table/tbody')#获得table内的元素
    users = tab.find_elements_by_tag_name('a')#从table中分离出包含有用数据的所有标签
    list = []
    for i, n in enumerate(users):
        if (i - 1) % 3 == 0:
            tu = (n.get_attribute('title'), n.get_attribute('href'))
            list.append(tu)#通过循环把所有的用户id和对应主页url成对放在列表list里
    for i, n in enumerate(list):
        print('[' + str(i) + ']\t' + n[0] + '\t' + n[1])#打印list
    num = input('请输入用户对应序号:')#输入对应想要查看的用户
    return list[int(num)]#返回该用户的url以及用户id,即(id,url)元组

0x02 获取歌单

获取到了用户的url,接下来我们就要获取对应用户的歌单列表,这里我偷了个懒,只获得用户自己创建的歌单,先来看看网页

Python爬虫:网易云音乐评论爬取_第4张图片
又是一个iframe,其中,用户自己创建的歌单在id为cbox的ul里,这里就跟搜索用户一样,直接上代码

def getSongs(user, driver):
    driver.get(user[1])#打开对应网页
    sleep(1)
    driver.switch_to.frame('g_iframe')#移动到iframe里
    box = driver.find_element_by_xpath('//*[@id="cBox"]')#找到歌单所在位置
    li = box.find_elements_by_tag_name('a')#找到里面包含歌单名和url的标签
    list = []
    for i, n in enumerate(li):
        if i % 3 == 0:
            tu = (n.get_attribute('title'), n.get_attribute('href'))
            list.append(tu)#循环写把歌单和url成对写入列表里
    print(user[0] + '创建的歌单如下:')
    for i, n in enumerate(list):
        print('[' + str(i) + ']\t' + n[0] + '\t' + n[1])#打印列表
    num = input('请选择对应歌单:')#输入序号选择
    return list[int(num)]

0x03 获取歌曲

打开了歌单网页,接下来就是获取歌曲名和对应url了,代码写到这里的时候我才发现网页上只能显示前20首歌,自己登陆了看自己的歌单能看前1000首…所以我在这里又多写了一个登陆的代码块

登陆按钮位置:
Python爬虫:网易云音乐评论爬取_第5张图片
对登录弹框的处理(这里我选的是手机登录):
Python爬虫:网易云音乐评论爬取_第6张图片
Python爬虫:网易云音乐评论爬取_第7张图片
别忘了要先勾选同意条款:
Python爬虫:网易云音乐评论爬取_第8张图片
接着输入账号密码:
Python爬虫:网易云音乐评论爬取_第9张图片
就能登录成功了,其实这一步可以省略,因为这步只是让我们爬取自己的歌单的时候可以爬到更多的信息…

接下来是歌曲的获取部分,位置如图(这里图方便我就只获得了歌曲名,没有去多获取歌手名,其实影响不大):

Python爬虫:网易云音乐评论爬取_第10张图片

ok,位置都找到了,接下来就是写代码了

def getSong(driver, songsname):
    sleep(3)#sleep都是为了等待网页加载...
    driver.find_element_by_xpath('/html/body/div[1]/div[1]/div/div[1]').click()#点击登录按钮
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[2]/div/div[3]').click()#点击[其他登录方式]
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[1]/div[3]/input').click()#勾选[同意条款]
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[1]/div[1]/div[2]').click()#点击[手机号登录]
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[1]/div/div/input').send_keys('手机号码')
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[2]/input').send_keys(
        '密码' + Keys.ENTER)#输入手机号跟密码并用enter表示登录
    sleep(1)
    driver.switch_to.frame('contentFrame')#登录成功后歌单能够加载完全,进入iframe准备抓取信息
    sleep(1)
    a = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div[2]/div/div[1]/table/tbody/tr/td[2]/div/div/div/span/a')#该a标签下包含了歌曲的url
    b = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div[2]/div/div[1]/table/tbody/tr/td[2]/div/div/div/span/a/b')#该b标签下包含了歌曲名,注意这里的ab都是find_elements,返回的是包含所有符合xpath的所有标签的列表
    dic = {
     }
    for i in range(len(a)):
        title = b[i].get_attribute('title')
        dic[title] = [a[i].get_attribute('href')]#循环把信息写入字典
    #这里后续还会有一部分函数
	for i in dic:
        # print(i,dic[i][0])
        comment = getComment(driver, dic[i][0], i)#把歌曲的名称和url传给下一个函数,并通过循环得到每个歌曲的评论

0x04 获取评论

走到了最后一步了,这里我当时花费了很长时间,一开始还是按部就班的先切换到iframe内,然后再找到评论的位置,如图
Python爬虫:网易云音乐评论爬取_第11张图片
这两个div分别带有评论内容,时间和点赞数,当然,如果是回复其它评论,会多出一个div来装其它的评论
Python爬虫:网易云音乐评论爬取_第12张图片
比如看这个评论
Python爬虫:网易云音乐评论爬取_第13张图片
用户,评论内容,回复的评论,发布时间,点赞数都能找到,然后我最初的想法是得到大的div标签下所有的text再用纯正则匹配分割所有的内容,在之前的实践中text()仅能够得到当前标签的文本,下级标签的文本是不会被读取的,但是,我们可以使用

get_attribute('textContent')

来得到该标签下的所有文本,所以我最初的想法是。。。

	driver.get(url)
    sleep(1)
    driver.switch_to.frame('g_iframe')#切换至iframe
    comment=driver.find_elements_by_xpath('/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]')#找到评论的最高级div标签
    print(song)#打印当前准备爬取的歌曲名
    for n, i in enumerate(comment):
        if n >= 15:#当前评论超过15个就break;因为精彩评论最多就15条,我也没有让他切换至下一页爬取最新评论,但是这也导致了精彩评论不足15条的时候会去爬取最新评论
            break
        print(i.get_attribute('textContent'))
        text = i.get_attribute('textContent')#获取所有文本并打印
        li = re.findall(r'(.*?):(.*)20(.*?)日 [(](.*?)[)][|]回复', text)[0]#通过正则分割用户名,评论,时间,点赞数
        print(li)
        content = {
     
            '用户': li[0],
            '内容': li[1],
            '时间': '20' + li[2] + '日',
            '点赞数': li[3]#将得到的数据放入字典
        }
        print(content)

可以看到,我的正则很有问题…首先,时间不一定都是年开头,还可能是月或者几点开头,其次,可能没有点赞数据,所以这样是不能够找到正确的数据的,所以我就又写了一个版本

def getComment(driver, url, song):
    driver.get(url)
    sleep(1)
    driver.switch_to.frame('g_iframe')
    client = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div/a')#定位用户名
    # content=driver.find_elements_by_xpath('/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div')
    time = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[@class="rp"]/div')#定位时间
    thumbs = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[@class="rp"]/a[1]')#定位点赞数
    text = driver.find_elements_by_xpath('/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]')获得评论最高级div下的所有文本,因为正则仅用来分割评论内容和回复的评论还是容易做到的
    print('正在爬取:' + song)
    li = []
    for n, i in enumerate(time):
        if n >= 15:#只爬取前15条
            break
        if re.search(r'[(](.*)[)]', thumbs[n].text) == None:
            thumb = '0'#如果点赞数为None,就把点赞数变成0,这里是str而不是int是为了防止后续的re报错
        else:
            thumb = re.findall(r'[(](.*)[)]', thumbs[n].text)[0]#否则就获取点赞数
        if re.search(r'万', thumb) != None:
            thumb = thumb.split('万')[0]
            thumb = int(float(thumb) * 10000)#如果带汉字万,就换成数字*10000,便于后续的排序
        else:
            thumb = int(thumb)#都变成int类型
        comment = {
     
            '歌曲': song,
            '用户': client[n].text,
            '内容': ILLEGAL_CHARACTERS_RE.sub(r'', re.findall(r':(.*)' + i.text, text[n].get_attribute('textContent'))[0]),#正则匹配评论正文,这里的ILLEGAL_CHARACTERS_RE.sub后续再做解释
            '时间': i.text,
            '点赞数': thumb,
            '类型': '评论',
            '关联评论': '无'#写入字典
        }
        if re.search(r'◆◆', comment['内容']) != None:#根据◆◆来判断是否是回复
            tu = re.findall(r'(.*)◆◆(.*)', comment['内容'])[0]
            # print(tu[0]+'回复了评论:'+tu[1])
            comment['内容'] = tu[0]
            comment['类型'] = '回复'
            comment['关联评论'] = tu[1]#是回复的话就改变字典里对应的值
        li.append(comment)把这个字典加入列表
    # print(li)
    return li#返回列表

至此我们已经获得了所有的数据并且都存在了列表里,后续我又有了个想法,就是把数据写入excel,这里用到了 openpyxl 库,有大佬已经详细的写了关于这个库的使用,我这里就不赘述了,直接上代码

#这部分代码是在getSong函数里的
    wb = openpyxl.Workbook()
    sheet = wb.active
    sheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=7)#这里是合并几个单元格
    sheet['A1'].value = songsname
    sheet['A2'].value = '歌曲名'
    sheet['B2'].value = '用户'
    sheet['C2'].value = '内容'
    sheet['D2'].value = '时间'
    sheet['E2'].value = '点赞数'
    sheet['F2'].value = '类型'
    sheet['G2'].value = '关联评论'#给第二行的单元格写入数据
    I = 3
    for i in dic:
        # print(i,dic[i][0])
        comment = getComment(driver, dic[i][0], i)
        for n in comment:
            sheet['A' + str(I)].value = n['歌曲']
            sheet['B' + str(I)].value = n['用户']
            sheet['C' + str(I)].value = n['内容']
            sheet['D' + str(I)].value = n['时间']
            sheet['E' + str(I)].value = n['点赞数']
            sheet['F' + str(I)].value = n['类型']
            sheet['G' + str(I)].value = n['关联评论']
            I += 1
    wb.save(filename=songsname + '歌单爬取.xlsx')#循环写入数据后并保存为文件

结果如图:
Python爬虫:网易云音乐评论爬取_第14张图片

0x05 完整代码

from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
from selenium.webdriver.common.keys import Keys
import re
from selenium import webdriver
from time import sleep
import openpyxl


def getUser(user, driver):
    url = "https://music.163.com/#/search/m/"
    driver.get(url)
    driver.switch_to.frame('g_iframe')
    sleep(1)
    driver.find_element_by_id('m-search-input').send_keys(user)
    driver.find_element_by_id('m-search-input').send_keys(Keys.ENTER)
    driver.find_element_by_xpath('//*[@class="m-tabs m-tabs-srch f-cb ztag"]/li[8]').click()
    sleep(1)
    tab = driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div[2]/div/table/tbody')
    users = tab.find_elements_by_tag_name('a')
    list = []
    for i, n in enumerate(users):
        if (i - 1) % 3 == 0:
            tu = (n.get_attribute('title'), n.get_attribute('href'))
            list.append(tu)
    for i, n in enumerate(list):
        print('[' + str(i) + ']\t' + n[0] + '\t' + n[1])
    num = input('请输入用户对应序号:')
    return list[int(num)]


def getSongs(user, driver):
    driver.get(user[1])
    sleep(1)
    driver.switch_to.frame('g_iframe')
    box = driver.find_element_by_xpath('//*[@id="cBox"]')
    li = box.find_elements_by_tag_name('a')
    list = []
    for i, n in enumerate(li):
        if i % 3 == 0:
            tu = (n.get_attribute('title'), n.get_attribute('href'))
            list.append(tu)
    print(user[0] + '创建的歌单如下:')
    for i, n in enumerate(list):
        print('[' + str(i) + ']\t' + n[0] + '\t' + n[1])
    num = input('请选择对应歌单:')
    return list[int(num)]


def getSong(driver, songsname):
    sleep(3)
    driver.find_element_by_xpath('/html/body/div[1]/div[1]/div/div[1]').click()
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[2]/div/div[3]').click()
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[1]/div[3]/input').click()
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[1]/div[1]/div[2]').click()
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[1]/div/div/input').send_keys('手机号')
    sleep(1)
    driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[2]/input').send_keys(
        '密码' + Keys.ENTER)
    sleep(1)
    driver.switch_to.frame('contentFrame')
    sleep(1)
    a = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div[2]/div/div[1]/table/tbody/tr/td[2]/div/div/div/span/a')
    b = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div[2]/div/div[1]/table/tbody/tr/td[2]/div/div/div/span/a/b')
    dic = {
     }
    for i in range(len(a)):
        title = b[i].get_attribute('title')
        dic[title] = [a[i].get_attribute('href')]
    wb = openpyxl.Workbook()
    sheet = wb.active
    sheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=7)
    sheet['A1'].value = songsname
    sheet['A2'].value = '歌曲名'
    sheet['B2'].value = '用户'
    sheet['C2'].value = '内容'
    sheet['D2'].value = '时间'
    sheet['E2'].value = '点赞数'
    sheet['F2'].value = '类型'
    sheet['G2'].value = '关联评论'
    I = 3
    for i in dic:
        # print(i,dic[i][0])
        comment = getComment(driver, dic[i][0], i)
        for n in comment:
            sheet['A' + str(I)].value = n['歌曲']
            sheet['B' + str(I)].value = n['用户']
            sheet['C' + str(I)].value = n['内容']
            sheet['D' + str(I)].value = n['时间']
            sheet['E' + str(I)].value = n['点赞数']
            sheet['F' + str(I)].value = n['类型']
            sheet['G' + str(I)].value = n['关联评论']
            I += 1
    wb.save(filename=songsname + '歌单爬取.xlsx')

def getComment(driver, url, song):
    driver.get(url)
    sleep(1)
    driver.switch_to.frame('g_iframe')
    # comment=driver.find_elements_by_xpath('/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]')
    # print(song)
    # for n, i in enumerate(comment):
    #     if n >= 15:
    #         break
    #     print(i.get_attribute('textContent'))
    #     text = i.get_attribute('textContent')
    #     li = re.findall(r'(.*?):(.*)20(.*?)日 [(](.*?)[)][|]回复', text)[0]
    #     print(li)
    #     content = {
     
    #         '用户': li[0],
    #         '内容': li[1],
    #         '时间': '20' + li[2] + '日',
    #         '点赞数': li[3]
    #     }
    #     print(content)
    client = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div/a')
    # content=driver.find_elements_by_xpath('/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div')
    time = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[@class="rp"]/div')
    thumbs = driver.find_elements_by_xpath(
        '/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]/div[@class="rp"]/a[1]')
    text = driver.find_elements_by_xpath('/html/body/div[3]/div[1]/div/div/div[2]/div/div[2]/div[2]/div/div[2]')
    print('正在爬取:' + song)
    li = []
    for n, i in enumerate(time):
        if n >= 15:
            break
        if re.search(r'[(](.*)[)]', thumbs[n].text) == None:
            thumb = '0'
        else:
            thumb = re.findall(r'[(](.*)[)]', thumbs[n].text)[0]
        if re.search(r'万', thumb) != None:
            thumb = thumb.split('万')[0]
            thumb = int(float(thumb) * 10000)
        else:
            thumb = int(thumb)
        comment = {
     
            '歌曲': song,
            '用户': client[n].text,
            '内容': ILLEGAL_CHARACTERS_RE.sub(r'', re.findall(r':(.*)' + i.text, text[n].get_attribute('textContent'))[0]),#这里的ILLEGAL_CHARACTERS_RE就是为了过滤excel的非法字符,防止程序报错
            '时间': i.text,
            '点赞数': thumb,
            '类型': '评论',
            '关联评论': '无'
        }
        if re.search(r'◆◆', comment['内容']) != None:
            tu = re.findall(r'(.*)◆◆(.*)', comment['内容'])[0]
            # print(tu[0]+'回复了评论:'+tu[1])
            comment['内容'] = tu[0]
            comment['类型'] = '回复'
            comment['关联评论'] = tu[1]
        li.append(comment)
    # print(li)
    return li


driver = webdriver.Chrome()
user = input('请输入你要查找的用户:')
li = getUser(user, driver)
songs = getSongs(li, driver)
print('准备开始爬取歌单: ' + songs[0] + ' 中的内容')
driver.get(songs[1])
getSong(driver, songs[0])

0x06 总结

这次爬取走了很多弯路,这也说明我学的还不是很到位…最后也是终于成功了,也算是对前面的一次复习吧,当然,也学到了很多新知识,接下来要继续努力呀

你可能感兴趣的:(python,python,selenium,经验分享)