【有效】最新爬取音乐,纯接口访问实现。Python3、requests、美丽汤、tqdm实战

目录:

爬虫之国内音乐爬取

一、设计思路

二、使用资源

三、具体代码编写

四、优化空间

五、应用

一、设计思路

1、选取目标音乐平台

2、获取目标的关键信息,如音乐接口信息、音乐名称和大小

3、分析接口属性,对这些信息做提取,获取到有用信息,存放在字典或者列表里

4、对数据进行处理和操作,如访问下载、保存命名

5、回顾整个编写过程,优化结构,整理归类。

二、使用资源

1、requests库、urllib库、lxml库,bs4库,tqdm进度条神器及其它自带库

2、Python3+Windows执行环境

3、网抑云云平台、其它平台音乐集地址

三、具体代码编写

选择平台

网易云反爬做的较好,去其它小站快捷获取。

发现了以前的通用接口几乎不可用,新接口变化挺大。

拿到网易接口的时候是挺高兴的,但是Post要提交的表单信息不知道每首歌是不是一样的,

在我总是去找ID的时候,看到表单几乎不敢相信,这个是啥

。。。上图。。。

 于是,我想到了是否每首歌的前面那一大串是否是一样的。。。

还是先拿到ID再说吧,

看到通过requests访问到的ID内容是隐藏的,

=====!

 网抑云让人忧郁。。。(虽然可以通过selenium以用户方式在已解码的网页上访问拿到ID)

但是还是顺其自然,去其它平台搞一搞。

果然,其它专业做音乐的,简单易懂,还没有防备之心!

对搜索结果页处理:

对歌曲详情页处理,获得下载链接:

 所有方法归位后最终验证:

过程中遇到的部分问题:连接超时,是格式问题还是访问超时的时长设置 过短?让我们来看看!

最终fixed了上诉gif图的问题,并下载成功:

然后,封装到方法里,对代码进行格式化,并重命名方法和临时变量:

第一个方法:[url_music_name]

def url_music_name(name_put):
    urlReady = "https://www.gequbao.com/s/" + name_put  # 拉取音乐页面,获取音乐标签里的关键信息
    site = requests.get(urlReady)
    # bs方法获取标签内容
    bs = BeautifulSoup(site.text, "lxml")  # hml.parser解析不能对复杂内容处理,这里使用lxml
    bs_tag = bs.select(".text-primary.font-weight-bold")  # 通过class属性定位到标签
    url_name_list = []
    for url_and_name in bs_tag:  # 测试限制了数据,记得改过来[:20]
        url_name_list.append(str(url_and_name)[48:].strip(
            r"\n                                                                    </a>"))  #
        # 有换行,对其进行处理,处理后再来个循环进行链接与歌曲名的分割
    out_array = []
    for m in url_name_list:
        url_str = m.split("\">")[0]  # 很好,数据越来越短,离想提取的数据越来越近。这里利用切片和数组索引那到了第一个值
        music_name_str = name_put + '_' + m.split("\">")[1][:-1]
        out_array.append([url_str, music_name_str])
    return out_array  # 搜索歌手的结果列表,取到链接与歌曲名的数组

第二个方法:[second_url_name]通过搜索具体歌名或者歌名+歌手的方式获取歌曲详情页的链接,然后可以从单个歌曲详情里提取单个歌曲的下载链接。

所以说还得是用requests,用selenium+webdriver那不得累死。。。

def second_url_name(name):
    urlReady = "https://www.gequbao.com/s/" + name
    site = requests.get(urlReady)

    # bs方法获取标签内容
    bs = BeautifulSoup(site.text, "lxml")  # html.parser解析不能对复杂内容处理,这里使用lxml
    bst_tag = bs.find_all('tr')  # 通过tr标签匹配

    print("\n【注意】:当搜索的是歌曲名时,为节省资源,\n  将仅下载搜索结果的前五首歌曲!  ")
    CertainContent = []
    for urlMusicName in bst_tag[1:6]:
        noReady = str(urlMusicName)[57:-56].split("\n")
        # print(noReady)
        CertainContent.append([noReady[0], noReady[-1]])
    print("----------------")
    second_array = []
    for m, n in CertainContent:
        urlStr = m.split("\">")[0]  # 很好,数据越来越短,离想提取的数据越来越近。这里利用切片和数组索引那到了第一个值
        MusicName = m.split("\">")[1]
        singer = n.strip('<td class="text-success">')

        MusicNameStr = MusicName + "_" + singer
        # print(f"MusicName's Content is |{MusicNameStr}|")
        second_array.append([urlStr, MusicNameStr])  # 添加到列表后,我们看看是不是真的提取到了这两个值
    return second_array

