[实战]爬取网抑云音乐评论

前言

月明星稀,凄神寒骨,独守窗台,

孤灯的残影烙印在远处,

遮住了我的双目,也挡住了去的路,

望着天顶的失落,原来最后的等待,叫未来可期,

正如我许多次半夜里突然转醒,久不能眠,

于是我吃饱没事干,心血来潮,拉下窗帘,

打开网抑云,写个爬虫,去感受最真实的人间~ T_T

相关知识

网页一般有两种渲染方式:

  1. 服务器渲染
  2. 客户端渲染

服务器渲染:在服务器那边直接把数据和html整合在一起,统一返回给浏览器

  • 在页面源代码中能看到数据

客户端渲染:第一次请求只要一个HTML框架,第二次请求拿到数据,进行数据展示。

  • 在页面源代码中,看不到数据

思路

大部分爬虫的思路基本都是一致的:

  1. 确认内容是否在源代码内
  2. 若在,则直接通过re,xpath,beautifulsoup等方式提取内容
  3. 若不在,则定位内容对应的URL,在其中提取内容

在页面源代码看不到数据的情况下,可以使用浏览器F12的开发者工具,利用其中给的网络模块查看整个网页请求数据的过程,并分析出数据所在的URL请求,最后写爬虫访问整个URL即可。

爬虫所用工具

  • Microsoft Edge 94.0.992.31

  • Pycharm 2019.3

  • Python 3.8

Python使用的相关库:

  • 内置库
  • beautifulsoup4 4.10.0
  • lxml 4.6.3
  • pycryptodome 3.10.4
  • requests 2.25.1

开始实战

本次爬虫以该歌单[戏腔]戏子多秋 可怜一处情深旧 - 歌单 - 网易云音乐 (163.com)为例,进行一步一步的分析。

在这里插入图片描述

图1 初始界面

查看热评。

在这里插入图片描述

图2 热评

在网页源代码中搜索其中一条热评,发现搜索不到。

在这里插入图片描述

图3 搜索热评

因此,启用方案B,打开F12开发者工具,在其中的网络模块找点线索。

在这里插入图片描述

图4 开发者工具

刷新网页,并在筛选器中,勾选XHR,发现有多条URL请求。

在这里插入图片描述

图5 刷新网页

从第一条请求开始,一条一条往下分析,查看有无相关的评论内容,如第一条URL请求就没有相关评论内容。

在这里插入图片描述

图6 未找到评论内容

get?csrf_token=这条URL中,发现了评论内容

在这里插入图片描述

图7 发现评论内容

这时候可能就会有小伙伴好奇,csrf_token=是啥,咋每条URL都有,这个不用管它,它只有在登录之后才会有值。

这个时候我们已经定位到了评论对应的URL,此时只需要像换个URL发起请求,就能得到我们想要的评论内容。

此时URL为https://music.163.com/weapi/comment/resource/comments/get?csrf_token=,请求的方式为POST。

在这里插入图片描述

图8 定位URL和确定请求方式

往下翻,会发现POST请求时所带的表单数据,参数分别为params和encSecKey。这时就有小伙伴砂岩了,哎呀我的妈,这是啥玩意,全是一堆人类无法理解的玩意。

在这里插入图片描述

图9 找到请求参数

不过不要慌,现在我们已经确定了,评论内容就是在该URL返回的结果里,结果是往服务器里发送一个叫params的参数和一个叫encSecKey的参数的POST数据得到的,而这两个玩意的真实内容,是被加密了的。

网易服务器收到请求后,就会将这两个参数解密,得到真实的内容,再像网页返回评论数据。

因此,我们要做的就是想办法找到未加密前的数据,以及找到加密过程,最后在我们的程序里模拟出加密过程,向服务器发送请求,得到结果。

  1. 找到未加密的参数
  2. 想办法把参数进行加密(必须参考网易的逻辑),params, encSecKey
  3. 请求到网易,拿到评论信息

