java网络爬虫-总结

来源:互联网 发布:快速排序算法 图解 编辑:程序博客网 时间:2024/05/17 20:13

从今年的三月份,正式接手项目。主要工作涉及到网页抓取,一直想花时间总结一下。但是博主太懒了..........睡觉

要写一篇博客很难的....思路稍有不畅,就没有办法继续写不下去了。网站上高手有的是,总不能拿一篇狗屁不通的文章糊弄大家吧!

我擦咧,把自己心里想的话不小心说出来了!微笑

反正我是鄙视那些花哨的空洞的东西!可以说深恶痛绝。......咳咳.......感觉压力越来越大,装逼的感觉很爽哒.......

看来我多虑了,写的文章不知道有没有人看呢。没人看也罢,权当备忘。(当然希望有人看,有人评论了),每每看到那些大牛写的文章,我都羡慕的口水直流。

围观(●>∀<●): 我擦咧,废话够没?滚粗.......  额,好吧 ....我还没有思路快哭了马上就来。


其实我想对网站的的抓取做一个类型区分<主要是根据我抓取的网站的经验>:

1 普通的网站,主要是要抓取的数据直接可以通过dom的解析获取。这类网站的抓取比较容易,关键点dom的解析。用正则获取想要的数据会很复杂,有更好的方式解决这个问题。这类可以用jsoup(注: jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提  供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操数据),一个简单易用的java解析框架。


2  用js封装数据的网站。这类网站如果直接获取html的话,你要的数据根本得不到。其实一般情况下在浏览器中看到的页面,是渲染过的页面。dom的部分的呈现用js控制的,数据是放js对象中的。针对这种网站,关键是js的解析。由于java本身在1.5版本的时候就已经集成了,一个Rhino的纯java实现的js引擎。可以利用Rhino解释javaScript脚本。又因为js的数据本身的封装的特点,我更喜欢把js对象转化为json处理。(用jackson这个框架和方便)。


3 反爬的网站。有些网站有反扒的策略,这种情况是最麻烦的。需要仔细的分析和琢磨。


要抓取网站上的有用数据,还有一个很重要,抓取的效率,这个问题的解决用多线程和任务队列的东西。

涉及到时候,我会慢慢讲。


基本的页面抓取----主要是jsoup,通常情况下接手到一个新的东西,我会直接在官网上看文档。这其中的原因

只有经历过的人才能明白。胡乱的在网上搜的话,会有各种问题。每个人理解的层次都不一样,这样就有可能曲解原作者的

意图。这不仅仅是因为不同的人的水平参差不齐,更重的要原因是不同的人面对情景不同.我花了很久才明白这个道理。


jsoup结构很简洁。在官网上下载的zip文件中由源码。

1    获取执行的url的document内容:  http://www.open-open.com/jsoup/selector-syntax.htm 这里有详细的介绍。

    Document doc = Jsoup.connect(url).get();

2   然后你就可以通过提供的一堆的选择器获取你想要的数据:

File input = new File("/tmp/input.html");Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");Elements links = doc.select("a[href]"); //带有href属性的a元素Elements pngs = doc.select("img[src$=.png]");  //扩展名为.png的图片Element masthead = doc.select("div.masthead").first();  //class等于masthead的div标签Elements resultLinks = doc.select("h3.r > a"); //在h3元素之后的a元素


 js封装的页面抓取:

1 首先要获取js的内容。用httpClient读取js内容(代码)

String jsContent = null;         /*        //HttpClient httpClient = new HttpClient();            MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();             HttpClient httpClient = new HttpClient(connectionManager);             GetMethod getMethod= new GetMethod(url);              //是设置系统参数            HttpClientParams httpClientParams = new HttpClientParams();            // 设置读取内容时间超时            httpClientParams.setSoTimeout(10000);            // 设置系统提供的默认恢复策略,在发生异常时候将自动重试3次            httpClientParams.setParameter(                HttpMethodParams.RETRY_HANDLER, //重复操作控制                new DefaultHttpMethodRetryHandler()            );         */         HttpClient httpClient = new HttpClient();          GetMethod getMethod = new GetMethod(url);          getMethod.addRequestHeader("Content-Type", "text/html; charset="+encode);        getMethod.addRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 5.1; rv:28.0) Gecko/20100101 Firefox/28.0");        try {              int statusCode = httpClient.executeMethod(getMethod);            if(statusCode == 200){             byte[] responseBody = getMethod.getResponseBody();                 jsContent = new String(responseBody,encode);            }        } catch (IOException e) {                throw e;        }finally{          getMethod.releaseConnection();          }  