第三个方法:[getDownUrl]

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 Chrome/103.0.0.0 '  # 
                  'Safari/537.36',
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9',
    'accept-encoding': 'gzip, deflate, br'
}

# 想办法由列表数据,生成一个只含下载链接的数组
def getDownUrl(input_list=None):
    if input_list is None:
        input_list = [['music/81215', '笨小孩 (Live)'], ['music/37479', '忘情水(Live)']]
    s = requests.session()
    # 设置连接活跃状态为False
    s.keep_alive = False

    checklist = []
    for xi, yi in input_list:
        final_url = "https://www.gequbao.com/" + xi
        response = requests.get(final_url, headers=header, timeout=100)  # 超时
        response.close()

        down_url = re.findall(r'url = (.+)\.', response.text)[0].strip('\'').strip('\'').replace(r'amp;',
                                                                                                 '')  # 合理提取MP3链接
        yi = yi.replace('...', '')

        checklist.append([down_url, yi])
    return checklist

# 中间用正则提取到了下载链接,即down_url

以及未启用的selenium下载方式,因为其中多组按键操作里的ctrl+s操作时灵时不灵,有专业搞按键的童鞋可以帮忙看看:

# def SeleniumDown(list_for=None):
#     from pykeyboard import PyKeyboard
#     from pymouse import PyMouse
#     k = PyKeyboard()
#     mouse = PyMouse()
#     xm, ym = mouse.screen_size()  # 获取屏幕尺寸
#     # print(xm,ym)
#     # 继续通过访问页面,再获取页面下的链接来
#     wd = webdriver.Chrome()
#     action = ActionChains(wd)
#
#     # 先打开一个基本页面
#     urlencoded = "https://www.gequbao.com/s/%E5%88%98%E5%BE%B7%E5%8D%8E"
#     wd.get(urlencoded)  # 是否调用浏览器
#     wd.maximize_window()
#
#     # 拿到下载页的button
#     downBtn = "//*[@id='btn-download-mp3']"
#     if list_for is None:
#         list_for = []
#
#     # 循环调用下载详情页地址
#     for content in list_for:
#         detailUrl = "https://www.gequbao.com/" + content[0]
#         after_string = str(random.randint(10, 20))
#         print(content[1])
#         detailMusicName = content[1] + after_string
#
#         wd.get(detailUrl)
#         handles = wd.window_handles
#         wd.switch_to.window(handles[-1])
#         wd.implicitly_wait(1.5)
#         # 使用webdriver调用button点击下载
#         wd.find_element(By.XPATH, downBtn).click()  # 注释掉
#         handles = wd.window_handles
#         wd.switch_to.window(handles[-1])
#         wd.implicitly_wait(2)
#
#         # 随机定位浏览器播放页的元素
#         # ele = "//body/video/source[@type='audio/mpeg']"      # 选取任意元素
#         # inputElement = wd.find_element(By.XPATH,ele)          # 找不到元素
#         # time.sleep(1)
#         # playEle = wd.find_element(By.CSS_SELECTOR,css_selector)
#         # playEle = wd.find_element(By.TAG_NAME,tag_name)
#
#         # ctrl+s 保存当前文件。重复保存一次,并给予弹窗足够保留时间
#         action.reset_actions()
#         action.move_by_offset(xm / 2, ym / 2).click(None).perform()  # 点击空白处
#         # action.key_down(Keys.CONTROL).send_keys('s').key_up(Keys.CONTROL).perform()
#         # print("------[1]------")
#         # playEle.send_keys(Keys.CONTROL,'s')
#         # print("------[2]------")
#         k.press_key(k.control_key)
#         k.tap_key("S", n=2, interval=1)
#         k.release_key(k.control_key)
#         time.sleep(3)
#
#         # 输入歌名
#         k.type_string(detailMusicName)
#         # print("输入音乐保存名了。。。")
#
#         # 联合点击Alt+s 快速定位到保存按钮: 替代回车键
#         # inputElement.send_keys(Keys.ENTER)
#         k.press_key(k.alt_key)  # 按住alt键
#         k.tap_key("S")  # 单骑s键
#         k.release_key(k.alt_key)  # 释放alt键
#
#         # 给两秒的下载时间
#         time.sleep(2)
#         # 慎用close操作
#         # wd.close()
#         print(f"执行了第{list_for.index(content) + 1}次")
#     wd.quit()
def displayProgress(module, totalsize, which):
    """
    Dynamic display of progress
    :param module:已经下载的数据块
    :param totalsize:数据块的大小
    :param which:远程文件的大小
    :return:不用return,直接打印
    """
    progressDown = module * totalsize * 100.0 / which
    if progressDown > 100:
        progressDown = 100

    print("\r downloading: %.1f%%" % progressDown, end="")


