微博爬虫“免登录”技巧详解及Java实现

一、微博一定要登录才能抓取?

目前,对于微博的爬虫,大部分是基于模拟微博账号登录的方式实现的,这种方式如果真的运营起来,实际上是一件非常头疼痛苦的事,你可能每天都过得提心吊胆,生怕新浪爸爸把你的那些账号给封了,而且现在随着实名制的落地,获得账号的渠道估计也会变得越来越少。

但是日子还得继续,在如此艰难的条件下,为了生存爬虫们必须寻求进化。好在上帝关门的同时会随手开窗,微博在其他诸如头条,一点等这类新媒体平台的冲击之下,逐步放开了信息流的查看权限。现在的微博即便在不登录的状态下,依然可以看到很多微博信息流,而我们的落脚点就在这里。

本文详细介绍如何获取相关的Cookie并重新封装Httpclient达到免登录的目的,以支持微博上的各项数据抓取任务。下面就从微博首页http://weibo.com开始。

二、准备工作

准备工作很简单,一个现代浏览器(你知道我为什么会写”现代”两个字),以及httpclient(我用的版本是4.5.3)

跟登录爬虫一样,免登录爬虫也是需要装载Cookie。这里的Cookie是用来标明游客身份,利用这个Cookie就可以在微博平台中访问那些允许访问的内容了。

这里我们可以使用浏览器的network工具来看一下,请求http://weibo.com之后服务器都返回哪些东西,当然事先清空一下浏览器的缓存。

不出意外,应该可以看到下图中的内容

第1次请求weibo.com的时候,其状态为302重定向,也就是说这时并没有真正地开始加载页面,而最后一个请求weibo.com的状态为200,表示了请求成功,对比两次请求的header:

明显地,中间的这些过程给客户端加载了各种Cookie,从而使得可以顺利访问页面,接下来我们逐个进行分析。

三、抽丝剥茧

第2个请求是https://passport.weibo.com/vi...……,各位可以把这个url复制出来,用httpclient单独访问一下这个url,可以看到返回的是一个html页面,里面有一大段Javascript脚本,另外头部还引用一个JS文件mini_original.js,也就是第3个请求。脚本的功能比较多,就不一一叙述了,简单来说就是微博访问的入口控制,而值得我们注意的是其中的一个function:


  1. // 为用户赋予访客身份 。 
  2.     var incarnate = function (tid, where, conficence) { 
  3.         var gen_conf = ""; 
  4.         var from = "weibo"; 
  5.         var incarnate_intr = window.location.protocol + "//" + window.location.host + "/visitor/visitor?a=incarnate&t=" + encodeURIComponent(tid) + "&w=" + encodeURIComponent(where) + "&c=" + encodeURIComponent(conficence) + "&gc=" + encodeURIComponent(gen_conf) + "&cb=cross_domain&from=" + from + "&_rand=" + Math.random(); 
  6.         url.l(incarnate_intr); 
  7.     };  