JS逆向

JS逆向主要就是通过调试,分析出JS代码的作用,从而推导出加密过程,最后找出参数的真实含义。

目前加密的方式总结有下面几点:

  1. 对称加密(加密解密密钥相同):DES、DES3、AES
  2. 非对称加密(分公钥私钥):RSA
  3. 信息摘要算法/签名算法:MD5、HMAC、SHA
  4. 前端实际使用中MD5、AES、RSA,自定义加密函数使用频率是最高的
  5. 几种加密方式配合次序:采用非对称加密算法管理对称算法的密钥,然后用对称加密算法加密数据,用签名算法生成非对称加密的摘要
  6. DES、DES3、AES、RSA、MD5、SHA、HMAC传入的消息或者密钥都是bytes数据类型,不是bytes数据类型的需要先转换;密钥一般是8的倍数
  7. Python实现RSA中,在rsa库中带有生成签名和校对签名的方法

总之,加密工作主要是在前端进行,因此我们可以在浏览器中分析出加密过程。

点击菜单栏里的“发起程序”,有的浏览器叫“启动器”,就会看到发送请求的时候,一共经历的JS脚本过程,也就是请求调用的堆栈,从下往上排列,最开始执行的在下面。

在这里插入图片描述

图10 请求调用堆栈

我们从最上面的开始调试,点击之后,页面会跳转到其中的一行代码,也就是说程序执行完这行代码后,请求就被发出。

在这里插入图片描述

图11 开始调试

在这一行设置断点,然后重新刷新页面,然后查看当前变量的数据,和当前请求的URL。

在这里插入图片描述

图12 设置断点

发现URL不是我们目标的URL,于是点击“恢复执行脚本”,放过本次的拦截,直至URL为目标URL为止。

在这里插入图片描述

图13 放过拦截

在这里插入图片描述

图14 到达目标

在这里发现了被加密的数据,在进入该函数之后,从箭头位置往下走,参数被加密了,所以我们要去看在进入该函数之前,参数是否被加密。

在这里插入图片描述

图15 发现加密数据

于是从调用堆栈这里,一步一步点击往下找。

在这里插入图片描述

图16 往下寻找

观察参数,直至发现加密前的参数,从而找出的相应的加密过程。

在这里插入图片描述

图17 加密前

在这里插入图片描述

图18 加密后

因此,判断在这段函数期间,参数被加密了。

在这里插入图片描述

图19 找到加密过程

范围已经被确定,因此可以从该函数的第一行开始设置断点,并点击“单步跳过下一个函数调用”,去观察参数究竟在哪一行代码中被加密了。

在这里插入图片描述

图20 断点调试

经过调试,发现加密过程是在这一行代码中,加密函数为window.asrsea

在这里插入图片描述

图21 发现加密函数

此时,我们将该函数名拿去搜索,会发现只有两个地方出现该函数,一个是刚才的地方,另一个如图所示,这一行window.asrsea = d代码的意思就是,将一个叫d的函数赋给了window.asrsea,也就是说d函数和window.asrsea函数是同一个函数。

在这里插入图片描述

图22 搜索加密函数

接下来的加密过程就与下面出现的函数有关。

在这里插入图片描述

图23 相关的加密函数

JS解密

到目前为止,我们已经进一步缩小了加密过程的范围,并确定了加密与上四个函数有关,我们采用逆推的思路,因此,我们先从d函数来开始分析:

d函数其实就是window.asrsea函数,其中window.asrsea所需的参数如下:

window.asrsea = d
//。。。
var bKf6Z = window.asrsea(JSON.stringify(i8a), bva3x(["流泪", "强"]), bva3x(Tu8m.md), bva3x(["爱心", "女孩", "惊恐", "大笑"]));

分别是:

  • i8a
  • bva3x([“流泪”, “强”])
  • bva3x(Tu8m.md)
  • bva3x([“爱心”, “女孩”, “惊恐”, “大笑”])

