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

目前,对于微博的爬虫,大部分是基于模拟微博账号登录的方式实现的,这种方式如果真的运营起来,实际上是一件非常头疼痛苦的事,你可能每天都过得提心吊胆,生怕新浪爸爸把你的那些账号给封了,而且现在随着实名制的落地,获得账号的渠道估计也会变得越来越少。
但是日子还得继续,在如此艰难的条件下,为了生存爬虫们必须寻求进化。好在上帝关门的同时会随手开窗,微博在其他诸如头条,一点等这类新媒体平台的冲击之下,逐步放开了信息流的查看权限。现在的微博即便在不登录的状态下,依然可以看到很多微博信息流,而我们的落脚点就在这里。
本文详细介绍如何获取相关的Cookie并重新封装Httpclient达到免登录的目的,以支持微博上的各项数据抓取任务。下面就从微博首页http://weibo.com开始。

二、准备工作

准备工作很简单,一个现代浏览器(你知道我为什么会写”现代”两个字),以及httpclient(我用的版本是4.5.3)
跟登录爬虫一样,免登录爬虫也是需要装载Cookie。这里的Cookie是用来标明游客身份,利用这个Cookie就可以在微博平台中访问那些允许访问的内容了。

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

我的博客:CODE大全www.codedq.net业余草www.xttblog.com爱分享www.ndislwf.comifxvn.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
  6. + "//" + window.location.host + "/visitor/visitor?a=incarnate&t="
  7. + encodeURIComponent(tid) + "&w=" + encodeURIComponent(where)
  8. + "&c=" + encodeURIComponent(conficence) + "&gc="
  9. + encodeURIComponent(gen_conf) + "&cb=cross_domain&from="
  10. + from + "&_rand=" + Math.random();
  11. url.l(incarnate_intr);
  12. };

我的博客:CODE大全www.codedq.net业余草www.xttblog.com爱分享www.ndislwf.comifxvn.com
这里是为请求者赋予一个访客身份,而控制跳转的链接也是由一些参数拼接起来的,也就是上图中第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 + '//'
  42. + window.location.host + '/' + tid.postInterface, "cb=gen_callback&fp="
  43. + encodeURIComponent(data), function (res) {
  44. if (res) {
  45. eval(res);
  46. }
  47. });
  48. });
  49. } else {
  50. util.postData(window.location.protocol + '//'
  51. + window.location.host + '/'
  52. + tid.postInterface, "cb=gen_callback", function (res) {
  53. if (res) {
  54. eval(res);
  55. }
  56. });
  57. }
  58. },
  59. ……
  60. //获得参数
  61. window.gen_callback = function (fp) {
  62. var value = false, confidence;
  63. if (fp) {
  64. if (fp.retcode == 20000000) {
  65. confidence = typeof(fp.data.confidence) != 'undefined' ? '000'
  66. + fp.data.confidence : '100';
  67. tid.recover = fp.data.new_tid ? 3 : 2;
  68. tid.confidence = confidence = confidence.substring(confidence.length - 3);
  69. value = fp.data.tid;
  70. Store.DB.set(tid.key, value + '__' + confidence);
  71. }
  72. }
  73. tid.value = value;
  74. tid.runstack();
  75. };

我的博客:CODE大全www.codedq.net业余草www.xttblog.com爱分享www.ndislwf.comifxvn.com

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

该请求的结果返回一串json

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

我的博客:CODE大全www.codedq.net业余草www.xttblog.com爱分享www.ndislwf.comifxvn.com
其中就包含了tid和confidence,这个confidence,我猜大概是推测客户端是否真实的一个置信度,不一定出现,根据window.gen_callback方法,不出现时默认为100,另外当new_tid为真时参数where等于3,否则等于2。
此时3个参数已经全部获得,现在就可以用httpclient发起上面第6个请求,返回得到另一串json:

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

