網絡爬蟲是經過網頁的連接地址來尋找網頁,從網站某一個頁面(一般是首頁)開始,讀取網頁的內容,找到java
在網頁中的其它連接地址,而後經過這些連接地址尋找下一個網頁,這樣一直循環下去,直到把這個網站全部node
的網頁都抓取完爲止。web
若是把整個互聯網當成一個網站,那麼網絡蜘蛛就能夠用這個原理把互聯網上全部的網頁都抓取下來。ajax
因此要想抓取網絡上的數據,不只須要爬蟲程序還須要一個能夠接受」爬蟲「發回的數據並進行處理過濾的服務正則表達式
器,爬蟲抓取的數據量越大,對服務器的性能要求則越高。數據庫
網絡爬蟲是作什麼的? 他的主要工做就是 跟據指定的url地址 去發送請求,得到響應, 而後解析響應 , 一方面從編程
響應中查找出想要查找的數據,另外一方面從響應中解析出新的URL路徑,而後繼續訪問,繼續解析;繼續查找須要的json
數據和繼續解析出新的URL路徑 .
經過上面的流程圖 能大概瞭解到 網絡爬蟲 幹了哪些活 ,根據這些 也就能設計出一個簡單的網絡爬蟲出來.
一個簡單的爬蟲 必需的功能:
下面我用咱們的官網將跟你們一塊兒來分析下如何實現這樣的一個爬蟲:
首先觀察咱們爬蟲的起始頁面是:http://www.wanho.net/a/jyxb/
//div[@class='content']/h4/a/text()
//div[@class='content']/p/text()
//a/img/@src
好了,咱們上面已經將在代碼中須要獲取的關鍵信息的XPath表達式都找到了,接下來就能夠正式寫代碼來實現了
代碼實現部分採用webmagic框架,由於這樣比使用基本的的Java網絡編程要簡單得多
注:關於webmagic框架能夠看一下面講義
Demo.java
/** * 程序入口 */ public class Demo { public static void main(String[] args) { // 爬取開始 Spider // 爬取過程 .create(new WanhoPageProcessor()) // 爬取結果保存 .addPipeline(new WanhoPipeline()) // 爬取的第一個頁面 .addUrl("http://www.wanho.net/a/jyxb/") // 啓用的線程數 .thread(5).run(); } }
package net.wanho.wanhosite; import java.util.ArrayList; import java.util.List; import java.util.Vector; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.processor.PageProcessor; import us.codecraft.webmagic.selector.Html; public class WanhoPageProcessor implements PageProcessor { // 部分一:抓取網站的相關配置,包括編碼、抓取間隔、重試次數等 private Site site = Site .me() .setTimeOut(10000) .setRetryTimes(3) .setSleepTime(1000) .setCharset("UTF-8"); // 獲得站點 @Override public Site getSite() { return site; } //爬取過程 @Override public void process(Page page) { //獲取當前頁的全部喜報 List<String> list = page.getHtml().xpath("//div[@class='main_l']/ul/li").all(); //要保存喜報的集合 Vector<ArticleVo> voLst = new Vector<>(); //遍歷喜報 String title; String content; String img; for (String item : list) { Html tmp = Html.create(item); //標題 title = tmp.xpath("//div[@class='content']/h4/a/text()").toString(); //內容 content = tmp.xpath("//div[@class='content']/p/text()").toString(); //圖片路徑 img = tmp.xpath("//a/img/@src").toString(); //加入集合 ArticleVo vo = new ArticleVo(title, content, img); voLst.add(vo); } //保存數據至page中,後續進行持久化 page.putField("e_list", voLst); //加載其它頁 page.addTargetRequests( getOtherUrls()); } //其它頁 public List<String> getOtherUrls(){ List<String> urlLsts = new ArrayList<>(); for(int i=2;i<5;i++){ urlLsts.add("http://www.wanho.net/a/jyxb/list_15_"+i+".html"); } return urlLsts; } }
package net.wanho.wanhosite; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.Vector; import us.codecraft.webmagic.ResultItems; import us.codecraft.webmagic.Task; import us.codecraft.webmagic.pipeline.Pipeline; public class WanhoPipeline implements Pipeline { @Override public void process(ResultItems resultItems, Task arg1) { // 獲取抓取過程當中保存的數據 Vector<ArticleVo> voLst =resultItems.get("e_list"); // 持久到文件中 PrintWriter pw=null; try { pw = new PrintWriter(new FileWriter("wanho.txt",true)); for(ArticleVo vo :voLst){ pw.println(vo); pw.flush(); saveImg(vo.getImg()); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ pw.close(); } } private void saveImg(String img) { String imgUrl = "http://www.wanho.net"+img; InputStream is = null; BufferedInputStream bis = null; BufferedOutputStream bos = null; try { URL url = new URL(imgUrl); URLConnection uc = url.openConnection(); is = uc.getInputStream(); bis = new BufferedInputStream(is); File photoFile = new File("photo"); if(!photoFile.exists()){ photoFile.mkdirs(); } String imgName = img.substring(img.lastIndexOf("/")+1); File saveFile = new File(photoFile,imgName); bos = new BufferedOutputStream(new FileOutputStream(saveFile)); byte[] bs = new byte[1024]; int len; while((len=bis.read(bs))!=-1){ bos.write(bs, 0, len); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ try { bos.close(); } catch (IOException e) { e.printStackTrace(); } try { bis.close(); } catch (IOException e) { e.printStackTrace(); } try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
package net.wanho.wanhosite; public class ArticleVo { private String title; private String content; private String img; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getImg() { return img; } public void setImg(String img) { this.img = img; } public ArticleVo(String title, String content, String img) { super(); this.title = title; this.content = content; this.img = img; } @Override public String toString() { return "Article [title=" + title + ", content=" + content + ", img=" + img + "]"; } }
在開源社區搜索java爬蟲框架 : 共有83種
咱們使用的是
webmagic的是一個無須配置、便於二次開發的爬蟲框架,它提供簡單靈活的API,只需少許代碼便可實現一
個爬蟲
webmagic採用徹底模塊化的設計,功能覆蓋整個爬蟲的生命週期(連接提取、頁面下載、內容抽取、持久
化),支持多線程抓取,分佈式抓取,並支持自動重試、自定義UA/cookie等功能
webmagic包含強大的頁面抽取功能,開發者能夠便捷的使用css selector、xpath和正則表達式進行連接和內
容的提取,支持多個選擇器鏈式調用
注:官方中文文檔:<http://webmagic.io/docs/zh/>;
可使用maven構建依賴,如:
<dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-core</artifactId> <version>0.6.1</version> </dependency> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-extension</artifactId> <version>0.6.1</version> </dependency>
固然,也能夠自行下載jar包,其地址是:<http://webmagic.io/>;
通常來講,若是咱們須要抓取的目標數據不是經過ajax異步加載進來的話,那麼咱們均可以在頁面的HTML源代碼中的某個位置找到咱們所須要的數據
注:若是數據是經過異步加載到頁面中,那麼通常有如下兩種方式來獲取數據:
下面我用咱們的官網將跟你們一塊兒來分析下如何實現這樣的一個爬蟲:
首先觀察咱們爬蟲的起始頁面是:http://www.wanho.net/a/jyxb/
從上圖能夠看出,咱們是能夠直觀地從首頁的HTML源代碼中找到咱們所須要的標題、內容、圖片連接等信息的。那麼接下來咱們能夠經過哪一種方式將這些目標數據提取出來呢?
其實,關於頁面元素的抽取webmagic框架主要支持如下三種方式:
固然,選擇哪一種方式來抽取數據須要根據具體頁面具體分析。在這個例子中,很顯然使用XPath來抽取數據是最方便的
所以,接下來我就直接給出咱們須要抓取的數據的所在XPath路徑了:
//div[@class='main_l']/ul/li
//div[@class='content']/h4/a/text()
//div[@class='content']/p/text()
//a/img/@src
注:「//」表示從相對路徑開始,最前面以「/」開始則表示從頁面的跟路經開始;後面的兩個/之間的內容表示一個元素,中括號裏面的內容則表示該元素的執行屬性,如:h1[@class=’entry-title’] 表示:擁有class屬性爲「entry-title」的h1元素
使用webmagic抽取頁面數據時須要自定義一個類實現PageProcessor接口。
這個實現了PageProcessor接口的類主要功能爲如下三步曲:
package net.wanho.wanhosite; import java.util.ArrayList; import java.util.List; import java.util.Vector; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.processor.PageProcessor; import us.codecraft.webmagic.selector.Html; public class WanhoPageProcessor implements PageProcessor { // 部分一:抓取網站的相關配置,包括編碼、抓取間隔、重試次數等 private Site site = Site .me() .setTimeOut(10000) .setRetryTimes(3) .setSleepTime(1000) .setCharset("UTF-8"); // 獲得站點 @Override public Site getSite() { return site; } //爬取過程 @Override public void process(Page page) { //獲取當前頁的全部喜報 List<String> list = page.getHtml().xpath("//div[@class='main_l']/ul/li").all(); //要保存喜報的集合 Vector<ArticleVo> voLst = new Vector<>(); //遍歷喜報 String title; String content; String img; for (String item : list) { Html tmp = Html.create(item); //標題 title = tmp.xpath("//div[@class='content']/h4/a/text()").toString(); //內容 content = tmp.xpath("//div[@class='content']/p/text()").toString(); //圖片路徑 img = tmp.xpath("//a/img/@src").toString(); //加入集合 ArticleVo vo = new ArticleVo(title, content, img); voLst.add(vo); } //保存數據至page中,後續進行持久化 page.putField("e_list", voLst); //加載其它頁 page.addTargetRequests( getOtherUrls()); } //其它頁 public List<String> getOtherUrls(){ List<String> urlLsts = new ArrayList<>(); for(int i=2;i<5;i++){ urlLsts.add("http://www.wanho.net/a/jyxb/list_15_"+i+".html"); } return urlLsts; } }
Spider是爬蟲啓動的入口。在啓動爬蟲以前,咱們須要使用一個PageProcessor建立一個Spider對象,而後使用run()進行啓動。同時Spider的其餘組件(Downloader、Scheduler、Pipeline)均可以經過set方法來進行設置
注:更多詳細參數介紹能夠參考這裏的官方文檔:<http://webmagic.io/docs/zh/posts/ch4-basic-page-processor/spider-config.html>;
Demo.java
package net.wanho.wanhosite; import us.codecraft.webmagic.Spider; public class Demo { /** * 程序的入口 */ public static void main(String[] args) { Spider // 爬取過程 .create(new WanhoPageProcessor()) // 爬取結果保存 .addPipeline(new WanhoPipeline()) //第一個頁面 .addUrl("http://www.wanho.net/a/jyxb/") //啓動三個線程 .thread(3) //開始 .run(); } }
對於抽取邏輯比較複雜的爬蟲咱們一般像上面那樣實現PageProcessor接口本身來寫頁面元素的抽取邏輯。可是對於抽取邏輯比較簡單的爬蟲來講,這時咱們能夠選擇在實體類上添加註解的方式來構建輕量型的爬蟲
package net.wanho.wanhosite; import java.util.List; import us.codecraft.webmagic.model.annotation.ExtractBy; import us.codecraft.webmagic.model.annotation.HelpUrl; import us.codecraft.webmagic.model.annotation.TargetUrl; @TargetUrl(value="http://www.wanho.net/a/jyxb/\\w+.html") @HelpUrl(value="http://www.wanho.net/a/jyxb/") public class ArticleExtra{ /** * 標題 */ @ExtractBy(value="//div[@class='content']/h4/a/text()",notNull=true) private List<String> title; /** * 描述 */ @ExtractBy(value="//div[@class='content']/p/text()",notNull=true) private List<String> content; /** * 圖片 */ @ExtractBy("//a/img/@src") private List<String> img; @Override public String toString() { return "ArticleExtra [title=" + title + ", content=" + content + ", img=" + img + "]"; } public List<String> getTitle() { return title; } public void setTitle(List<String> title) { this.title = title; } public List<String> getContent() { return content; } public void setContent(List<String> content) { this.content = content; } public List<String> getImg() { return img; } public void setImg(List<String> img) { this.img = img; } }
從上面的代碼能夠看出,這裏除了添加了幾個註解以外,這個實體類就是一個普通的POJO,沒有依賴其餘任何東西。關於上面使用到的幾個註解的大概含義是:
雖然在PageProcessor中咱們能夠實現數據的持久化(PS:基於註解的爬蟲能夠實現AfterExtractor 接口達到相似的目的),將爬蟲抓取到的數據保存到文件、數據庫、緩存等地方。可是很顯然PageProcessor或者實體類主要負責的是頁面元素的抽取工做,所以更好的處理方式是在另外一個地方單獨作數據的持久化。這個地方也就是——Pipeline
爲了實現數據的持久化,咱們一般須要實現Pipeline 或者PageModelPipeline接口。普通爬蟲使用前一個接口,基於註解的爬蟲則使用後一個接口
package net.wanho.wanhosite; import java.util.List; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.Task; import us.codecraft.webmagic.model.OOSpider; import us.codecraft.webmagic.pipeline.PageModelPipeline; /** * 自定義Pipeline以實現數據的保存 * */ public class WanhoPipeline2 implements PageModelPipeline<ArticleExtra> { public void process(ArticleExtra articleExtra, Task task) { List<String> titleLst = articleExtra.getTitle(); List<String> contentLst = articleExtra.getContent(); for(int i=0;i<titleLst.size();i++){ System.out.println("title:"+titleLst.get(i)); System.out.println("content:"+contentLst.get(i)); } } public static void main(String[] args) { Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8"); OOSpider.create(site,new WanhoPipeline2 (), ArticleExtra.class) .addUrl("http://www.wanho.net/a/jyxb") .thread(5) .run(); } }
基於註解的爬蟲,其啓動類就不是Spider了,而是OOSpider類了,固然兩者的使用方式相似。示例代碼以下:
public static void main(String[] args) { Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8"); OOSpider.create(site,new WanhoPipeline2 (), ArticleExtra.class) .addUrl("http://www.wanho.net/a/jyxb") .thread(5) .run(); }
jsoup 是一款 Java 的HTML 解析器,可直接解析某個URL地址、HTML文本內容。它提供了一套很是省力的API,可經過DOM,CSS以及相似於JQuery的操做方法來取出和操做數據
Jsoup是Java世界的一款HTML解析工具,它支持用CSS Selector方式選擇DOM元素,也可過濾HTML文本,防止XSS×××。
下載Jsoup<http://jsoup.org/download>;
查看官方提供的手冊:<http://jsoup.org/cookbook/>;
Jsoup是Java世界用做html解析和過濾的不二之選。支持將html解析爲DOM樹、支持CSS Selector形式選擇、支持html過濾,自己還附帶了一個Http下載器。
Jsoup的代碼至關簡潔,Jsoup總共53個類,且沒有任何第三方包的依賴,對比最終發行包9.8M的SAXON,實在算得上是短小精悍了。
jsoup ├── examples #樣例,包括一個將html轉爲純文本和一個抽取全部連接地址的例子。 ├── helper #一些工具類,包括讀取數據、處理鏈接以及字符串轉換的工具 ├── nodes #DOM節點定義 ├── parser #解析html並轉換爲DOM樹 ├── safety #安全相關,包括白名單及html過濾 └── select #選擇器,支持CSS Selector以及NodeVisitor格式的遍歷
Jsoup的入口是Jsoup
類。examples包裏提供了兩個例子,解析html後,分別用CSS Selector以及NodeVisitor來操做Dom元素。
這裏用ListLinks
裏的例子來講明如何調用Jsoup:
package net.wanho.blog; import java.io.IOException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; /** * * 使用JSoup 解析網頁,語法使用 JS,css,Jquery 選擇器語法,方便易懂 * Jsoup教程網:http://www.open-open.com/jsoup/ * */ public class JsoupDemo { public static void main(String[] args) throws IOException { String url = "https://www.oschina.net/news"; Document document = Jsoup.connect(url) .userAgent("Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0").get(); Elements elements = document.select("#all-news div "); Elements es = document.select("#all-news div div "); for (Element e : es) { System.out.println(e.select("a.title>span").text()); System.out.println(e.select("div").text()); System.out.println("-----------"); } System.out.println(elements.size()); } }
Jsoup使用了本身的一套DOM代碼體系,這裏的Elements、Element等雖然名字和概念都與Java XML
APIorg.w3c.dom
相似,但並無代碼層面的關係。就是說你想用XML的一套API來操做Jsoup的結果是辦不到的,
可是正由於如此,才使得Jsoup能夠拋棄xml裏一些繁瑣的API,使得代碼更加簡單。