技术学院

12-19
2017
小程序发布后搜索不到是怎么回事
      随着小程序商城的热度不减,更多的电商大佬纷纷使用小程序,如今已建立起2万开发者、1.2亿用户日活的小程序生态。微信对小程序的大力支持,也让无数的开发者和商户看到了微信小程序的无限发展潜力。但是,身边有很多朋友都会遇到一些问题,常见的就是在发布完的小程序后,自己的小程序在微信里搜不到的情况,这让很多企业商户摸不到头脑,那么出现这种状况的原因是什么呢?一、需登录小程序平台      在大家遇到小程序发布后搜索不到的情况时,首先需要登录自己的小程序平台,由于小程序审核通过后,还需要登录微信小程序平台“点击发布”这一项,在发布后的两小时内才可以搜索到自己的小程序,可以首先尝试一下此方法。二、小程序关键词排名规则      当你在微信中搜索小程序后,还是没有发现自己的小程序。很有可能是在小程序商城中小程序的关键词排名因素导致的。那么如何让自己的小程序关键词排名靠前呢?小编就和大家介绍一下小程序关键词的排名规则:1、小程序上线时间越早,排名就会越靠前。2、微信小程序用户使用的数量越多,排名就会越靠前。3、完全匹配出现关键词次数越多,排名就会越靠前。4、小程序的名称作为核心关键词语,排名就会越靠前。5、小程序中的关键词出现1次,并且整体标题的字数越短的话,排名就会越靠前。当大家遇到自己的小程序发布后在微信中搜索不到的情况时,可以通过以上提到的方法进行调整。随着微信对微信小程序商城的大力优化和扶持,越来越多的优势被大众所熟知,加之微信小程序正处于红利期,因此微信小程序的爆红也就不无道理了。
