爬取网易云音乐歌词

最近迷上了古风歌手等什么君的歌,比较好奇她都写了些什么,就打算写个爬虫获取下歌词。
整体想法是,先去网易云音乐网页端去分析网页,获取正确的网页源码;然后再使用bs4的BeautifulSoup去解析网页源码,获得歌曲的ID;再根据歌曲ID获取歌词,写入本地文件。
那么就开工吧

1.分析网页


打开等什么君的歌手网页,打开F12,找到歌手信息的请求,可以看到是GET请求参数为 id=30285885 ,并且有防盗链referer:https://music.163.com ,以及user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36,同时HTTP/1.1 的所有请求报文中必须包含一个Host头字段。

2.获取网页HTML

根据分析实现一个获取网页html的方法:

def get_html(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/83.0.4103.61 Safari/537.36',
        'Referer': 'http://music.163.com/',
        'Host': 'music.163.com'
    }
    try:
        # print(url)
        response = requests.get(url, headers=headers)
        one_html = response.text
        # print(one_html)
        return one_html
    except Exception as ex:
        print(ex)
        pass

使用requests的get方法,设置headers,再使用response的text属性获得网页HTML

3.分析获取的HTML

在获取到的html中有个h2的内容是歌手名称



在获取到的html中歌曲列表在一个ul中



这样想要的都有了,下面就是提取出来了,代码如下:
def get_singer_info(singer_html):
    soup = BeautifulSoup(singer_html, 'html.parser')
    singer_name = soup.find('h2', id='artist-name').get_text()
    # print(singer_name)
    links = soup.find('ul', class_='f-hide').find_all('a')
    song_ids = []
    song_names = []
    for link in links:
        song_id = link.get('href').split('=')[-1]
        song_name = link.get_text()
        # print(song_id)
        # print(song_name)
        song_ids.append(song_id)
        song_names.append(song_name)
    return singer_name, zip(song_names, song_ids)

使用BeautifulSoup的 "html.parser" 获取soup实例,然后使用find去找到名字所在的h2然后使用get_text方法得到标签内容,再找到歌曲所在ul。再获取所有a标签,再循环获取歌曲名和ID,使用ZIP把歌曲名和ID一一对应成字典。

4.获取歌词

再点击一首歌,分析获取歌词的请求:


可以看到是POST请求,而且还是用token校验的,这样对我们来说就不好搞了呀。
通过查资料明白了,可以通过网易云的Api获取。
网易云的相关Api可以参考这篇文章:收藏这些API,获取网易云音乐数据超轻松
获取歌词可以使用这个Api:

https://music.163.com/api/song/lyric?id={歌曲ID}&lv=1&kv=1&tv=-1

代码如下:

def get_lyric(song_id):
    try:
        lyric_url = 'http://music.163.com/api/song/lyric?id=' + str(song_id) + '&lv=1&kv=1&tv=-1'
        lyric_html = get_html(lyric_url)
        # print(lyric_html)
        json_dict = json.loads(lyric_html)
        # print(json_dict)
        initial_lyric = json_dict['lrc']['lyric']
        # print(initial_lyric)
        regex_rule_1 = re.compile(r'\[.*]')
        middle_lyric = re.sub(regex_rule_1, '', initial_lyric).strip()
        regex_rule_2 = re.compile(r'.*[::].*')
        final_lyric = re.sub(regex_rule_2, '', middle_lyric).strip()
        # print(final_lyric)
        return final_lyric
    except Exception as ex:
        print(ex)
        pass

请求此地址返回的是json串:



使用json.loads把json转换成字典,再获取lrc的lyric
这样获取的内容前面带有歌词出现的时间以及作词作曲等介绍,对我们来说这些都是脏数据,应该清洗掉,我这使用正则处理。

5.写入本地

获取到歌词后要做的就是把歌词写入本地了,我这里提供了两种方法,一种是分开写入,另一种是写入一个文件:

def write_dir_txt(singer_name, song_name, lyric):
    print('正在写入歌曲:{}'.format(song_name))
    if lyric is not None:
        if not os.path.exists(singer_name):
            os.makedirs(singer_name)
        with open(singer_name + '/{}.txt'.format(song_name), 'a', encoding='utf-8') as fp:
            fp.write(lyric)


def write_fil_txt(singer_name, lyric):
    print('正在写入{}歌曲...'.format(singer_name))
    if lyric is not None:
        with open('{}.txt'.format(singer_name), 'a', encoding='utf-8') as fp:
            fp.write(lyric)

注意:这里需要加上if lyric is not None的判断,因为有些歌曲是伴奏是没有歌词的,避免异常退出。

6.通过歌手ID获取

可以通过在网页上获取的歌手ID获取歌手的歌曲歌词,代码如下:

def lyric_by_singerid(singer_id):
    start_url = 'http://music.163.com/artist?id={}'.format(singer_id)
    html = get_html(start_url)
    singer_name, song_infos = get_singer_info(html)
    # print(singer_infos)
    for song_info in song_infos:
        lyric = get_lyric(song_info[1])
        write_dir_txt(singer_name, song_info[0], lyric)
        # write_fil_txt(singer_name, lyric)

7.通过歌手名字获取

通过歌手的ID获取歌词还是有些不太友好,就想着用歌手名字来获取。



看搜索的请求还是如同获取歌词的请求一样不能直接获取,所以还是使用网易云的Api:

http://music.163.com/api/search/get/web?csrf_token=hlpretag=&hlposttag=&s={搜索内容}&type=1&offset=0&total=true&limit=20

limit:返回数据条数(每页获取的数量),默认为20,可以自行更改
offset:偏移量(翻页),offset需要是limit的倍数
type:搜索的类型
type=1 单曲
type=10 专辑
type=100 歌手
type=1000 歌单
type=1002 用户
type=1004 MV
type=1006 歌词
type=1009 主播电台

我们这里搜索歌手所以type使用100

def lyric_by_singername(singer_name):
    search_url = 'http://music.163.com/api/search/get/web?csrf_token=hlpretag=&hlposttag=&' \
                 's={}&type=100&offset=0&total=true&limit=20'.format(quote(singer_name))
    search_html = get_html(search_url)
    # print(search_html)
    search_dict = json.loads(search_html)
    singer_infos = search_dict['result']['artists'][0]
    singer_id = singer_infos['id']
    # print(singer_infos)
    # print(singer_id)
    lyric_by_singerid(singer_id)

使用quote对汉字进行编码
然后解析获取到的json串获得歌手的ID
再调用上面的根据歌手ID获取歌词

8.获取结果


至此一个完整的获取网易云音乐歌手所以歌曲的歌词的爬虫就写好了。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。