简单的java爬虫实现

去年中旬开始接触爬虫一直都是浅显带过 期间也写过 知乎爬虫和科技网站定向抓取及爬取整个互联网的爬虫

今天和大家分享一下第三个 及其实现方式和代码 早期的实现想法 附代码

关于爬虫其实理论上很简单 就是通过互联网上的超链接导航实现页面的调转与抓取 互联网的网也因此而来 

我也会一步一步的将实现方式和想法展现出来 方便大家能够明白每一步要做什么应该怎么做


爬虫可以分为6个部分:

1.下载器 ——实现爬虫的基础

2.链接解析器——获取文档超链接

3.链接队列——负责管理链接(分为两部分 1已经抓取的,2待抓取(实现去重))

4.页面分析器——负责将有用信息剥离出来

5.存储器——将页面信息进行存储(这里为了方便展示选择了生成html文件,同样也可以持久化信息)

6.任务分发器——负责以上模块的协作


1.下载器我们选择了apache提供的httpClient(还有其他一些也不错,自由选择)

  1. package com.search.sprider;


  2. import java.io.IOException;
  3. import org.apache.http.HttpEntity;
  4. import org.apache.http.HttpStatus;
  5. import org.apache.http.ParseException;
  6. import org.apache.http.client.ClientProtocolException;
  7. import org.apache.http.client.config.RequestConfig;
  8. import org.apache.http.client.methods.CloseableHttpResponse;
  9. import org.apache.http.client.methods.HttpGet;
  10. import org.apache.http.impl.client.CloseableHttpClient;
  11. import org.apache.http.impl.client.HttpClients;
  12. import org.apache.http.util.EntityUtils;


  13. /**
  14.  * @see 爬取网页内容
  15.  * @author zhuGe
  16.  *
  17.  */
  18. public class Sprider {


  19. public static String get(String url) {
  20. CloseableHttpClient httpClient = HttpClients.createDefault();


  21. // 创建httpget
  22. HttpGet httpGet;
  23. try {
  24. httpGet = new HttpGet(url);
  25. } catch (Exception e1) {
  26. return null;
  27. }
  28. // 设置表头
  29. httpHeader(httpGet);
  30. //设置超时
  31. RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();//设置请求和传输超时时间
  32. httpGet.setConfig(requestConfig);
  33. String download = null;


  34. try {
  35. // 执行get请求.
  36. CloseableHttpResponse response = httpClient.execute(httpGet);
  37. // 获取响应实体
  38. HttpEntity entity = response.getEntity();
  39. // System.out.println(httpGet.getURI());
  40. // // 打印响应状态
  41. // System.out.println(response.getStatusLine());
  42. // System.out.println("--------------------------------------");
  43. /**
  44.  * 爬虫
  45.  */


  46. if(entity != null){
  47. if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
  48. download = EntityUtils.toString(entity);
  49. }
  50. }
  51. //  if (entity != null) {
  52. //  // 打印响应内容长度
  53. //  System.out.println("Response content length: " +
  54. //  entity.getContentLength());
  55.  // 打印响应内容
  56. //  System.out.println(download);
  57. } catch (ClientProtocolException e) {
  58. // TODO Auto-generated catch block
  59. e.printStackTrace();
  60. return null;
  61. } catch (ParseException e) {
  62. // TODO Auto-generated catch block
  63. e.printStackTrace();
  64. return null;
  65. } catch (IOException e) {
  66. // TODO Auto-generated catch block
  67. new Exception("ioe");
  68. return null;
  69. }finally {
  70. // 关闭连接,释放资源
  71. try {
  72. httpClient.close();
  73. } catch (IOException e) {
  74. e.printStackTrace();
  75. return null;
  76. }
  77. }
  78. return download;
  79. }
  80. //设置表头
  81. public static void httpHeader(HttpGet httpGet){
  82. httpGet.setHeader("Accept", "Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");


  83. httpGet.setHeader("Accept-Charset", "GB2312,utf-8;q=0.7,*;q=0.7");


  84. httpGet.setHeader("Accept-Encoding", "gzip, deflate");


  85. httpGet.setHeader("Accept-Language", "zh-cn,zh;q=0.5");


  86. httpGet.setHeader("Connection", "keep-alive");


  87. // httpGet.setHeader("Cookie", "__utma=226521935.73826752.1323672782.1325068020.1328770420.6;");


  88. // httpGet.setHeader("Host", "www.cnblogs.com");


  89. httpGet.setHeader("refer",
  90. "http://www.baidu.com/s?tn=monline_5_dg&bs=httpclient4+MultiThreadedHttpConnectionManager");


  91. httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2");


  92. // System.out.println("Accept-Charset: " + httpGet.getFirstHeader("Accept-Charset"));


  93. }


  94. }