i8a其实就是加密前的参数data,也就是:

{
    cursor: -1
    offset: 0
    orderType: 1
    pageNo: 1
    pageSize: 20
    rid: "A_PL_0_2217610700"
    threadId: "A_PL_0_2217610700"
}

bva3x是一个新出现的函数,看着参数的值也很奇怪,但是不用管它,直接丢进控制台里面运行得到结果,可以多运行几遍,确认结果是否是随机的。如果运行显示函数未定义,需要将调用堆栈切换到加密后的那个堆栈中。

在这里插入图片描述

图24 新函数的运行结果

因此,现在能够确定d函数其中的三个参数是固定的,会变的只有data数据,我们继续分析:

var h = {} 创建一个 空对象,暂时没什么用,

i = a(16) 调用a函数,并将返回值赋给i

那么a函数是干嘛的呢?我们继续分析:

function a(a) {  // 参数传为16
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)  // 循环16次
            e = Math.random() * b.length,   // 随机数  
            e = Math.floor(e),  // 取整
            c += b.charAt(e);   // 取在字符串b中的XXX位置
        return c                // 产生16位随机的字母或数字
    }

a函数的过程也没什么好说的,就是产生一个16位的随机数,此时我们将a函数丢进控制台里面运行或者设置断点查看变量值,就能得到结果,虽然是个随机数,但是我们可以将这个结果固定住,不管这个值怎么变化,我们固定住的这个结果永远都是正确的。
在这里插入图片描述

图25 i的值

回到d函数,我们接着往下分析:

function d(d, e, f, g) { //  d:数据,e:010001,f:很长的定值,e:定值
    var h = {}   // 空对象
      , i = a(16);  // i就是一个16位的随机值,我们可以把i设置成定值
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
}

看着上面的返回值长得那么奇怪,其实就是:

function d(d, e, f, g) { //  d:数据,e:010001,f:很长的定值,e:定值
    var h = {}   // 空对象
      , i = a(16);  // i就是一个16位的随机值,我们可以把i设置成定值
   	h.encText = b(d, g),          
    h.encText = b(h.encText, i),  
    h.encSecKey = c(i, e, f), 
   	return h
}

我们先从h.encSecKey = c(i, e, f)这里开始分析,因为这部分比较简单,

i我们已经固定了,现在是个定值,e也是定值,f也是定值,同时,c函数内没有产生随机数的函数,所以说c函数的返回值也是个定值,c函数是怎么运行的我们就不用管它了。将c函数丢进控制台里面运行或者设置断点,即可查看变量值

接下来分析h.encText = b(d, g),其中d就是我们的data数据,g是个定值,因此返回的结果与d有关,因为我们请求的数据是不一样的例如获取不同歌单的评论

b函数是个加密数据的函数,其中涉及到了AES加密:

高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。

对称加密算法也就是加密和解密用相同的密钥。

对称加密算法:

加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦。

非对称加密算法:

加密和解密用的密钥是不同的,这种加密方式是用数学上的难解问题构造的,通常加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal。

实际中,一般是通过RSA加密AES的密钥,传输到接收方,接收方解密得到AES密钥,然后发送方和接收方用AES密钥来通信。

我们只有分析出该函数的执行才能,才能使用python对爬虫所请求的参数进行加密

其中出现最多的CryptoJS.enc.Utf8.parse()的作用是从UTF8编码解析出原始字符串

从这一行代码能看出,f = CryptoJS.AES.encrypt(e, c, { iv: d, mode: CryptoJS.mode.CBC }),这是执行AES加密的函数,e也就是a是加密的数据,c也就是b是加密的密钥,iv也就是d是加密所需的偏移量,加密的模式为CBC

