java爬虫抓取动态网页(网易云音乐有哪些评论过万的歌曲?(图) )

优采云 发布时间: 2021-11-12 06:05

  java爬虫抓取动态网页(网易云音乐有哪些评论过万的歌曲?(图)

)

  天马营崖发表

  原因

  前两天在知乎看到一个帖子,“网易云音乐哪些歌曲评论过万?” ”有段时间,我用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实战入门培训。

  

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线