12-06
2018
微信小程序获取用户头像+昵称+openid,小程序登录!小程序发送模板消息
<view class="container">   <view class="userinfo">     <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>     <block wx:else>       <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>       <text class="userinfo-nickname">{{userInfo.nickName}}</text>     </block>   </view>   <view class="usermotto">     <text class="user-motto">{{motto}}</text>   </view>//获取应用实例 const app = getApp()   Page({   data: {     motto: 'Hello World',     userInfo: {},     hasUserInfo: false,     canIUse: wx.canIUse('button.open-type.getUserInfo')   },     onLoad: function () {     if (app.globalData.userInfo) {       this.setData({         userInfo: app.globalData.userInfo,         hasUserInfo: true       })     } else if (this.data.canIUse){       // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回       // 所以此处加入 callback 以防止这种情况       app.userInfoReadyCallback = res => {         this.setData({           userInfo: res.userInfo,           hasUserInfo: true         })       }     } else {       // 在没有 open-type=getUserInfo 版本的兼容处理       wx.getUserInfo({         success: res => {           app.globalData.userInfo = res.userInfo           this.setData({             userInfo: res.userInfo,             hasUserInfo: true           })         }       })     }   },   getUserInfo: function(e) {     console.log(e)     app.globalData.userInfo = e.detail.userInfo       //获取openid     wx.login({       success: function (res) {         console.log(res.code)         //发送请求获取openid         wx.request({           url: '你的域名/openid.php?code=code', //接口地址           data: { code: res.code },           header: {             'content-type': 'application/json' //默认值           },           success: function (res) {             //返回openid             console.log(res.data.openid)             //向数据库注册用户,验证用户             var that = this;             wx.request({               url: '你的域名/server.php?nickname=' + e.detail.userInfo.nickName + '&avatarUrl=' + e.detail.userInfo.avatarUrl + '&openid=' + res.data.openid,               data: {                 },               header: {                 'content-type': 'application/json'               },               success: function (res) {                 //打印用户信息                 console.log(res.data)               }             })           }         })       }     })             this.setData({       userInfo: e.detail.userInfo,       hasUserInfo: true,     })   } })<?php //声明CODE,获取小程序传过来的CODE $code = $_GET["code"]; //配置appid $appid = "你的小程序APPID"; //配置appscret $secret = "你的小程序APPSRCRET"; //api接口 $api = "https://api.weixin.qq.com/sns/jscode2session?appid={$appid}&secret={$secret}&js_code={$code}&grant_type=authorization_code"; //获取GET请求 function httpGet($url){     $curl = curl_init();     curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);     curl_setopt($curl, CURLOPT_TIMEOUT, 500);     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true);     curl_setopt($curl, CURLOPT_URL, $url);     $res = curl_exec($curl);     curl_close($curl);     return $res; } //发送 $str = httpGet($api); echo $str; ?><?php header("Content-type:text/html;charset=utf8"); $nickname = $_GET["nickname"]; $avatarUrl = $_GET["avatarUrl"]; $openid = $_GET["openid"]; $con = mysql_connect("数据库地址","数据库账号","数据库密码"); mysql_select_db("数据库名", $con); mysql_query("INSERT INTO 表名 (nickname, avatarUrl, openid) VALUES ('$nickname', '$avatarUrl', '$openid')"); echo "注册成功!"; mysql_close($con); ?>微信模板消息<form bindsubmit="submit" report-submit='true' >   <input type='text' value='填写openid' name="openid"></input>   <input type='text' value='填写ACCESS_TOKEN' name="token"></input>   <input type='text' value='填写模板ID' name="template"></input>   <input type='text' value='模板的第1个关键词' name="keyword1"></input>   <input type='text' value='模板的第2个关键词' name="keyword2"></input>   <input type='text' value='模板的第3个关键词' name="keyword3"></input>   <input type='text' value='模板的第4个关键词' name="keyword4"></input>   <input type='text' value='模板的第5个关键词' name="keyword5"></input>   <button form-type="submit" type="default">推送</button> </form>Page({   data: {          },      submit: function (e) {     var openid = e.detail.value.openid;     var access = e.detail.value.token;     var template = e.detail.value.template;     var keyword1 = e.detail.value.keyword1;     var keyword2 = e.detail.value.keyword2;     var keyword3 = e.detail.value.keyword3;     var keyword4 = e.detail.value.keyword4;     var keyword5 = e.detail.value.keyword5;     var that = this;     wx.request({       url: '域名/muban.php?openid=' + e.detail.value.openid + '&token=' + e.detail.value.token + '&template=' + e.detail.value.template + '&formid=' + e.detail.formId + '&keyword1=' + e.detail.value.keyword1 + '&keyword2=' + e.detail.value.keyword2 + '&keyword3=' + e.detail.value.keyword3 + '&keyword4=' + e.detail.value.keyword4 + '&keyword5=' + e.detail.value.keyword5, //接口地址,我学习就用get,建议用post       data: {         open_id: openid,         tok_en: access,         temp_late: template,         form_id: e.detail.formId,         keyword_1: keyword1,         keyword_2: keyword2,         keyword_3: keyword3,         keyword_4: keyword4,         keyword_5: keyword5       },       success: function (res) {                // console.log(e.detail.formId);       // console.log(res.data);       }     })   } })//后端 <?php     //GET参数     $access_token=$_GET['token'];     $openid=$_GET['openid'];     $templateid=$_GET['template'];     $formid=$_GET['formid'];     $keyword1=$_GET['keyword1'];     $keyword2=$_GET['keyword2'];     $keyword3=$_GET['keyword3'];     $keyword4=$_GET['keyword4'];     $keyword5=$_GET['keyword5'];     echo $keywordd1;     //此处开始处理数据     $dataa=array(         "keyword1"=>array(                             "value"=>$keyword1,                             "color"=>"#9b9b9b"),         "keyword2"=>array(                             "value"=>$keyword2,                             "color"=>"#9b9b9b"),         "keyword3"=>array(                             "value"=>$keyword3,                             "color"=>"#9b9b9b"),         "keyword4"=>array(                             "value"=>$keyword4,                             "color"=>"#9b9b9b"),         "keyword5"=>array(                             "value"=>$keyword5,                             "color"=>"#9b9b9b")                 );         $data=array();     $data['touser']=$openid;     $data['template_id']=$templateid;     $data['form_id']=$formid;     $data['data']=$dataa;             $url = 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token='.$access_token;       $type="json";         if($type=='json'){//json $_POST=json_decode(file_get_contents('php://input'), TRUE);             $headers = array("Content-type: application/json;charset=UTF-8","Accept: application/json","Cache-Control: no-cache", "Pragma: no-cache");             $data=json_encode($data);         }         $curl = curl_init();         curl_setopt($curl, CURLOPT_URL, $url);         curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);         curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);         if (!empty($data)){             curl_setopt($curl, CURLOPT_POST, 1);             curl_setopt($curl, CURLOPT_POSTFIELDS,$data);         }         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);         curl_setopt($curl, CURLOPT_HTTPHEADER, $headers );         $output = curl_exec($curl);         if (curl_errno($curl)) {             echo 'Errno'.curl_error($curl);//捕抓异常         }         curl_close($curl);         echo $output; ?>
10-23
2018
熊掌号与SEO
什么是百度官方号相信不少站长在百度站长平台收到了站内消息:百度站长平台的原创保护功能迁移到百度官方号,百度官方号正在内测,收到邀请的站长建议赶紧尝试。虽然我首批收到内测邀请,然并卵,目前百度官方号注册是通过百家号系统的,而百家号是需要国内身份证、电话等认证的,所以和我的博客不能备案、主机不能搬到国内,我也不能开通微信公众号一样,我现在也无法开通百度官方号。甚至前些天发现我的微信号不但不能向别人转账,连别人转账或发红包给我都不行了,也是因为没有实名认证,而我又没有国内身份证,无法认证,所以很多国内方便、流行的东西我用不了。所以百度官方号后台是什么样子,这里暂时无法分享了。百度官方号到底是什么可以到百度官方号说明页面看一下,摘抄几句重要的:官方号是帮助互联网上所有内容和服务提供者实现搜索用户留存,并且为其赋予用户运营能力,提供多样的用户运营途径,从而实现网站的多元化变现。(Zac插一句话:这貌似是个病句?)网站开通官方号后,每一个网站将在百度建立一个官方号页面,新用户在首次浏览网站信息后,可以关注官方号称为官方号粉丝;老用户下一次可以通过百度首页、搜索结果页或者多重用户运营入口直接进入网站官方号页面,浏览内容或者消费服务。通过官方号提交入口的链接,经过质量校验后可以享受快速收录的特权,对于符合质量标准的页面,会同时在搜索结果页和官方号主页中展现,再配合官方号的运营给站点提供更多收益。所以这是一个:用户可以关注的号,所以能留存搜索用户。以后用户想找你,在“我的关注”就能找到。搜索用户在搜索结果页面上就可以关注你的官方号,也就是说,有官方号的页面在搜索结果中的展现方式必然是不一样的,至少得多一个关注按钮。有官方号的页面有快速收录的特权。与百度工程师的交流表明,有官方号的页面也有排名的优势。是否能实现收录、排名的特权,页面是需要经过质量校验的。这是一个人工+程序的混合过程。所以,SEO们不得不关注百度官方号,进入官方号系统的页面在收录、排名、展现上都将有优势。百度官方号实例比如搜索“红烧肉”,最上面的轮转图大部分是有官方号的页面:结果下面列出了网站名称和一个挺显眼的网站图标。这些结果也大部分是百度MIP页面,点击结果,会进入MIP页面:可以看到右上角有个“关注”按钮,用户可以关注,成为粉丝。点击关注右侧的下拉菜单,可以看到访问官方号的选项:官方号页面是这个样子:(11月6号注:最后确定的熊掌号有的时候还在关注按钮下面还显示百度认证信息)官方号是在author.baidu.com这个子域名下,站长可以在官方号中发表文章,与微信公众号挺相似。官方号页面即使不是MIP页面,也在展现上与普通页面有很大差别,而且似乎更为显著,比如这个:(11月6号注:里面的“官方号”字样改为了“熊掌号”,其它倒没有什么大变化。)这么明显的图标、关注按钮,相信对点击率、用户留存率都有相当大的影响。关于百度官方号的一些内部信息通过和谭待他们的交流,还了解到一些百度官方号还没公布、其它地方看不到的信息。官方号平台可以直接发布文章,用户可以关注,类似微信公众号,不过百度官方号带来了用户、移动搜索和网站内容的联通,似乎比公众号更为开放。现在能看到的官方号页面显示的都是百度百家号的文章,以后百家号应该是直接合并到官方号。对SEO更有意义的是,官方号也会和网站页面产生关联。站长可以通过官方号以类似sitemap的形式提交页面,页面上也可以加上一个标签,声明这个页面属于某个官方号。这样的页面经过质量校验后,在收录、排名、展现上都比普通页面有优势。前面提到,原来的原创保护即将转移到百度官方号,所以提交的页面要求是比较高的,很可能需要是原创的。质量校验的第一关显然就是开通账号时的人工审核。其后通过官方号提交的页面很可能是巨量的,质量上也是参差不齐的,肯定要由算法检验质量。为了让站长了解自己提交的页面质量是否符合标准,提高透明度,官方号有可能在后台显示一个大致的页面质量评级,甚至更多信息,如果提交的页面并没有获得收录、排名、展现等方面的优势,站长也可以查看一下质量评级,心里大概明白是为什么。(11月6号注:这个指数现在命名为熊掌号搜索指数:是由您发布的内容质量、用户喜爱、原创能力、活跃表现、领域专注五个维度的计算而得出的客观评分结果。)即使目标关键词没有获得好的质量评级,因此没有排名和特殊展现,在搜索品牌词、公司名,或者直接搜索URL时,通常还是会得到官方号的展现,这对很多企业老板也是挺管用的。站长通过官方号提交网址时,虽然可以自己选择提交哪些页面,但也要谨慎,质量不够就不要提交了,如果提交的页面大量存在质量问题,官方号等级可能会下降。                           
10-23
2018
百度算法更新大全
盘点一下百度官方公布过的算法更新,数量不少,信息散落在百度资源平台各处,集中在这里,省得以后记不住,其实现在已经记不住了,读者们也可以做个参考。以后有百度官方命名的更新,会更新在这个页面上。有一些可疑的算法名称,如所谓瑞丽算法等,我没有看到任何官方说法,不知道出处,没有列出来。从算法名称看,百度是从植物系列转向了天气预报系列。百度绿箩算法上线时间:2013年2月19号打击对象:买卖链接的行为,包括超链中介、出卖链接的网站、购买链接的网站。绿箩算法影响了10W+低质站点。百度石榴算法上线时间:2013年5月17号打击对象:大量妨碍用户正常浏览的恶劣广告的页面、低质量内容页面石榴算法针对的尤其以弹出大量低质弹窗广告、混淆页面主体内容的垃圾广告页面为代表。绿箩算法2.0上线时间:2013年7月1号打击对象:软文中的外链及惩罚发软文的站点。百度冰桶算法1.0上线时间:2014年8月30号打击对象:移动端影响用户体验的落地页。冰桶1.0主要打击对象包括强行弹窗app下载、用户登录、大面积广告等影响用户正常浏览体验的页面,尤其以必须下载app才能正常使用的站点为代表。冰桶算法2.0上线时间:打击对象:全屏下载、在狭小的手机页面布设大面积广告遮挡主体内容、强制用户登录才可以使用等。冰桶算法3.0上线时间:2016年7月7号公布,7月15号上线打击对象:打断用户完整搜索路径的行为。所谓打断用户完整搜索路径,主要指的是用户访问移动页面时,被强迫打开、下载该网站的APP才能继续浏览或使用,但这些APP又不一定是必要或者常用的,简单获取信息的行为被强行变成复杂或高成本的下载行为。百度移动搜索会大幅降低这类页面在移动搜索中的评价。百度天网算法上线时间:2016年8月10号公布打击对象:盗取用户隐私的网站。盗取用户隐私主要表现为网页嵌恶意代码,用于盗取网民的QQ号、手机号等。被检测处罚的网站经过整改,达到标准,会解除处罚。冰桶算法4.0上线时间:2016年9月19号公布打击对象:广告过多、影响用户体验的移动页面。SEO需要优化页面广告布局,控制每屏广告的比例,保障用户浏览体验,以免被算法策略命中,影响网站排名和流量。冰桶算法4.5上线时间:2016年10月26号公布打击对象:发布恶劣诱导类广告的页面。所谓恶劣诱导类广告,指的是通过色情动图、色情导航、露骨文本、赌博等等吸引眼球的形态诱导用户点击非法广告。就我所见,目前这种页面依然很多,但排名能保持的很少。百度蓝天算法上线时间:2016年11月21号公布打击对象:打击新闻源售卖软文、目录行为。触发“蓝天算法”问题网站将被清理出新闻源,同时降低其在百度搜索系统中的评价。百度烽火计划上线时间:2017年2月23号公布打击对象:百度移动搜索页面劫持。所谓移动搜索页面劫持,指的是用户浏览完这类作弊页面后返回搜索结果时,没有返回到真正的百度搜索结果页面,而是进入到一个假的百度移动搜索结果页,该页面模拟了百度搜索结果首页,但实际上是一个虚假的高风险站点,用户访问存在极大的安全隐患。百度没有具体说明这种劫持是怎样实现的。我猜想的一个可能方式是连续跳转加上检测浏览器referrer(看懂就看懂,看不懂不解释),以前在唯一的一次网上SEO培训中提到过这种方法在搜索广告中的使用,不知道是不是有心人受到了启发?百度飓风算法上线时间:2017年7月4号公布打击对象:严厉打击以恶劣采集为内容主要来源的网站。百度搜索对恶劣采集零容忍,恶劣采集的页面将从索引库中被彻底清除,给优质原创内容提供更多展示机会。飓风算法会例行产出惩罚数据,同时会根据情况随时调整迭代。貌似是线下预处理数据,计算后集中上线。优质原创站点如果发现索引量大幅减少且流量大幅下滑现象,可以在反馈中心进行反馈。所以,算法是有可能误判的。至少到目前为止,采集、转载的内容排名比原创好是很常见的,飓风算法并没有充分发挥作用。百度蜘蛛升级https抓取上线时间:2017年8月30号公布,8月期间已上线主要影响:升级了对HTTPS数据的抓取力度,HTTPS数据将更快被Spider抓取到。除了抓取,百度表示过,https页面在权重上也有加分,百度的原话是“网站评价高、落地页评价高、搜索展示等收益优待。”Google几年前就开始对https页面提权。HTTPS是大势所趋,主要还是为了安全,搜索引擎对https页面在抓取、排名上也会有更多优待。建议站长尽快把网站从http转移到https,早晚要转,不如早转。百度清风算法上线时间:2017年9月14号公布,9月底上线打击对象:提过页面标题作弊,欺骗用户获得点击的行为。标题作弊主要指标题内容虚假(诸如标题党,假官方网站,假下载页面,假在线观看等),或在标题中故意堆砌关键词等行为。百度闪电算法上线时间:2017年10月19号公布,10月初已上线主要影响:移动页面首屏加载时间将影响搜索排名移动网页首屏在2秒之内完成打开的,在移动搜索排名中获得一定提权和流量倾斜,移动页面首屏加载非常慢(3秒及以上),将会被降权。2-3秒之间的,不升不降。以前就提过,页面速度是移动SEO的重要内容。提高页面打开速度牵扯到很多东西,包括但不限于:服务器位置、带宽、性能,数据库优化,HTML代码、图片优化、压缩,减少请求次数,CDN和缓存使用等等。有时间再单独说。像我的博客这样备案困难的网站,想把服务器转移到国内而不可得,速度上就先吃亏了。我在国内的几个城市试过,大部分情况下3秒是打不开页面的,所以我的SEO每天一贴已经被处罚过了?百度惊雷算法上线时间:2017年11月20号公布,2017年11月底上线打击对象:通过刷点击提高百度排名的行为,也就是近两年颇为流行的百度快速排名,简称快排。摘自百度官方关于惊雷算法的说明:惊雷算法会例行产出惩罚数据,对存在点击流量作弊的行为进行惩罚,另对有判罚纪录的网站加以严惩,严重者将长期封禁。既然是刷排名,就可以给竞争对手刷,为了防止别人陷害,惊雷算法会综合考虑网站质量、历史数据等多维度特征,不可能仅仅看点击数据,不然太容易陷害竞争对手了。有两个值得注意的对方。一是,即使有防范措施,有没有可能误伤无辜网站?应该是有可能,所以官方特意加了一句:在此期间如有流量异常,可以到反馈中心投诉。二是,从百度官方的语气看,惊雷算法是追溯既往的。百度清风算法2.0上线时间:2018年5月上旬打击对象:欺骗下载,欺骗用户下载的网站永久封禁所谓欺骗下载包括:实际下载资源与用户需求不符,下载资源无效,利用无关内容诱导用户下载。百度季风算法上线时间:2018年6月中旬打击对象:领域和内容严重不匹配的熊掌号领域与内容严重不匹配主要指的是两个领域:注册领域不是健康领域,也没有医疗资质,却发布医疗内容;不是财经领域,没有财经专业资质,却发布财经类内容。百度细雨算法上线时间:2018年6月28号公布,7月中旬上线打击对象:页面标题、正文作弊的供求、黄页类B2B网站标题作弊包括:冒出官网,堆积关键词,穿插火星文或特殊符号意图吸引眼球,穿插受益方式如联系电话。正文作弊包括:无价值内容的采集、拼接或内容不完整,各种方式(英文、谐音、图片等)穿插联系方式。    
02-01
2018
div css布局中CSS图片大小自动按比例等比例缩小图片不变形解决技巧
div css布局中对于图片列表或图片排版时,图片不是固定宽度高度大小,但图片占位是固定宽度高度,这个时候如果使用CSS固定死图片大小(宽度 高度),这个时候如果图片相对于这个位置不是等比例大小,那么这张图片就会变形,让图片变的不清晰,这个时候想让图片不变形又按比例缩放,如何解决?CSS图片缩小不变形,图片自动缩小,图片按比例等比例缩小不变形解决。解决方法有两种:第一种,让图片和布局宽度高度成等比例,这样CSS设置死宽度和高度,图片也是等比例缩小,图片也不会变形。比如淘宝,要求店铺主上传产品封面图片是正方形的,为什么,因为图片宝贝展示列表都是正方形的排版布局,这样要求上传合适正方形宝贝封面图片,也是让图片不变形。所以有条件的情况下,大家将首页、图片列表页的布局宽度高度保持一致,上传图片时候将图片先进行处理为布局宽度高度时等比例放大尺寸的。第二种,使用CSS max-width和max-height实现图片自动等比例缩小很简单我们要使用到max-width和max-height,这样即可设置对象图片最大宽度和最大高度,这样图片就会等比例缩放图片,然图片相对不变形清晰。以下通过实例对比方法让大家掌握CSS控制图片缩小不变形技巧。一、原始描述 这里有个div,CSS宽度和CSS高度方便为300px和100px同时设置1px黑色边框,里面放了一张图片(图片原始宽度650px为高度为406px)。并通过CSS固定死图片宽度高度。1、HTML源代码:<!DOCTYPE html>  <html>  <head>  <meta charset="utf-8" />  <title>图片缩小不变形实例 www.divcss.com</title>  <style>  .divcss{ border:1px solid #000; width:300px; height:100px}  .divcss img{width:300px; height:100px}  </style>  </head>    <body>  <div class="divcss">  <img src="img.jpg" />  </div>  </body>  </html>2、CSS固定死图片宽度高度实例截图原始图片展示:原始图片截图css固定宽度高度后变形的图片截图3、小结,通过CSS固定对象内图片高度宽度,这样图片如果不是等比例缩小,那么图片就变形了。二、CSS解决图片缩小不变形实例使用max-width:300px或max-height:100px,即可解决图片比例缩小。但这样存在一个问题,如果按照宽度缩放,但图片过高会超出溢出盒子,这个时候需要对父级使用overflow:hidden隐藏超出图片内容。但是使用max-width或max-height,IE6不支持,我们需要设置个width:expression(this.width > 300 ? "300px" : this.width);或者height:e­xpression(this.height>100?"100px":this.height);。解决IE6支持max-heightdiv css解决IE6支持max-width一般情况下只需要设置好宽度限制,比如这里只设置最大宽度为300px(max-width:300px),然后对父级使用overflow:hidden隐藏溢出图片,同时为了兼容IE6我们设置个width:expression(this.width > 300 ? "300px" : this.width);解决即可。1、具体解决DIV+CSS实例代码如下:<!DOCTYPE html>  <html>  <head>  <meta charset="utf-8" />  <title>图片缩小不变形实例 www.divcss.com</title>  <style>  .divcss{ border:1px solid #000; width:300px; height:100px;overflow:hidden}  .divcss img{max-width:300px;_width:expression(this.width > 300 ? "300px" : this.width);}  </style>  </head>    <body>  <div class="divcss">  <img src="img.jpg" />  </div>  </body>  </html>2、浏览器测试效果截图css图片缩小等比例缩小后不变形截图3、缺点介绍,如果使用此方法,兼容各大浏览器不变形,但图片不是完整显示的。
01-31
2018
盘点那些曾经让程序员目瞪口呆的Bug都有什么?
盘点那些曾经让程序员目瞪口呆的Bug都有什么?程序员一生与bug奋战,可谓是杀敌无数,见怪不怪了!在某知识社交平台中,一个“有哪些让程序员目瞪口呆的bug”的话题引来了6700多万的阅读,可见程序员们对这个话题的敏感度有多高。本文,笔者特意精选了部分优质答案供广大程序员参考!作者:佚名来源:IT168程序员一生与bug奋战,可谓是杀敌无数,见怪不怪了!在某知识社交平台中,一个“有哪些让程序员目瞪口呆的bug”的话题引来了6700多万的阅读,可见程序员们对这个话题的敏感度有多高。本文,笔者特意精选了部分优质答案供广大程序员参考!1、麻省理工“只能发500英里的邮件”该bug发生于麻省理工,当时其系统管理员接到统计系主任的求助电话,主任在电话中说:“咱们的邮件系统无法发送距离500英里以外的地方,准确地说好像是520英里。”此时的系统管理员内心是“毫无波澜”的,嗯!然后,他开始了漫长且苦逼的测试,最后发现邮件服务器操作系统(SunOS)被人更新了,因为操作系统发行版往往配备旧软件,因此邮件软件实际上是被降级了(Sendmail 8 -> Sendmail 5) ,最后的结果是:Sendmail 5试图解析Sendmail 8的配置文件。所以,为什么一定是500英里呢?且看大神讲解:2、int mian()这其实是一个书写上的错误,之所以会放在本文中,是因为很多程序员的职业生涯中都有过写!错!的经历!main和mian傻傻看不出来!3、医院急诊科的程序bug一位程序员为医院急诊科设计了一套应用程序,毕竟是为急诊病人服务,所以程序员在实验室内认真地测试无数遍,直至确定没有问题,才让医院部署使用。但是,医院方面却总是出现问题,一拿到实验室就没问题。该名程序员于是深入医院调查,最后发现是医院的X光射线导致电脑内存丢失了几个bit信息,进而让程序出现问题!4、谷歌的 Google Arts & Culture APP谷歌推出的Google Arts&Culture APP是一个可以将普通人的照片与艺术照进行对比,匹配出与用户上传的照片最相像的一张艺术画,运行效果是这样的:图片上也会给出匹配度,但偏偏有些人的照片上传后,给出来的艺术画让人哭笑不得,比如:5、硬件开光的必要性某数据中心的火灾报警器因损坏,而在没有发生火灾的情况下响起。诡异的是,数据中心内确实出现了大面积的磁盘损坏和读写性能下降!经排查,因为报警器声音太大影响了磁头的运动!网友吐槽:看来给硬盘开光很有必要啊!6、某外资通信设备商的逆天bug(实在太长,给各位上图)7、足以让数据库瞬间崩溃的bug愿望:在百万量级的数据库里实现快速自我交叉匹配查询。手段:建立临时表提速。Bug:条件里忘记添加”a.id=b.prio”结果:临时表从预计的几千条达到了上亿条,数据库崩溃!!!!8、足以让系统瘫痪的bug9、程序员都能看懂的bug(反正笔者没看懂,看懂的麻烦解释一下)if (object == null) {object.doSomething();} else {object.doSomethingElse();}10、据传,iPhone手机日历上的bug11、购买微软Office套件visio不可使用outlook邮箱注册网友爆料,自己在购买正版Office套件visio时,当他在注册页面输入微软的outlook邮箱,系统居然提示系统中没有outlook.com!12、集群宿主机已售内存为负值?13、比较弱智的bug某网友:让我目瞪口呆的BUG是update不加where...14、人类历史上第一个程序BUG
01-31
2018
成为Java顶尖程序员 ,看这11本书就够了
以下是我推荐给Java开发者们的一些值得一看的好书。但是这些书里面并没有Java基础、Java教程之类的书,不是我不推荐,而是离我自己学习 Java基础技术也过去好几年了,我学习的时候看的什么也忘了,所以我不能不负责任地推荐一些我自己都没有看过的书给大家。“学习的最好途径就是看书“,这是我自己学习并且小有了一定的积累之后的第一体会。个人认为看书有两点好处:1.能出版出来的书一定是经过反复的思考、雕琢和审核的,因此从专业性的角度来说,一本好书的价值远超其他资料2.对着书上的代码自己敲的时候方便“看完书之后再次提升自我的最好途径是看一些相关的好博文“,我个人认为这是学习的第二步,因为一本书往往有好几百页,好的博文是自己看书学习之后的一些总结和提炼,对于梳理学习的内容很有好处,当然这里不是说自己的学习方法,就不再扯下去了。很多程序员们往往有看书的冲动,但不知道看哪些书,下面我就给各位Java程序猿们推荐一些好书(每本书的作者会加粗标红),其中绝大多数都是我自己平时在看的书,也算是我对于平时读的书做一个小总结和读后感吧。首先推荐的不是一本书,而是一个博客,也是我们博客园另外一位博友java_my_life。目前市面上讲解设计模式的书很多,虽然我前面讲了看书是最好的,但是对设计模式感兴趣的朋友们,我推荐的是这个博客。这位博友的设计模式讲得非常非常好,我认为90%的内容都是没有问题且很值得学习的,其讲解设计模式的大体路线是:1、随便开篇点明该设计模式的定义2、图文并茂讲解该设计模式中的结构3、以详细的代码形式写一下该种设计模式的实现4、补充内容5、讲解该设计模式的优缺点对于一个设计模式我们关注、学习的知识点,不就是上面这些吗?不 过我要重点提醒一下网友们,同一种设计模式的写法有多种,并不是说只有按某种写法来写才是这种设计模式。比方说适配器模式,我们关注适配器模式一定要关注 的是什么是适配器模式不是怎么写适配器模式,不要认为某段代码不是按照适配器模式的写法写下来的它就不是适配器模式了,记住这一点,你在学习设计模式的时 候一定会对代码中用到的设计模式有更深入的理解。《深入理解Java虚拟机:JVM高级特性与最佳实践》如果你不满足于做一个只会写if…else…的Java程序员,而是希望更进一步,我随便举几个例子吧:1、了解Java代码的底层运行机制2、定位性能问题3、对整个系统进行性能调优4、解决各种奇奇怪怪的线上线下问题5、更加高级别的,为自己的项目量身定做一款适合自己项目的虚拟机那 么Java虚拟机是你必学的一门技术。《深入理解Java虚拟机:JVM高级特性与最佳实践》作者是周志明,这本书可以说是国内写得最好的有关Java虚 拟机的书籍,近半年,前前后后这本书我起码看了有5遍。国内写虚拟机的书除了这本,其实还有一些其他的,我也买过,不过粗略看下来,很多内容也是《深入理 解Java虚拟机:JVM高级特性与最佳实践》此书里面的。另外值得一提的是,《深入理解Java虚拟机:JVM高级特性与最佳实践》这本 书,有电子版的,网上搜一下就能下载到了。不过建议有兴趣的朋友还是去买书看,电子版本下载到的一般是比较老的版本,相比最新修订版的《深入理解Java 虚拟机:JVM高级特性与最佳实践》,有很多作者新补充的知识点是没有的。《HotSpot实战》所有的Java虚拟机都是遵循着Java虚拟机规范来的,市面上的Java虚拟机几十款,《深入理解Java虚拟机:JVM高级特性与最佳实践》一书里面讲的虚拟机并不针对某种特定的虚拟机,而是从Java虚拟机规范的角度来讲解Java虚拟机。我们平时使用的乃至商用的大多数Java虚拟机都是Sun公司的HotSpot,大家cmd进入命令行,使用”java -version”命令就可以看到了。如果希望在Java虚拟机规范的基础上更加深入地去理解虚拟机的一些细节是怎么实现的,就可以看一下《HotSpot实战》一书,作者是陈涛。不过由于HotSpot的源码都是C/C++写的,所以要求读者有非常好的C/C++基础,如果对这两门语言不是很熟悉的朋友,看这本书可能对你帮助不是很大。最后提一句,如果有兴趣的朋友,不妨先去网上下载一个openJDK,HotSpot的源码就在里面。《Java并发编程实战》这本书常常被列入Java程序员必读十大书籍排行榜前几位,不过个人不是很推荐这本书。《Java并发编程实战》作者是Brian Goetz,怎么说呢,这本书前前后后我也看了两遍左右,个人感受是:1、文字多代码少2、讲解多实践少我 觉得这可能就是老外写书的特点吧,因为Java是北美国家(加拿大、美国)开发和维护的,所以老外对Java方方面面的理论知识体系都掌握得是非常清楚和 透彻的。翻开这本书看,多线程什么用、什么是死锁、什么是竞争、什么是线程安全等等,方方面面的知识点都用大量的文字篇幅讲解,不免让人感觉十分枯燥,也 难让读者有实质性的进步。我这本书看了两遍也属于一目十行意思,有兴趣的地方就重点看一下。无论如何,作为一本常常位于Jva程序员必读十大书籍排行榜前几名的书,还是一定要推荐给大家的。《java多线程编程核心技术》《Java多线程编程核心技术》作者高洪岩。想要学习多线程的朋友,这本书是我大力推荐的,我的个人博客里面二十多篇的多线程博文都是基于此书,并且在这本书的基础上进行提炼和总结而写出来的。此书和《Java并发编程实战》 相反,这本书的特点是大篇幅的代码+小篇幅的精讲解,可能这和中国人写的书比较偏向实用主义的风格有关。本书关于线程安全、synchronized、 Reentrant、Timer等等都用详细的代码进行了讲解,而且每个大知识点下的多个小知识点都会详细讲解到,非常有实践价值。有兴趣的朋友们,我相信只要你们跟着这本书里面的代码敲、运行、思考,三步走,对于多线程的使用与理解一定会进几大步。不 过这本书的缺点就是对于Java并发包下的一些类像CountDownLatch、Semphore、CyclicBarrier、Future、 Callable等都没有讲到,重点的CAS和AQS也没有触及,重点类的实现原理也没有提。当然,这很深入了,在学习了这本书之后如果能再去对这些知识 进行一些学习、研究的话,你一定会慢慢成长为一个很厉害的多线程高手。《Effective Java中文版》这是唯一一本我没有买的书。初识这本书,是在我的博文Java代码优化(长期更新)里面,底下评论的时候有朋友提到了这本书,当时我说要去买,不过这两个月一直都没时间去逛书店,甚是遗憾,之后肯定会找时间去买这本书的。《Effective  Java中文版》的作者是Joshua   Bloch,这个人就很厉害了,他是谷歌的首席架构师,属于超级技术大牛级别了吧,呵呵。由于没有看过这本书,所以我不好发表评论,但是从这本书的知名度 以及其作者的来头来看(多提一句,这本书也是Java之父James Gosling博士推崇的一本书),我相信这一定是一本值得一看的好书。好 的代码是每个Java程序员都应该去追求的,不是说我今天写一段好代码相比写一段烂代码对性能会有多大的提升,更多的应该是提升了代码的可读性以及可以规 避许多潜在的、未知的问题,避免代码上线之后出问题而花时间去维护—-无论从时间成本、人力成本还是风险成本来说,这都是非常高的。《深入分析Java Web技术内幕》《深入分析Java Web技术内幕》,作者许令波,淘宝工程师。这本书我用一个字概括就是:全。真的非常全,HTTP、DNS、CDN、静态化、Jetty、Tomcat、Servlet、Spring、MyBatis等等,什么都有,涉及知识面非常广,但又不像专门精讲某个知识点的书籍一样讲得非常深入,感觉这本书就是尽量去用短的篇幅讲清楚一些Java Web使用到的技术的内幕,让读者对这些知识点的技术内幕有一个理性的认识。不过,尽管每个知识点的篇幅都不多,但是重点都基本讲到了,是一本让人真正有收获的书。如果想进一步了解这些技术的技术内幕,就要自己去买相关书籍或者自己上网查资料了,有种抛砖引玉,或者说师傅领进门、修行在个人的感觉。《大型网站技术架构 核心原理与案例分析》一个字评价这本书,屌;两个字评价这本书,很屌;三个字评价这本书,非常屌。呵呵,好了,再说下去可能别人以为我是水军了。《大型网站技术架构 核心原理与案例分析》的作者是李智慧,原阿里巴巴技术专家。Java 的大多数应用都是用在Web上的,现在只要稍微大型一点的Web应用,都一定是一个分布式系统,那么一个分布式系统用到了哪些技术?一个大型网站是如何从 一个小型网站成长起来的?如何保证你的网站安全?分布式系统使用到了缓存,有哪些缓存?缓存的使用有哪些值得注意的事项?关 于分布式的知识点,都在这本书里面有体现,只有你想不到,没有他写不到,而且写得非常易懂,基本属于看一两遍,再记一些笔记就知道是怎么一回事儿了。多看 几遍,对分布式的理解一定会加深不少。而且里面不仅仅是分布式的知识,还非常接地气地写了如何做一个好的架构师,其实我认为这不仅仅是写给想做架构师的读 者看的,就是给读者一些建议,如何更好地提出意见、如何更让别人关注你的声音、如何看到他人的优点,入木三分,让人获益匪浅。《大型网站系统与Java中间件实践》《大型网站系统与Java中间件实践》作者曾宪杰,是淘宝的技术总监,算起来应该在阿里有至少P8的级别了吧。这本书的部分内容和上面一本李智慧的《大型网站技术架构 核心原理与案例分析》有所重合,像分布式系统的演化、CDN、CAP理论和BASE理论等等,这也更说明这些都是分布式系统或者说是一个大型网站重点关注的内容,当作一次再学习也不错。本书要突出的重点是中间件三个字,中间件是分布式系统中一个非常重要的东西,其最重要的作用应该就是解耦,降低模块与模块之间的强依赖,不同的模块之间的依赖度降低,便可以各自独立地开发自己的功能,这也可以说是软件工程发展的目标和驱动力。因此,本书有一部分的内容就是基于中间件,详细讲解了中间件与JMS的各种知识,适合对分布式系统比较熟悉并且想要往中间件方面有一定研究的读者。《从Paxos到ZooKeeper 分布式一致性原理与实践》《从Paxos到ZooKeeper 分布式一致性原理与实践》,作者倪超,阿里巴巴工程师。这本书是我最近在研读的一本书,和上面的《大型网站系统与Java中间件实践》一样,属于分布式组件的范畴,属于有些深入的内容,当然也是我自己的个人兴趣。当然,如果有志向做一个出色的大型网站架构师、公司的技术总监之类,这些知识当然是必须掌握的。本书从分布式系统基本理论开始讲起,讲到Paxos算法,最后慢慢引入到Zookeeper,循序渐进。当然,更多的我目前还不方便发表什么看法,因为这本书的第二张Paxos算法我都还没有弄懂(Paxos算法确实有些难以理解和不太易懂),接下来的章节还没有看下去。如果网友们所在的公司在使用Zookeeper,并且你又对Zookeeper感兴趣想要研究一下它的原理的,这本书将是不二之选。《MySQL5.6从零开始学》《MySQL5.6从零开始学》,作者刘增杰和李坤。作为一名Java程序员,我认为我们千万不要觉得数据库是DBA的事情,数据库对一个Java程序员来说也是必须掌握的一门知识,丰富的数据库性能优化经验是一个顶尖程序员必备技能。目前主流的数据库有Oracle和MySQL,当然推荐大家的是MySQL,主要原因我认为有两点:1、MySQL相比Oracle更轻量级、更小、安装和卸载更方便,SQL其实都是差不多的,如果想学数据库,学MySQL就可以了,在家里面可以自己方便地研究,如果你的公司使用Oracle,只要再用对比学习法,关注一下Oracle和MySQL的差别即可2、随着2009年阿里巴巴去IOE的运动的进行,目前国内的很多互联网公司都会选择MySQL作为它们使用的数据库,因为MySQL免费,所以既省钱又不需要出了问题就依赖甲骨文公司MySQL学习我推荐的是这本我自己学习看的《MySQL5.6从零开始学》,我是觉得挺好的这本书,书里面的知识点很细致、很全面,读者选择书籍的标准大多不就是这两点吗?《Spring源码深度解析》《Spring源码深度解析》,作者郝佳。Spring 这个框架做得太好了,功能太强大了,以至于很多开发者都只知Spring,不知什么是工厂、什么是单例、什么是代理(我面试别人的真实体会)。这种功能强 大的框架内部一定是很复杂的实现,这就导致一旦你的程序使用Spring,出了问题,可能是Error、可能是Exception、可能是程序运行结果不 是你的预期的,出现诸如此类问题的时候,将会让你感到困惑,除了上网查资料或者问别人似乎没有更好的解决办法。研读Spring的源代码不失为一种很好的学习方法,我个人认为这有很多好处:1、理解框架内部的实现之后,可以主动去解决问题,而不需要依赖别人2、Spring框架内部实现用到了很多设计模式,很好的代码设计思路,这将会对你写代码、对你理解设计模式有很大的提高3、研究Spring框架将会大大增强你读代码的能力,我相信只要你能研究清楚Spring内部是如何实现的,其他任何一个框架的源代码都难不倒你总而言之,我认为读代码的能力是一个普通的程序员和一个好的程序员之间最大的差别之一,前者只会把别人写好的东西拿来用,后者不仅能用好,还清楚知道别人写好的东西底层是如何实现的,在出现问题的时候可以轻松解决。Spring源代码,个人推荐《Spring源码深度解析》一书,真要研究透并且写清楚Spring源代码,恐怕三四本书都不够,作者在近400页的篇幅中尽量去讲解Spring源代码是如何实现的,殊为不易,尽管无法讲得完全,但是相信作者的讲解配合上读者自己的研究,一定可以对Spring的实现有更深度的理解。后记以 上就是我推荐给Java开发者们的一些值得一看的好书。但是这些书里面并没有Java基础、Java教程之类的书,不是我不推荐,而是离我自己学习 Java基础技术也过去好几年了,我学习的时候看的什么也忘了,所以我不能不负责任地推荐一些我自己都没有看过的书给大家。对于Java基础知识的学习, 我提两点建议吧:1、多写多敲代码,好的代码与扎实的基础知识一定是实践出来的2、可以去尚学堂下载一下马士兵的视频来学习一下Java基础,还挺不错的,如果尚学堂官网上下载不了可以底下回复,我的电脑里有最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步。
01-29
2018
爬虫需谨慎,你不知道的爬虫与反爬虫套路!
面试的时候,因为双方爬虫理念或者反爬虫理念不同,也很可能互不认可,影响自己的求职之路。本来程序员就有“文人相轻”的倾向,何况理念真的大不同。爬虫与反爬虫,是一个很不阳光的行业。这里说的不阳光,有两个含义。第一是,这个行业是隐藏在地下的,一般很少被曝光出来。很多公司对外都不会宣称自己有爬虫团队,甚至隐瞒自己有反爬虫团队的事实。这可能是出于公司战略角度来看的,与技术无关。第二是,这个行业并不是一个很积极向上的行业。很多人在这个行业摸爬滚打了多年,积攒了大量的经验,但是悲哀的发现,这些经验很难兑换成闪光的简历。面试的时候,因为双方爬虫理念或者反爬虫理念不同,也很可能互不认可,影响自己的求职之路。本来程序员就有“文人相轻”的倾向,何况理念真的大不同。然而这就是程序员的宿命。不管这个行业有多么的不阳光,依然无法阻挡大量的人进入这个行业,因为有公司的需求。那么,公司到底有什么样的需求,导致了我们真的需要爬虫/反爬虫呢?反爬虫很好理解,有了爬虫我们自然要反爬虫。对于程序员来说,哪怕仅仅是出于“我就是要证明我技术比你好”的目的,也会去做。对于公司来说,意义更加重大,最少,也能降低服务器负载,光凭这一点,反爬虫就有充足的生存价值。那么爬虫呢?最早的爬虫起源于搜索引擎。搜索引擎是善意的爬虫,可以检索你的一切信息,并提供给其他用户访问。为此他们还专门定义了 robots.txt 文件,作为君子协定,这是一个双赢的局面。然而事情很快被一些人破坏了,爬虫很快就变的不再“君子”了。后来有了“大数据”,无数的媒体鼓吹大数据是未来的趋势,吸引了一批又一批的炮灰去创办大数据公司。这些人手头根本没有大数据,他们的数据只要用一个 U 盘就可以装的下,怎么好意思叫大数据呢?这么点数据根本忽悠不了投资者,于是他们开始写爬虫,拼命地爬取各个公司的数据。很快他们的数据,就无法用一个 U 盘装下了。这个时候终于可以休息休息,然后出去吹嘘融资啦。然而可悲的是,大容量 U 盘不断地在发布,他们总是在拼命地追赶存储增加的速度。以上是爬虫与反爬虫的历史,下面通过四个方面深入谈下爬虫与反爬虫:爬虫反爬虫运行现状爬虫反爬虫技术现状爬虫反爬虫套路现状爬虫反爬虫的未来爬虫反爬虫运行现状电子商务行业的爬虫与反爬虫更有趣一些,最初的爬虫需求来源于比价。这是某些电商网站的核心业务,大家买商品的时候,是一个价格敏感型用户的话,很可能用过网上的比价功能(真心很好用啊)。毫无悬念,他们会使用爬虫技术来爬取所有相关电商的价格。他们的爬虫还是比较温柔的,对大家的服务器不会造成太大的压力。然而,这并不意味着大家喜欢被他爬取,毕竟这对其他电商是不利的,于是需要通过技术手段来做反爬虫。按照技术人员的想法,对方用技术怼过来,我们就要用技术怼回去,不能怂啊。这个想法是很好的,但是实际应用起来根本不是这么回事。诚然,技术是很重要的,但是实际操作上,更重要的是套路。谁的套路更深,谁就能玩弄对方于鼓掌之中。谁的套路不行,有再好的技术,也只能被耍的团团转。这个虽然有点伤技术人员的自尊,然而,我们也不是第一天被伤自尊了。大家应该早就习惯了吧。真实世界的爬虫比例大家应该听过一句话吧,大概意思是说,整个互联网上大概有 50% 以上的流量其实是爬虫。第一次听这句话的时候,我还不是很相信,我觉得这个说法实在是太夸张了。怎么可能爬虫比人还多呢? 爬虫毕竟只是个辅助而已。现在做了这么久的反爬虫,我依然觉得这句话太夸张了。50%?你在逗我?就这么少的量?举个例子,某公司,某个页面的接口,每分钟访问量是 1.2 万左右,这里面有多少是正常用户呢?50%?60%?还是?正确答案是:500 以下。也就是说,一个单独的页面,12000 的访问量里,有 500 是正常用户,其余是爬虫。注意,统计爬虫的时候,考虑到你不可能识别出所有的爬虫,因此,这 500 个用户里面,其实还隐藏着一些爬虫。那么爬虫率大概是:(12000-500)/12000=95.8%。这个数字你猜到了吗?这么大的爬虫量,这么少的用户量,大家到底是在干什么?是什么原因导致了明明是百人级别的生意,却需要万级别的爬虫来做辅助? 95% 以上,19 保 1?答案可能会相当令人喷饭,这些爬虫大部分是由于决策失误导致的。哭笑不得的决策思路举个例子,这个世界存在 3 家公司,售卖相同的电商产品,三家公司的名字分别是 A,B,C。这个时候,客户去 A 公司查询了下某商品的价格,看了下发现价格不好,于是他不打算买了,他对整个行业的订单贡献为 0。然而 A 公司的后台会检测到,我们有个客户流失了,原因是他来查询了一个商品,这个商品我们的价格不好,没关系,我去爬爬别人试试。于是他分别爬取了 B 公司和 C 公司,B 公司的后台检测到有人来查询价格,但是呢,最终没有下单。他会认为,嗯,我们流失了一个客户。怎么办呢?我可以爬爬看,别人什么价格。于是他爬取了 A 和 C,C 公司的后台检测到有人来查询价格。。。。。过了一段时间,三家公司的服务器分别报警,访问量过高。三家公司的 CTO 也很纳闷,没有生成任何订单啊,怎么访问量这么高?一定是其他两家禽兽写的爬虫没有限制好频率。妈的,老子要报仇!于是分别做反爬虫,不让对方抓自己的数据。然后进一步强化自己的爬虫团队抓别人的数据。一定要做到:宁叫我抓天下人,休叫天下人抓我。然后,做反爬虫的就要加班天天研究如何拦截爬虫,做爬虫的被拦截了,就要天天研究如何破解反爬虫策略。大家就这么把资源全都浪费在没用的地方了,直到大家合并了,才会心平气和的坐下来谈谈,都少抓点。最近国内的公司有大量的合并,我猜这种“心平气和”应该不少吧?爬虫反爬虫技术现状下面我们谈谈,爬虫和反爬虫分别都是怎么做的。为 Python 平反首先是爬虫,爬虫教程你到处都可以搜的到,大部分是 Python 写的。我曾经在一篇文章提到过:用 Python 写的爬虫是最薄弱的,因为天生并不适合破解反爬虫逻辑,因为反爬虫都是用 JavaScript 来处理。然而慢慢的,我发现这个理解有点问题(当然我如果说我当时是出于工作需要而有意黑 Python,你们信吗。。。)。Python 的确不适合写反爬虫逻辑,但是 Python 是一门胶水语言,他适合捆绑任何一种框架。而反爬虫策略经常会变化的翻天覆地,需要对代码进行大刀阔斧的重构,甚至重写。这种情况下,Python 不失为一种合适的解决方案。 举个例子,你之前是用 selenium 爬取对方的站点,后来你发现自己被封了,而且封锁方式十分隐蔽,完全搞不清到底是如何封的,你会怎么办?你会跟踪 selenium 的源码来找到出错的地方吗?你不会,你只会换个框架,用另一种方式来爬取,然后你就把两个框架都浅尝辄止地用了下,一个都没有深入研究过。因为没等你研究好,也许人家又换方式了,你不得不再找个框架来爬取。毕竟,老板等着明天早上开会要数据呢。老板一般都是早上八九点开会,所以你七点之前必须搞定。等你厌倦了,打算换个工作的时候,简历上又只能写“了解 n 个框架的使用”,仅此而已。 这就是爬虫工程师的宿命,爬虫工程师比外包还可怜。外包虽然不容易积累技术,但是好歹有正常上下班时间,爬虫工程师连这个权利都没有。 然而反爬虫工程师就不可怜了吗?也不是的,反爬虫有个天生的死穴,就是:误伤率。 无法绕开的误伤率我们首先谈谈,面对对方的爬虫,你的第一反应是什么?如果限定时间的话,大部分人给我的答案都是:封杀对方的 IP。然而,问题就出在,IP 不是每人一个的,大的公司有出口 IP,ISP 有的时候会劫持流量让你们走代理,有的人天生喜欢挂代理,有的人为了翻墙 24 小时挂 VPN。最坑的是,现在是移动互联网时代,你如果封了一个 IP?不好意思,这是中国联通的 4G 网络,5 分钟之前还是别人,5 分钟之后就换人了哦!因此,封 IP 的误伤指数最高,并且,效果又是最差的,因为现在即使是最菜的新手,也知道用代理池了。你们可以去淘宝看下,几十万的代理价值多少钱?我们就不谈到处都有的免费代理了。也有人说:我可以扫描对方端口,如果开放了代理端口,那就意味着是个代理,我就可以封杀了呀。 事实是残酷的,我曾经封杀过一个 IP,因为他开放了一个代理端口,而且是个很小众的代理端口。不出一天就有人来报事件,说我们一个分公司被拦截了,我一查 IP,还真是我封的 IP。我就很郁闷地问他们 IT,开这个端口干什么?他说做邮件服务器啊。我说为啥要用这么奇怪的端口?他说,这不是怕别人猜出来么?我就随便取了个。扫描端口的进阶版,还有一种方式,就是去订单库查找这个 IP 是否下过订单,如果没有,那么就是安全的;如果有,那就不安全,有很多网站会使用这个方法。然而这只是一种自欺欺人的办法而已,只需要下一单,就可以永久洗白自己的 IP,天下还有比这更便宜的生意吗?因此,封 IP,以及封 IP 的进阶版:扫描端口再封 IP,都是没用的。根本不要考虑从 IP 下手,因为对手会用大量的时间考虑如何躲避 IP 封锁,你干嘛和人家硬碰呢?这没有任何意义。那么,下一步你会考虑到什么?很多站点的工程师会考虑:既然没办法阻止对方,那我就让它变的不可读吧。我会用图片来渲染关键信息,比如价格。这样,人眼可见,机器识别不出来。 这个想法曾经是正确的,然而,坑爹的技术发展,带给我们一个坑爹的技术,叫机器学习。顺便带动了一个行业的迅猛发展,叫 OCR。很快,识别图像就不再是任何难题了,甚至连人眼都很难识别的验证码,有的 OCR 都能搞定,比我肉眼识别率都高。更何况,现在有了打码平台,用资本都可以搞定,都不需要技术。那么,下一步你会考虑什么?这个时候,后端工程师已经没有太多的办法可以搞了。 不过后端搞不定的事情,一般都推给前端啊,前端从来都是后端搞不定问题时的背锅侠。多少年来我们都是这么过来的,前端工程师这个时候就要勇敢地站出来了:“都不要得瑟了,来比比谁的前端知识牛逼,你牛逼我就让你爬。”我不知道这篇文章的读者里有多少前端工程师,我只是想顺便提一下:你们以后将会是更加抢手的人才。前端工程师的逆袭我们知道,一个数据要显示到前端,不仅仅是后端输出就完事了,前端要做大量的事情,比如取到 json 之后,至少要用 template 转成 html 吧?这已经是步骤最少最简单的了,然后你总要用 css 渲染下吧? 这也不是什么难事。等等,你还记得自己第一次做这个事情的时候的经历吗?真的,不是什么难事吗?有没有经历过,一个 html 标签拼错,或者没有闭合,导致页面错乱?一个 css 没弄好,导致整个页面都不知道飘到哪去了?这些事情,你是不是很想让别人再经历一次?这件事情充分说明了:让一个资深的前端工程师来把事情搞复杂一点,对方如果配备了资深前端工程师来破解,也需要耗费 3 倍以上的时间。毕竟是读别人的代码,别人写代码用了一分钟,你总是要读两分钟,然后骂一分钟吧?这已经算很少的了。如果对方没有配备前端工程师。。。那么经过一段时间,他们会成长为前端工程师。之后,由于前端工程师的待遇比爬虫工程师稍好一些,他们很快会离职做前端,既缓解了前端人才缺口,又可以让对方缺人,重招。而他们一般是招后端做爬虫,这些人需要再接受一次折磨,再次成长为前端工程师,这不是很好的事情吗?所以,如果你手下的爬虫工程师离职率很高,请仔细思考下,是不是自己的招聘方向有问题。那么前端最坑爹的技术是什么呢?前端最坑爹的,也是最强大的,就是我们的:JavaScript。JavaScript 有大量的花样可以玩,毫不夸张的说,一周换一个 feature(Bug)给对方学习,一年不带重样的。这个时候你就相当于一个面试官,对方要通过你的面试才行。举个例子,在 Array.prototyp e里,有没有 map 啊?什么时候有啊?你说你是 xx 浏览器,那你这个应该是有还是应该没有啊?你说这个可以有啊?可是这个真没有啊。那[]能不能在 string 里面获取字符啊?哪个浏览器可以哪个不行啊?咦!你为什么支持 WebKit 前缀啊?等等,刚刚你还支持怎么现在不支持了啊?你声明的不对啊。这些对于前端都是简单的知识,已经习以为常了,但是对于后端来说简直就是噩梦。然而,前端人员自己作死,研究出了一个东西,叫:Nodejs。基于 V8,秒杀所有的 js 运行。不过 Nodejs 实现了大量的 feature,都是浏览器不存在的,你随随便便访问一些东西(比如你为什么会支持 process.exit),都会把 node 坑的好惨好惨。而且浏览器里的 js,你拉到后台用 Nodejs 跑,你是不是想到了什么安全漏洞?这个是不是叫,代码与数据混合?如果他在 js 里跑点恶心的代码,浏览器不支持但是 node 支持怎么办?还好,爬虫工程师还有 phantomjs。但是,你怎么没有定位啊? 哈哈,你终于模拟出了定位。但是不对啊,根据我当前设置的安全策略你现在不应该能定位啊?你是怎么定出来的?连 phantomjs 的作者自己都维护不下去了,你真的愿意继续用吗?当然了,最终,所有的反爬虫策略都逃不脱被破解的命运。但是这需要时间,反爬虫需要做的就是频繁发布,拖垮对方。如果对方两天可以破解你的系统,你就一天一发布,那么你就是安全的。这个系统甚至可以改名叫做“每天一道反爬题,轻轻松松学前端”。误伤,还是误伤这又回到了我们开始提到的“误伤率”的问题了。我们知道,发布越频繁,出问题的概率越高。那么,如何在频繁发布的情况下,还能做到少出问题呢?此外还有一个问题,我们写了大量的“不可读代码”给对方,的确能给对方造成大量的压力,但是,这些代码我们自己也要维护啊。如果有一天忽然说,没人爬我们了,你们把代码下线掉吧。这个时候写代码的人已经不在了,你们怎么知道如何下线这些代码呢?这两个问题我暂时不能公布我们的做法,但是大家都是聪明人,应该都是有自己的方案的,软件行业之所以忙的不得了,无非就是在折腾两件事,一个是如何将代码拆分开,一个是如何将代码合并起来。关于误伤率,我只提一个小的 tip:你可以只开启反爬虫,但是不拦截,先放着,发统计信息给自己,相当于模拟演练。等统计的差不多了,发现真的开启了也不会有什么问题,那就开启拦截或者开启造假。这里就引发了一个问题,往往一个公司的各个频道,爬取难度是不一样的。原因就是,误伤检测这种东西与业务相关,公司的基础部门很难做出通用的,只能各个部门自己做,甚至有的部门做了有的没做。因此引发了爬虫界一个奇葩的通用做法:如果 PC 页面爬不到,就去 H5 试试,如果 H5 很麻烦,就去 PC 碰碰运气。爬虫反爬虫套路现状那么一旦有发现对方数据造假怎么办?早期的时候,大家都是要抽查数据,通过数据来检测对方是否有造假,这个需要人工核对,成本非常高。可是那已经是洪荒时代的事情了。如果你们公司还在通过这种方式来检测,说明你们的技术还比较落伍。之前我们的竞争对手是这么干的:他们会抓取我们两次,一次是他们解密出来 key 之后,用正经方式来抓取,这次的结果定为 A。一次是不带 key,直接来抓,这次的结果定为 B。根据前文描述,我们可以知道,B 一定是错误的。那么如果 A 与 B 相等,说明自己中招了,这个时候会停掉爬虫,重新破解。不要回应所以之前有一篇关于爬虫的文章,说如何破解我们的。一直有人要我回复下,我一直觉得没什么可以回复的。第一,反爬虫被破解了是正常的。这个世界上有个万能的爬虫手段,叫“人肉爬虫”。假设我们就是有钱,在印度开个分公司,每天雇便宜的劳动力用鼠标直接来点,你能拿我怎么办?第二,我们真正关心的是后续的这些套路。而我读了那篇文章,发现只是调用了selenium并且拿到了结果,就认为自己成功了。我相信你读到这里,应该已经明白为什么我不愿意回复了。我们最重要的是工作,而不是谁打谁的脸。大家如果经常混技术社区就会发现,每天热衷于打别人脸的,一般技术都不是很好。当然这并不代表我们技术天下第一什么的,我们每天面对大量的爬虫,还是遇到过很多高手的。就如同武侠小说里一样,高手一般都比较低调,他们默默地拿走数据,很难被发现,而且频率极低,不会影响我们的考评。你们应该明白,这是智商与情商兼具的高手了。我们还碰到拉走我们 js,砍掉无用的部分直接解出 key,相当高效不拖泥带水的爬虫,一点废请求都没有(相比某些爬虫教程,总是教你多访问,写没用的 url 免得被发现,真的不知道高到哪里去了。这样做除了会导致机器报警,导致对方加班封锁以外,对你自己没有任何好处)。而我们能发现这一点仅仅是是因为他低调地写了一篇博客,通篇只介绍技术,没有提任何没用的东西。这里我只是顺便发了点小牢骚,就是希望后续不要总是有人让我回应一些关于爬虫的文章。线下我认识很多爬虫工程师,水平真的很好,也真的很低调(不然你以为我是怎么知道如何对付爬虫的。。。),大家都是一起混的,不会产生“一定要互相打脸”的情绪。进化早期我们和竞争对手打的时候,双方的技术都比较初级。后来慢慢的,爬虫在升级,反爬虫也在升级,这个我们称为“进化”。我们曾经给对方放过水,来试图拖慢他们的进化速度,然而,效果不是特别理想。爬虫是否进化,取决于爬虫工程师自己的 KPI,而不是反爬虫的进化速度。后期打到白热化的时候,用的技术越来越匪夷所思。举个例子,很多人会提,做反爬虫会用到 canvas 指纹,并认为是最高境界。其实这个对于反爬虫来说也只是个辅助,canvas 指纹的含义是,因为不同硬件对 canvas 支持不同,因此你只要画一个很复杂的 canvas,那么得出的 image,总是存在像素级别的误差。考虑到爬虫代码都是统一的,就算起 selenium,也是 Ghost 的,因此指纹一般都是一致的,因此绕过几率非常低。但是!这个东西天生有两个缺陷。第一是,无法验证合法性。当然了,你可以用非对称加密来保证合法,但是这个并不靠谱。其次,canvas 的冲突概率非常高,远远不是作者宣称的那样,冲突率极低。也许在国外冲突是比较低,因为国外的语言比较多。但是国内公司通常是 IT 统一装机,无论是软件还是硬件都惊人的一致。我们测试 canvas 指纹的时候,在携程内部随便找了 20 多台机器,得出的指纹都完全一样,一丁点差别都没有。因此,有些“高级技巧”一点都不实用。法律途径此外就是大家可能都考虑过的:爬虫违法吗?能起诉对方让对方不爬吗?法务给的答案到是很干脆,可以,前提是证据。遗憾的是,这个世界上大部分的爬虫爬取数据是不会公布到自己网站的,只是用于自己的数据分析。因此,即使有一些关于爬虫的官司做为先例,并且已经打完了,依然对我们没有任何帮助。反爬虫,在对方足够低调的情况下,注定还是个技术活。搞事情,立 Flag到了后来,我们已经不再局限于打打技术了,反爬虫的代码里我们经常埋点小彩蛋给对方,比如写点注释给对方。双方通过互相交战,频繁发布,居然聊的挺 high 的。比如问问对方,北京房价是不是很高啊?对方回应,欧巴,我可是凭本事吃饭哦。继续问,摇到号了吗?诸如此类等等。这样的事情你来我往的,很容易动摇对方的军心,还是很有作用的。试想一下,如果你的爬虫工程师在大年三十还苦逼加班的时候,看到对方留言说自己拿到了 n 个月的年终奖,你觉得你的工程师,离辞职还远吗?最后,我们终于搞出了大动作,觉得一定可以坑对方很久了。我们还特意去一家小火锅店吃了一顿,庆祝一下,准备明天上线。大家都知道,一般立 Flag 的下场都比较惨的,两个小时的自助火锅,我们刚吃五分钟,就得到了我们投资竞争对手的消息。后面的一个多小时,团队气氛都很尴尬,谁也说不出什么话。我们组有个实习生,后来鼓足勇气问了我一个问题:“我还能留下来吗?”毕竟,大部分情况下,技术还是要屈服于资本的力量。爬虫反爬虫的未来与竞争对手和解之后,我们去拜访对方,大家坐在了一起。之前网上自称妹子的,一个个都是五大三粗的汉子,这让我们相当绝望。在场唯一的一个妹子还是我们自己带过去的(就是上面提到的实习生),感觉套路了这么久,最终还是被对方套路了。好在,吃的喝的都很好,大家玩的还是比较 high 的。后续就是和平年代啦,大家不打仗了,反爬虫的逻辑扔在那做个防御,然后就开放白名单允许对方爬取了。群里经常叫的就是:xxx 你怎么频率这么高,xxx 你为什么这个接口没给我开放,为什么我爬的东西不对我靠你是不是把我封了啊,诸如此类的。和平年代的反爬虫比战争年代还难做,因为战争年代,误伤率只要不是太高,公司就可以接受。和平年代大家不能搞事情,误伤率稍稍多一点,就会有人叫:好好的不赚钱,瞎搞什么搞。此外,战争年代只要不拦截用户,就不算误伤。和平年代还要考虑白名单,拦截了合作伙伴也是误伤,因此各方面会更保守一些。不过,总体来说还是和平年代比较 happy,毕竟,谁会喜欢没事加班玩呢。然而和平持续的不是很久,很快就有了新的竞争对手选择爬虫来与我们打,毕竟,这是一个利益驱使的世界。只要有大量的利润,资本家就会杀人放火,这不是我们这些技术人员可以决定的,我们希望天下无虫,但是我们又有什么权利呢。好在,这样可以催生更多的职位,顺便提高大家的身价,也算是个好事情吧。
01-31
2018
webrtc中rtcp反馈与码率控制模块分析
0. 参考文档1 google congestion control1. 简介webrtc的带宽估计分为两部分,一部分为发送端根据rtcp反馈信息进行反馈,另一部分为接收端根据收到的rtp数据进行相应的码率估计[1]。 本文先分析发送端根据rtcp反馈信息进行码率调整的部分代码。具体计算公式: 2. 代码结构2.1 类关系rtp_stream_receiver中有一个继承自抽象类RtpRtcp的ModuleRtpRtcpImpl,ModuleRtpRtcpImpl中有一个rtcp_receiver。当有RTCP包到来时,逐层处理至rtcp_receiver,当包是rtcp receiver report包,则会将包解析,然后在ModuleRtpRtcpImpl中再次调用rtcp_receiver中的TriggerCallbacksFromRTCPPacket函数,触发对应rtcp的一些事件,反馈触发的主要是_cbRtcpBandwidthObserver的观察者(RtcpBandwidthObserverImpl),这个观察者收到对应的report block之后会计算成带宽估计所需要的参数,并调用属主bitratecontrolImpl类对带宽进行估计,这里会调用SendSideBandwidthEstimation中的UpdateReceiverBlock进行实际的带宽评估。2.2 调用关系图3. 代码分析3.1 HandleReportBlock这个函数中最主要的部分就是RTT的计算,webrtc中对于RTT平滑的因子是一个线性增长的因子。/* 这个函数根据对应的report block生成了一个新的RTCPReportBlockInformation结构体,  * 并计算出对应的RTT,多report block在调用点处执行循环。  */ void RTCPReceiver::HandleReportBlock(     const RTCPUtility::RTCPPacket& rtcpPacket,     RTCPPacketInformation& rtcpPacketInformation,     uint32_t remoteSSRC)     EXCLUSIVE_LOCKS_REQUIRED(_criticalSectionRTCPReceiver) {   // This will be called once per report block in the RTCP packet.   // We filter out all report blocks that are not for us.   // Each packet has max 31 RR blocks.   //   // We can calc RTT if we send a send report and get a report block back.   // |rtcpPacket.ReportBlockItem.SSRC| is the SSRC identifier of the source to   // which the information in this reception report block pertains.   // Filter out all report blocks that are not for us.   if (registered_ssrcs_.find(rtcpPacket.ReportBlockItem.SSRC) ==       registered_ssrcs_.end()) {     // This block is not for us ignore it.     return;   }   RTCPReportBlockInformation* reportBlock =       CreateOrGetReportBlockInformation(remoteSSRC,                                         rtcpPacket.ReportBlockItem.SSRC);   if (reportBlock == NULL) {     LOG(LS_WARNING) << "Failed to CreateReportBlockInformation("                     << remoteSSRC << ")";     return;   }   // 用于RTCP超时的计算。   _lastReceivedRrMs = _clock->TimeInMilliseconds();   // 其他字段的拷贝。   const RTCPPacketReportBlockItem& rb = rtcpPacket.ReportBlockItem;   reportBlock->remoteReceiveBlock.remoteSSRC = remoteSSRC;   reportBlock->remoteReceiveBlock.sourceSSRC = rb.SSRC;   reportBlock->remoteReceiveBlock.fractionLost = rb.FractionLost;   reportBlock->remoteReceiveBlock.cumulativeLost =       rb.CumulativeNumOfPacketsLost;   if (rb.ExtendedHighestSequenceNumber >       reportBlock->remoteReceiveBlock.extendedHighSeqNum) {     // We have successfully delivered new RTP packets to the remote side after     // the last RR was sent from the remote side.     _lastIncreasedSequenceNumberMs = _lastReceivedRrMs;   }   reportBlock->remoteReceiveBlock.extendedHighSeqNum =       rb.ExtendedHighestSequenceNumber;   reportBlock->remoteReceiveBlock.jitter = rb.Jitter;   reportBlock->remoteReceiveBlock.delaySinceLastSR = rb.DelayLastSR;   reportBlock->remoteReceiveBlock.lastSR = rb.LastSR;   if (rtcpPacket.ReportBlockItem.Jitter > reportBlock->remoteMaxJitter) {     reportBlock->remoteMaxJitter = rtcpPacket.ReportBlockItem.Jitter;   }   int64_t rtt = 0;   uint32_t send_time = rtcpPacket.ReportBlockItem.LastSR;   // RFC3550, section 6.4.1, LSR field discription states:   // If no SR has been received yet, the field is set to zero.   // Receiver rtp_rtcp module is not expected to calculate rtt using   // Sender Reports even if it accidentally can.   if (!receiver_only_ && send_time != 0) {     // 当RR在SR之前发送,send_time为0.     // delay计算:     // Send SR                                                       Receive RR     //  |                          delay in RR                           |     //  |                        ||                         |     //  ||             ||     //     // RTT = total_time - delay_in_RR     //     = receiver_rr_time - send_sr_time - delay_in_RR     // 即使中间几个SR丢包,但是如果RTT本身是平滑的,那么RTT不会受到这几个丢包的影响     // 因为SR->RR之间的delay可以精确计算。     uint32_t delay = rtcpPacket.ReportBlockItem.DelayLastSR;     // Local NTP time.     uint32_t receive_time = CompactNtp(NtpTime(*_clock));     // RTT in 1/(2^16) seconds.     uint32_t rtt_ntp = receive_time - delay - send_time;     // Convert to 1/1000 seconds (milliseconds).     rtt = CompactNtpRttToMs(rtt_ntp);     if (rtt > reportBlock->maxRTT) {       // Store max RTT.       reportBlock->maxRTT = rtt;     }     if (reportBlock->minRTT == 0) {       // First RTT.       reportBlock->minRTT = rtt;     } else if (rtt < reportBlock->minRTT) {       // Store min RTT.       reportBlock->minRTT = rtt;     }     // Store last RTT.     reportBlock->RTT = rtt;     // store average RTT     // RTT的平滑计算。     // 如果这个块是在CreateOrGetReportBlockInformation新生成的,     // 则权重会从0开始随着受到的report逐渐递增。     // srtt(i) = i/(i+1)*srtt(i-1) + 1/(i+1)*rtt + 0.5     if (reportBlock->numAverageCalcs != 0) {       float ac = static_cast(reportBlock->numAverageCalcs);       float newAverage =           ((ac / (ac + 1)) * reportBlock->avgRTT) + ((1 / (ac + 1)) * rtt);       reportBlock->avgRTT = static_cast(newAverage + 0.5f);     } else {       // First RTT.       reportBlock->avgRTT = rtt;     }     reportBlock->numAverageCalcs++;   }   TRACE_COUNTER_ID1(TRACE_DISABLED_BY_DEFAULT("webrtc_rtp"), "RR_RTT", rb.SSRC,                     rtt);   // 添加回rtcpPacketInformation,在ModuleRtpRtcpImpl中会使用这个进行事件回调。   rtcpPacketInformation.AddReportInfo(*reportBlock); }3.2 UpdateMinHistory这个函数主要用于更新变量min_bitrate_history_,这个变量将会作用于上升区间,用来作为基数,这里简单描述下。// Updates history of min bitrates. // After this method returns min_bitrate_history_.front().second contains the // min bitrate used during last kBweIncreaseIntervalMs. // 主要结合这个函数解释下变量min_bitrate_history_ // 这个变量的两个维度,front记录的是离当前最远的时间, // 每个速率都是按照时间先后顺序逐渐push到尾部。 // 因此更新的时候,需要先将超时的元素从列表头剔除。 // 后一个维度是最小速率值, // 在相同的时间区间内,保留最小的速率值。 // |-------Interval 1---------|----------Interval 2------| // |                          |                          | // |--t1 < t2 < t3 < t4 < t5--|--t1 < t2 < t3 < t4 < t5--| // 这样的操作较为简单,不用在每次插入元素时去判断对应的时间区域,再找到对应时间区间的最小值,用部分冗余的内存换取操作的快捷。 void SendSideBandwidthEstimation::UpdateMinHistory(int64_t now_ms) {   // Remove old data points from history.   // Since history precision is in ms, add one so it is able to increase   // bitrate if it is off by as little as 0.5ms.   while (!min_bitrate_history_.empty() &&          now_ms - min_bitrate_history_.front().first + 1 >              kBweIncreaseIntervalMs) {     min_bitrate_history_.pop_front();   }   // Typical minimum sliding-window algorithm: Pop values higher than current   // bitrate before pushing it.   while (!min_bitrate_history_.empty() &&          bitrate_ <= min_bitrate_history_.back().second) {     min_bitrate_history_.pop_back();   }   min_bitrate_history_.push_back(std::make_pair(now_ms, bitrate_)); }3.3 UpdateEstimate函数UpdateReceiverBlock会根据当前的report block对当前带宽估计的一些变量进行相应的赋值,此外,只有当传输包的数量达到一定数量才会再次触发带宽估计的调整。函数UpdateEstimate是主要用于带宽估计的函数。void SendSideBandwidthEstimation::UpdateEstimate(int64_t now_ms) {   // We trust the REMB and/or delay-based estimate during the first 2 seconds if   // we haven't had any packet loss reported, to allow startup bitrate probing.   if (last_fraction_loss_ == 0 && IsInStartPhase(now_ms)) {     uint32_t prev_bitrate = bitrate_;     // bwe_incoming_是remb更新的值,如果当前无丢包且在启动阶段,直接使用remb的值。     if (bwe_incoming_ > bitrate_)       bitrate_ = CapBitrateToThresholds(now_ms, bwe_incoming_);       ...     }   }   UpdateMinHistory(now_ms);   // Only start updating bitrate when receiving receiver blocks.   // TODO(pbos): Handle the case when no receiver report is received for a very   // long time.   if (time_last_receiver_block_ms_ != -1) {     if (last_fraction_loss_ <= 5) {       // Loss < 2%: Increase rate by 8% of the min bitrate in the last       // kBweIncreaseIntervalMs.       // Note that by remembering the bitrate over the last second one can       // rampup up one second faster than if only allowed to start ramping       // at 8% per second rate now. E.g.:       //   If sending a constant 100kbps it can rampup immediatly to 108kbps       //   whenever a receiver report is received with lower packet loss.       //   If instead one would do: bitrate_ *= 1.08^(delta time), it would       //   take over one second since the lower packet loss to achieve 108kbps.         //TODO:tjl       // 这里与公式有一定不同:       // 1. 系数不同,且附带一定的修正值(向上取整加1kbps)       // 2. 取的是上一个时间间隔之内最小值,比较平滑。       bitrate_ = static_cast(           min_bitrate_history_.front().second * 1.08 + 0.5);       // Add 1 kbps extra, just to make sure that we do not get stuck       // (gives a little extra increase at low rates, negligible at higher       // rates).       bitrate_ += 1000;       event_log_->LogBwePacketLossEvent(           bitrate_, last_fraction_loss_,           expected_packets_since_last_loss_update_);     } else if (last_fraction_loss_  10%: Limit the rate decreases to once a kBweDecreaseIntervalMs +       // rtt.       if (!has_decreased_since_last_fraction_loss_ &&           (now_ms - time_last_decrease_ms_) >=               (kBweDecreaseIntervalMs + last_round_trip_time_ms_)) {         time_last_decrease_ms_ = now_ms;         // Reduce rate:         //   newRate = rate * (1 - 0.5*lossRate);         //   where packetLoss = 256*lossRate;           //TODO:tjl         // 当从未开始降低窗口值,且距离上一次衰减的时间差大于衰减周期加上rtt。         // 其实当前貌似只有这个case下会对这两个变量赋值。         // 这里的last_fraction_loss_是一次统计间隔(一定包数)之间的总丢包率。         // 丢包率的单位是1/256,因此这里是(1 - 丢包率/2) * 当前速率         // 与公式相同。         bitrate_ = static_cast(             (bitrate_ * static_cast(512 - last_fraction_loss_)) /             512.0);         has_decreased_since_last_fraction_loss_ = true;       }       event_log_->LogBwePacketLossEvent(           bitrate_, last_fraction_loss_,           expected_packets_since_last_loss_update_);     }   }   // 在有效范围内修正。   bitrate_ = CapBitrateToThresholds(now_ms, bitrate_); }
01-22
2018
常见SEO关键词选择误区及选择技巧
如果网站是一个企业的脸,然后关键词是面部美容。只有当化妆品用得好,会有更多的人记得你,知道你。当我们在做关键词的排名,我们该如何选择关键词?你不能陷入这些误区!     一、关键词是从来不使用的  有的伙伴为了让网站很快又好的排名,会自创一些词。诚然这样的词会很快又好的排名。但是同时这样的词也没有搜索量。即使排在首页也没有多大的效果。  二、不一定最热门的关键词就是最适合的  热门的关键词可以带来大量的流量,但大多都没有转化,形成了流量的浪费,这个关键词不一定是网站合适的。比如“SEO”这个词每天的搜索量是很大,但是很多搜索SEO的人并不是想学习SEO,很可能是行业内的人观察排名的变化,也有可能是小白想对这个词有些了解而已。所以热的词不一定就是好的。  三、在关键词的选取上没有考虑到用户意图  关键词的选取时,要考虑用户的心理,以达到转化促成销售的关键词为首选。在搜索中,往往产品的性能搜索是购买前的比较选择,而产品的名字搜索是消费的选择。  四、只选通用词而没有选长尾词  对于网站选取关键词来说,大多网站都重视了通用关键词,没有选择长尾关键词。比如我们做祛痘的产品,往往主关键词就是。  五、关键词的混乱  对有些关键词来说,一个词是两个概念,它包含了两个不同的意思,这样会造成混乱,要选择最精准的关键词。还有就是选择与自己不相关的关键词,虽然可以带来流量,但是无转化。seo关键词选择技巧一:关键词收缩量越大越好很多人在选取关键词时会通过工具查看这个关键词的搜索量大小,然后就想当然的认为搜索量大的词,就是好的关键词,因为搜索量大,说明搜索的人多,那么展现的次数自然就多,这么像当然无可厚非,但是并不是关键词搜索量大就一定好。关键是精准性。比如做做旅游行业的,旅游景点搜索量日均一万就比旅游团搜索量日均三千大的多,但是旅游团的精准度却高于旅游景点。二:核心关键词越多越好很多人在设置核心关键词的时候恨不得把自己所有产品都放在核心关键词上,但其实并不是设置的核心越多越好,核心关键词应该以主营产品为主,因为核心关键词太多,太分散就会分薄网站权重,所以并不建议设置太多核心关键词,一般以5到6个为宜,以核心产品为主,如果真的有很多种产品都想做核心关键词,那么可以选择做站群。(至于站群的问题这里就先不讲了)我们平时所说的关键词分类是根据关键词的竞争度,搜索量这些来分的。 按照淘宝SEO,我们对关键词的属性可分为三个级别: 】1:顶级关键词(一级词) 一般由3个字组成,搜索量非常大,竞争度非常大如, 新手很难通过 。顶级关键词获得稳定的流量如:“手机”,“电脑”,“男装”,“女装”,“连衣裙” 2:二级关键词(二级词) 一般由4、5个字组成,竞争度比较大,新手也比较难通过二极关键词获得 稳定的流量。如“苹果手机”,“商务男装”,“韩版女装”,“日式连衣裙”“新春女装”3:长尾关键词 长尾关键词一般由 5个字或者更多关键词组成,精准度高,竞争度不大,搜索量不大,很难用长尾关键词打造爆款宝贝,如:“苹果手机第四代黑色” “日式碎花莫代尔长连衣裙”“雪纺韩版女装”“新款春季女装”。
01-22
2018
网站长尾关键词部局方法
  长尾关键词布局非常重要---因为涉及到网站后期优化效果。布局长尾词的第一点是挖掘和筛选长尾词,然后根据长尾词的竞争度以及相关性布局在网站的栏目页和内页。内页的长尾词围绕栏目页的竞争度大一点的短词来布局,不同栏目的长尾词不能互相交叉以及重叠。   挖掘长尾关键词:挖掘长尾关键词的方法是利用百度相关搜索、百度下拉框以及百度知道、搜搜问问这些工具寻找用户经常搜索的长尾关键词。也可以利用其它的一些工具,比如百度推广助手,挖掘的长尾词会更加精确;观其站长工具、站长工具等。   挖掘好的关键词放在电子表格里面,去除重复的以及明显刷的长尾词和不相关长尾词。比如攻丝机一类的词出现钢管舞相关的长尾词,那么公司级别相关的长尾词必须去除,这样的词和关键词本身毫无相关。   栏目页和内页布局:在筛选关键词时有短词和长词的区分、如果是短词并且指数和竞争度比较大就作为栏目页的关键词,而长词和指数小竞争度小的关键词作为内页长尾词排名。比如攻丝机厂家是一个长尾词,但指数和竞争度相对比较大,所以作为栏目页的长尾词布局,而卧室攻丝机厂家指数和竞争度都比较小,所以作为内页长尾词布局。   攻丝机长尾关键词案例   攻丝机企业长尾关键词布局案例,可供参考   问:SEO如何布局长尾关键词?   请问如何给网站做长尾关键词布局?关键词可以挖掘出很多,但不知道怎么把这些关键词使用上。   1、什么是长尾关键词?   2006年的时候,营销管理业界有一个新的理论,叫长尾理论。长尾理论不同于二八定律。长尾理论一出现,就首先得到了SEO业内的高度认同。直接借用了长尾这个名词来定义领域里面比较泛化而大量的关键词,统称为长尾关键词。   2、长尾理论是互联网时代的独有现象   马云之前有个理论,说我的产品规模要翻几倍,增加一些服务器就好了。沃尔玛要再建多少多少店面。因为内容和产品的数字化后,信息存储方便。搜索引擎的出现,让长尾理论得以体现出来。所以,对网站来说也是内容越多越好。内容多,就意味着总会有一些内容适合这类需求的用户。   3、长尾关键词等于做内容   没有内容页面来承载关键词,那关键词就无法部署,这是要解决关键词落地的问题。关键词是无法脱离内容来谈的。所以,做长尾关键词,本质上就是等于要做内容。如果网站没有几个做内容的策略方法,是很难把长尾关键词策略部署到站内去的。   4、布局长尾关键词常见方法   做聚合,是做长尾关键词策略的常用方法。但聚合的页面质量不好的话,也会影响长尾策略效果的发挥。长尾关键词因为关键词量大的原因,所以只能考虑那种能够产生大量内容的方法来布局长尾关键词。   5、长尾关键词的布局是SEO工作的核心   把长尾关键词布局得当,可以说是SEO工作的最重要的核心一点之一了。这个问题要解决并不简单。因为解决的好,就等于网站能够长期生产出高质量的内容。所以,这个问题如何解决的更好,是值得SEO人员好好思考的问题。      我的观点:不做长尾策略,网站的SEO效果终归有限。所以,如何部署长尾关键词策略,就是每个网站要发展起来过程中必须要解决的问题:   筛选长尾词的作用是找出有价值的长尾关键词并且避免重复,而做指数和竞争度分析是为了避免重复内容出现。   1、禁止出现完全不相干和刷出来的长尾关键词,有的SEO为了作弊会用工具刷长尾关键词   2、很多站长舍不得意思相近的长尾词,便把两个差不多的长尾词布局在站内,但是内容还差不多,大量这样的内容会导致网站降权。因为搜索引擎不会把一个内容重复的网站排名放在首页的。   3、在做栏目分类时,栏目下面内页的长尾词一定是栏目关键词的扩展词或者是相关长尾词,站内的长尾词不能交叉显示在网站的其他栏目。
01-19
2018
提升网站点击率的SEO标题写法
比如说我们举个例子,题目是:68岁老干妈,身价70亿,开劳斯莱斯ZF送AA8888牌照,她才是中国最任性的女神。这是我去年写的一个80万加阅读量的文章,30个字左右的标题中用了三组数字,一组字母,所以他在朋友圈里肯定是属于点开率比较高的那种,特别吸引眼球。再举个例子,坑了170名员工败光500万CEO跑了,这只是创业公司倒闭潮中的一个样本。这是我去年在资本寒冬的时候写的一个公司倒闭的一个案例。这篇文章的阅读量70万家应该是这篇文章是30个字左右的标题中利用了两个数字,一组字母也是非常吸引眼球的,辨识度非常高,这是第一点。第二点为什么要用数字。数字能够给人基本的预期,加速读者的判断时间。什么判断时间,就是读者判断是否要打开你这篇文章的时间。那数据能给人基本的预期,给人什么基本的预期呢?就是说我们通过数字可以把整个文章的架构和重点表现出来。举个例子,任正飞新闻联播讲话:48秒230个字却值得每个中国人反思。通过48秒230个字给读者一个基本的预期,我进来这篇文章主要是看任正非讲的这230个字。再举个例子,关于微信运营新手最容易犯的30个错误,他用30这个数字给人一个基本的预期,就是说我读了这篇文章很可能就会能帮我避免以后犯这30个错误,这是第二点。第三点就是数字的表达,它天然就更具体更直观。我们简单举个例子,张一鸣,十年面世过2000人,混得好的年轻人都有这五种特质。我想表达张一民特别会看人,看得特别准,我怎么表达他看过的人多,怎么个多法,十年面世过2000人,在表达一下就更具体更直观了,所以这就是为什么要用数字。第一数字的辨识度高吸眼球。第二,数字能够给人基本的预期。第三数字的表达天然更具体更直观。这三点具体怎么做,一般要用到哪些数字,你写完文章一般怎么操作呢?我的经验是你写完文章之后,把文章通读一遍挨着读一行一行的读,只要带数字的点,你把它提炼出来,你把它放在一块,看看这篇文章中都是有哪些点哪些数字。然后看看用什么样的逻辑结构能尽可能多的多带上几组数字就是能用两组数字就别用一组数字,能用三组就别用两组。我总结了一共有四点,也就是四个方法。第一点用人物的年龄数字,因为大家写文章很多都是写这个人物就有个年龄,我总结了一个套路。年龄小就是年轻有为,年龄大就是老当益壮,年龄不大不小,去找别的角度。我们举一个例子,98年的妹子创业,五个投资大佬争抢融资1500万,90后大叔大妈咋想是吧?98年创业,年轻有为。再举个对立的例子,56岁才创业,如今利润却是华为的1.5倍,它是个让对手发抖的人,老当益壮。跟前面那个对比,我们再举一个对比的例子,年轻有为的例子,17岁,它说要把海洋清洗干净,没人相信。21岁,他做到。再举个对立的例子,褚时健,年轻人太急了,我80岁还在摸爬滚打。老当益壮,所以经常能用到的是人物的年龄数字,我是能很高兴大家。第二个经常用的是表示时间的数字,这个怎么理解呢。就是一般写故事,写一个做一件什么事情的时候,经常用到表示时间的数字。这个我也总结了一个套路叫:时间短,就是发展迅速,时间长,就是工匠精神,意志坚定,时间不长不短,找别的角度。我们举个例子:一分钟卖出3万件衣服,老板比比尔盖茨还有钱,它靠一个字颠覆了整个零售业,这个其实是写的ZARA的老板。但是老板今年是在全球富豪榜上排名第二,去年的时候超过了比尔盖茨,我们表达它发展迅速,就一分钟卖出3万件衣服。再举个例子,近年来发展最快的互联网公司滴滴出行,我们给他起的题目是四年350亿美金的背后滴滴,是这样干掉30家竞争对手的。用时间短来表达它的发展迅速,那时间长就是工匠精神意志坚定。这些题目例子更多,比如说为女朋友连续做了900天不重样的早餐。最会做饭的男人竟然是点点点,意志坚定。日本匠人捏了40年的面团,结果连马卡龙大师都飞过半个地球来请教。40年工匠精神。因为妻子的一句话,他在九平米的拉面店待了46年,有人为了吃上一碗拉面,竟然等了九个小时,工匠精神,46年,九个小时,这是第二点经常用的数字表示时间的数字。第三点这个是用的可能更多就是跟钱有关的数字。谁跟钱有仇,看见钱就是看一下跟钱有关的数字比较多,比如说工资,比如说年薪,比如说身价,估值融资额,这些只要有你尽可能的放在标题里。举个例子,普通文案和优秀文案的区别,这样的标题有人看,但很少人看改成什么样的标题跟钱有关,月薪3000,与月薪3万的文案差别究竟在哪里?这是一篇在朋友圈刷屏的一个文章。再举个例子,从月薪500的行政到执掌750亿美金。这名中学女教师用17年的坚持逆袭人生,这个其实写的是马云背后的女人彭雷。月薪5000,执掌750亿美金,跟钱有关的数字,特别吸引眼球。第四点是什么意思呢?提炼你最想表达的东西,然后用数字来描述。什么意思呢?整篇文章你总有个想表达的东西,你表达穷还是表达富?你表达胖和瘦,你表达高和矮,大和小,长和短,快和慢,远和近,你基本上你表达的所有东西都能找到数字来表达或者是强化。再举个例子,关于新媒体编辑可能感冒的。如何通过爆款标题达到10万加内容。是不是看烦了,是不是不想点开看了。但是修改后的标题可以是这样的,这10篇文章只改了下标题,阅读量就从1万加涨到了10万加。用数字来加强你要表达的东西,从1万加涨到10万加翻了十倍来表达爆款标题有多厉害。所以整个是具体用哪些数字大概这四个方面。最后再给大家提一个注意事项,第一点,也就是大多数小编经常犯的一个错误,他用数字了,但他用的是中文数字。这个是个机会,你把所有的中文数字都代替成阿拉伯数字,这样就对了,这是第一点注意事项。第二点是数字靠前放,尽量靠前放,就是你最初推出来的时候推文的时候如果用户没有去点一下它。这个标题是被折叠的。如果你是一个30次左右的标题,后面20个字基本上就被折叠了,只显示前十个字左右,但是中文它这个逻辑结构其实可以你去调整的,你把它带数字的那部分调在前面,这样是比较好的。