function b(a, b) {   // a是要加密的内容
        var c = CryptoJS.enc.Utf8.parse(b)    // b是密钥
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)   // e是数据
          , f = CryptoJS.AES.encrypt(e, c, {  // c是加密的密钥
            iv: d,   // 偏移量
            mode: CryptoJS.mode.CBC  // 加密模式:CBC
        });
        return f.toString()
    }

d函数最后是返回了两个值,分别是encTextencSecKey,其实分别对应了data参数里的paramsencSecKey

在这里插入图片描述

图26 data参数

至此,我们基本就分析得出了参数加密的整个过程,现在梳理一下:

  1. 通过网络模块,定位到URL,发现参数加密
  2. 通过调试JS代码,定位到加密过程,锁定加密函数
  3. 逐步分析代码作用,得到加密原理

PS.可能不同时候进行分析,看到的变量名不一样,但是过程和原理是一样的

Python实现

还原加密过程

我们使用python来还原参数的加密过程:

先将所固定的值列出来:

# 服务于d函数的
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
i = 'nYXLafpydDFlqRNh'  # 手动固定,人家的是随机的

首先,还原d函数中的encSecKey变量,该变量实际也是个定值

# 由于i,e,f固定,那么c函数结果固定
def get_encSecKey():  
    return "8f2960e5fa10ec2f643aa6a9f76f6b40f85dc4e0f7cfadc70370991ffa3234b08987d5f684619660448a8f0880dbc34436011b1f5b1091d1de4b448acc8ae259d71f84573229ade8ed9894ea55ebbfb6cd1a92e827c93ae14f5af34bdd994c004286dfa3fee40c12cf1d9da5cc3a33313a9f6b19cb10f1eb28d45d9cb8933590"

其次,还原d函数中的encText变量,该变量实际就是进行两次加密得到的

# 把参数进行加密
# 其中g, i是定值
def get_params(data):  # data为json字符串
    first = enc_params(data, g)
    second = enc_params(first, i)
    return second

接下来,我们还原b函数的加密过程,由于AES加密的规则,加密的明文的长度必须为16的倍数,且不满足则需补充至16位,补充的内容为char(所差的长度)

如:1234567890则补充6位的char(6)

1234567890char(6)char(6)char(6)char(6)char(6)char(6)

加密过程如下,注意字节和字符串的转换:

# 加密需要用到两个库
from Crypto.Cipher import AES
from base64 import b64encode

# 转化成16的倍数,为下方加密算法服务
def to_16(data):
    pad = 16 - len(data) % 16
    data += chr(pad) * pad
    return data

# 加密过程
def enc_params(data, key): 
    iv = '0102030405060708'
    data = to_16(data)
    # 创建加密器
    aes = AES.new(key=key.encode('utf-8'), iv=iv.encode('utf-8'),mode=AES.MODE_CBC)  
    # 加密,加密的内容的长度必须是16的倍数,AES加密的逻辑
    bs = aes.encrypt(data.encode('utf-8'))  
    # bs的结果不能直接转换成字节,需要先转换成base64

    return str(b64encode(bs), 'utf-8')  # 转换成字符串返回

PyCryptodome是python一个强大的加密算法库,可以实现常见的单向加密、对称加密、非对称加密和流加密算法。直接pip安装即可:

pip install pycryptodome

爬虫实现

搞了半天,终于到了真正爬虫的环节!

经过了上述的解密过程,接下来的爬虫其实就很容易了,只需提交参数,找到评论内容输出即可

data中的参数有很多,其中

  • pageNo:当前评论的页数

  • pageSize:一页评论的数量

  • rid和threadId:歌单的ID或一首歌的ID

    歌单的ID格式一般为A_PL_0_XXXXXXXXXX

    一首歌的ID格式一般为R_SO_4_XXXXXXXXXX

不仅是歌单的评论,一首歌的评论也能爬取哦

data = {
    "csrf_token": "",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "pageNo": "1",
    "pageSize": "20",
    "rid": "A_PL_0_2022186054",
    "threadId": "A_PL_0_2022186054"
}

