日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

网易云音乐java爬虫_用Java实现网易云音乐爬虫

發(fā)布時(shí)間:2023/12/9 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 网易云音乐java爬虫_用Java实现网易云音乐爬虫 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

起因

前兩天在知乎上看到一個(gè)帖子《網(wǎng)易云音樂(lè)有哪些評(píng)論過(guò)萬(wàn)的歌曲?》,一時(shí)技癢,用Java實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的爬蟲(chóng),這里簡(jiǎn)單記錄一下。

最終的結(jié)果開(kāi)放出來(lái)了,大家可以隨意訪問(wèn),請(qǐng)戳這里>>>>>> 網(wǎng)易云音樂(lè)爬蟲(chóng)結(jié)果。

爬蟲(chóng)簡(jiǎn)介

網(wǎng)絡(luò)爬蟲(chóng)是一種按照一定的規(guī)則,自動(dòng)地抓取萬(wàn)維網(wǎng)信息的程序或者腳本,一個(gè)通用的網(wǎng)絡(luò)爬蟲(chóng)大致包含以下幾個(gè)步驟:

網(wǎng)絡(luò)爬蟲(chóng)的大致流程如上圖所示,無(wú)論你是做什么樣的爬蟲(chóng)應(yīng)用,整體流程都是大同小異。現(xiàn)在,我們就根據(jù)網(wǎng)易云音樂(lè)來(lái)定制一個(gè)專門(mén)爬取音樂(lè)評(píng)論數(shù)量的特定網(wǎng)絡(luò)爬蟲(chóng)。

前期準(zhǔn)備

網(wǎng)頁(yè)類型分析

首先,我們需要對(duì)網(wǎng)易云音樂(lè)整個(gè)網(wǎng)站有個(gè)大致的了解,進(jìn)入網(wǎng)易云音樂(lè)首頁(yè),瀏覽后發(fā)現(xiàn)其大概有這么幾種類型的URL:推薦頁(yè)面

排行榜列表以及排行榜頁(yè)面

歌單列表以及歌單頁(yè)面

主播電臺(tái)列表以及主播電臺(tái)頁(yè)面

歌手列表以及歌手頁(yè)面

專輯列表(新碟上架)以及專輯頁(yè)面

歌曲頁(yè)面

我們最終需要爬取的數(shù)據(jù)在歌曲頁(yè)面中,該頁(yè)面里包含了歌曲的名稱以及歌曲的評(píng)論數(shù)量。

另外,我們還需要盡可能多的獲取歌曲頁(yè)面,這些信息我們可以從前面6種類型的頁(yè)面拿到。其中,歌單列表以及歌單頁(yè)面結(jié)構(gòu)最簡(jiǎn)單,歌單列表直接分頁(yè)就可以拿到。因此,我們選擇歌單頁(yè)面作為我們的初始頁(yè)面,然后歌單列表--歌單--歌曲一路爬下去即可。

設(shè)計(jì)數(shù)據(jù)模型

通過(guò)上述分析,我們可以知道我們要做兩件事情,一是爬取頁(yè)面歌單列表--歌單--歌曲,二是將最終的結(jié)果存儲(chǔ)起來(lái)。因此,我們只需要兩個(gè)對(duì)象,一個(gè)用來(lái)存儲(chǔ)頁(yè)面相關(guān)的信息,url、頁(yè)面類型、是否被爬過(guò)(html和title作為臨時(shí)數(shù)據(jù)存儲(chǔ)),另外一個(gè)用來(lái)存儲(chǔ)歌曲相關(guān)信息,url、歌曲名,評(píng)論數(shù)。因此,model類如下:

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;

...

}

獲取網(wǎng)頁(yè)內(nèi)容并解析

根據(jù)之前的分析,我們需要爬的頁(yè)面有三種:歌單列表、歌單以及歌曲。為了驗(yàn)證想法的可行性,我們先用代碼來(lái)解析這三種類型的網(wǎng)頁(yè),我們將網(wǎng)頁(yè)內(nèi)容獲取以及解析的代碼都放入CrawlerThread當(dāng)中。

獲取html

