网页分为两个部分,歌单广场和歌单详情页。总体思路是先从歌单广场获取所有的URL然后去详情页进行解析。 最后的数据大概这个样子:
在广场中需要实现获取所有歌单详情页的URL链接。 研究URL不难发现这样的规律,改变cat可以换歌单的大分类(华语,流行,全部等),limit是每页显示35个歌单,这里是第二页所以offset是35*2=70。那么只需要采用for循环就可以。 右键打开检查我们可以发现关于歌单详情页URL就在a标签下面herf,之后beautifulsoup就可以获取,查看一下具体歌单详情页的URL确实是这样,只需要做一下字符串的拼接就可以了。 下面是代码:
import requests as rq from bs4 import BeautifulSoup import pandas as pd import numpy as np import time import concurrent.futures from multiprocessing.dummy import Pool as pool ##后面所有的代码都是调的这些个库,我习惯写pd np因为懒。。。 list1=[] headers = { 'Referer': 'http://music.163.com/', 'Host': 'music.163.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' } def getHTMLText(url,headers): #通用的获取网站内容的框架 try: r = rq.get(url,headers=headers) r.raise_for_status() r.encoding = r.apparent_encoding return r.text except: return "网络解析错误" def get_url(cat):#获取首页该分类下面的歌单url,形成url_list depth=38 start_url='https://music.163.com/discover/playlist/?order=hot&cat='+cat for i in range(depth): try: url=start_url+'&limit=35'+'&offset='+str(35*(i+1)) html=getHTMLText(url,headers) parse_main(html) except: print('失败') continue def parse_main(html):#解析每个广场页,bs4弄出来歌单名,歌单URL soup=BeautifulSoup(html,'html.parser') c=soup.find_all('li') for unit in c: try: name_url=unit.find('a',{'class':"tit f-thide s-fc0"})#m这里有URL,名字的信息 number=eval(unit.find('span',{'class':'nb'}).text.replace('万','0000'))#这里获取的是播放量的信息,用于初步筛选 list1=[name_url['title'].replace(u'\xa0', u' '),number,name_url['href']] url_list.append(list1) except: continue弄出来list1大概这个样子: [歌单名,播放次数,URL] 之后按照每个URL进入相应的详情页解析就可以了。
需要获取具体信息: 这里渴望拿到的是所属标签,播放次数,转发次数,收藏次数,评论量,歌单标题和歌单长度。他们的解析途径都差不多,都是beautifulsoup。我们可以看一个: 不难看到总共有两个属性,都在div标签下的a标签下的i,我们根据特征找到左右class为u-tag的标签然后弄出来它的text就行了。
tags=soup.find_all('a',{'class':'u-tag'}) ##中间有省略 tag1=tags[0].text.replace(u'\xa0', u' ')下面是具体代码:
finallist=[] headers1={ 'Referer': 'http://music.163.com/', 'Host': 'music.163.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' } def parse_single(listid):#进入歌单内部解析,获取播放量,收藏量,标签等信息 global count count+=1 singleurl='https://music.163.com'+listid singletext=getHTMLText(singleurl,headers=headers1) soup=BeautifulSoup(singletext,'html.parser') play_count=eval(soup.find('strong',{'class':'s-fc6'}).text) fav=eval(soup.find('a',{'class':'u-btni u-btni-fav'}).i.text.strip('(').strip(')')) share=eval(soup.find('a',{'class':'u-btni u-btni-share'}).i.text.strip('(').strip(')')) comment=eval(soup.find('a',{'data-res-action':'comment'}).i.span.text) length=eval(soup.find('span',{'id':'playlist-track-count'}).text) date=soup.find('span',{'class':'time s-fc4'}).text[:10] name=soup.find('h2',{"class":'f-ff2 f-brk'}).text.replace(u'\xa0', u' ') try: tags=soup.find_all('a',{'class':'u-tag'}) p=len(tags) if p==3: tag1=tags[0].text.replace(u'\xa0', u' ') tag2=tags[1].text.replace(u'\xa0', u' ') tag3=tags[2].text.replace(u'\xa0', u' ') elif p==2: tag1=tags[0].text.replace(u'\xa0', u' ') tag2=tags[1].text.replace(u'\xa0', u' ') tag3="nan" else : tag1=tags[0].text.replace(u'\xa0', u' ') tag2="nan" tag3="nan" list1=[name,date,play_count,fav,share,comment,length,tag1,tag2,tag3] finallist.append(list1) print('解析第{}个歌单成功'.format(count)) except: tag1='nan' tag2='nan' tag3='nan' list1=[name,date,play_count,fav,share,comment,length,tag1,tag2,tag3] finallist.append(list1) print('解析第{}个歌单成功'.format(count))最后是我的main函数和多线程的使用当然这里你弄个列表个main函数传参也可以。
def main(type): get_url(type) print("歌单列表获取完成") print(url_list) main('轻音乐') #这里需要自己改动标签(比如改为流行,华语等,下面存储同样改变) a=pd.DataFrame(url_list) b=list(a[2]) with concurrent.futures.ThreadPoolExecutor() as executor: executor.map(parse_single,b) #多线程 print(finallist) a=pd.DataFrame(finallist) b=pd.DataFrame(url_list) title_list=['名称','创建日期','播放次数','收藏量','转发量','评论数','歌单长度','tag1','tag2','tag3'] c=pd.Series(title_list) a.columns=c a.to_excel(r'C:\Users\Leo\Desktop\轻音乐.xlsx') #数据输出到Excel(1)headers很重要,并且广场和详情页的headers还不太一样,这个我捣鼓了好长时间,尽量还是都加相应页的headers。 (2)听从大佬的建议采用了多线程,用之前三秒钟爬出来一个,用之后不到一秒一个,很舒适。感觉这个操作计算和io都很密集,但是我没想到怎么同时使用多线程和多进程。(不明白的小伙伴可以看我的另外一个多线程多进程的博客)
–本人准大二小白,欢迎dalao指教–
最后附上全部代码:
import requests as rq from bs4 import BeautifulSoup import pandas as pd import numpy as np import time import concurrent.futures from multiprocessing.dummy import Pool as pool limit=10000#热门歌单播放量筛选下限 headers = { 'Referer': 'http://music.163.com/', 'Host': 'music.163.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' } headers1={ 'Referer': 'http://music.163.com/', 'Host': 'music.163.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' } finallist=[] url_list=[] def getHTMLText(url,headers): #通用的获取网站内容的框架 try: r = rq.get(url,headers=headers) r.raise_for_status() r.encoding = r.apparent_encoding return r.text except: return "网络解析错误" def get_url(cat):#获取首页该分类下面的歌单url,形成url_list depth=38 start_url='https://music.163.com/discover/playlist/?order=hot&cat='+cat for i in range(depth): try: url=start_url+'&limit=35'+'&offset='+str(35*(i+1)) html=getHTMLText(url,headers) parse_main(html) except: print('失败') continue def parse_main(html):#解析单个url soup=BeautifulSoup(html,'html.parser') c=soup.find_all('li') for unit in c: try: name_url=unit.find('a',{'class':"tit f-thide s-fc0"})#m这里有URL,名字的信息 number=eval(unit.find('span',{'class':'nb'}).text.replace('万','0000'))#这里获取的是播放量的信息,用于初步筛选 list1=[name_url['title'].replace(u'\xa0', u' '),number,name_url['href']] url_list.append(list1) except: continue def parse_single(listid):#进入歌单内部解析,获取播放量,收藏量,标签等信息 global count count+=1 #print(count) #print('\0') singleurl='https://music.163.com'+listid #print(singleurl) #print('\0') singletext=getHTMLText(singleurl,headers=headers1) #print("**") soup=BeautifulSoup(singletext,'html.parser') play_count=eval(soup.find('strong',{'class':'s-fc6'}).text) fav=soup.find('a',{'class':'u-btni u-btni-fav'}).i.text.strip('(').strip(')') if('万') in fav: fav=eval(fav.replace('万','0000')) try: share=eval(soup.find('a',{'class':'u-btni u-btni-share'}).i.text.strip('(').strip(')')) except: share=0 try: comment=eval(soup.find('a',{'data-res-action':'comment'}).i.span.text) except: comment=0 length=eval(soup.find('span',{'id':'playlist-track-count'}).text) date=soup.find('span',{'class':'time s-fc4'}).text[:10] name=soup.find('h2',{"class":'f-ff2 f-brk'}).text.replace(u'\xa0', u' ') try: tags=soup.find_all('a',{'class':'u-tag'}) p=len(tags) if p==3: tag1=tags[0].text.replace(u'\xa0', u' ') tag2=tags[1].text.replace(u'\xa0', u' ') tag3=tags[2].text.replace(u'\xa0', u' ') elif p==2: tag1=tags[0].text.replace(u'\xa0', u' ') tag2=tags[1].text.replace(u'\xa0', u' ') tag3="nan" else : tag1=tags[0].text.replace(u'\xa0', u' ') tag2="nan" tag3="nan" list1=[name,date,play_count,fav,share,comment,length,tag1,tag2,tag3] finallist.append(list1) print('解析第{}个歌单成功'.format(count)) except: tag1='nan' tag2='nan' tag3='nan' list1=[name,date,play_count,fav,share,comment,length,tag1,tag2,tag3] finallist.append(list1) print('解析第{}个歌单成功'.format(count)) def main(type): get_url(type) print("歌单列表获取完成") print(url_list) main('轻音乐') #这里需要自己改动标签(比如改为流行,华语等,下面存储同样改变) a=pd.DataFrame(url_list) b=list(a[2]) #for i in range(40): #parse_single(b[i]) with concurrent.futures.ThreadPoolExecutor() as executor: executor.map(parse_single,b) #多线程 print(finallist) a=pd.DataFrame(finallist) b=pd.DataFrame(url_list) title_list=['名称','创建日期','播放次数','收藏量','转发量','评论数','歌单长度','tag1','tag2','tag3'] c=pd.Series(title_list) a.columns=c a.to_excel(r'C:\Users\Leo\Desktop\轻音乐.xlsx') #数据输出到Excel