java爬虫抓取动态网页(网易云音乐有哪些评论过万的歌曲?(图) )
优采云 发布时间: 2021-11-12 06:05java爬虫抓取动态网页(网易云音乐有哪些评论过万的歌曲?(图)
)
天马营崖发表
原因
前两天在知乎看到一个帖子,“网易云音乐哪些歌曲评论过万?” ”有段时间,我用Java实现了一个简单的爬虫,这里简单记录一下。
最终结果是开放的。可以随意参观。请点击这里>>>>>> 网易云音乐爬虫结果。
爬虫简介
网络爬虫是按照一定的规则自动抓取万维网上信息的程序或脚本。一般的网络爬虫大致包括以下几个步骤:
网络爬虫的一般流程如上图所示。不管你在做什么爬虫应用,整体流程都是类似的。现在,我们将定制一个特定的网络爬虫,专门爬取基于网易云音乐的音乐评论数。
网页类型分析的前期准备
首先,我们需要对网易云音乐的整个网站有一个大致的了解。进入网易云音乐首页后,我们发现大概有几种类型的网址:
我们最终需要爬取的数据在歌曲页面中,其中收录歌曲名称和歌曲评论数。
此外,我们还需要获取尽可能多的歌曲页面。我们可以从前面的 6 类页面中获取这些信息。其中播放列表列表和播放列表页面结构最简单,可以通过分页直接访问播放列表列表。因此,我们选择播放列表页面作为我们的初始页面,然后播放列表列表-播放列表-歌曲就可以一路向下爬取。
设计数据模型
通过上面的分析,我们可以知道我们要做两件事,一是抓取页面播放列表list-playlist-songs,二是存储最终结果。所以,我们只需要两个对象,一个用来存放页面相关信息,url,页面类型,是否被爬取(html和title作为临时数据存储),另一个用来存放歌曲相关信息, url, 歌曲名, 评论数。因此,模型类如下:
public class WebPage {
public enum PageType {
song, playlist, playlists;
}
public enum Status {
crawled, uncrawl;
}
private String url;
private String title;
private PageType type;
private Status status;
private String html;
...
}
public class Song {
private String url;
private String title;
private Long commentCount;
...
}
获取网页内容并解析
根据前面的分析,我们需要爬的页面有3种:播放列表、播放列表和歌曲。为了验证这个想法的可行性,我们先用代码对这三类网页进行解析,并将网页内容获取和分析的代码放到CrawlerThread中。
获取 html
不管你想从网站获取什么数据,获取它的html代码都是最基本的一步。这里我们使用jsoup获取页面信息,在CrawlerThread中添加如下代码:
private boolean fetchHtml(WebPage webPage) throws IOException {
Connection.Response response = Jsoup.connect(webPage.getUrl()).timeout(3000).execute();
webPage.setHtml(response.body());
return response.statusCode() / 100 == 2 ? true : false;
}
public static void main(String[] args) throws Exception {
WebPage playlists = new WebPage("http://music.163.com/#/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset=0", PageType.playlists);
CrawlerThread crawlerThread = new CrawlerThread();
crawlerThread.fetchHtml(playlists);
System.out.println(playlists.getHtml());
}
运行后可以看到输出的html文本
解析播放列表列表页面
拿到html后,我们解析播放列表,获取页面上的所有播放列表。Jsoup 收录了 html 解析相关的功能。我们不需要添加其他依赖,直接在CrawlerThread中添加如下代码:
private List parsePlaylist(WebPage webPage) {
Elements songs = Jsoup.parse(webPage.getHtml()).select("ul.f-hide li a");
return songs.stream().map(e -> new WebPage(BASE_URL + e.attr("href"), PageType.song, e.html())).collect(Collectors.toList());
}
public static void main(String[] args) throws Exception {
WebPage playlists = new WebPage("http://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset=0", PageType.playlists);
CrawlerThread crawlerThread = new CrawlerThread();
crawlerThread.fetchHtml(playlists);
System.out.println(crawlerThread.parsePlaylists(playlists));
}
解析播放列表页面
类似于播放列表页面,你只需要找出歌曲的相关元素:
private List parsePlaylist(WebPage webPage) {
Elements songs = Jsoup.parse(webPage.getHtml()).select("ul.f-hide li a");
return songs.stream().map(e -> new WebPage(BASE_URL + e.attr("href"), PageType.song, e.html())).collect(Collectors.toList());
}
public static void main(String[] args) throws Exception {
WebPage playlist = new WebPage("http://music.163.com/playlist?id=454016843", PageType.playlist);
CrawlerThread crawlerThread = new CrawlerThread();
crawlerThread.fetchHtml(playlist);
System.out.println(crawlerThread.parsePlaylist(playlist));
}
注意,为了方便起见,我们也得到了歌曲名,这样以后就不需要再获取歌曲名了。
解析歌曲页面
终于,我们到达了歌曲页面。在这里,网易云音乐做了防爬处理。获取数据时的参数需要加密。具体算法这里我们就不纠结了。有兴趣的直接看参考代码,我们只看关键代码:
private Song parseSong(WebPage webPage) throws Exception {
return new Song(webPage.getUrl(), webPage.getTitle(), getCommentCount(webPage.getUrl().split("=")[1]));
}
public static void main(String[] args) throws Exception {
WebPage song = new WebPage("http://music.163.com/song?id=29999506", PageType.song, "test");
CrawlerThread crawlerThread = new CrawlerThread();
crawlerThread.fetchHtml(song);
System.out.println(crawlerThread.parseSong(song));
}
嗯,获取过程确实很曲折,经过多次加密,但不管怎样,最终还是得到了我们想要的数据。下一步是使用爬虫来运行整个机制。
实现爬虫
回顾流程图,我们发现最重要的对象之一是爬虫队列。爬虫队列的实现方式有很多种。自己实施。MySQL、redis、MongoDB等都可以满足我们的需求。不同的选择会导致我们取得的不一致。
综合考虑,我们使用Mysql + Spring Data JPA + Spring MVC来运行我们的整套框架,最终可以通过web服务展示抓取到的数据。更深入的Spring MVC学习,请参考Spring MVC实战入门培训。
确认无误后,我们就可以开始一步步实现了。Spring Data JPA 的代码这里就不展示了。了解 Spring Data JPA,请参考 Spring Data JPA 实战入门培训。直接上核心代码,我们把整个爬虫过程相关的代码全部放到了CrawlerService中。
初始网址
第一步是建立一个初始URL,我们可以根据播放列表列表的分页特性得到:
private void init(String catalog) {
List webPages = Lists.newArrayList();
for(int i = 0; i < 43; i++) {
webPages.add(new WebPage("http://music.163.com/discover/playlist/?order=hot&cat=" + catalog + "&limit=35&offset=" + (i * 35), PageType.playlists));
}
webPageRepository.save(webPages);
}
public void init() {
webPageRepository.deleteAll();
init("全部");
init("华语");
init("欧美");
init("日语");
init("韩语");
init("粤语");
init("小语种");
init("流行");
init("摇滚");
init("民谣");
init("电子");
init("舞曲");
init("说唱");
init("轻音乐");
init("爵士");
init("乡村");
init("R&B/Soul");
init("古典");
init("民族");
init("英伦");
init("金属");
init("朋克");
init("蓝调");
init("雷鬼");
init("世界音乐");
init("拉丁");
init("另类/独立");
init("New Age");
init("古风");
init("后摇");
init("Bossa Nova");
init("清晨");
init("夜晚");
init("学习");
init("工作");
init("午休");
init("下午茶");
init("地铁");
init("驾车");
init("运动");
init("旅行");
init("散步");
init("酒吧");
init("怀旧");
init("清新");
init("浪漫");
init("性感");
init("伤感");
init("治愈");
init("放松");
init("孤独");
init("感动");
init("兴奋");
init("快乐");
init("安静");
init("思念");
init("影视原声");
init("ACG");
init("校园");
init("游戏");
init("70后");
init("80后");
init("90后");
init("网络歌曲");
init("KTV");
init("经典");
init("翻唱");
init("吉他");
init("钢琴");
init("器乐");
init("儿童");
init("榜单");
init("00后");
}
在这里,我们已经初始化了播放列表的所有分类列表,通过这些列表,我们可以获得网易云音乐的大部分歌曲。
从爬虫队列中获取一个 URL
这里的逻辑很简单,就是获取一个不是从mysql爬取的网页,但是因为我们需要爬取很多的url,所以必须使用多线程,所以需要考虑异步的情况:
public synchronized WebPage getUnCrawlPage() {
WebPage webPage = webPageRepository.findTopByStatus(Status.uncrawl);
webPage.setStatus(Status.crawled);
return webPageRepository.save(webPage);
}
抓取页面
刚才说了,我们需要抓取很多页面,所以我们采用多线程的方式来运行我们的代码,首先我们将CrawlThread改写成线程的方式,核心代码如下:
public class CrawlerThread implements Runnable {
@Override
public void run() {
while (true) {
WebPage webPage = crawlerService.getUnCrawlPage(); // TODO: 更好的退出机制
if (webPage == null)
return; // 拿不到url,说明没有需要爬的url,直接退出
try {
if (fetchHtml(webPage))
parse(webPage);
} catch (Exception e) {}
}
}
}
在 CrawlerService 中,我们还需要提供一个入口来启动爬虫:
public void crawl() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREADS);
for(int i = 0; i {
WebPage p = webPageRepository.findOne(s.getUrl());
p.setStatus(Status.uncrawl);
webPageRepository.save(p);
});
crawl();
}
整个站点是用 Spring MVC 假设的。学习Spring MVC请参考Spring MVC入门培训和Spring MVC入门实例。
想深入了解的同学,请参考网易云音乐编写Java爬虫。
进一步阅读
Spring MVC 的介绍性示例。
更深入的Spring MVC学习,请参考Spring MVC实战入门培训。