这里是为请求者赋予一个访客身份,而控制跳转的链接也是由一些参数拼接起来的,也就是上图中第6个请求。所以下面的工作就是获得这3个参数:tid,w(where),c(conficence,从下文来看应为confidence,大概是新浪工程师的手误)。继续阅读源码,可以看到该function是tid.get方法的回调函数,而这个tid则是定义在那个mini_original.js中的一个对象,其部分源码为:


  1.  var tid = { 
  2.         key: 'tid', 
  3.         value: '', 
  4.         recover: 0, 
  5.         confidence: '', 
  6.         postInterface: postUrl, 
  7.         fpCollectInterface: sendUrl, 
  8.         callbackStack: [], 
  9.         init: function () { 
  10.             tid.get(); 
  11.         }, 
  12.         runstack: function () { 
  13.             var f; 
  14.             while (f = tid.callbackStack.pop()) { 
  15.                 f(tid.value, tid.recover, tid.confidence);//注意这里,对应上述的3个参数 
  16.             } 
  17.         }, 
  18.         get: function (callback) { 
  19.             callback = callback || function () { 
  20.             }; 
  21.             tid.callbackStack.push(callback); 
  22.             if (tid.value) { 
  23.                 return tid.runstack(); 
  24.             } 
  25.             Store.DB.get(tid.key, function (v) { 
  26.                 if (!v) { 
  27.                     tid.getTidFromServer(); 
  28.                 } else { 
  29.                     …… 
  30.                 } 
  31.             }); 
  32.         }, 
  33.     …… 
  34.     } 
  35. …… 
  36.  getTidFromServer: function () { 
  37.             tid.getTidFromServer = function () { 
  38.             }; 
  39.             if (window.use_fp) { 
  40.                 getFp(function (data) { 
  41.                     util.postData(window.location.protocol + '//' + window.location.host + '/' + tid.postInterface, "cb=gen_callback&fp=" + encodeURIComponent(data), function (res) { 
  42.                         if (res) { 
  43.                             eval(res); 
  44.                         } 
  45.                     }); 
  46.                 }); 
  47.             } else { 
  48.                 util.postData(window.location.protocol + '//' + window.location.host + '/' + tid.postInterface, "cb=gen_callback", function (res) { 
  49.                     if (res) { 
  50.                         eval(res); 
  51.                     } 
  52.                 }); 
  53.             } 
  54.         }, 
  55. …… 
  56. //获得参数 
  57. window.gen_callback = function (fp) { 
  58.         var value = false, confidence; 
  59.         if (fp) { 
  60.             if (fp.retcode == 20000000) { 
  61.                 confidence = typeof(fp.data.confidence) != 'undefined' ? '000' + fp.data.confidence : '100'; 
  62.                 tid.recover = fp.data.new_tid ? 3 : 2; 
  63.                 tid.confidence = confidence = confidence.substring(confidence.length - 3); 
  64.                 value = fp.data.tid; 
  65.                 Store.DB.set(tid.key, value + '__' + confidence); 
  66.             } 
  67.         } 
  68.         tid.value = value; 
  69.         tid.runstack(); 
  70.     };  

显然,tid.runstack()是真正执行回调函数的地方,这里就能看到传入的3个参数。在get方法中,当cookie为空时,tid会调用getTidFromServer,这时就产生了第5个请求https://passport.weibo.com/vi...,它需要两个参数cb和fp,其参数值可以作为常量:

该请求的结果返回一串json


  1.   "msg": "succ", 
  2.   "data": { 
  3.     "new_tid": false, 
  4.     "confidence": 95, 
  5.     "tid": "kIRvLolhrCR5iSCc80tWqDYmwBvlRVlnY2+yvCQ1VVA=" 
  6.   }, 
  7.   "retcode": 20000000 
  8. }  

其中就包含了tid和confidence,这个confidence,我猜大概是推测客户端是否真实的一个置信度,不一定出现,根据window.gen_callback方法,不出现时默认为100,另外当new_tid为真时参数where等于3,否则等于2。

此时3个参数已经全部获得,现在就可以用httpclient发起上面第6个请求,返回得到另一串json:


  1.   "msg": "succ", 
  2.   "data": { 
  3.     "sub": "_2AkMu428tf8NxqwJRmPAcxWzmZYh_zQjEieKYv572JRMxHRl-yT83qnMGtRCnhyR4ezQQZQrBRO3gVMwM5ZB2hQ..", 
  4.     "subp": "0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWU2MgYnITksS2awP.AX-DQ" 
  5.   }, 
  6.   "retcode": 20000000 
  7. }  

参考最后请求weibo.com的header,这里的sub和subp就是最终要获取的cookie值。大家或许有一个小疑问,第一个Cookie怎么来的,没用吗?是的,这个Cookie是第一次访问weibo.com产生的,经过测试可以不用装载。