2 分析数据对象,加载引擎并执行js,获取数据对象json,jackson封装成java对象。

        Context cx = Context.enter();        try {         Scriptable scope = cx.initStandardObjects();         Object rs1 = cx.evaluateString(scope, response, "arr", 1, null);//执行就脚本         Object arr = scope.get("arr", scope);            if (arr == Scriptable.NOT_FOUND) {            logger.error("js数据解析失败---"+url);            }else{            String  groupData = (String)NativeJSON.stringify(cx, scope, arr, null, null);            JSONArray groups = JSONArray.fromObject(groupData);            }            }        }catch(Exception e){        e.printStackTrace();        }finally{        cx.exit();        }

4 保存数据。


针对反扒网站的抓取:

1 限制单位时间内ip的访问次数,即是访问的频率。解决方案,可以用代理,在某些时候只需要简单的延时操作。

  方式一 :网上有给出用拨号获取ip的方式解决,很多时候这种方式是不可行的。不过还是要贴一下代码:

import java.io.BufferedReader; import java.io.InputStreamReader;  import org.apache.log4j.Logger;  /** *  * ADSL拨号上网 * Windwos操作系统需要是GBK编码 * @author yijianfeng *  */  public class ConnectAdslNet {     static Logger logger = Logger.getLogger(ConnectAdslNet.class);           /**      * 执行CMD命令,并返回String字符串      */       public static String executeCmd(String strCmd) throws Exception {           Process p = Runtime.getRuntime().exec("cmd /c " + strCmd);           StringBuilder sbCmd = new StringBuilder();                    //注意编码 GBK         BufferedReader br = new BufferedReader(new InputStreamReader(p                   .getInputStream(),"GBK"));           String line;           while ((line = br.readLine()) != null) {               sbCmd.append(line + "\n");           }           return sbCmd.toString();       }          /**      * 连接ADSL      */       public static boolean connectAdsl(String adslTitle, String adslName, String adslPass) throws Exception {           System.out.println("正在建立连接.");           String adslCmd = "rasdial " + adslTitle + " " + adslName + " "                   + adslPass;           String tempCmd = executeCmd(adslCmd);                    // 判断是否连接成功           if (tempCmd.indexOf("已连接") > 0) {               System.out.println("已成功建立连接.");               return true;           } else {               System.out.println(tempCmd);               System.out.println("建立连接失败");               return false;           }       }          /**      * 断开ADSL      */       public static boolean disconnectAdsl(String adslTitle) throws Exception {           String disconnectAdsl = "rasdial " + adslTitle + " /disconnect";           String result = executeCmd(disconnectAdsl);                          if (result.indexOf("没有连接")!=-1){               System.out.println(adslTitle + "连接不存在!");               return false;           } else {               System.out.println("连接已断开");               return true;           }       }            /**     * adsl重新拨号,支持失败不断重拨     * @param args     * @throws Exception     */     public static boolean reconnectAdsl(String adslTitle, String adslName, String adslPass){         boolean bAdsl = false;         try {             disconnectAdsl(adslTitle);              Thread.sleep(3000);                      bAdsl = connectAdsl(adslTitle,adslName,adslPass);             Thread.sleep(3000);             int i = 0;             while (!bAdsl){                 disconnectAdsl(adslTitle);                  Thread.sleep(3000);                 bAdsl = connectAdsl(adslTitle,adslName,adslPass);                 Thread.sleep(3000);                 if(i>5){                     break;                 }             }         }catch(Exception e){             logger.error("ADSL拨号异常:", e);         }                  return bAdsl;            }           public static void main(String[] args) throws Exception { //        reconnectAdsl("宽带","adsl账号","密码");       }         } 

方式二:用代理

// 创建 HttpClient 的实例HttpClient httpClient = new HttpClient();// 代理的主机(实际上是一个代理的数组,网上有很多提供代理服务器的)// 推荐:http://www.xici.net.co/ 动态爬去这个网站的代理ip,然后使用,也可以购买// 我并没有试过,不过应该是可行的。主要是提供一种解决问题的思路ProxyHost proxy = new ProxyHost("117.59.224.62", 80);// 使用代理httpClient.getHostConfiguration().setProxyHost(proxy);// 创建Get连接方法的实例HttpMethod getMethod = new GetMethod("http://vip.bet007.com/OverDown_n.aspx?id=279403");// 使用系统提供的默认的恢复策略getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler());try {// 请求URISystem.out.println("executing request " + getMethod.getURI());// 执行getMethodint status = httpClient.executeMethod(getMethod);System.out.println("status:" + status);// 连接返回的状态码if (HttpStatus.SC_OK == status) {System.out.println("Connection to " + getMethod.getURI()+ " Success!");// 获取到的内容//String responseBody = getMethod.getResponseBodyAsString();  byte[] responseBody = getMethod.getResponseBody();          String  html=new String(responseBody,"gbk");System.out.println("==="+html);}} catch (Exception e) {e.printStackTrace();} finally {// 释放连接getMethod.abort();}



多线程多任务并发执行的爬虫:

public class Grab_Team {public static void main(String[] args){   // 构造一个线程池             /*           ThreadPoolExecutor threadPool = new ThreadPoolExecutor(                5,//corePoolSize                 10,//maxPoolSize                 60l,//线程活跃时间 60s                TimeUnit.SECONDS, //活跃时间的度量                new LinkedBlockingDeque<Runnable>(),//无界队列 同步 ,优先执行,核心线程全部占用时增加新线程到最大值                                                //此时如果所有的线程被占用,加入执行队列(队列无界)。                new ThreadPoolExecutor.CallerRunsPolicy()                                                //由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序                                                //此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。                                                //如果该线程池尚未终止,则立即执行该队列的run方法             );*/              ThreadPoolExecutor threadPool =                       new ThreadPoolExecutor(20, 40, 60l, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());             //ExecutorService threadPool = Executors. newSingleThreadExecutor();//单线程调试     for(int i = 0;i < teamIds.size();i++){threadPool.execute(new GrabTeam(teamIds.get(i)));       }}}class GrabTeam implements Runnable, Serializable{private Logger logger = Logger.getLogger(GrabTeam.class); public void run() {             //doSomeThing        }}


0 0
原创粉丝点击