def downloadMp3(uurl, singer_music):
    if '.' in uurl[-6:]:
        postfix = uurl.split(".")[-1]
    else:
        postfix = uurl.split("=")[-1]
    MusicName = singer_music + "." + postfix
    print(f'MusicName = {MusicName}')
    downloadPath = "./" + input_name

try:
    os.makedirs(input_name, exist_ok=True)
    print("目录已创建!")
finally:
    pass
    downloadDir = downloadPath + "/" + MusicName
    urlretrieve(uurl, downloadDir, displayProgress)  # api接口的可以直接下,部分接口暂无法正常下载
    print("  下载完成!")

上诉是通过urllib.request方法下载的。

接下来是正文,requests下载:

def fileDownloadUsingRequests(file_url, music_name):
    """It will download file specified by url using requests module"""
    global r

    # 处理文件后缀名
    if file_url.count("=") >= 2:
        file_suf = '.' + file_url.split('=')[-1]
    else:
        file_suf = '.' + file_url.split('.')[-1]
    # 处理文件命名异常,非法命名无法保存到windows磁盘里
    if "<" in music_name:
        music_name = music_name.split("<")[0]  # .replace("<",""),多了个斜杠
    if "|" in music_name or "/" in music_name:
        music_name = music_name.replace("/", "")
        music_name = music_name.replace("|", "")
    if True:
        music_name = music_name.replace(r"amp;", "")
        music_name = music_name.replace(r":", "")

    file_name = music_name.replace(",", "") + file_suf
    print(f"已获取歌名:{file_name}!")
    pwd = os.path.join(os.getcwd(), input_name, file_name)
    fileDir = os.path.join(os.getcwd(), input_name)
    # print(pwd)
    if not os.path.exists(fileDir):
        os.mkdir(input_name)
        print(f"目录'{input_name}'已创建!")

    if os.path.exists(pwd):
        print('File already exists')
        return
    try:
        r = requests.get(file_url, stream=True, timeout=200)
    except requests.exceptions.SSLError:
        try:
            response = requests.get(file_url, stream=True, verify=False, timeout=200)
            print(f'连接状态:{response.status_code}')
        except requests.exceptions.RequestException as e:
            print(e)
            quit()
    except requests.exceptions.RequestException as e:
        print(e)
        quit()
    chunk_size = 1024
    total_size = int(r.headers['Content-Length'])

    total_chunks = total_size / chunk_size
    print('歌曲包totalsize:%.2fKB' % total_chunks)

    file_iterable = r.iter_content(chunk_size=chunk_size)
    # 用tqdm进度提示信息,包括执行进度、处理速度等信息,且可在一定程度上进行定制。
    tqdm_iter = tqdm(iterable=file_iterable, total=total_chunks, unit='KB',
                     leave=False, colour='MAGENTA'
                     )
    with open(pwd, 'wb') as f:  # file_name
        for data in tqdm_iter:
            f.write(data)  # 字节写入

    # total_size=float(r.headers['Content-Length'])/(1024*1024)
    '''
    print 'Total size of file to be downloaded %.2f MB '%total_size
    '''
    f.close()
    print('Downloaded file %s' % file_name)
    print(print("------------------------------------\n"))  # 因为是循环写入,加上换行,便于查看
fileDownloadUsingRequests里面的内容分这几个部分,

1)从歌曲的播放地址里获得歌曲的格式,也就是后缀

2)处理文件命名异常,非法命名无法保存到windows磁盘里

3)预建立歌曲的下载地址和下载目录,下载地址通常是os.cwd()当前目录+歌曲名,为了方便管理,我们还把搜索关键字当作目录,所以小伙伴一定要正确去搜索歌曲哦~

4)异常处理

5)拿到响应头,将请求的迭代字节对象放入可迭代插件tqdm里

6)等待数歌,哦不,听歌即可。

来查看调用main的过程:

if __name__ == "__main__":
    global input_name

    # 搜歌手 和 搜歌名的方式结合后,去下载
    print('\n小Tips:输入2后,按歌名+歌手名搜索。如搜索:"Mojito 周杰伦"')
    while 1:
        down_type = input("请输入搜索方式-- 1:歌手;2:歌名!:")
        down_type = down_type.strip()
        if down_type in ('1', '2'):
            break
        else:
            print('请输入 1 或者 2 ')
    while 1:
        try:
            input_name = input("请输入要搜索的内容:")
            if input_name:
                print('歌曲下载源的解析速率取决于源服务器效率,下载时长取决于歌曲大小,请耐心等待!')
                break
        finally:
            pass
    if down_type == "1":
        get_now = getDownUrl(url_music_name(input_name))
        if len(get_now) < 1:
            print('暂未搜索到歌曲资源,请等待作者更新歌源!')
            print('5秒钟后将会关闭下载通道~~~')
            for i in range(5)[::-1]:
                print(f'还剩 {i + 1} 秒!')
                time.sleep(1)
        else:
            for du, dn in get_now:
                fileDownloadUsingRequests(du, dn)

    elif down_type == "2":
        letGo = getDownUrl(second_url_name(input_name))
        print(letGo)
        if len(letGo) < 1:
            print('暂未搜索到歌曲资源,请等待作者更新歌源!')
            print('5秒钟后将会关闭下载通道~~~')
            for i in range(5)[::-1]:
                print(f'还剩 {i + 1} 秒!')
                time.sleep(1)
        else:
            for mm, nn in letGo:
                fileDownloadUsingRequests(mm, nn)

    else:
        print("Unknown error.Check ur code again pls.")
    print("\n全部下载:已完成!<-- Author:HuZK --> 将在8秒后自动关闭窗口!")
    for i in range(8)[::-1]:
        print(f'还剩 {i + 1} 秒!')
        time.sleep(1)
    # time.sleep(8)  # 打包成EXE文件必备停顿

 >>>下载完成,请在EXE所在目录(如果用的是工具)查看歌曲。

最精华的部分讲完,我们谈谈其它的。

请求五要素。

了解接口特点,对接口进行访问处理。

请求content-type的四种格式


text/xml    -- 它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范。
它的使用也很广泛,如 WordPress 的 XML-RPC Api,搜索引擎的 ping 服务等等。JavaScript 中,也有现成的库支持以这种方式进行数据交互,能很好的支持已有的 XML-RPC 服务
​
application/json    
用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。
​
multipart/form-data
post数据的提交方式。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 –boundary– 标示结束
​
application/x-www-form-urlencoded
这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。

如果是get,必要时需要填入params,如果访问方式是post,必要时需要填入data内容。

传入的数据信息随响应格式的要求,需要进行格式化。

四、优化空间

1、多类别搜索下载:不光通过歌手搜索,通过歌曲搜索实现下载(歌曲的搜索下载又有不同)。

2、歌曲重复:下载的100首歌曲里,除了校验本地文件重复外,有些重复的歌曲,其实名字不尽相同,可以写个算法排除。

3、并行下载:歌曲的下载任务都是单程,并没有冲突,考虑实现并行下载。

5、内存优化:逻辑简化,也就是内存优化。

6、通用适配:如果在方法里内嵌多层校验,直到找到想要的下载地址为止,那就可以不局限于一个音乐平台了,可以是任意平台,只要能输入该音乐平台的地址,就能给用户自主选择去哪儿下载的空间。

目前优化项暂时做了第一条。

五、应用

比较selenium与requests在爬虫上的各个方向的优劣.

【比较接口测试与爬虫的区别】

·接口获取:

这个很好理解,接口测试的获取是现成的。而爬虫需要自己找接口,没有接口时,需要绕开接口,使用其它技术手段实现,如页面获取、点击操作、图片识别等方式。

·接口对接:

接口对接,参照接口文档,用什么方式或信息建立有效连接,有的需要认证,按照标准提供认证信息即可。

而爬虫一般需要伪装user-agent,并需要知道接口对接的方式,需要哪些关键信息。有时候为了应对服务器封杀,还要变换IP,并控制访问频率。

·接口数据格式化处理:

有序格式,按序列提取关键参数。

无需格式,需要做校验与判断,并分层提取关键参数。

·接口信息的应用:

普通接口主要用于前后端数据交互,或者对外开放API,适配多终端实时获取数据。

爬虫的应用:比较有业务价值,主要用于获取目标信息,并能提供海量数据互相比较,如果能分门别类,就是搜索引擎的雏形;如果能做数据仓库和开发,就是大数据的开端;如果能不停歇批量地访问可配置接口,就是压力攻击。综上所述,用的好的时候,可以说是大数据及数字化的前一步。

·对企业产生的价值:

接口对企业价值:使用户可以在多终端访问并使用公司提供的服务,且能存储重要信息。连接客户,连接企业,连接数据。

爬虫对企业:帮助寻找目标商品或者酒店最优惠的价格。根据点击量和有效阅读数,生成热搜或者其它排行榜。

留下的漏洞】:有的小破站,对音乐格式处理比较随便,我们获取不到

headers['Content-Length']

也就没法完美展示tqdm进度条了,大伙拿到源码后,可以注释掉这个,然后把tqdm()里的total去掉就行

 打包成.EXE工具后,分享给大家:

百度网盘 请输入提取码 码:wynb

项目地址:GitHub - HardyHu/MusicEasy: 用来从名为收费、实则垄断的听歌生态里爬取喜欢的音乐。妈妈再也不用担心我买了网易会员下不了周杰伦的歌了!

猜你喜欢

转载自blog.csdn.net/qq_17195161/article/details/127420365