最后我们用上面两个Cookie装载到HttpClient中请求一次weibo.com,就可以获得完整的html页面了,下面就是见证奇迹的时刻:


  1. <!doctype html> 
  2. <html> 
  3. <head> 
  4. <meta charset="utf-8"> 
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 
  6. <meta name="viewport" content="initial-scale=1,minimum-scale=1" /> 
  7. <meta content="随时随地发现新鲜事!微博带你欣赏世界上每一个精彩瞬间,了解每一个幕后故事。分享你想表达的,让全世界都能听到你的心声!" name="description" /> 
  8. <link rel="mask-icon" sizes="any" href="//img.t.sinajs.cn/t6/style/images/apple/wbfont.svg" color="black" /> 
  9. <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /> 
  10. <script type="text/javascript"> 
  11. try{document.execCommand("BackgroundImageCache", false, true);}catch(e){} 
  12. </script> 
  13. <title>微博-随时随地发现新鲜事</title> 
  14. <link href="//img.t.sinajs.cn/t6/style/css/module/base/frame.css?version=6c9bf6ab3b33391f" type="text/css" rel="stylesheet" charset="utf-8" /> 
  15. <link href="//img.t.sinajs.cn/t6/style/css/pages/growth/login_v5.css?version=6c9bf6ab3b33391f" type="text/css" rel="stylesheet" charset="utf-8"> 
  16. <link href="//img.t.sinajs.cn/t6/skin/default/skin.css?version=6c9bf6ab3b33391f" type="text/css" rel="stylesheet" id="skin_style" /> 
  17. <script type="text/javascript"> 
  18. var $CONFIG = {}; 
  19. $CONFIG['islogin'] = '0'; 
  20. $CONFIG['version'] = '6c9bf6ab3b33391f'; 
  21. $CONFIG['timeDiff'] = (new Date() - 1505746970000); 
  22. $CONFIG['lang'] = 'zh-cn'; 
  23. $CONFIG['jsPath'] = '//js.t.sinajs.cn/t5/'; 
  24. $CONFIG['cssPath'] = '//img.t.sinajs.cn/t5/'; 
  25. $CONFIG['imgPath'] = '//img.t.sinajs.cn/t5/'; 
  26. $CONFIG['servertime'] = 1505746970; 
  27. $CONFIG['location']='login'; 
  28. $CONFIG['bigpipe']='false'; 
  29. $CONFIG['bpType']='login'; 
  30. $CONFIG['mJsPath'] = ['//js{n}.t.sinajs.cn/t5/', 1, 2]; 
  31. $CONFIG['mCssPath'] = ['//img{n}.t.sinajs.cn/t5/', 1, 2]; 
  32. $CONFIG['redirect'] = ''; 
  33. $CONFIG['vid']='1008997495870'; 
  34. </script> 
  35. <style>#js_style_css_module_global_WB_outframe{height:42px;}</style> 
  36. </head> 
  37. ……  

如果之前有微博爬虫开发经验的小伙伴,看到这里,一定能想出来很多玩法了吧。

四、代码实现

