赞
踩
查看歌单链接 https://music.163.com/#/discover/playlist
用 requests 下载此链接的网页发现不是原网页,应该是被跳转到了登录页面。分析之后得到真实网页应该是:
https://music.163.com/discover/playlist (去掉 #)
同时还需要设置一下 headers,如下:
DEFAULT_REQUEST_HEADERS = {
'Host': 'music.163.com',
'Referer':'https://music.163.com/discover/playlist',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
}
再用 requests 下载就能得到需要的网页源码了。
命令行创建一个 scrapy 爬虫,初始配置如下:
class Music163spiderSpider(Spider):
name = 'music163spider'
allowed_domains = ['music.163.com']
start_urls = ['https://music.163.com/discover/playlist',]
base_url = 'https://music.163.com'
api_url = 'http://music.163.com/api/playlist/detail?id={id}'
其中的 api_url
链接为歌单详情 api 接口,本来歌单详情链接应该是https://music.163.com/playlist?id={id},但用 requests 下载源码发现同样被跳转到其他页面了,设置 headers 也没用。上网查过之后发现了这个 api 接口,比网页源码好用多了。
给起始链接设置解析函数
def start_requests(self):
for url in self.start_urls:
yield Request(url=url,callback=self.parse_playlists)
def parse_playlists(self, response): playlists = response.xpath('//div[@class="g-bd"]//ul[@id="m-pl-container"]//li') for lists in playlists: item = ListsItem() item['cover'] = lists.xpath('.//div[contains(@class,"u-cover")]/img/@src').extract_first() item['title'] = lists.xpath('.//div[contains(@class,"u-cover")]/a/@title').extract_first() item['author'] = lists.xpath('.//p[2]/a/@title').extract_first() item['times'] = lists.xpath('.//div[@class="bottom"]//span[@class="nb"]//text()').extract_first() item['url'] = lists.xpath('.//div[contains(@class,"u-cover")]/a/@href').extract_first() list_id = lists.xpath('.//div[@class="bottom"]/a/@data-res-id').extract_first() item['id'] = list_id if len(item.get('times')) >= 4 and item.get('times').endswith('万'): yield item yield Request(url=self.api_url.format(id=list_id),callback=self.parse_list) next = response.xpath('//div[@id="m-pl-pager"]//div[@class="u-page"]//a[contains(@class,"znxt")]/@href').extract_first() if next: yield Request(url=self.base_url+next,callback=self.parse_playlists,dont_filter=True)
先获取包含歌单所有歌曲的网页源码块,歌曲详情被写在 li
标签中,接下来就是从标签中提取需要的信息,并赋值给 Item 即可。
通过对播放次数 times
简单的判断,过滤出播放量达到百万的歌单,提取该歌单的 id ,构造歌单详情 api 链接,指定解析函数。
解析当前页是否有下一页的链接,如果有,将其加入到 Request 队列中,指定解析函数。
def parse_list(self,response): item = MusicItem() result = json.loads(response.text).get('result') item['name'] = result.get('name') item['songslist'] = [] item['create_time'] = str(result.get('createTime')) item['description'] = result.get('description') item['tags'] = result.get('tags') item['count'] = result.get('trackCount') item['playcount'] = result.get('playCount') tracks = result.get('tracks') for track in tracks: info = {} info['album'] = track.get('album').get('name') info['songid'] = track.get('id') info['artists'] = [singer.get('name') for singer in track.get('artists')] info['name'] = re.sub('\xa0',' ',track.get('name'))#在此处对 name 简单清洗 item['songslist'].append(info) if item.get('playcount') >= 1000000: yield item
api 接口返回的是 json 格式,将需要的信息提取赋值就可以了。
提取出来的信息中 create_time
返回的是一个13位的时间戳,以及其他信息中包含不需要的部分,需要在 Item Pipeline 进行简单的数据清洗。
class Music163Pipeline(object): def open_spider(self, spider): self.file = codecs.open('list.json', 'w', encoding='utf-8') def close_spider(self, spider): self.file.close() def process_item(self, item, spider): if isinstance(item,MusicItem): timestamp = item.get('create_time') time_local = time.localtime(int(timestamp[:-3])) item['create_time'] = time.strftime("%Y-%m-%d",time_local) text = item.get('description') item['description'] = re.sub('\n',' ',text) for song in item.get('songslist'): song['artists'] = '/'.join(re.sub('\xa0',' ',artist) for artist in song.get('artists')) return item if isinstance(item,ListsItem): item['url'] = 'https://music.163.com' + item.get('url') line = json.dumps(dict(item),indent=4,ensure_ascii=False) + "\n" self.file.write(line) return item
将13位的时间戳截取前10位生成需要的时间格式。
description
中出现未转义的 “\n” 字符,将其替换成空格。
将歌手从原本的列表拼接成字符串,并用"/"连接。
将歌单链接拼接完整,然后将爬取到的歌单信息写入json文件中。
ps:本来想将写成两个 ItemPipeline 的,但分开之后再运行,后一个返回的结果是None,可能跟 ItemPipeline 处理 Item 的顺序有关。
贴上爬取的部分结果:
ps:在爬取倒数第二页,生成最后一页的链接时,生成的链接不是正确的链接,查看之后发现按 xpath 里爬的内容应该是正确的。但最后几页也没啥内容可爬就算了。
文件部分内容:
{ "cover": "http://p2.music.126.net/wsPS7l8JZ3EAOvlaJPWW-w==/109951163393967421.jpg?param=140y140", "title": "2018上半年最热新歌TOP50", "author": "网易云音乐", "times": "1264万", "url": "https://music.163.com/playlist?id=2303649893", "id": "2303649893" } { "cover": "http://p2.music.126.net/wpahk9cQCDtdzJPE52EzJQ==/109951163271025942.jpg?param=140y140", "title": "你的青春里有没有属于你的一首歌?", "author": "mayuko然", "times": "4576万", "url": "https://music.163.com/playlist?id=2201879658", "id": "2201879658" } { "cover": "http://p2.music.126.net/8TZecCv5K_yYMbeZ6gieFw==/19029247742518751.jpg?param=140y140", "title": "耳朵喜欢你 好听到可以单曲循环", "author": "鹿白川", "times": "3606万", "url": "https://music.163.com/playlist?id=2232237850", "id": "2232237850" } { "cover": "http://p2.music.126.net/lZSsBdcR9xc4YK82MfYjYQ==/18637821604685232.jpg?param=140y140", "title": "点起一支烟,听一首粤语歌", "author": "丑萌的猫", "times": "1192万", "url": "https://music.163.com/playlist?id=2197936899", "id": "2197936899" }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。