我的博客:CODE大全www.codedq.net业余草www.xttblog.com爱分享www.ndislwf.comifxvn.com
参考最后请求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="随时随地发现新鲜事!微博带你欣赏世界上每一个精彩瞬间,了解每一个幕后故事。
  8. 分享你想表达的,让全世界都能听到你的心声!" name="description" />
  9. <link rel="mask-icon" sizes="any" href="//img.t.sinajs.cn/t6/style/images/apple/wbfont.svg" color="black" />
  10. <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
  11. <script type="text/javascript">
  12. try{document.execCommand("BackgroundImageCache", false, true);}catch(e){}
  13. </script>
  14. <title>微博-随时随地发现新鲜事</title>
  15. <link href="//img.t.sinajs.cn/t6/style/css/module/base/frame.css?version=6c9bf6ab3b33391f"
  16. type="text/css" rel="stylesheet" charset="utf-8" />
  17. <link href="//img.t.sinajs.cn/t6/style/css/pages/growth/login_v5.css?version=6c9bf6ab3b33391f"
  18. type="text/css" rel="stylesheet" charset="utf-8">
  19. <link href="//img.t.sinajs.cn/t6/skin/default/skin.css?version=6c9bf6ab3b33391f" type="text/css"
  20. rel="stylesheet" id="skin_style" />
  21. <script type="text/javascript">
  22. var $CONFIG = {};
  23. $CONFIG['islogin'] = '0';
  24. $CONFIG['version'] = '6c9bf6ab3b33391f';
  25. $CONFIG['timeDiff'] = (new Date() - 1505746970000);
  26. $CONFIG['lang'] = 'zh-cn';
  27. $CONFIG['jsPath'] = '//js.t.sinajs.cn/t5/';
  28. $CONFIG['cssPath'] = '//img.t.sinajs.cn/t5/';
  29. $CONFIG['imgPath'] = '//img.t.sinajs.cn/t5/';
  30. $CONFIG['servertime'] = 1505746970;
  31. $CONFIG['location']='login';
  32. $CONFIG['bigpipe']='false';
  33. $CONFIG['bpType']='login';
  34. $CONFIG['mJsPath'] = ['//js{n}.t.sinajs.cn/t5/', 1, 2];
  35. $CONFIG['mCssPath'] = ['//img{n}.t.sinajs.cn/t5/', 1, 2];
  36. $CONFIG['redirect'] = '';
  37. $CONFIG['vid']='1008997495870';
  38. </script>
  39. <style>#js_style_css_module_global_WB_outframe{height:42px;}</style>
  40. </head>
  41. ……

我的博客:CODE大全www.codedq.net业余草www.xttblog.com爱分享www.ndislwf.comifxvn.com

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

四、代码实现

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

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

我的博客:CODE大全www.codedq.net业余草www.xttblog.com爱分享www.ndislwf.comifxvn.com

微博爬虫“免登录”技巧详解及 Java 实现(业余草的博客)的更多相关文章

  1. 【转】log4j.properties 详解与配置步骤 - edward0830ly的专栏 - 博客频道 - CSDN.NET

    一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...

  2. iOS中 视频直播功能-流媒体的使用(详解)韩俊强的CSDN博客

    上一篇博客:(流媒体实现视频播放和下载功能):http://blog.csdn.net/qq_31810357/article/details/50574914 最近视频直播功能比较火,处于需求,研究 ...

  3. WebConfig配置文件详解(转载自逆心的博客)

    <?xml version="1.0"?> <!--注意: 除了手动编辑此文件以外,您还可以使用 Web 管理工具来配置应用程序的设置.可以使用 Visual S ...

  4. javascript工具--控制台详解(转自 阮一峰博客)

    大神这篇博客是写在2011年,主要介绍 “Firefox” 浏览器插件 “Firebug” 的操作,如今主流浏览器对控制台都已经提供了很好的支持.我自己用的最多是谷歌的 “chrome” 浏览器,下面 ...

  5. iOS中 支付宝钱包详解/第三方支付 韩俊强的博客

    每日更新关注:http://weibo.com/hanjunqiang  新浪微博! iOS开发者交流QQ群: 446310206 一.在app中成功完成支付宝支付的过程 1.申请支付宝钱包.参考网址 ...

  6. SCP免密传输和SSH登录流程详解

    SCP免密传输和SSH登录协议详解 在linux下开发时,经常需要登录到其他的设备上,例如虚拟机内ubuntu.树莓派等等,经常涉及到传输文件的操作,传输文件有很多中方法,如物理磁盘拷贝,基于网络的s ...

  7. 前端html、CSS快速编写代码插件-Emmet使用方法技巧详解

    前端html.CSS快速编写代码插件-Emmet使用方法技巧详解   Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生.它使用仿CSS选择器的语法来 ...

  8. Linux实现利用SSH远程登录服务器详解

    Linux实现利用SSH远程登录服务器详解 http://www.111cn.net/sys/linux/55152.htm

  9. 3dmax联机分布式渲染方法技巧详解

      3dmax联机分布式渲染方法技巧详解 \测试环境:win7系统 3DMAX2009 Vray2.0 .首先要保证你的两台电脑能在局域网里互相访问如图: 其他电脑上也一样都能打开对方的电脑! 步! ...