無(wú)論想要從什么網(wǎng)站中拿到數(shù)據(jù),獲取其html代碼都是最最基礎(chǔ)的一步,這里我們使用jsoup來(lái)獲取頁(yè)面信息,在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());

}

運(yùn)行后即可看到html文本的輸出

解析歌單列表頁(yè)面

得到html后,我們來(lái)解析歌單列表,拿到頁(yè)面中的所有歌單,Jsoup包含了html解析相關(guān)的功能,我們無(wú)需添加其他依賴,直接在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));

}

解析歌單頁(yè)面

和歌單列表頁(yè)面類似,只需要將歌曲相關(guān)的元素找出來(lái)即可:

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));

}

注意,這里為了方便,我們將歌曲的名稱也拿到了,這樣后面我們就不需要再次獲取歌曲名稱了。

解析歌曲頁(yè)面

終于到歌曲頁(yè)面了,這里網(wǎng)易云音樂(lè)做了反爬處理,獲取數(shù)據(jù)時(shí)的參數(shù)需要經(jīng)過(guò)加密處理,這里我們不糾結(jié)于具體算法,如果有興趣的直接看參考代碼,我們只看關(guān)鍵代碼:

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));

}

好吧,獲取過(guò)程確實(shí)比較曲折,經(jīng)過(guò)了多次的加密,不過(guò)不管怎么樣,最終我們還是拿到了我們想要的數(shù)據(jù)。接下來(lái),就是使用爬蟲(chóng)將整套機(jī)制run起來(lái)了。

實(shí)現(xiàn)爬蟲(chóng)

重新回顧一下流程圖,我們發(fā)現(xiàn)其中有很重要的一個(gè)對(duì)象是爬蟲(chóng)隊(duì)列,爬蟲(chóng)隊(duì)列的實(shí)現(xiàn)方法有很多種,自己實(shí)現(xiàn),mysql、redis、MongoDB等等都可以滿足我們的需求,不同的選擇會(huì)導(dǎo)致我們實(shí)現(xiàn)的不一致。

綜合考慮,我們使用Mysql+ Spring Data JPA + Spring MVC來(lái)跑我們的整套框架,最終還可以將爬下來(lái)的數(shù)據(jù)通過(guò)web服務(wù)展現(xiàn)出來(lái)。更深入地學(xué)習(xí)Spring MVC,請(qǐng)大家參考Spring MVC實(shí)戰(zhàn)入門(mén)訓(xùn)練。

確定好之后,我們就可以開(kāi)始一步步實(shí)現(xiàn)了。這里Spring Data JPA的代碼就不展示了。了解Spring Data JPA,請(qǐng)參考Spring Data JPA實(shí)戰(zhàn)入門(mén)訓(xùn)練。直接上核心代碼,所有和爬蟲(chóng)整體流程相關(guān)的代碼我們都放進(jìn)CrawlerService中。

初始網(wǎng)址

第一步建立一個(gè)初始網(wǎng)址,我們可以根據(jù)歌單列表分頁(yè)的特征得到:

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("華語(yǔ)");

init("歐美");

init("日語(yǔ)");

init("韓語(yǔ)");

init("粵語(yǔ)");

init("小語(yǔ)種");

init("流行");

init("搖滾");

init("民謠");

init("電子");

init("舞曲");

init("說(shuō)唱");

init("輕音樂(lè)");

init("爵士");

init("鄉(xiāng)村");

init("R&B/Soul");

init("古典");

init("民族");

init("英倫");

init("金屬");

init("朋克");

init("藍(lán)調(diào)");

init("雷鬼");

init("世界音樂(lè)");

init("拉丁");

init("另類/獨(dú)立");

init("New Age");

init("古風(fēng)");

init("后搖");

init("Bossa Nova");

init("清晨");

init("夜晚");

init("學(xué)習(xí)");

init("工作");

init("午休");

init("下午茶");

init("地鐵");

init("駕車");

init("運(yùn)動(dòng)");

init("旅行");

init("散步");

init("酒吧");

init("懷舊");

init("清新");

init("浪漫");

init("性感");

init("傷感");

init("治愈");

init("放松");

init("孤獨(dú)");

init("感動(dòng)");

init("興奮");

init("快樂(lè)");

init("安靜");

init("思念");

init("影視原聲");