2.链接解析器选择了jsoup 配合正则(通过dom树更方便获取,可以选择单纯使用正则或jsoup>_< 早期写代码失误了下版升级优化)

  1. package com.search.split;


  2. import java.util.HashSet;
  3. import java.util.Set;
  4. import java.util.regex.Matcher;
  5. import java.util.regex.Pattern;


  6. import org.jsoup.nodes.Document;
  7. import org.jsoup.nodes.Element;
  8. import org.jsoup.select.Elements;
  9. /**
  10.  * 
  11.  * @author zhuGe
  12.  * @see 链接获取器
  13.  */
  14. public class HrefOfPage {


  15. /**
  16.  * 
  17.  * @see 获取所有符合要求的链接
  18.  * @param doc
  19.  * @return 所有的http://的a链接里面的href属性值
  20.  * 
  21.  */
  22. @SuppressWarnings({ "rawtypes", "unchecked" })
  23. public static Set<String> printHref(Document  doc){
  24. Set aHref = null;
  25. if(aHref==null){
  26. aHref = new HashSet<String>();
  27. }
  28. aHref.clear();
  29. //获取所有的a元素
  30. Elements aS = doc.getElementsByTag("a");
  31. for (Element element : aS) {
  32. //正则匹配
  33. //获取属性href里面满足条件的内容
  34. String href = (element.attr("href"));
  35. String regex ="(http://.+)";
  36. Pattern p = Pattern.compile(regex);
  37. Matcher m = p.matcher(href);
  38. //获取遍历所有满足条件的标签并获取链接
  39. while(m.find()){
  40. String a = m.group(0);
  41. aHref .add(a);
  42. }
  43. }
  44. // System.out.println("页面链接数量:"+aHref.size());
  45. return aHref;
  46. }
  47. }


3.链接队列 待抓取队列 选择了LinkedList的集合(队列(queue)方便管理)


  1. package com.search.url;


  2. import java.util.LinkedList;


  3. public class UrlQueue {
  4.  /**超链接队列*/
  5.     public static LinkedList<String> urlQueue = new LinkedList<String>();  
  6.          
  7.     /**队列中对应最多的超链接数量*/
  8.     public static final int MAX_SIZE = 10000;  
  9.     
  10.     public synchronized static void addElem(String url)  
  11.     {  
  12.         urlQueue.add(url);  
  13.     }  
  14.          
  15.     public synchronized static String outElem()  
  16.     {  
  17.      String outUrl = urlQueue.removeFirst();
  18.      //将查询过的去除掉
  19.      if(urlQueue.contains(outUrl)){
  20.      urlQueue.remove(outUrl);
  21.      System.out.println("faxxx");
  22.      }
  23.         return outUrl;  
  24.     }


  25.     public synchronized static boolean isEmpty()  
  26.     {  
  27.         return urlQueue.isEmpty();  
  28.     }  
  29.     




  30. }

3.链接队列 以抓取队列 选择了set结婚(可以去重)

  1. package com.search.url;


  2. import java.util.HashSet;  


  3. /**  
  4. * 已访问url队列  
  5. * @author zhuGe
  6. *  
  7. */
  8. public class VisitedUrlQueue  
  9. {  
  10.     public static HashSet<String> visitedUrlQueue = new HashSet<String>();  
  11.      
  12.     public synchronized static void addElem(String url)  
  13.     {  
  14.         visitedUrlQueue.add(url);  
  15.     }  
  16.      
  17.     public synchronized static boolean isContains(String url)  
  18.     {  
  19.         return visitedUrlQueue.contains(url);  
  20.     }  
  21.      
  22.     public synchronized static int size()  
  23.     {  
  24.         return visitedUrlQueue.size();  
  25.     }  
  26. }

4.页面分析器同样采用jsoup(2和4分开方便后期维护管理,只获取了网站标题,可以定制)

  1. package com.search.split;


  2. import org.jsoup.nodes.Document;
  3. import org.jsoup.select.Elements;


  4. public class PageTitle {
  5. public static String printTitle(Document doc){
  6. Elements title = doc.getElementsByTag("title");
  7. return title.text();
  8. }


  9. }