随机推荐

  1. 前后端分离之CORS和WebApi

    目前的项目是前端mv*+api的方式进行开发的,以前都是没有跨域的方案,前后端人员在同一个解决方案里边进行开发,前端人员要用IIS或VS来开发和调试Api,这样就很不方便,迫切需要跨域访问Api. 评 ...

  2. Junit4X系列--hamcrest的使用

    OK,在前面的一系列博客里面,我整理过了Assert类下面常用的断言方法,比如assertEquals等等,但是org.junit.Assert类下还有一个方法也用来断言,而且更加强大.这就是我们这里 ...

  3. MyEclipse中好用的快捷键汇总

    MyEclipse中常用的快捷键有很多,合理的使用其中一些快捷键组合,可以有效提高开发的效率和质量. 1.Ctrl + Shift + R:打开资源.可以查找并打开工作区中任何一个文件,且支持使用通配 ...

  4. 豹哥嵌入式讲堂:ARM知识概要杂辑(4)- Cortex-M处理器性能指标

    1.处理器的性能指标 用于评价CPU的性能指标非常多,不同的性能侧重点下的测试标准可能得出的指标值不同,下面介绍嵌入式行业广泛使用的两个经典的测试标准. 1.1 Dhrystone标准 Dhrysto ...

  5. awk数组结合+=统计题

    awk增加统计列值为增加列数或进行运行结果统计,使用符号 + =.增加的结果赋给符号左边变量值,增加到变量的域在符号右边.例如将 $ 1加入变量total,表达式为toatl+=$1.列值增加很有用. ...

  6. CentOS7修改主机名(hostname)

    Linux中的hostname在大多数应用中至为重要,例如有些应用强制使用主机名称而不能使用IP地址,如果默认主机名称都为localhost.localdomain 的话那一定会出现问题,而且看起来也 ...

  7. Win10微软帐户切换不回Administrator本地帐户的解决方法--(转,虽转但亲测有效)

    在Win10系统中经常会用到微软帐户登录,如应用商店等地方,不过一些用户反馈原来使用Administrator帐户被绑定微软帐户后无法切换回本地帐户,连[改用本地帐户登录]按钮都没有,那么怎么解决呢? ...

  8. Prime - 程序员的修养

    求质数算法的N种境界 求质数算法的N种境界[1] - 试除法和初级筛法 过程 尽管题目并没有要我们写一个最优的算法,但是身为一个程序员,优化应该是一种习惯,在编程的过程中,随着思考进行优化. 如果你只 ...

  9. C++——带默认参数值的函数

    函数在声明时可以预先给出默认的形参值,调用时如给出实参,则采用实参值,否则采用预先给出的默认参数值. ,) { return x + y;} int main() { add(,);//10+20 a ...

  10. Model和ModelAndView

    在请求处理方法可出现和返回的参数类型中,最重要的就是Model和ModelAndView.对于MVC框架,控制器Controller执行业务逻辑,用于产生模型数据Model,而试图View则用于渲染模 ...