目录:
爬虫之国内音乐爬取
一、设计思路
二、使用资源
三、具体代码编写
四、优化空间
五、应用
一、设计思路
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: 用来从名为收费、实则垄断的听歌生态里爬取喜欢的音乐。妈妈再也不用担心我买了网易会员下不了周杰伦的歌了!
今天的文章 【有效】最新爬取音乐,纯接口访问实现。Python3、requests、美丽汤、tqdm实战分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/82707.html