上一篇介绍了用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();
数据库内可以存放多条网页信息,最后爬下来的数据都存入到了数据库内。
神龙|纯净稳定代理IP免费测试>>>>>>>>天启|企业级代理IP免费测试>>>>>>>>IPIPGO|全球住宅代理IP免费测试