使用AiPa爬蟲框架同時爬取多個網頁信息

上一篇介紹了用Java+Jsoup實現簡單的網頁爬蟲功能,這次我們要做的稍微深一點,同時爬取多個新聞網站,並將其中有用的信息(新聞標題,URL,新聞內容等)保存在數據庫中。首先介紹一個很好用的多線程爬蟲框架,名字叫AiPa。

AiPa爬蟲框架

Aipa是一款小巧,靈活,擴展性高的多線程爬蟲框架。
AiPa依賴當下最簡單的HTML解析器Jsoup。
AiPa只需要使用者提供網址集合,即可在多線程下自動爬取,並對一些異常進行處理。

Maven

直接引入

<dependency>
    <groupId>cn.yueshutong</groupId>
    <artifactId>AiPa</artifactId>
    <version>1.0.0.RELEASE</version>
</dependency>

使用

1.必須實現的接口

public class MyAiPaWorker implements AiPaWorker {

    @Override
    public String run(Document doc, AiPaUtil util) {
        //使用JSOUP進行HTML解析獲取想要的div節點和屬性
        //保存在數據庫或本地文件中
        //新增aiPaUtil工具類可以再次請求網址
        return doc.title() + doc.body().text();
    }

    @Override
    public Boolean fail(String link) {
        //任務執行失敗
        //可以記錄失敗網址
        //記錄日誌
        return false;
    }
}

2.Main 方法

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ExecutionException, InterruptedException {
        //準備網址集合
        List<String> linkList = new ArrayList<>();
        linkList.add("http://jb39.com/jibing/FeiQiZhong265988.htm");
        linkList.add("http://jb39.com/jibing/XiaoErGuoDu262953.htm");
        linkList.add("http://jb39.com/jibing/XinShengErShiFei250995.htm");
        linkList.add("http://jb39.com/jibing/GaoYuanFeiShuiZhong260310.htm");
        linkList.add("http://jb39.com/zhengzhuang/LuoYin337449.htm");
        //第一步:新建AiPa實例
        AiPaExecutor aiPaExecutor = AiPa.newInstance(new MyAiPaWorker()).setCharset(Charset.forName("GBK"));
        //第二步:提交任務
        for (int i = 0; i < 10; i++) {
            aiPaExecutor.submit(linkList);
        }
        //第三步:讀取返回值
        List<Future> futureList = aiPaExecutor.getFutureList();
        for (int i = 0; i < futureList.size(); i++) {
            //get() 方法會阻塞當前線程直到獲取返回值
            System.out.println(futureList.get(i).get());
        }
        //第四步:關閉線程池
        aiPaExecutor.shutdown();
    }

3. AiPaWorker接口
AiPaWorker接口是用戶必須要實現的業務類。
該接口的方法如下:

public interface AiPaWorker<T,S> {
    /**
     * 如何解析爬下來的HTML文檔?
     * @param doc JSOUP提供的文檔
     * @param util 爬蟲工具類
     * @return
     */
    T run(Document doc, AiPaUtil util);

    /**
     * run方法異常則執行fail方法
     * @param link 網址
     * @return
     */
    S fail(String link);
}

注意,接口中run方法的參數doc,即Jsoup通過連接網頁URL獲得的docment,可以直接使用。
在run方法中可以通過Jsoup方法爬取所想要的數據,然後存入數據庫中,注意是在run方法內執行
數據庫存取操作。

也就是,run()方法是用戶自定義處理爬取的HTML內容,一般是利用Jsoup的Document類進行解析,
獲取節點或屬性等,然後保存到數據庫或本地文件中。如果在業務方法需要再次請求URL,可以使用
工具類Util。比如訪問新聞具體頁面的時候。
fail()方法是當run方法出現異常或爬取網頁時異常,多次處理無效的情況下進入的方法,該方法的參數
爲此次出錯的網址。一般是對其進行日誌記錄等操作。