接下来就以某一歌单为例,爬取评论

import requests
import json
# 发送请求得到评论
resp = requests.post(url,data={
    'params':get_params(json.dumps(data)),
    'encSecKey':get_encSecKey()
})
resp_data = json.loads(resp.text)['data']
# 获取热评
hotComments = resp_data['hotComments']

if hotComments == None:
    print('数据为空')
    exit()

lengh = len(hotComments)

# 保存用户名和评论
user_name = []
comments = []

# 获取用户名
for i in range(lengh):
    user_name.append(hotComments[i]['user']['nickname'])
# 获取评论内容
for i in range(lengh):
    comments.append(hotComments[i]['content'])

# print(user_name)
# print(comments)
# 输出评论
for i in range(lengh):
    print(user_name[i], '说:', comments[i])
    print('-'*20)

脚本的爬取的评论和在浏览器看到的评论,是一致的:

在这里插入图片描述

图27 爬取评论

在这里插入图片描述

图28 评论

爬取网抑云的评论,成功实现!史前巨感动

扩展

网抑云的评论爬取到了,也可以爬爬歌单名和简介这些

from bs4 import BeautifulSoup
# 获取歌单名和介绍
url_music = 'https://music.163.com/playlist?id=2022186054'
resp = requests.get(url_music)
html = resp.text
soup = BeautifulSoup(html,'lxml')
# 获取歌单标题
title = soup.title.string
# 获取歌单介绍
introduce = soup.find(name='p',attrs={'id':'album-desc-more'})
print(title)
print(introduce.text)

结果如下:

在这里插入图片描述

图29 歌单简介

可扩展的方面还有很多,例如:

  • 爬取歌词
  • 爬取歌单里的歌名
  • 爬取首页的歌单或单曲的链接,使用多线程技术对不同的歌单评论进行爬取
  • 。。。

由于写文章 + 贴图史前巨尼玛麻烦,这里就不继续展示了,有兴趣的小伙伴可以自己动手试试哦,网易的网站分析过程基本都是这样。

当然,去爬爬B站啥的也可以

总结

本次的爬虫之旅到此结束,爬虫本身不难,由于网站的反爬虫机制,难就难在要分析各种参数的加密过程,不管怎么说,本次爬虫的收获还是挺大的(光是在这吹水就吹了很久)

完整代码

分析过程 + 爬虫完整代码,如下:

# 1. 找到未加密的参数
# 2. 想办法把参数进行加密(必须参考网易的逻辑) ,params => encText, encSecKey => encSecKey
# 3. 请求到网易,拿到评论信息

# pip install pycryptodome
from Crypto.Cipher import AES
from base64 import b64encode
from bs4 import BeautifulSoup
import requests
import json

url = "https://music.163.com/weapi/comment/resource/comments/get"
# 请求方式是POST
data = {
    "csrf_token": "",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "pageNo": "1",
    "pageSize": "20",
    "rid": "A_PL_0_2217610700",
    "threadId": "A_PL_0_2217610700"
}

# 单曲歌 R_SO_4_1313118277
# 歌单 "A_PL_0_2022186054"

# 服务于d函数的
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
i = 'nYXLafpydDFlqRNh'  # 手动固定,人家的是随机的


def get_encSecKey():  # 由于i,e,f固定,那么c函数结果固定
    return "8f2960e5fa10ec2f643aa6a9f76f6b40f85dc4e0f7cfadc70370991ffa3234b08987d5f684619660448a8f0880dbc34436011b1f5b1091d1de4b448acc8ae259d71f84573229ade8ed9894ea55ebbfb6cd1a92e827c93ae14f5af34bdd994c004286dfa3fee40c12cf1d9da5cc3a33313a9f6b19cb10f1eb28d45d9cb8933590"

# 转化成16的倍数,为下方加密算法服务
def to_16(data):
    pad = 16 - len(data) % 16
    data += chr(pad) * pad
    return data