init("ACG");

init("校園");

init("游戲");

init("70后");

init("80后");

init("90后");

init("網(wǎng)絡(luò)歌曲");

init("KTV");

init("經(jīng)典");

init("翻唱");

init("吉他");

init("鋼琴");

init("器樂(lè)");

init("兒童");

init("榜單");

init("00后");

}

這里,我們初始化了歌單所有分類的列表,通過(guò)這些列表,我們就能拿到網(wǎng)易云音樂(lè)大部分的歌曲。

從爬蟲(chóng)隊(duì)列中拿到一個(gè)URL

這里的邏輯非常簡(jiǎn)單,從mysql中獲取一個(gè)狀態(tài)為未爬的網(wǎng)頁(yè)即可,但是由于我們需要爬的網(wǎng)址非常的多,肯定要用到多線程,因此需要考慮異步的情況:

public synchronized WebPage getUnCrawlPage() {

WebPage webPage = webPageRepository.findTopByStatus(Status.uncrawl);

webPage.setStatus(Status.crawled);

return webPageRepository.save(webPage);

}

爬取頁(yè)面

剛剛說(shuō)到,我們需要爬取的頁(yè)面很多,因此我們使用多線程的方式來(lái)運(yùn)行我們的代碼,首先我們來(lái)將CrawlThread改寫(xiě)成線程的方式,核心代碼如下:

public class CrawlerThread implements Runnable {

@Override

public void run() {

while (true) {

WebPage webPage = crawlerService.getUnCrawlPage(); // TODO: 更好的退出機(jī)制 if (webPage == null)

return; // 拿不到url,說(shuō)明沒(méi)有需要爬的url,直接退出 try {

if (fetchHtml(webPage))

parse(webPage);

} catch (Exception e) {}

}

}

}

在CrawlerService中,我們還需要提供一個(gè)啟動(dòng)爬蟲(chóng)的入口:

public void crawl() throws InterruptedException {

ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREADS);

for(int i = 0; i < MAX_THREADS; i++) {

executorService.execute(new CrawlerThread(this));

}

executorService.shutdown();

executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);

Ehcache ehcache = cacheManager.getEhcache(cacheName);

ehcache.removeAll();

}

這樣,爬蟲(chóng)的所有核心代碼就搞定了,先運(yùn)行CrawlerService.init()方法初始化爬蟲(chóng)隊(duì)列,之后運(yùn)行CrawlerService.crawl()就能讓我們的爬蟲(chóng)跑起來(lái)啦。

提供WEB應(yīng)用

之前我們提到,我們還要使用Spring MVC,通過(guò)Spring MVC,我們就能很方便的提供爬蟲(chóng)管理的API啦。更深入地學(xué)習(xí)Spring MVC,請(qǐng)大家參考Spring MVC實(shí)戰(zhàn)入門(mén)訓(xùn)練。

@RestController

public class CrawlerController {

@Autowired

private CrawlerService crawlerService;

@Value("${auth.key}")

private String key;

@ModelAttribute

public void AuthConfig(@RequestParam String auth) throws AccessException {

if(!key.equals(auth)) {

throw new AccessException("auth failed");

}

}

@GetMapping("/init")

public void init() {

crawlerService.init();

}

@GetMapping("/crawl")

public void crawl() throws InterruptedException {

crawlerService.crawl();

}

}

最后,我們將所有爬取到的音樂(lè)通過(guò)頁(yè)面展示出來(lái):

@Controller

public class SongController {

@Autowired SongRepository songRepository;

@GetMapping("/songs")

public String songs(Model model,

@PageableDefault(size = 100, sort = "commentCount", direction = Sort.Direction.DESC) Pageable pageable) {

model.addAttribute("songs", songRepository.findAll(pageable));

return "songs";

}

}

這樣,我們的整個(gè)爬蟲(chóng)就完成了,整個(gè)應(yīng)用是通過(guò)Spring Boot運(yùn)行的,感興趣的話可以參考Spring Boot——開(kāi)發(fā)新一代Spring Java應(yīng)用。

后續(xù)

爬取效率