以上是AiPa框架的基本介紹,下面開始做一個簡單的爬取多個新聞網站上的新聞的例子。
網頁配置信息都存放在數據庫中,第一步要從數據庫中取要爬取的網站的各種配置信息。

		//取數據庫內配置列表
		List<ArticleCrawler> articleCrawler= this.articleCrawlerDomain.getAllArticle();	
		
		List<String> linkList = new ArrayList<>();
		for (int i = 0; i < articleCrawler.size(); i++) {
			String url = articleCrawler.get(i).getUrl();//獲取網頁Url
			String ulClass = articleCrawler.get(i).getUlClass();//獲取ul標籤樣式
			String articleName = articleCrawler.get(i).getTname();//獲取網頁新聞名稱
			String contentClass = articleCrawler.get(i).getContentClass();//獲取網頁內容樣式
			String authorClass = articleCrawler.get(i).getAuthorClass();//獲取新聞作者樣式
			String webCode = articleCrawler.get(i).getWebCode();//獲取網頁編碼格式
			String tableClass = articleCrawler.get(i).getTableClass();//獲取表格樣式
			String listClass = articleCrawler.get(i).getListClass();//獲取li標籤樣式
			String divClass = articleCrawler.get(i).getDivClass();//獲取div標籤樣式
			String websiteUrl = articleCrawler.get(i).getWebsiteUrl();//獲取網站官網URL
			linkList.add(url);//設置網址集合

取完配置信息之後,新建一個AiPa框架,在run方法裏利用Jsoup的Document類進行解析,獲取節點和屬性,然後
調方法存入本地數據庫內。

//新建AiPa框架類
			AiPaExecutor executor = AiPa.newInstance(new AiPaWorker(){
				
	            @Override
	            public Boolean run(Document document, AiPaUtil util) {
	                List contentList = new ArrayList<>();
	                List articleList = new ArrayList<>();
	                
	                //如果新聞格式爲無序列表且ul上有樣式,格式爲<ul class=""><li></li></ul>
	                if(ulClass!=null){
		                Elements ulLinks = document.getElementsByClass(ulClass);//根據ul的樣式獲取ul
		                for (Element ulLink : ulLinks) {
		                	Elements liLinks = ulLink.getElementsByTag("li");//獲取ul下的li
		        			for (Element liLink : liLinks){
		        				Elements aLinks = liLink.select("a");//獲取li下的a標籤
		        				for (Element aLink : aLinks){
		        					String title = aLink.text();//獲取新聞標題
		        					String titleUrl = aLink.attr("href");//獲取新聞鏈接
		        					List articleExist = articleDomain.queryByProperty("inforsource", titleUrl);
		    	        			try {
		    	        				if(title.length()>4 && titleUrl != "javascript:void(0);" && (articleExist == null || articleExist.size()== 0)){
			    							Article article = (Article) articleDomain.getBaseObject();
			    							article.setTname(title);
			    							article.setInforsource(titleUrl);
			    							article.setValid(false);
			    							article.setColumnName(articleName);
			    							article.setCreateId("admin");
			    							contentList.add(titleUrl);
			    							articleList.add(article);
		    	        				}
		    						} catch (Exception e) {
		    							e.printStackTrace();
		    						}
		        				}
		        			}
						}
		                articleDomain.deleteAndSaveAndUpdate(null, articleList, null);
	                }
	                
	                //如果新聞格式爲無序列表且ul上有樣式,格式爲<div><ul><li></li></ul></div>
	                if (ulClass == null && listClass !=null ) {
	                	Elements listEle = document.getElementsByClass(listClass);
	                	for(Element list : listEle){
	                		Elements ulEle = list.getElementsByTag("ul");
	                		for(Element ulLink :ulEle){
			                	Elements liLinks = ulLink.getElementsByTag("li");//獲取ul下的li
			        			for (Element liLink : liLinks){
			        				Elements aLinks = liLink.select("a");//獲取li下的a標籤
			        				for (Element aLink : aLinks){
			        					String title = aLink.text();//獲取新聞標題
			        					String titleUrl = aLink.attr("href");//獲取新聞鏈接
			        					List articleExist = articleDomain.queryByProperty("inforsource", titleUrl);
			    	        			try {
			    	        				if(title.length()>4 && titleUrl != "javascript:void(0);" && (articleExist == null || articleExist.size()== 0)){
				    							Article article = (Article) articleDomain.getBaseObject();
				    							article.setTname(title);
				    							article.setInforsource(titleUrl);
				    							article.setValid(false);
				    							article.setColumnName(articleName);
				    							article.setCreateId("admin");
				    							contentList.add(titleUrl);
				    							articleList.add(article);
			    	        				}
			    						} catch (Exception e) {
			    							e.printStackTrace();
			    						}
			        				}
			        			}	                			
	                		}
	                	}
	                	articleDomain.deleteAndSaveAndUpdate(null, articleList, null);	
	                	                	
	                }
	                
	                //如果新聞格式沒有列表,爲<div><a></a><div>
	                if(ulClass == null && divClass != null){
	                	Elements divEle = document.getElementsByClass(divClass);
	                	for(Element div : divEle){
	                		Elements aEle = div.select("a");//取div下的a標籤
	                		for(Element aLink : aEle){
	        					String title = aLink.text();//獲取新聞標題
	        					String titleUrl = aLink.attr("href");//獲取新聞鏈接
	        					List articleExist = articleDomain.queryByProperty("inforsource", titleUrl);
	    	        			try {
	    	        				if(title.length()>4 && titleUrl != "javascript:void(0);" && (articleExist == null || articleExist.size()== 0)){
		    							Article article = (Article) articleDomain.getBaseObject();
		    							article.setTname(title);
		    							article.setInforsource(titleUrl);
		    							article.setValid(false);
		    							article.setColumnName(articleName);
		    							article.setCreateId("admin");
		    							contentList.add(titleUrl);
		    							articleList.add(article);
	    	        				}
	    						} catch (Exception e) {
	    							e.printStackTrace();
	    						}
	                		}
	                	}
	                	articleDomain.deleteAndSaveAndUpdate(null, articleList, null);	
	                }
	                            
	                
	                //獲取新聞詳細內容信息
	                for (int j = 0; j < contentList.size(); j++) {
						String articleUrl = (String) contentList.get(j);//獲取新聞詳細內容鏈接
						//驗證新聞URL是否有效
						Boolean testUrl = testUrl(articleUrl, 2000);
						if(testUrl == false){
							String substring = articleUrl.substring(0, 1);
							if(".".equals(substring)){
								articleUrl =  articleUrl.replace("./", url);	
							}else{
								articleUrl = websiteUrl + articleUrl;
							}
						}
						try {
							Document contentDoc = util.getHtmlDocument(articleUrl);//根據鏈接獲取詳細頁面Document
							Elements contentEle = contentDoc.getElementsByClass(contentClass);
							String author = "";
							if(authorClass!=null){
								Elements authorEle = contentDoc.getElementsByClass(authorClass);	
								author = authorEle.text();//獲取作者信息
							}
							
							String contentHtml = contentEle.html();//獲取詳細內容的HTML
							String content = Jsoup.clean(contentHtml, Whitelist.basicWithImages());//Jsoup對網頁HTML進行過濾,篩選標籤內容
							
							List articleExist = articleDomain.queryByProperty("inforsource", (String) contentList.get(j));
							for (int k = 0; k < articleExist.size(); k++) {
								Article article = (Article) articleExist.get(k);
								String id = article.getId();
								String clobInfo = clobInfoDomain.query(id);
	        					if (!contentHtml.equals("") && (clobInfo == null || clobInfo.length() <= 0 )) {
	        						clobInfoDomain.save(id, content);//將新聞具體內容添加到clob裏
	        					}    								
								article.setCreater(author);
								articleDomain.update(article);
							}
							
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
	                
	                return true;
	            }

	            
	            @Override
	            public Boolean fail(String s) {   
	                return false;
	            }
			}).setCharset(Charset.forName(webCode));
			executor.submit(linkList);
	        executor.shutdown();

數據庫內可以存放多條網頁信息,最後爬下來的數據都存入到了數據庫內。

發佈了37 篇原創文章 · 獲贊 47 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章