def enc_params(data, key):  # 加密过程
    iv = '0102030405060708'
    data = to_16(data)
    aes = AES.new(key=key.encode('utf-8'), iv=iv.encode('utf-8'),mode=AES.MODE_CBC)   # 创建加密器
    bs = aes.encrypt(data.encode('utf-8'))  # 加密,加密的内容的长度必须是16的倍数,AES加密的逻辑
    # bs的结果不能直接转换成字节,需要先转换成base64

    return str(b64encode(bs), 'utf-8')  # 转换成字符串返回

# 把参数进行加密
def get_params(data):  # data为json字符串
    first = enc_params(data, g)
    second = enc_params(first, i)
    return second

# 处理加密过程
'''
function a(a) {  # 参数传为16
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)  # 循环16次
            e = Math.random() * b.length,   # 随机数  
            e = Math.floor(e),  # 取整
            c += b.charAt(e);   # 取在字符串b中的XXX位置
        return c                # 产生16位随机的字母或数字
    }
    function b(a, b) {   # a是要加密的内容
        var c = CryptoJS.enc.Utf8.parse(b)    # b是密钥
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)   # e是数据
          , f = CryptoJS.AES.encrypt(e, c, {  # c是加密的密钥
            iv: d,   # 偏移量
            mode: CryptoJS.mode.CBC  # 加密模式:CBC
        });
        return f.toString()
    }
    function c(a, b, c) {
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {   d:数据,e:010001,f:很长的定值,e:定值
        var h = {}   # 空对象
          , i = a(16);  # i就是一个16位的随机值,我们可以把i设置成定值
        return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h
    }
    上面那部分相当于
        h.encText = b(d, g),          # g是密钥
        h.encText = b(h.encText, i),  # 得到的就是params   i也是密钥
        h.encSecKey = c(i, e, f),      # 得到的就是encSecKey,e和f是定死的,如果此时把i固定,c函数返回的值也是固定的
        return h
    window.asrsea = d
    .....
    var bKf6Z = window.asrsea(JSON.stringify(i8a), bva3x(["流泪", "强"]), bva3x(Tu8m.md), bva3x(["爱心", "女孩", "惊恐", "大笑"]));
    bva3x(["流泪", "强"])运算结果为:010001
    bva3x(Tu8m.md)运算结果为:00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
    bva3x(["爱心", "女孩", "惊恐", "大笑"])运算结果为:0CoJUm6Qyw8W8jud
    
'''

# 发送请求得到评论
resp = requests.post(url,data={
    'params':get_params(json.dumps(data)),
    'encSecKey':get_encSecKey()
})
resp_data = json.loads(resp.text)['data']
# 获取热评
hotComments = resp_data['hotComments']

if hotComments == None:
    print('数据为空')
    exit()

lengh = len(hotComments)

# 保存用户名和评论
user_name = []
comments = []

# 获取用户名
for i in range(lengh):
    user_name.append(hotComments[i]['user']['nickname'])
# 获取评论内容
for i in range(lengh):
    comments.append(hotComments[i]['content'])

for i in range(lengh):
    print(user_name[i], '说:', comments[i])
    print('-'*20)

# 获取歌单名和介绍
url_music = 'https://music.163.com/playlist?id=2022186054'
url_music = 'https://music.163.com/playlist?id=2217610700'
resp = requests.get(url_music)
html = resp.text
soup = BeautifulSoup(html,'lxml')
# 获取歌单标题
title = soup.title.string
# 获取歌单介绍
introduce = soup.find(name='p',attrs={'id':'album-desc-more'})
print(title)
print(introduce.text)

参考文献

https://blog.csdn.net/weixin_41173374/article/details/103474801
https://blog.csdn.net/qq_28205153/article/details/55798628
https://blog.csdn.net/weixin_30347335/article/details/99123821
。。。。。。
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值