Scrapy 爬取网易云音乐播放量百万以上的歌单以及歌单详情

创建爬虫

查看歌单链接 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 接口,比网页源码好用多了。

start_requests

给起始链接设置解析函数

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 进行简单的数据清洗。

ItemPipeline 数据清洗

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"
}
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值