下面附上我的源码,通过上面的详细介绍,应该已经比较好理解,因此这里就简单地说明一下:

  1. 我把Cookie获取的过程做成了一个静态内部类,其中需要发起2次请求,一次是genvisitor获得3个参数,另一次是incarnate获得Cookie值;
  2. 如果Cookie获取失败,会调用HttpClientInstance.changeProxy来改变代理IP,然后重新获取,直到获取成功为止;
  3. 在使用时,出现了IP被封或无法正常获取页面等异常情况,外部可以通过调用cookieReset方法,重新获取一个新的Cookie。这里还是要声明一下,科学地使用爬虫,维护世界和平是程序员的基本素养;
  4. 虽然加了一些锁的控制,但是还未在高并发场景实测过,不能保证百分百线程安全,如使用下面的代码,请根据需要自行修改,如有问题也请大神们及时指出,拜谢!
  5. HttpClientInstance是我用单例模式重新封装的httpclient,对于每个传进来的请求重新包装了一层RequestConfig,并且使用了代理IP;
  6. 不是所有的微博页面都可以抓取得到,但是博文,评论,转发等基本的数据还是没有问题的;
  7. 后续我也会把代码push到github上,请大家支持,谢谢! 

  1. import com.fullstackyang.httpclient.HttpClientInstance; 
  2. import com.fullstackyang.httpclient.HttpRequestUtils; 
  3. import com.google.common.base.Strings; 
  4. import com.google.common.collect.Maps; 
  5. import com.google.common.net.HttpHeaders; 
  6. import lombok.NoArgsConstructor; 
  7. import lombok.extern.slf4j.Slf4j; 
  8. import org.apache.commons.lang3.StringUtils; 
  9. import org.apache.http.client.config.CookieSpecs; 
  10. import org.apache.http.client.config.RequestConfig; 
  11. import org.apache.http.client.methods.HttpGet; 
  12. import org.apache.http.client.methods.HttpPost; 
  13. import org.json.JSONObject; 
  14.   
  15. import java.io.UnsupportedEncodingException; 
  16. import java.math.BigDecimal; 
  17. import java.net.URLEncoder; 
  18. import java.util.Map; 
  19. import java.util.concurrent.locks.Lock; 
  20. import java.util.concurrent.locks.ReentrantLock; 
  21.   
  22. /** 
  23.  * 微博免登陆请求客户端 
  24.  * 
  25.  * @author fullstackyang 
  26.  */ 
  27. @Slf4j 
  28. public class WeiboClient { 
  29.   
  30.     private static CookieFetcher cookieFetcher = new CookieFetcher(); 
  31.   
  32.     private volatile String cookie; 
  33.   
  34.     public WeiboClient() { 
  35.         this.cookie = cookieFetcher.getCookie(); 
  36.     } 
  37.   
  38.     private static Lock lock = new ReentrantLock(); 
  39.   
  40.     public void cookieReset() { 
  41.         if (lock.tryLock()) { 
  42.             try { 
  43.                 HttpClientInstance.instance().changeProxy(); 
  44.                 this.cookie = cookieFetcher.getCookie(); 
  45.                 log.info("cookie :" + cookie); 
  46.             } finally { 
  47.                 lock.unlock(); 
  48.             } 
  49.         } 
  50.     } 
  51.   
  52.     /** 
  53.      * get方法,获取微博平台的其他页面 
  54.      * @param url 
  55.      * @return 
  56.      */ 
  57.     public String get(String url) { 
  58.         if (Strings.isNullOrEmpty(url)) 
  59.             return ""; 
  60.   
  61.         while (true) { 
  62.             HttpGet httpGet = new HttpGet(url); 
  63.             httpGet.addHeader(HttpHeaders.COOKIE, cookie); 
  64.             httpGet.addHeader(HttpHeaders.HOST, "weibo.com"); 
  65.             httpGet.addHeader("Upgrade-Insecure-Requests", "1"); 
  66.   
  67.             httpGet.setConfig(RequestConfig.custom().setSocketTimeout(3000) 
  68.                     .setConnectTimeout(3000).setConnectionRequestTimeout(3000).build()); 
  69.             String html = HttpClientInstance.instance().tryExecute(httpGet, null, null); 
  70.             if (html == null) 
  71.                 cookieReset(); 
  72.             else return html; 
  73.         } 
  74.     } 
  75.   
  76.      /** 
  77.      * 获取访问微博时必需的Cookie 
  78.      */ 
  79.     @NoArgsConstructor 
  80.     static class CookieFetcher { 
  81.   
  82.         static final String PASSPORT_URL = "https://passport.weibo.com/visitor/visitor?entry=miniblog&a=enter&url=http://weibo.com/?category=2" 
  83.                 + "&domain=.weibo.com&ua=php-sso_sdk_client-0.6.23"; 
  84.   
  85.         static final String GEN_VISITOR_URL = "https://passport.weibo.com/visitor/genvisitor"; 
  86.   
  87.         static final String VISITOR_URL = "https://passport.weibo.com/visitor/visitor?a=incarnate"; 
  88.   
  89.         private String getCookie() { 
  90.             Map<String, String> map; 
  91.             while (true) { 
  92.                 map = getCookieParam(); 
  93.                 if (map.containsKey("SUB") && map.containsKey("SUBP") && 
  94.                         StringUtils.isNoneEmpty(map.get("SUB"), map.get("SUBP"))) 
  95.                     break; 
  96.                 HttpClientInstance.instance().changeProxy(); 
  97.             } 
  98.             return " YF-Page-G0=" + "; _s_tentry=-; SUB=" + map.get("SUB") + "; SUBP=" + map.get("SUBP"); 
  99.         } 
  100.   
  101.         private Map<String, String> getCookieParam() { 
  102.             String time = System.currentTimeMillis() + ""; 
  103.             time = time.substring(0, 9) + "." + time.substring(9, 13); 
  104.             String passporturl = PASSPORT_URL + "&_rand=" + time; 
  105.   
  106.             String tid = ""; 
  107.             String c = ""; 
  108.             String w = ""; 
  109.             { 
  110.                 String str = postGenvisitor(passporturl); 
  111.                 if (str.contains("\"retcode\":20000000")) { 
  112.                     JSONObject jsonObject = new JSONObject(str).getJSONObject("data"); 
  113.                     tid = jsonObject.optString("tid"); 
  114.                     try { 
  115.                         tid = URLEncoder.encode(tid, "utf-8"); 
  116.                     } catch (UnsupportedEncodingException e) { 
  117.                     } 
  118.                     c = jsonObject.has("confidence") ? "000" + jsonObject.getInt("confidence") : "100"; 
  119.                     w = jsonObject.optBoolean("new_tid") ? "3" : "2"; 
  120.                 } 
  121.             } 
  122.             String s = ""; 
  123.             String sp = ""; 
  124.             { 
  125.                 if (StringUtils.isNoneEmpty(tid, w, c)) { 
  126.                     String str = getVisitor(tid, w, c, passporturl); 
  127.                     str = str.substring(str.indexOf("(") + 1, str.indexOf(")")); 
  128.                     if (str.contains("\"retcode\":20000000")) { 
  129.                         System.out.println(new JSONObject(str).toString(2)); 
  130.                         JSONObject jsonObject = new JSONObject(str).getJSONObject("data"); 
  131.                         s = jsonObject.getString("sub"); 
  132.                         sp = jsonObject.getString("subp"); 
  133.                     } 
  134.   
  135.                 } 
  136.             } 
  137.             Map<String, String> map = Maps.newHashMap(); 
  138.             map.put("SUB", s); 
  139.             map.put("SUBP", sp); 
  140.             return map; 
  141.         } 
  142.   
  143.         private String postGenvisitor(String passporturl) { 
  144.   
  145.             Map<String, String> headers = Maps.newHashMap(); 
  146.             headers.put(HttpHeaders.ACCEPT, "*/*"); 
  147.             headers.put(HttpHeaders.ORIGIN, "https://passport.weibo.com"); 
  148.             headers.put(HttpHeaders.REFERER, passporturl); 
  149.   
  150.             Map<String, String> params = Maps.newHashMap(); 
  151.             params.put("cb", "gen_callback"); 
  152.             params.put("fp", fp()); 
  153.   
  154.             HttpPost httpPost = HttpRequestUtils.createHttpPost(GEN_VISITOR_URL, headers, params); 
  155.   
  156.             String str = HttpClientInstance.instance().execute(httpPost, null); 
  157.             return str.substring(str.indexOf("(") + 1, str.lastIndexOf("")); 
  158.         } 
  159.   
  160.         private String getVisitor(String tid, String w, String c, String passporturl) { 
  161.             String url = VISITOR_URL + "&t=" + tid + "&w=" + "&c=" + c.substring(c.length() - 3) 
  162.                     + "&gc=&cb=cross_domain&from=weibo&_rand=0." + rand(); 
  163.   
  164.             Map<String, String> headers = Maps.newHashMap(); 
  165.             headers.put(HttpHeaders.ACCEPT, "*/*"); 
  166.             headers.put(HttpHeaders.HOST, "passport.weibo.com"); 
  167.             headers.put(HttpHeaders.COOKIE, "tid=" + tid + "__0" + c); 
  168.             headers.put(HttpHeaders.REFERER, passporturl); 
  169.   
  170.             HttpGet httpGet = HttpRequestUtils.createHttpGet(url, headers); 
  171.             httpGet.setConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); 
  172.             return HttpClientInstance.instance().execute(httpGet, null); 
  173.         } 
  174.   
  175.         private static String rand() { 
  176.             return new BigDecimal(Math.floor(Math.random() * 10000000000000000L)).toString(); 
  177.         } 
  178.   
  179.         private static String fp() { 
  180.             JSONObject jsonObject = new JSONObject(); 
  181.             jsonObject.put("os", "1"); 
  182.             jsonObject.put("browser", "Chrome59,0,3071,115"); 
  183.             jsonObject.put("fonts", "undefined"); 
  184.             jsonObject.put("screenInfo", "1680*1050*24"); 
  185.             jsonObject.put("plugins", 
  186.                     "Enables Widevine licenses for playback of HTML audio/video content. (version: 1.4.8.984)::widevinecdmadapter.dll::Widevine Content Decryption Module|Shockwave Flash 26.0 r0::pepflashplayer.dll::Shockwave Flash|::mhjfbmdgcfjbbpaeojofohoefgiehjai::Chrome PDF Viewer|::internal-nacl-plugin::Native Client|Portable Document Format::internal-pdf-viewer::Chrome PDF Viewer"); 
  187.             return jsonObject.toString(); 
  188.         } 
  189.     } 
  190. }  

