上一篇介紹了用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();
數據庫內可以存放多條網頁信息,最後爬下來的數據都存入到了數據庫內。