我需要一些音乐数据,而 豆瓣音乐 上相关数据是非常齐全的。
即使是十分小众、鲜有人听过的专辑,豆瓣也提供了较为详细的描述。
摆在我面前的第一个问题是,如何将豆瓣音乐上的专辑一网打尽,一个不遗漏地抓取到。
看到subject后跟着一串数字ID,这时候一个简单的思路已经出来了:
将id从1不断递增,扫描完所有的ID,检查哪些ID是存在的
又观察到大多数专辑页面上存在相关推荐,也就是那个“喜欢某某专辑的人也喜欢… …”。
这里存在一对N的超链接映射,最终形成了网络,于是可以有第二个思路:
将某个专辑加入队列,从它出发,通过 “也喜欢” 来获取新专辑,并将这些新专辑加入队尾
每完成一次操作,就从队列中删除队首的专辑
循环上面的过程,直到处理完队列中的所有元素
最终获取到的就是包含第一个元素的网
当然,上面的思路是有缺陷的,那些孤立的专辑无法被包括进来。
我决定使用ID自增的方式来扫专辑。
在python中,直接使用urllib2.urlopen去下载完整的HTML页面是不太经济的,
特别是一开始我并不需要解析HTML,我只是想知道这个页面是否存在。可以使用head请求。
我还观察到直接访问www.douban.com/subject/id可能会产生一个302的重定向,或者404错误
于是我可以通过请求www.douban.com/subject/id/来确定对应ID是否是一个音乐专辑的ID
302还可能是重定向到其他douban.com二级域名,比如book、movie,但我只需要music,其他的直接忽略了。
(获取豆瓣所有的电影和图书也很方便,不是吗?)
MySQL下创建表album:
CREATE TABLE `album` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`album_url` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_album_id` (`album_url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
它包括id字段和album_url字段,但album_url是int,并非varchar,因为我只需要保存数字id。
这里有一点小的技巧:
headers伪装成了goolebot,减小IP被屏蔽的几率
id从1扫描到3000万,这是个估值上界,通过查看豆瓣最近收录的新专辑判断
同步队列id_queue最大的长度是100,不要忽略了参数,设置成无限长度,3000万太多
这里有50个线程同时工作,但是数据库连接dbconn是全局的、公用的,使用锁lock来确保每次只有一个线程进入
在id_queue中加入了50个None,作为结束标志
可以直接HEAD请求music.douban.com的页面判断是否存在,404表示这个id不存在、302表示这个id是其他内容而不是音乐,只有status返回200才表示对应的专辑存在
使用上面的脚本,从昨晚运行到现在,我已经扫描到了70万个专辑。 🙂