5.存储器使用输出流输出数据生成html页面 6.任务分发器配合多线程提升效率(加入和深度筛选 控制深度优先 )

  1. package com.search.tread;


  2. import java.io.BufferedWriter;
  3. import java.io.FileWriter;
  4. import java.io.IOException;
  5. import java.util.Set;
  6. import org.jsoup.Jsoup;
  7. import org.jsoup.nodes.Document;
  8. import com.search.split.HrefOfPage;
  9. import com.search.split.PageTitle;
  10. import com.search.sprider.Sprider;
  11. import com.search.url.UrlQueue;
  12. import com.search.url.VisitedUrlQueue;
  13. import com.search.util.Depth;


  14. /**
  15.  * @author zhuGe
  16.  * @data 2016年1月17日
  17.  */
  18. public class UrlTread implements Runnable{


  19. @Override
  20. public void run() {
  21. while(!UrlQueue.isEmpty()){
  22. String url = UrlQueue.outElem();
  23. System.out.println("移除"+url);
  24. String context = null;
  25. if(!VisitedUrlQueue.isContains(url)){
  26. context = Sprider.get(url);
  27. }


  28. if(context!=null){
  29. //访问过的链接
  30. addHref(context,url);
  31. }
  32. VisitedUrlQueue.addElem(url);
  33. }
  34. }


  35. /**
  36.  * @see 获取链接并输出标题
  37.  * @param context
  38.  * @param url
  39.  */
  40. public  void addHref(String context,String url){
  41. Document doc = Jsoup.parse(context);
  42. //获取所有链接
  43. Set<String> hrefSet = HrefOfPage.printHref(doc);
  44. //获取网站标题
  45. String title = PageTitle.printTitle(doc);
  46. System.out.println(Thread.currentThread().getName());
  47. String html =("<li><a href='"+url+"'>"+title+"</a></li>\n");
  48. //添加文件到输出对象


  49. outFile(html);
  50. System.out.println(html);
  51. //进行深度筛选
  52. if(hrefSet!=null){
  53. hrefSet = Depth.depth(hrefSet, 1);
  54. }
  55. //将链接添加进待访问队列
  56. for (String string : hrefSet) {
  57. if(!VisitedUrlQueue.isContains(string)){//判断是否已被访问
  58. System.out.println("加入队列"+string);
  59. UrlQueue.addElem(string);
  60. }else{
  61. System.out.println("重复"+string);
  62. }
  63. }


  64. }


  65. public void outFile(String html){
  66. try {
  67. @SuppressWarnings("resource")
  68. BufferedWriter out = new BufferedWriter(new FileWriter("d://test.html",true));
  69. out.write(html);
  70. out.flush();
  71. } catch (IOException e) {
  72. // TODO Auto-generated catch block
  73. e.printStackTrace();
  74. }
  75. }


  76. }


其他扩展 


深度控制器

  1. package com.search.util;


  2. import java.util.HashSet;
  3. import java.util.Set;


  4. /**
  5.  * @see 筛选链接的深度
  6.  * @author zhuGe
  7.  *
  8.  */
  9. public class Depth {
  10. /**
  11.  * 
  12.  * @param hrefSet 注入需要控制深度的链接
  13.  * @param depth 筛选满足深度的链接
  14.  */
  15. public static Set<String> depth(Set<String> hrefSet,int depth){
  16. Set<String> deptahHrefSet=null;
  17. if(deptahHrefSet==null){
  18. deptahHrefSet = new HashSet<String>();
  19. }
  20. deptahHrefSet.clear();
  21. String[] str = null;
  22. for (String href : hrefSet) {
  23. str = href.split("/");
  24. //链接深度
  25. int idepth = str==null?0:str.length-2;
  26. //
  27. // System.out.println(href+" [深度:"+idepth+"]");
  28. if(idepth<=depth){
  29. //去除最后的反斜杠
  30. if(href.lastIndexOf("/")==href.length()-1){
  31. deptahHrefSet.add(href.substring(0, href.length()-1));
  32. }else{
  33. deptahHrefSet.add(href);
  34. }
  35. }
  36. }
  37. return deptahHrefSet;
  38. }


  39. }

启动入口(‘加入睡眠防止开启时链接数目过少导致线程没有获取任务“)


  1. package com.search.control;


  2. import com.search.tread.UrlTread;
  3. import com.search.url.UrlQueue;


  4. public class controlCentre {
  5. public static void main(String[] args) {
  6. UrlQueue.addElem("http://www.ifanr.com");
  7. UrlQueue.addElem("http://www.leiphone.com");
  8. UrlQueue.addElem("http://www.huxiu.com");
  9. UrlTread[] t = new UrlTread[8];
  10. for(int i=0;i<t.length;i++){
  11. t[i] = new UrlTread();
  12. try {
  13. Thread.sleep(2000);
  14. } catch (InterruptedException e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. }
  18. new Thread(t[i],"蜘蛛人:"+i+"号").start();
  19. }
  20. //


  21. }

  22. }
代码还有待优化(这只是简单爬虫实现的基础,不过理论上他已经可以爬取整个互联网了) ,源码下载可以邮箱留言

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值