最近在知乎上看到一个话题,说使用爬虫技术获取网易云音乐上的歌曲,甚至还包括付费的歌曲,哥瞬间心动了,这年头,好听的流行音乐或者经典老歌都开始收费了,只能听不能下载,着实很郁闷,如今机会来了,因而开始研究爬虫技术,翻阅各类资料,最终选择网友们一致认为比较好用的webcollector框架来实现。css
首先,咱们来认识一下webcollector,webcollector是一个无需配置,便于二次开发的爬虫框架,它提供精简的API,只需少许代码便可实现一个功能强大的爬虫,webcollector+hadoop是webcollector的hadoop版本,支持分布式爬取。而且在2.x版本中提供了selenium,能够处理javaScript生成的数据。咱们边说便看图,看图说话,便于理解:html
以上就是webcollector的架构图,咱们来简单分析一下:java
爬取逻辑:git
第一层:爬取一个网页,http://www.apache.org/,解析网页,获取3个连接,将3个连接保存到CrawlDB中,设置状态为未爬取。同时将http://www.apache.org/的爬取状态设置为已爬取。结束第一轮。github
第二层,找到CrawlDB中状态为未爬取的页面(第一层解析出来的3个连接),分别爬取,并解析网页,一共得到8个连接。和第一层操做同样,将解析出的连接放入CrawlDB,设置为未爬取,并将第二层爬取的三个页面,状态设置为已爬取。web
第三层,找到CrawlDB中状态为未爬取的页面(第二层解析出来的8个连接)…………….. 正则表达式
每一层均可以做为一个独立的任务去运行,因此能够将一个大型的广度遍历任务,拆分红一个一个小任务。爬虫里有个参数,设置爬取的层数,指的就是这个。redis
插件机制:数据库
框架图中的 Injector、Generator、Request(由RequestFactory生成)、Parser(由ParserFactory生成)、DbUpdater、Response都是以插件实现的。制做插件每每只须要自定义一个实现相关接口的类,并在相关Factory内指定便可。apache
WebCollector内置了一套插件(cn.edu.hfut.dmic.webcollector.plugin.redis)。基于这套插件,能够把WebCollector的任务管理放到redis数据库上,这使得WebCollector能够爬取海量的数据(上亿级别)。
对于用户来讲,关注的更多的不是爬虫的爬取流程,而是对每一个网页要进行什么样的操做。对网页进行抽取、保存仍是其余操做,应该是由用户自定义的。
因此咱们使用WebCollector来写爬虫不用那么麻烦,只用集成爬虫框架里的BreadthCrawler类并重写visit方法便可,咱们先来看下官网爬取知乎的例子:
/*visit函数定制访问每一个页面时所需进行的操做*/ @Override public void visit(Page page) { String question_regex="^http://www.zhihu.com/question/[0-9]+"; if(Pattern.matches(question_regex, page.getUrl())){ System.out.println("正在抽取"+page.getUrl()); /*抽取标题*/ String title=page.getDoc().title(); System.out.println(title); /*抽取提问内容*/ String question=page.getDoc().select("div[id=zh-question-detail]").text(); System.out.println(question); } } /*启动爬虫*/ public static void main(String[] args) throws IOException{ ZhihuCrawler crawler=new ZhihuCrawler(); crawler.addSeed("http://www.zhihu.com/question/21003086"); crawler.addRegex("http://www.zhihu.com/.*"); crawler.start(5); } }
咱们来简单分析一下:
visit()方法
在整个抓取过程当中,只要抓到一个复合的页面,wc都会回调该方法,并传入一个包含了全部页面信息的page对象。
addSeed()
添加种子,种子连接会在爬虫启动以前加入到上面所说的抓取信息中并标记为未抓取状态.这个过程称为注入。
addRegex
为一个url正则表达式, 过滤没必要抓取的连接好比.js .jpg .css等,或者指定抓取连接的规则。好比我使用时有个正则为:http://news.hexun.com/2015-01-16/[0-9]+.html, 那么个人爬虫则只会抓取http://news.hexun.com/2015-01-16/172431075.html,http://news.hexun.com/2015-01-16/172429627.html 等news.hexun.com域名下2015-01-16日期的.html结尾的连接。
start()
表示启动爬虫,传入参数5表示抓取5层(深度为5),这个深度为5怎么理解呢,当只添加了一个种子, 抓这个种子连接为第1层, 解析种子连接页面跟据正则过滤想要的连接保存至待抓取记录. 那么第2层就是抓取1层保存的记录并解析保存新记录,依次类推。
至此,咱们已经对webcollector有了一个大体的了解,更深刻的理论知识咱们就再也不往下追究,毕竟高端的东西是须要更恒久的毅力和耐心去不断挖掘的,而目前咱们只须要掌握简单的应用便可实现一个爬虫。
(一)需求分析:
OK,那咱们先来分析一下咱们这次的需求,咱们要使用webcollector爬虫技术获取网易云音乐所有歌曲,咱们先来看下一个网易云音乐的歌曲页面连接:http://music.163.com/#/album?id=2884361,咱们会发现这个连接后面带有参数,传不一样的id,能够获得不一样的歌曲,因此,这就是一个模版,咱们能够遍历整个网易云音乐,把其中url与上面相似的网页提取出来就能够获得网易云音乐的全部歌曲了,对吧?
那么,第二个问题,咱们如何获取音乐的真实地址呢?这个一般是要用到抓包工具的,经过抓包工具获取HTTP请求下的头信息,从而获得请求的真实路径,咱们经过抓包分析获得网易云音乐有一个api接口,能够获得歌曲的真实地址,api地址:http://music.163.com/api/song/detail,咱们发现这个接口有几个参数:
id 传入上面获得的歌曲的id
ids ids是由id拼接而成的,ids = '%5B+' + id + '%5D'(这里的%5B...%5d是js传参的时候防止乱码加的,这个在以前的项目里有遇到过)
而后咱们能够把上面的API复制进浏览器,咱们会获得一段json,里面有歌曲的音频源地址。
好了,通过分析,咱们已经准备好了咱们须要的东西,接下来就能够开始动手操做了。
(二)开发
开发就相对很简单,没有太多的类,只是普通的Java工程,引入相应的jar包便可。
1.进入WebCollector官方网站下载最新版本所需jar包。最新版本的jar包放在webcollector-version-bin.zip中。
2.打开Eclipse,选择File->New->Java Project,按照正常步骤新建一个Java项目。
在工程根目录下新建一个文件夹lib,将刚下载的webcollector-version-bin.zip解压后获得的全部jar包放到lib文件夹下。将jar包引入到build path中。
三、新建一个类继承BreadthCrawler,重写visit方法进行url的正则匹配,抽取出url,歌曲Id,歌曲名称,演唱者,url。以及真实路径。过程很简单,咱们直接看代码:
package com.ax.myBug; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.Charset; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.csvreader.CsvWriter; import cn.edu.hfut.dmic.webcollector.model.CrawlDatums; import cn.edu.hfut.dmic.webcollector.model.Page; import cn.edu.hfut.dmic.webcollector.plugin.berkeley.BreadthCrawler; import org.json.*; /** * 获取网易云音乐全部歌曲写入csv文件 * @author AoXiang */ public class GetAllSongs extends BreadthCrawler { private CsvWriter r = null; public void closeCsv() { this.r.close(); } /** * 转换字节流 * @param instream * @return * @throws IOException */ public static byte[] readInputStream(InputStream instream) throws IOException { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1204]; int len = 0; while ((len = instream.read(buffer)) != -1){ outStream.write(buffer,0,len); } instream.close(); return outStream.toByteArray(); } /** * 根据URL得到网页源码 * @param url 传入的URL * @return String * @throws IOException */ public static String getURLSource(URL url) throws IOException { HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(500000);//时间能够设置的久一点,若是控制台常常提示read time out InputStream inStream = conn.getInputStream(); byte[] data = readInputStream(inStream); String htmlSource = new String(data); return htmlSource; } /** * 重写构造函数 * @param crawlPath 爬虫路径 * @param autoParse 是否自动解析 */ public GetAllSongs(String crawlPath, boolean autoParse) throws FileNotFoundException { super(crawlPath, autoParse); // 逗号进行分割,字符编码为GBK this.r = new CsvWriter("songId.csv", ',', Charset.forName("GBK")); } @Override public void visit(Page page, CrawlDatums next) { // 继承覆盖visit方法,该方法表示在每一个页面进行的操做 // 参数page和next分别表示当前页面和下个URL对象的地址 // 生成文件songId.csv,第一列为歌曲id,第二列为歌曲名字,第三列为演唱者,第四列为歌曲信息的URL // 网易云音乐song页面URL地址正则 String song_regex = "^http://music.163.com/song\\?id=[0-9]+"; // 建立Pattern对象 http://music.163.com/#/song?id=110411 Pattern songIdPattern = Pattern.compile("^http://music.163.com/song\\?id=([0-9]+)"); Pattern songInfoPattern = Pattern.compile("(.*?)-(.*?)-"); // 对页面进行正则判断,若是有的话,将歌曲的id和网页标题提取出来,不然不进行任何操做 if (Pattern.matches(song_regex, page.getUrl())) { // 将网页的URL和网页标题提取出来,网页标题格式:歌曲名字-歌手-网易云音乐 String url = page.getUrl(); @SuppressWarnings("deprecation") String title = page.getDoc().title(); String songName = null; String songSinger = null; String songId = null; String infoUrl = null; String mp3Url = null; // 对标题进行歌曲名字、歌手解析 Matcher infoMatcher = songInfoPattern.matcher(title); if (infoMatcher.find()) { songName = infoMatcher.group(1); songSinger = infoMatcher.group(2); } System.out.println("正在抽取:" + url); // 建立Matcher对象,使用正则找出歌曲对应id Matcher idMatcher = songIdPattern.matcher(url); if (idMatcher.find()) { songId = idMatcher.group(1); } System.out.println("歌曲:" + songName); System.out.println("演唱者:" + songSinger); System.out.println("ID:" + songId); infoUrl = "http://music.163.com/api/song/detail/?id=" + songId + "&ids=%5B+" + songId + "%5D"; try { URL urlObject = new URL(infoUrl); // 获取json源码 String urlsource = getURLSource(urlObject); JSONObject j = new JSONObject(urlsource); JSONArray a = (JSONArray) j.get("songs"); JSONObject aa = (JSONObject) a.get(0); mp3Url = aa.get("mp3Url").toString(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } String[] contents = {songId, songName, songSinger, url, mp3Url}; try { this.r.writeRecord(contents); this.r.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 歌曲id爬虫开始 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { URL url = new URL("http://music.163.com/api/song/detail/?id=110411&ids=%5B110411%5D"); String urlsource = getURLSource(url); System.out.println(urlsource); JSONObject j = new JSONObject(urlsource); JSONArray a = (JSONArray) j.get("songs"); JSONObject aa = (JSONObject) a.get(0); System.out.println(aa.get("mp3Url")); GetAllSongs crawler = new GetAllSongs("crawler", true); // 添加初始种子页面http://music.163.com crawler.addSeed("http://music.163.com/#/album?id=604667405"); // 设置采集规则为全部类型的网页 crawler.addRegex("http://music.163.com/.*"); // 设置爬取URL数量的上限 crawler.setTopN(500000000); // 设置线程数 crawler.setThreads(30); // 设置断点采集 crawler.setResumable(false); // 设置爬虫深度 crawler.start(5); } }
(三)测试
直接运行Java程序,查看控制台
而后去到咱们的workSpace,咱们会发现提取出的歌曲信息已经写入了csv文件,
咱们打开文件,能够看到里面已经拿到了咱们想要的数据
OK,通过一番折腾,咱们已经大功告成了,是否是很简单呢?固然,学习的过程也是很曲折的,有了这个技术,咱们不只能够爬取网易云音乐,还能够爬取各种新闻网站,拿到他们的数据来为咱们本身所用,固然,如今的不少网站在安全方面都作的至关不错,或者比较抠门,不肯意资源共享,采用了反爬机制,因此,咱们要作的就是更深刻的更全面的了解爬虫技术,掌握其中的要领和精髓,灵活运用,那么,我相信再密不透风的网站咱们也能爬的进去。由于咱们的目标是星辰大海!
附上项目源码以及已经爬取的17万多的网易云音乐歌曲Excel:https://git.oschina.net/AuSiang/myBug/attach_files