本文作者:fullstackyang

来源:51CTO

时间: 2024-09-28 06:07:48

微博爬虫“免登录”技巧详解及Java实现的相关文章

轻松快捷 Photoshop中的快捷键技巧详解

技巧|详解 Photoshop中的快捷键技巧虽已是老生常谈,但是网上通常都是简单讲述,本文将从应用功能出发详细讲解这些常用的快捷键的使用.相信对大家都会有帮助. ■ 你可以用以下的快捷键来快速浏览你的图像: Home卷动至图像的左上角:End卷动至图像的右下角:Page UP卷动至图像的上方:Page Down卷动至图像的下方:Ctrl+Page Up卷动至图像的左方:Ctrl+Page Down卷动至图像的右方. ■ 按Ctrl键+Alt键+0键即可使图像按1:1比例显示. ■ 当你想"紧排&

java(jsp)整合discuz同步登录功能详解

 jsp整合discuz同步登录功能详解,Uenter是Comsenz旗下各个产品之间信息直接传递的一个桥梁,通过UCenter站长可以无缝整合Comsenz系列产品,Center拥有机制完善的接口,经过简单修改便可以挂接其它任何平台的第三方的网络应用程序 最近做了一个资源库系统的项目,老师说可以搭建开源论坛替代自己开发社交模块,正好在开源中国上看到了一个利用discuz的UCenter功能实现同步登录的开源项目(https://code.google.com/p/discuz-ucenter-

PS新手教程:通道混和器的应用技巧详解

PS新手教程:通道混和器的应用技巧详解 一.归纳的几个要点 根据通道和三原色原理,有规律(在头脑里一定要熟记!): 在RGB颜色模式中, 通道红--越亮画面就越红少青;越暗就越青少红; 通道绿--越亮画面就越绿少品;越暗就越品少绿; 通道蓝--越亮画面就越蓝少黄;越暗就越黄少蓝; (这个规律正如在曲线中,对R红.G绿.B蓝曲线的调整一样) 通道混和器的规律有: 规律1: 在通道混和器中,如果对某通道始终有等式成立: 红色百分比%+绿色百分比%+蓝色百分比%=总计100% 那么,该通道的中性灰的颜

PS字体的层次感小技巧详解

  PS字体的层次感小技巧详解 效果图如下 具体的制作步骤如下: 步骤1:打开PS ,建立一个大小合适的图层. 步骤2:输入 A.B.C.D四个字母,分别打出来;并记得复制一层; 步骤3:画一个矩形,进行透视,分别调整字母的大小 步骤4:ABCD四个字母在按住Shift键进行水平移动,使四个字母进行叠加; 步骤5:新建图层,拉渐变,复制,渐变层分别对ABCD 四个字母进行剪贴蒙版.分别调整渐变层位置,使字母与字母之间相接处颜色有区别层次感; 步骤6:把刚才复制的4个字母填充黑色,向左边移动几个像

jQuery源码分析之jQuery中的循环技巧详解_jquery

jQuery的源码中有很多值得学习借鉴的技巧,本文即收集了jQuery中出现的各种遍历技巧和场景.具体分析如下: // 简单的for-in(事件) for ( type in events ) { } // 缓存length属性,避免每次都去查找length属性,稍微提升遍历速度 // 但是如果遍历HTMLCollection时,性能提升非常明显,因为每次访问HTMLCollection的属性,HTMLCollection都会内部匹配一次所有的节点 for ( var j = 0, l = ha

Illustrator不透明度蒙版的使用经验技巧详解

给各位Illustrator软件的使用者们来详细的解析分享一下不透明度蒙版的使用经验技巧. 解析分享: 1.按下M键,切换到矩形工具,在页面中绘制出一个矩形,填充为红色. 2.画一个单一闭合的无填充的路径(形状)将被处理的图形框起来,然后将其与被处理图形同时选择.(在本文,我们选中绘制好的红色矩形,按下CTRL+C复制,CTRL+F原位粘贴,并将填充色设置为无,使用选择工具框选同时选中这两个矩形) 3.单击"窗口--透明度"打开透明度调板,单击调板右上角的菜单,在弹出的选项中单击&qu

详解在Java的Struts2框架中配置Action的方法_java

在Struts2中Action部分,也就是Controller层采用了低侵入的方式.为什么这么说?这是因为在Struts2中action类并不需要继承任何的基类,或实现任何的接口,更没有与Servlet的API直接耦合.它通常更像一个普通的POJO(通常应该包含一个无参数的execute方法),而且可以在内容定义一系列的方法(无参方法),并可以通过配置的方式,把每一个方法都当作一个独立的action来使用,从而实现代码复用. 例如: package example; public class U

Linux精华 Tomcat服务器配置技巧详解分析

服务器|技巧|精华|详解 1.配置系统管理(Admin Web Application) 大多数商业化的J2EE服务器都提供一个功能强大的管理界面,且大都采用易于理解的Web应用界面.Tomcat按照自己的方式,同样提供一个成熟的管理工具,并且丝毫不逊于那些商业化的竞争对手.Tomcat的Admin Web Application最初在4.1版本时出现,当时的功能包括管理context.data source.user和group等.当然也可以管理像初始化参数,user.group.role的多

Javascript函数绑定应用技巧详解

文章简介:函数绑定要创建一个函数,可以在特定环境中以指定参数调用另一个函数. 函数绑定要创建一个函数,可以在特定环境中以指定参数调用另一个函数.该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境.请看以下例子: var handler = { message: "Event handled", handleClick: function (event) { alert(this.message); } }; var btn = document.