爬蟲(chóng)爬了兩天后,一共爬到了573945條數(shù)據(jù),此時(shí)數(shù)據(jù)庫(kù)訪問(wèn)速度已經(jīng)變成龜速... 事實(shí)證明,對(duì)于大型爬蟲(chóng)而言,這樣簡(jiǎn)單粗暴的將數(shù)據(jù)庫(kù)作為爬蟲(chóng)隊(duì)列是不科學(xué)的,簡(jiǎn)單想了一下,我們可以用下列方式來(lái)優(yōu)化爬蟲(chóng)的效率:將webpage表分拆成playlist、album、song三張表,按照數(shù)據(jù)順序先爬playlist,再爬album,最后再爬song(甚至將song拆成多張表)

由于網(wǎng)易云音樂(lè)的各種對(duì)象都有id,將id作為索引,提高mysql的效率

獲取url的時(shí)候按照id從小到大獲取,獲取完一條刪除一條

既然mysql達(dá)不到我們的要求,可以考慮直接將mysql替換掉,使用redis作為爬蟲(chóng)隊(duì)列

優(yōu)化的方式有很多種,有些可以借助工具來(lái)實(shí)現(xiàn),有些需要考慮具體的業(yè)務(wù)邏輯。這里我們不具體實(shí)現(xiàn),感興趣的同學(xué)可以自行實(shí)現(xiàn),看看如何優(yōu)化可以達(dá)到最大的效率。

音樂(lè)頁(yè)面訪問(wèn)效率

數(shù)據(jù)量大了之后,影響的不僅僅是爬蟲(chóng)爬的效率,當(dāng)然還有訪問(wèn)音樂(lè)列表的速度,隨意訪問(wèn)一個(gè)頁(yè)面都需要4秒左右。最后,我通過(guò)緩存解決了這個(gè)問(wèn)題,具體實(shí)現(xiàn)我們也不多講了,可以參考文章基于Spring的緩存。加上緩存之后頁(yè)面訪問(wèn)速度達(dá)到了100ms左右。

數(shù)據(jù)更新

除了爬蟲(chóng)的爬取效率外,還有一個(gè)很重要環(huán)節(jié),就是數(shù)據(jù)的更新,評(píng)論數(shù)據(jù)是每天都會(huì)變化的,我們的數(shù)據(jù)當(dāng)然也要每天更新。這里,我們使用最簡(jiǎn)單粗暴的方式,建立一個(gè)定時(shí)任務(wù)(有關(guān)定時(shí)任務(wù)可以參考基于Spring Boot的定時(shí)任務(wù)),在每天的凌晨1點(diǎn),找到評(píng)論數(shù)量大于5000的歌曲,將其狀態(tài)設(shè)置為uncrawl(未爬),啟動(dòng)爬蟲(chóng)即可:

@GetMapping("/update")

@Scheduled(cron = "0 1 0 * * ?")

public void update() throws InterruptedException {

crawlerService.update();

}

@Async

public void update() throws InterruptedException {

List webPages = songRepository.findByCommentCountGreaterThan(5000L);

webPages.forEach(s -> {

WebPage p = webPageRepository.findOne(s.getUrl());

p.setStatus(Status.uncrawl);

webPageRepository.save(p);

});

crawl();

}

整個(gè)站點(diǎn)是用Spring MVC假設(shè)的,學(xué)習(xí)Spring MVC,請(qǐng)大家參考和Spring MVC實(shí)戰(zhàn)入門(mén)訓(xùn)練和Spring MVC的入門(mén)實(shí)例。

希望進(jìn)一步深入了解的同學(xué)請(qǐng)參考一起來(lái)寫(xiě)網(wǎng)易云音樂(lè)Java爬蟲(chóng)

進(jìn)一步閱讀

更深入地學(xué)習(xí)Spring MVC,請(qǐng)大家參考Spring MVC實(shí)戰(zhàn)入門(mén)訓(xùn)練。

歡迎關(guān)注天碼營(yíng)微信公眾號(hào): TMY-EDU

小編重點(diǎn)推薦:

更多精彩內(nèi)容請(qǐng)?jiān)L問(wèn)天碼營(yíng)網(wǎng)站

總結(jié)

以上是生活随笔為你收集整理的网易云音乐java爬虫_用Java实现网易云音乐爬虫的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。