大數據時代雖然給咱們的生活帶來了不少的便利,可是每每咱們想要獲取或整理咱們想要的資源卻仍是一件很難的事情,難在查找和搜尋資料,有了可共享數據的網站,卻還要一頁一頁的點進去,篩選咱們想要的信息,是否是很麻煩?是的,那麼,這個時候你必定要有一個會寫爬蟲的朋友(或者男友^_^),前幾回咱們也已經實現了利用webcollector和htmlparser爬取網易雲音樂和豆瓣圖書,可是有不少網友評論說看不懂或者不明白,並且網上的資源少之又少,我本身其實也在摸索階段,確實關於爬蟲的資料不多,想買本書來學學,都沒有找到適合入門的書籍,要麼都是高深的理論,要麼就是合併在搜索引擎裏的一部分,總之,仍是要靠咱們本身經過查找資料,看看幫助文檔,不斷摸索一點一點積累起來的。html
今天,咱們就一塊兒來學一下利用Jsoup技術實現一個簡單的爬蟲。首先,先了解一下Jsoup的幫助文檔,這裏是下載地址:http://git.oschina.net/AuSiang/myBug/attach_files點擊名稱便可下載。java
1、需求分析node
關於文檔部分咱們就再也不贅述了,你們能夠有時間仔細閱讀,咱們直接開始咱們今天的案例,利用Jsoup爬取一個簡單網站的案例git
首先,咱們選取陝西省信用建設官方網站做爲咱們今天爬取的對象,咱們先來分析一下這個網站,以下圖:web
經過分析,咱們能夠拿到網址也就是url:http://www1.sxcredit.gov.cn/public/infocomquery.do?method=publicIndexQuery還有方法,參數等必須的條件。數據庫
2、開發apache
一、咱們先定義一個規則類,來存放一些須要用到的常量。函數
/** * */ package com.ax.bug; /** * @description:規則類 * 這個規則類定義了咱們查詢過程當中須要的全部信息,方便咱們的擴展 * 以及代碼的重用,由於咱們不可能針對每一個需求都要寫一遍 * @author AoXiang * @date 2017年3月21日 * @version 1.0 */ public class Rule { /* * 連接 */ private String url; /* * 參數集合 */ private String[] params; /* * 參數對應的值 */ private String[] values; /* * 對返回的html,第一次過濾所使用的標籤,先設置type */ private String resultTagName; /* * class/id/selection * 設置resultTagName的類型,默認爲id */ private int type = ID; /* * GET/POST * 請求的類型,默認爲Get */ private int requestMethod = GET; public static final int GET =0; public static final int POST=1; public static final int CLASS=0; public static final int ID =1; public static final int SELECTION=2; /* * 當有參構造函數存在時,無參構造函數必定要表現出來 * 若是有參構造函數沒有,無參構造函數能夠不寫,默認就是無參的 */ public Rule(){ } /** * @param url * @param params * @param values * @param resultTagName * @param type * @param requestMethod */ public Rule(String url, String[] params, String[] values, String resultTagName, int type, int requestMethod) { super(); this.url = url; this.params = params; this.values = values; this.resultTagName = resultTagName; this.type = type; this.requestMethod = requestMethod; } /** * @return the url */ public String getUrl() { return url; } /** * @param url the url to set */ public void setUrl(String url) { this.url = url; } /** * @return the params */ public String[] getParams() { return params; } /** * @param params the params to set */ public void setParams(String[] params) { this.params = params; } /** * @return the values */ public String[] getValues() { return values; } /** * @param values the values to set */ public void setValues(String[] values) { this.values = values; } /** * @return the resultTagName */ public String getResultTagName() { return resultTagName; } /** * @param resultTagName the resultTagName to set */ public void setResultTagName(String resultTagName) { this.resultTagName = resultTagName; } /** * @return the type */ public int getType() { return type; } /** * @param type the type to set */ public void setType(int type) { this.type = type; } /** * @return the requestMethod */ public int getRequestMethod() { return requestMethod; } /** * @param requestMethod the requestMethod to set */ public void setRequestMethod(int requestMethod) { this.requestMethod = requestMethod; } }
二、定義一個類存放須要接收的結果post
/** * */ package com.ax.bug; /** * @description:須要的數據對象,也就是實體類(但咱們不須要數據庫作鏈接,直接返回輸出) * @author AoXiang * @date 2017年3月21日 * @version 1.0 */ public class LinkTypeData { /* * 序號id */ private String id; /* * url連接 */ private String linkHref; /* * 連接標題 */ private String linkText; /* * 摘要 */ private String summary; /* * 內容 */ private String content; /** * @return the id */ public String getId() { return id; } /** * @param id the id to set */ public void setId(String id) { this.id = id; } /** * @return the linkHref */ public String getLinkHref() { return linkHref; } /** * @param linkHref the linkHref to set */ public void setLinkHref(String linkHref) { this.linkHref = linkHref; } /** * @return the linkText */ public String getLinkText() { return linkText; } /** * @param linkText the linkText to set */ public void setLinkText(String linkText) { this.linkText = linkText; } /** * @return the summary */ public String getSummary() { return summary; } /** * @param summary the summary to set */ public void setSummary(String summary) { this.summary = summary; } /** * @return the content */ public String getContent() { return content; } /** * @param content the content to set */ public void setContent(String content) { this.content = content; } }
咱們之因此要分開定義這麼多類,看起來很繁瑣,其實都是爲了咱們的代碼可以更好的重用,以及擴展和維護。單元測試
四、接下來是核心的查詢類
/** * */ package com.ax.bug; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; /** * @description:核心的查詢類 * @author AoXiang * @date 2017年3月21日 * @version 1.0 */ public class ExtractService { public static List<LinkTypeData> extract(Rule rule) throws IOException{ // 校驗url validateRule(rule); // 定義和獲取變量 List<LinkTypeData> datas = new ArrayList<LinkTypeData>(); LinkTypeData data = null; String url = rule.getUrl(); String[] params = rule.getParams(); String[] values = rule.getValues(); String resultTagName = rule.getResultTagName(); int type = rule.getType(); int requestType = rule.getRequestMethod(); // 創建和url的連接(參考jsoup的用法) Connection conn = Jsoup.connect(url); // 設置查詢參數 if(params != null){ for(int i=0;i<params.length;i++){ conn.data(params[i],values[i]); } } // 定義節點 Document doc = null; // 設置請求類型 switch(requestType){ case Rule.GET: doc = conn.timeout(100000).get(); break; case Rule.POST: doc = conn.timeout(100000).post(); break; } /* 根據不一樣的請求類型處理返回的數據,當爲Id時取到的只有一個指定元素, * 因此加入elements的集合中,可是爲class時,取到的已是一個集合 * 不須要再額外添加,若是沒有拿到請求參數,取所有,也就是body * 主體中的內容 */ Elements results = new Elements(); switch(type){ case Rule.CLASS: results = doc.getElementsByClass(resultTagName); break; case Rule.ID: Element result = doc.getElementById(resultTagName); results.add(result); break; case Rule.SELECTION: results = doc.select(resultTagName); break; default: if(resultTagName == null || resultTagName == ""){ results = doc.getElementsByTag("body"); } } /* * 提取結果中的連接地址和連接標題,返回數據 */ for(Element result : results){ Elements links = result.getElementsByTag("a");//能夠拿到連接 for(Element link : links){ String linkText = link.text(); // 對取得的html中的 出現問號亂碼進行處理 linkText = new String(linkText.getBytes(),"GBK").replace('?', ' ').replace(' ', ' '); String id = link.parent().firstElementSibling().text(); id = new String(id.getBytes(),"GBK").replace('?', ' ').replace(' ', ' '); String address = link.parent().nextElementSibling().text(); address = new String(address.getBytes(),"GBK").replace('?', ' ').replace(' ', ' '); String code = link.parent().lastElementSibling().text(); code = new String(code.getBytes(),"GBK").replace('?', ' ').replace(' ', ' '); data = new LinkTypeData(); data.setLinkText(linkText); data.setSummary(address); data.setContent(code); data.setId(id); datas.add(data); } } return datas; } /** * 對傳入的參數(url連接)進行必要的校驗 * @author AoXiang * @date 2017年3月21日 */ public static void validateRule(Rule rule){ String url = rule.getUrl(); if(url==null || url == ""){ throw new RuleException("url不能爲空"); } if(!url.startsWith("http://")){ throw new RuleException("url的格式不正確"); } if(rule.getParams()!=null && rule.getValues()!=null){ if(rule.getParams().length!=rule.getValues().length){ throw new RuleException("參數的鍵值對不匹配"); } } } }
五、核心查詢類中用到的異常類
/** * */ package com.ax.bug; /** * @description:異常類 * @author AoXiang * @date 2017年3月21日 * @version 1.0 */ public class RuleException extends RuntimeException { private static final long serialVersionUID = 1L; public RuleException() { super(); // TODO Auto-generated constructor stub } /** * @param message * @param cause * @param enableSuppression * @param writableStackTrace */ public RuleException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); // TODO Auto-generated constructor stub } /** * @param message * @param cause */ public RuleException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } /** * @param message */ public RuleException(String message) { super(message); // TODO Auto-generated constructor stub } /** * @param cause */ public RuleException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } }
3、測試
核心代碼咱們已經完成了,代碼並很少,接下來咱們進行測試,建一個單元測試類叫Test。
/** * */ package com.ax.bug; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; /** * @description: * @author AoXiang * @date 2017年3月21日 * @version 1.0 */ public class Test { /** * 不帶查詢參數 * @author AoXiang * 2017年3月21日 */ @org.junit.Test public void getDataByClass() throws IOException{ Rule rule = new Rule( "http://www1.sxcredit.gov.cn/public/infocomquery.do?method=publicIndexQuery", null,null, "cont_right", Rule.CLASS, Rule.POST); List<LinkTypeData> extracts = ExtractService.extract(rule); printf(extracts); } /** * 帶查詢參數 * @author AoXiang * 2017年3月21日 */ /*@org.junit.Test public void getDatasByCssQuery() throws IOException { Rule rule = new Rule("http://www.11315.com/search", new String[] { "name" }, new String[] { "興網" }, "div.g-mn div.con-model", Rule.SELECTION, Rule.GET); List<LinkTypeData> extracts = ExtractService.extract(rule); printf(extracts); }*/ public void printf(List<LinkTypeData> datas){ // 第一步,建立一個webbook,對應一個Excel文件 HSSFWorkbook wb = new HSSFWorkbook(); // 第二步,在webbook中添加一個sheet,對應Excel文件中的sheet HSSFSheet sheet = wb.createSheet("信用企業"); // 第三步,在sheet中添加表頭第0行,注意老版本poi對Excel的行數列數有限制short HSSFRow row = sheet.createRow((int) 0); // 第四步,建立單元格,並設置值表頭 設置表頭居中 HSSFCellStyle style = wb.createCellStyle(); style.setAlignment(HSSFCellStyle.ALIGN_CENTER); // 建立一個居中格式 HSSFCell cell = row.createCell((short) 0); cell.setCellValue("序號"); cell.setCellStyle(style); cell = row.createCell((short) 1); cell.setCellValue("企業名稱"); cell.setCellStyle(style); cell = row.createCell((short) 2); cell.setCellValue("地址"); cell.setCellStyle(style); cell = row.createCell((short) 3); cell.setCellValue("工商註冊號"); cell.setCellStyle(style); for (int i=0;i<datas.size();i++) { LinkTypeData ltd = (LinkTypeData) datas.get(i); System.out.println(ltd.getId()+"======="+ltd.getLinkText()+"========="+ltd.getSummary()+"========"+ltd.getContent()); // 第五步,寫入實體數據 row = sheet.createRow((int)i + 1); // 第四步,建立單元格,並設置值 row.createCell((short) 0).setCellValue(ltd.getId()); row.createCell((short) 1).setCellValue(ltd.getLinkText()); row.createCell((short) 2).setCellValue(ltd.getSummary()); row.createCell((short) 3).setCellValue(ltd.getContent()); } // 第六步,將文件存到指定位置 try { FileOutputStream fout = new FileOutputStream("F:/信用企業.xls"); wb.write(fout); fout.close(); } catch (Exception e) { e.printStackTrace(); } } }
看一下運行結果:
結果正是咱們想要的數據,不過可能會有朋友問了,你這只是第一頁的數據,若是想爬取所有的數據怎麼辦呢?^_^彆着急,下次咱們再一塊兒學習翻頁爬取的方法,好啦,今天就到這裏了,咱們下次見!
源碼已上傳至:http://git.oschina.net/AuSiang/myBug/attach_files
若是您對代碼有什麼異議歡迎您的留言,咱們一塊兒交流!