java网络爬虫-总结

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

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

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

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

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

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


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

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 {
			// 请求URI
			System.out.println("executing request " + getMethod.getURI());

			// 执行getMethod
			int 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
        }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值