怎样和开发者交流

项目做多了,总会碰到很多争论,特别是在需求交接的时候。今天说一说如何与开发者交流的话题。

通俗易懂

人懂得多了,难免会有点掉书袋,一方面是习惯使然,另一方面也有卖弄的嫌疑。譬如海龟喜欢聊天中蹦几个外语单词,产品会找来一堆方法论和缩写,程序员也喜欢说术语。

当面交流不像书面,阅读者遇到不懂的时候可以随时 Google。遇到有人听不懂的时候,就需要停下来解释这些词是什么意思,打断了本来需要表达的内容。大多数时候,没听懂的会装作听懂,然后在心里骂了千百遍。

通俗易懂是基本原则,把意思表达的小孩子也能听懂,没有人会觉得啰嗦,反而会避免不少误解。

可能的话,边画边说。

逻辑清晰

开发者对逻辑的敏感程度非常的高,如果有自相矛盾的地方,可不是用一两句话就可以糊弄过去的,一定要注意逻辑清晰。

开会中我听到最多的一句话就是,「这里走不通啊」。开发者的思维就像水流,总是尽可能的流向每条支流,如果哪一条无路可达,就会揪着不放。

所以,请一定将逻辑思考清楚,为了达到目标,少绕弯子,找到捷径是最好的方式。如果一个问题自己都没想清楚,就不要忙着公开讨论。

学会 Coding

如今的代码世界已经不是一群胡子拉碴头发很长的大叔们的专利了,在未来,编写代码可能会变得和使用锤子一样平常,我很好奇有人会因为太难或没有必要而拒绝使用锤子。

开发的学习成本已经降得非常低,有很多不错的网站和文档可以查询,如果你想做互联网应用开发,入门 JS 也不过就是一周的事情。

学会 Coding 带来的好处是显而易见的,忽略语法的因素,任何语言都是单纯的逻辑表达,如果你能通过代码将思考过程表达出来,胜过写上一千页的 PRD。同时,和开发者找到了一门共同语言,就不会让他们将你拒之门外。

纯粹的 API

前不久有同事和我谈论起现下 API 维护的困难,抱怨诸多,总结起来大概就是两点,一是需求考虑不完善,很多地方需要在开发中补充条件,二是向前兼容困难,很多历史遗留无法修改,代码臃肿。

在我看来,这些问题的产生,都只有一个原因,就是现有 API 的设计和开发理念,不够「纯粹」。API 的设计就好比凿山开路,必须沿着最短的途径到达目标。有些人为了「Flexible」,喜欢在开发中「留一手」,假象出很多未来可能出现的情况,用额外的代码来做兼容。例如设计一套简单的权限系统,本来简单的 ACL 就能实现,非要觉得以后可能会增加更多的管理员类型,而去赋予各种角色,关系,权限表,这是需求上的多余。再比如设计一套 CURD API,在刚起步的时候就考虑缓存,异步,Job 等等,这是架构上的多余。

如今的 Web 设计趋势是去芜存菁,去掉繁复的视觉效果,注重排版,留白,突出内容,因为 Web 最重要的功能还是让用户获取信息。API 设计有相通之处,一个「纯粹」的 API,应始终保持单纯与直观,一个叫「Create user」的 API,目的就只有一个,创建这条用户信息。至于是否要给这个人发邮件,要帮他初始化哪些业务资料,这些统统都拆分到 Service 中去做,如果需求中没提到这些,那么这个 Service 更没必要存在。这样简单设计的代码将具有更长的生命力,在历经无数次需求修改之后,这些代码能像水中冲刷的基石一样,屹立不倒。

现实已经如此复杂,API 还是让它单纯一些吧。

工具软件的未来

上一篇工具软件的困境分析了我对工具软件现状的看法,归纳一下就是两个问题:1. 使用体验难创新。2. 用户需求难覆盖。下面说一下我的解法。

新的平台

颠覆性的创新更容易出现在新的平台上,移动设备的普及让人们习惯了手势操作。但在今天,移动设备上做交互创新已然不是一个明智的选择,而新的平台其实就在我们身边。

一个叫 Pokemon GO 的游戏火遍了全世界,其最大的特点就是利用了增强现实技术,让人们在现实世界中寻找游戏的乐趣。

增强现实和虚拟现实之所以有吸引力,是因为他能给人带来新奇的同时又不感到陌生,天然的容易上手。工具软件在这个领域大有作为,例如,如今许多人喜欢用电子名片,或者把自己的信息发布在互联网上,如果软件能通过摄像头识别出对方并且显示信息的话,对于我这种永远记不住陌生人名字的人来说,实在算是一种福音。同样适用这种技术还有地图,视频,社交,以及很多意想不到的地方,充斥着生活的方方面面。

如果觉得新的平台实验性太强,那么在旧的平台上,也可以用新的方式来创新。在我看来,语音在移动平台上还没有完全发挥出应有的作用,目前的技术对于语音识别的准确率已经完全没有问题,难点在于对语义的分析和揣测用户的目的。Siri 大家都觉得好玩,但是不好用,问题就在于人们不知道能用它干什么,Siri 也「听」不懂你想干什么。而这在目标比较单一的工具软件中就能得到很好的应用。如果有一款应用是用来推荐餐馆的,那么我更乐于通过自然语言的方式让他为我推荐附近好吃的餐馆,而不是每次想好了关键词再写在输入框里。

海纳百川

工具软件需要应对用户提出的各种需求,这些需求零散,独立,无法整合在一个应用中,然而对于留住用户又必不可少。传统软件的做法是自己开发,然后发行一款配置无限繁琐的软件,或者针对大客户做定制化开发。这种做法在工具匮乏的时代可以在市场中站稳脚跟,如今成本这么高的做法是走不通了。

于是就出现了开放 API,各个软件将自己的 API 开放出来,供其他软件接入。这在工具类软件中尤其鲜明。一款好用的软件,只需要做好自己的核心功能,并接入其他优秀软件。

仔细想一想,这其实就是各种软件在做的「应用平台」。但是我不愿意将它称作「平台」,而更乐意称作「协议」。「平台」意味着大家只能加入到我的软件生态中来,「协议」则表明了一种开放的态度,大家可以自由的选择与我的协议对接,或选择其他应用的协议,甚至利用我的协议,和其他第三方应用对接。

服务至上

软件作为一种看不见摸不着的产品,天然的隔绝了用户和开发者,用户看不到开发软件背后的付出,于是很容易低估软件的成本。现实的软件发行方式也增强了用户的这种观念,大部分软件都以免费的方式发行,软件变得和说明书一样,成为了「载体」的附庸。

而在其他领域,用户的付费意愿则比软件高得多,用户拿到一台相机,很容易通过做工和质感估算出这台相机的成本,而其附带的软件则看起来一文不值。甚至改变了世界的 iPhone,大部分用户还是直接将它和手里的手机划上等号,而很少有人去了解 iOS 的成本。

这种观念根深蒂固,那么工具软件该从另一个角度出发,找到自己的「载体」。如果工具软件没有对应的硬件,可以将与软件配套的服务作为「载体」来出售。例如给用户的咨询,培训,发展线下的社区,一方面可以给开发者带来直接的收益,同时也大大提高了用户粘性。一款软件能否和竞争者拉开差距,更多的是看与软件配套的服务,我想这也是「软件即服务」的定义。

写在最后

在软件这个行业,每天都能看到很多很棒的点子,希望这些点子能尽早变成现实,而不是像我这样的拖延症患者,挖了一堆,离完成却遥遥无期。

工具软件的困境

这个话题改成「软件的困境」,我觉得也没什么不妥,但是船小好调头,从一个领域说起,总能更有说服力。平时写的最多,用的最多的就是工具软件,现在来分析一下工具软件的困境。

工欲善其事,必先利其器。随着互联网的普及,软件从一个专业领域的工具,飞入寻常百姓家,成为了人们生活的一部分。软件界的摩尔定律依然有效,曾经动辄上百万的解决方案,如今也能找到免费的替代品。而这些成熟的软件背后,是几百人在键盘面前的敲敲打打,软件的人力成本节节攀升,盈利就成了一个很大的问题。

匮乏的盈利模式

软件的收费模式基本可分两类,一类贩售软件本身的价值,给软件一个定价,由人去下载,例如 App store 上的一票收费软件。另一类是贩售软件附带的服务,例如依靠购买会员,道具等虚拟物品盈利的网站。

上面这两类盈利方式都有局限性,第一类本身造就了一个很高的门槛,大部分人不肯为素未谋面的软件买单,而且这类软件如果能在自己的行业内做到占有量第一,也就意味着触到了天花板,难以发展新用户,老用户也不再买单,这时的出路就只有推出新产品或进行大版本升级,接下来造就的产品,要么华而不实,要么臃肿不堪。

第二类收费模式通常会带一个免费版,满足 80% 用户的需求,然后让另外 20% 用户为软件买单,养活所有的用户和开发者自己。这就很容易造成需求的失衡,为了追求「转化率」,开发者肯定优先考虑付费端的需求,对于大部分用户需要的功能则被「列入计划」迟迟得不到开发。开发者大部分时间在做的都是给需求排优先级,而忘了考虑软件本身的定位。这也是为什么很多软件刚面世时非常惊艳,迭代了几个版本之后却变得平庸。

固化的使用体验

工具软件如何从一并同僚中脱颖而出,使用体验起到了相当重要的作用。软件的发展过程就是一个去繁化简的过程,早期糟糕的软件培养了糟糕的用户习惯,例如常见的顶部菜单就是一个错误的存在,懒惰的开发者将所有的功能藏在菜单里面,让用户通过反复点击去寻找。如果当初的工具设计者能够合理的布置空间,用户使用软件的效率必能得到一个极大的提升。因为用户已经习惯了这种操作,往后的所有桌面端软件都沿袭了顶部菜单的模式,因为改变习惯需要冒较大的风险,体验好坏很难说的清楚,于是用户继续忍耐着打开一个文件需要点击 4 次的操作。

好在如今的移动端应用已经很少用到菜单模式,工具软件很容易在一个新的平台上面找到创新点,例如 Clear 就靠着方便的手势操作获得了大批拥趸。而到了今天,经过了大量 App 的锻炼之后,用户在移动端的使用习惯也已经固化,新的软件将很难再依赖使用体验来吸引用户。

难以满足的需求

关于工具软件应该是大而全还是专注于一个功能,一直难下定论。有意思的是,这个问题在国内外呈现出完全不同的两派做法,在国外我们发现了很多小而美的工具软件,例如前面提到的 Clear, sunrise, paper 等,在国内知名的软件都是功能完备的,例如做支付的微信和做社交的支付宝,似乎每家都觉得能把自家的软件做成操作系统。

一个软件获得了一批种子用户,证明了这个软件的价值,也带来了各种稀奇古怪的需求。工具应用本身的目标是提高用户效率,而如果每个软件只能满足一个需求,用户就不得不在一大堆的软件中频繁切换来满足日常所需,这本身就非常低效。所以我认为工具软件迭代了几个周期之后,免不了加入很多不曾计划的功能,以至于同质化越来越重,竞争对手越来越多,想要脱颖而出,光靠软件自身的素质是非常难的了。

写在最后

如今想要盈利,开发工具软件实在不能算是一门好生意。固化的使用习惯,难以满足的个性化需求,都会让开发者入不敷出。然而市场依然在,需求依然强烈,仔细思考一下工具软件的未来,成为这个领域的黑马也未可知。

生活的品质

说起程序员,在众人眼中可能是最不注重生活品质的群体了,每天穿着印有各种 Logo 的T恤,头发乱糟糟,工作在显示器的包围中,这大概就是对这些人的固有印象了。

现实中,其实每个人心里都有着自己对生活品质的追求。我认识的很多程序员,在工作之余也有着丰富的生活,有的喜欢健身,有的喜欢远足,还有的可能觉得吃烧烤就是最大的乐趣。懂得生活的人,在工作中表现也不会差,因为这些人有着积极乐观的态度,知道去追求更好的生活,也知道工作是提高生活品质的基础。

下面来说说我对于生活品质的理解。

舒适

平时所用未必需要美酒佳肴香车豪宅,舒适是基本的标准。

前不久我在某粗粮品牌电商上买了几条毛巾,号称使用了更好的原料,达到了 A 类的标准。到手之后感觉果然比以前用的好很多,柔软度和吸水性都很好,价格也不贵。以小见大,在购买每件日常用品时,多考虑一下舒适性,比以前的标准提高一点点,那么整个生活品质就有了极大的提升。

便捷

学生时代没有钱,但有时间去折腾。所以需要用一些软件或玩游戏的时候,就会想方设法的去找破解,实在找不到破解时,宁可使用质量较差的免费替代品,也不愿意出钱去购买。我想大多数 80 后都有同样的体验,也逐渐形成了国内软件必须免费才有人用的大环境。

然而不同的时代有不同的消费观,如今软件影响到生活的方方面面,如果一款软件将你每天的计划事项安排的井井有条,节省了大量的时间。如果一款游戏让你爱不释手,陪你度过了大量无聊的时光。那么就它们带来的价值而言,价格就显得微不足道了。

与其花大量的时间在寻找破解上面,不如花少量的钱,获得更好,更早的体验。在虚拟世界如此,现实生活中亦是。

兴趣

就如上面所说,有人喜欢运动,有人喜欢宅,这与性格相关。每天能有几个小时自由的时间,做着自己爱做的事,就是最大的乐趣了。

小的时候,为了融入集体,发展自己兴趣的时候往往也要受别人看法的影响。就拿足球这项运动来说,很多人为其痴迷,也有很多人认为这是一项无聊的运动,但是一旦到了某些大赛的时候,足球就变成了每个人的话题,仿佛所有人都成了球迷。其实大可不必如此,既然是兴趣,就得由自己做主。我喜欢 RTS,喜欢看书,喜欢台球,有段时间还迷过 F1,即使同好的人再少,也能乐在其中。

生活的品质需要以经济作为基础,但是品质的提升却发自内心。如果没有了对美好生活的期待和追求,那么即使腰缠万贯,不过就是金钱的奴隶,成不了生活的主人。

好好享受每一天吧。

存在的意义

外公走的时候 79 岁,从我上次见到外公已经有一段时间了,对于亲人的突然离去,本以为会是一件很难接受的事情,然而真的到了那时,内心却是非常的平静,仿佛一瞬间参透了生命存在的意义。

人这一生,大部分时间都是为别人活着。在社交中在意别人的眼光,在家庭中考虑家人的感受,很少有完全出于自主意愿的生活。这在心理学上也早已有了解释,只有在婴幼儿时期人们才会完全由「本我」驱使,而一旦进入了社会这个大熔炉,大部分的日常行为就由「超我」来驱使。

对于长生不老,自古以来就是人们的终极追求之一,而往常所说的长生基本等同于「本我」的长生。现代医学的发展确实也让人类的平均寿命在不断的提高,但是人力终须遵循自然的规则,生老病死也是人生完整的一个组成部分。与其期待形体的长生不老,不如常常思考怎样让自己的「超我 - 精神」绵延不绝。

纵观历史,有很多人确实做到了死而不朽,例如到现在还在折磨着广大考生的牛顿高斯等辈,让江山如此多娇的各代能人志士们。他们的思想和事迹传颂了千百年,而且想必还能传递到更久的将来。可以说他们的「超我」已经达到了永恒。

然而毕竟不是每个人都能达到这样的高度,即使有人有这样的天赋,也未必时运能济。所以我觉得只要让自己的行为能真实的符合「自我」的意愿,又能实现「超我」做出一点点努力,那么已经算是实现了自己的价值。

在生活和工作中,如果能保持高产,让家人和朋友感受到有你存在的快乐,自己也能在日常中发现自己的价值,那么这一生就是有意义的。

祝外公在天堂安息。

竞技的乐趣

这本是在 2012 年的时候准备写的一篇文章,当时写了个标题就没下文了,现在想来,应该是忙着「竞技」去了,哪有这功夫来写文章嘛。

时过三年,最近开始常玩星际2,于是又想起这个主题,终于可以补全这篇文章。

打星际你快乐吗,这是知乎上面的一个提问,回答的都是星际爱好者,答案自然是一边倒。如果是半年前,我的答案是模棱两可,因为水平实在是菜,被虐是常有的事情,自然也就很难体会到游戏的快乐。好在一段之后之后,总算入了门,也能和同一分组的人打的有来有回,并且开始思考战术,运营,操作,这个时候仿佛才真正感受到这个游戏的魅力,这种快乐不来自于游戏本身,来自和你对战的人,而最终则是来自自己的满足。

RTS 中你的对手是电脑和人,是两种截然不同的体验,电脑循规蹈矩,打的多了,你就会对什么时候会发生什么事情一清二楚,好比是参与考试,努力总能获得回报。而一旦对手换成了真人,变数就增加了很多,总能在一些比赛中出现意想不到的战术,也就是俗称的大招,将每场游戏的体验都变得不同,就好比是工作中遇到难解的问题,很多无法从书中找到答案,以往的知识解决不了,就要强迫你去思考和尝试。

星际2不是一个适合放松休闲的游戏,一场十分钟的游戏就包含了早中晚三个阶段,必须时时刻刻洞察局势调整战略。当战局陷入僵局时,又需要足够的耐心来等待胜利的时机,有无数次我的对手因为耐不住性子强冲而全军覆没,反之亦是如此。我本是一个不服输的人,然而在游戏过程中,却是胜负各半才能让人真正感受到竞技的挑战和乐趣。现在的游戏阶段中,我学到了耐心,尝到了胜利的喜悦,希望以后还能从游戏中得到更多。

只要比赛还没结束,永远不知道下一秒将发生什么。

最后推荐一个歌单,在上班时听着这张专辑写代码,感觉真是爽到飞起。

OKR 解读

什么是 OKR

关于 OKR(Objectives and Key Results)是什么,网上的资料实在是太多了,为了节省时间,在这里简短的概括一下:

  1. 目标(Objectives),为未来的某个节点(通常为三个月)设定几个目标,这些目标需要可量化,有相关性,并且有一定的挑战性
  2. 关键成果(Key Results),为了达成和量化这些目标,为每个目标设置一些关键成果,例如为了完成早晨起床出门的目标,需要完成穿衣,刷牙,洗脸等关键成果。

从我的了解来看,一个好的 OKR 必须具备的三个特征:周期,创新,关联性

周期

周期是指目标需要在有效的时间内达成,之所以把时间放在第一位考虑,因为任何人面对一个没有明确时间的计划是没有执行力可言的。

一个长久的愿景,适合作为理想,而目标则要求我们只争朝夕。

明确的结束时间可以很好的帮助你回顾和开启下一轮任务,形成一个良好的任务周期能让你的工作习惯更为健康。

创新

好的目标应该能激发你的动力,所以必须有一定的挑战性,而创新则能鼓励你走出自己的舒适区,去挑战陌生的领域,激发自己的潜能。

关联性

目标要有关联性,团队中各人的目标都是可见的,在制定目标时,不妨与其他人交流一下,看看自己的目标是否与团队的战略方向一致,是否目标与他人有重合的地方。如果自己的目标与任何人都无法重合,则很有可能是自己跑偏了方向。

一般而言,上级的 Key Results 很可能会与下属的 Objectives 一致,由此在公司中可以形成一个瀑布式的目标关系链,源头就是公司的总体战略。

OKR upon SMART

SMART 是八十年代提出的一种任务制定方式,OKR 也是源自于此。它要求制定任务时参考下面五个指标:

  1. 明确性(Specific)
  2. 可量化(Measurable)
  3. 可实现性(Attainable)
  4. 相关性(Relevant)
  5. 有时间限制(Time-bound)

可以看到 OKR 对于目标的大部分要求都与上述条件契合,但是弱化了”可实现性”,提升了”挑战性”的比重,以激发成员对任务目标的追求。因为 OKR 强调与绩效考核分离,成员在制定更有挑战性的目标时也会少一些顾忌。

然而我却觉得绩效仍然是不可缺少的一环,如果 OKR 不能作为绩效的评估依据,则需要寻找其他的方式来评估绩效,例如 Peer Review 或 Focal Review。但是这又增加了成员的负担和时间成本。而且,无关绩效的目标,实现的动力也将大打折扣了。

OKR vs KPI

OKR 和 KPI 最大的区别就在于 OKR 分离了绩效考核的部分,而 KPI 则使用更突出的量化标准将目标与绩效严格挂钩。这也是 KPI 最为人诟病的一个地方,会导致成员只关注绩效而忽略了本该有的创造性工作,甚至出现绩效造假的情况。

不管 OKR 还是 KPI,让每个成员各尽所长才是制度的目的,而且,找到合适的人,比任何制度更有效。

IM 同步之路

关于 IM 的同步,首先需要提的应当是协议,选择一个好的协议,对系统的扩展性,稳定性都有帮助。我们先来说一说协议的事。

公有协议 vs 私有协议

几乎所有 IM 开发起初都会考虑一下公有协议,即使最后没有选用,也会先对比一下各种协议的优劣。选用公有协议的好处显而易见,开发简单,兼容性好,很多协议也考虑了安全性。下面是两个比较流行的 IM 协议:

  1. XMPP,实现多,体积大,对移动端不友好
  2. MQTT,轻量级,移动端友好,消息送达率高

然而,选择了一个公有协议,也意味着更多的掣肘,协议的臃肿会让你的应用被迫实现很多不必要的接口。另外,扩展上面也不能随性所欲。所以,很多开发者或应用在积累了一定的基础之后,转而制定自己的私有协议。

反观私有协议,可以让应用更轻量,传输更少的数据,但是同时它也增加了开发的难度。我们粗略的看一下实现一个私有协议需要考虑哪些点。

连接协议

首先,选择使用什么连接协议,是你的私有协议的基石,QQ 在互联网萌芽时代选择了 UDP 连接,以支持大规模用户在线,而现代 IM 协议基本被 TCP 协议一统江湖,而且现在各类语言对应的 TCP 库也非常丰富。

信息载体

在信息的编码格式上有二进制和纯文本两种选择,序列化方式则有 xml, json, msgpack 等,综合下来,我认为使用纯文本+json,可读性和消息体积上面都比较理想。

连接/订阅/广播/断线

这四个步骤是一个 IM 协议最基础的功能,也是一个会话从开始到结束的最简单流程,协议中需要明确定义这四个步骤的接口

其他需要考虑的问题

  1. QOS(quality of service),一个好的 IM 协议应该可以为消息设定不同的等级,以便在效率和送达率上面找到一个平衡点,开发者也可以根据实际需求为消息选择是必须确认送达还是可容忍一定的丢失率
  2. 安全性,消息是否需要加密,使用什么加密算法,也是可以在协议中约定的,不过加密必然导致效率降低,如果消息重要程度不高,则可以不为消息加密,据说 QQ 多年以来一直使用明文传输消息(未考证哦)

Restful vs 消息队列

从前后端分离的架构上来看,Restful 是一种理想的方式,接口相对标准化,有规律可循。但是从节约流量的角度看,这并不是一种很理想的方案。

IM 应用对数据同步的实时性要求较高,如果使用 Restful 接口,就会存在需要反复查询同一接口,得到的结果重复率较高的问题。例如简聊的用户信息接口 https://jianliao.com/v2/users/me,在每次进入应用的时候都会查询这个接口,每次得到的数据几乎 99% 都是和上次请求的时候相同的,而为了这 1% 的区别,又不得不反复查询。

而如果将数据离线存储,通过消息队列的方式来更新,则会让请求数量大大减少,加载效率就会得到大幅提升。

简聊在 3.0 的更新中同时也改变了以往 Restful 同步数据的方式,将每次最新的数据对象保存在队列中,客户端则通过队列去同步本地数据,从目前的实现方案来说,改善了频繁抓取热数据的情况,但是仍然有一些遗留问题值得优化和解决:

  1. 队列中保存的数据为完整对象,而不是操作记录,这样做是因为保存完整数据可以避免客户端在同步状态异常时出现无法恢复的脏数据,然而这样在流量上面开销就大得多了。
  2. 队列只追加不更新,这样的一个结果就是导致在队列内部也会出现重复数据,增加了服务端存储的空间,也增加了流量的开销。

数据库同步

最近研究同步的问题,还发现了另一种很有意思的方案,我感觉在未来会成为一种趋势,值得关注。

同步存在于我们开发中的方方面面,在服务端,我们使用数据库集群,以达到横向扩展的需求。做版本控制,我们选择 git,push 和 pull 也是在做同步。是通过版本管理和操作记录,客户端实现增量的同步服务端数据,最后达到保存镜像的目的。

其实做 IM 应用,客户端和服务端的同步,也同样可以借鉴这种思想。现在的移动客户端都使用本地数据库,Web 也有 LocalStorage 可用,所以在离线存储上面不存在问题。于是有人想到了一个方法,来使客户端和服务端保持数据同步,那么在业务上面,前后端就分离的更加清楚了,这就是 PouchDB

PouchDB 借鉴了 CouchDB 的分布式思想,是一个 JavaScript 版本的实现,以便能在客户端利用和 CouchDB 一模一样的接口,由于在数据层进行同步,与业务逻辑无关,双方只需要协商好需要的数据表结构,就可以安心的去做自己平台的事了。

但是同时也留下值得思考的问题:

  1. 怎样将服务端的多用户关系表转换成客户端的单用户数据
  2. 因为同步是双向的,怎样将客户端的单用户数据转换为服务端的多用户关系表
  3. 当服务端数据结构升级时,怎样解决兼容性问题
  4. 大数据量时,diff 的效率非常低下,大量的 diff 会拖慢应用的速度

拓展

如何写出好爬虫

写爬虫可以说是一个程序员必修课,因为上手简单,成效明显,深受各大培训机构和教学材料的青睐。于是无数新手也加入了造轮子的队列,写的爬虫满天飞。

说爬虫上手简单,是有原因的,只要了解了 http 请求和 html/xml 结构,谁都能做出一个可用的爬虫,再辅以一个好用的请求库和解析库,完成老板的任务简直就是分分钟的事情,老婆孩子再也不用担心我加班了有木有?

———————- 下面是转折的分割线 ———————-

但是,这样写出来的爬虫真的达到了生产级别吗,答案是否定的。码农界常说的一个词是 Robust,来看看怎样让我们的爬虫变得更加 Robust

编码问题

互联网上存在各种各样的编码,虽然现代的浏览器都能智能的识别编码,但是对于爬虫来说它们却是需要跨过的第一道坎。

多数网站都会在响应头中给出本站编码,我们只需要检测响应头中 content-type encoding 就能准确的得到站点的编码。再不济也会在 html meta 中标识出编码内容,检测 meta 中的 charset 属性就行了,例如 <meta http-equiv="Content-Type" content="text/html" charset="utf-8"> 就表示该网页使用的是 utf8 编码。

如果站长丧心病狂到什么提示都不给,那么我们就只能靠猜了,iconv 是一个不错的库,可以检测文本内容的编码,而且准确率比较高,在各种语言中也能找到对应的 iconv 模块。

如果最后还是检测不出对应的编码,那就默认当做目前最常见的 utf8 编码吧,这段简单的代码片段可以说明检测编码的过程。

压缩问题

某些网站会在响应内容是使用 gzip 压缩以便节约流量(例如整天被爬的知乎同学),遇到这种站点,爬虫也需要做特殊的解析才能得到最后的纯文本内容。

解析的过程与检测编码类似,先通过响应头检测网站使用的是什么压缩编码,然后使用对应的解压缩方法解压内容即可,使用压缩的网站会在响应头 Content-Encoding 中加上压缩编码,例如 Content-Type: gzip 就表示该网页使用的是 gzip 压缩,这段代码可以说明检测压缩编码的过程

添加 User-Agent

下面的问题就是与内容提供者斗智斗勇的过程了,有些站点会识别爬虫行为然后屏蔽掉一些爬虫的请求。我们要做的就是尽量让我们的爬虫看起来,像个人。

首先请求头中必须添加 User-Agent,如果你还不知道 user-agent 是什么,出门左转看了wiki再回来。由于 ip 轻易无法修改,但是 user-agent 却是可以任意修改的,如果内容网站条件较宽,只使用 user-agent 识别爬虫的话,那么我们就乖乖的写上一些常用浏览器的 user-agent,如果你不知道有哪些,去这里可以查到所有的 ua。

至于专业的搜索引擎,内容网站欢迎还来不及,怎么会屏蔽呢。所以它们一般都会使用自己的特定 ua 标识,例如 Baidu spider,Google spider 等,普通野生的爬虫就不要参考了。

合并相同请求

如果我们的内容提供者地址是由用户添加的,难免碰到会有重复地址的情况,这时最好就是将这些链接给合并掉,以免重复请求。例如 A 和 B 同时添加了链接 abc,那么我们的爬虫只需请求一次 abc 然后将内容分别返回给 A 和 B 就行了,或者请求 abc 之后缓存一段时间,当其他用户添加这个链接时,返回缓存内容就行了。我们的宗旨就是用最少的请求干最多的事。

错误

有的时候爬虫在请求网站时会碰到一些错误,这些有的是由于内容网站的错误,有些是由于权限或受到了屏蔽,在大多数情况下面这些错误都可以通过 http status code 来分辨(如果不知道什么是 http status code,再出门去看一遍 wiki)。

如果区分每个 http status code 太麻烦,有一个基本的原则就是看 code 前缀,例如:

  • 3xx 开头的表示这个地址被跳转到其他地址了,爬虫可以根据响应头跟进,很多库自动做了跳转跟进,返回最终网址的内容,所以找到合适的请求库最重要,例如request
  • 4xx 表示内容网站拒绝了你的请求,有可能是你的 ip 被屏蔽,或者这个地址需要验证。遇到这种情况,可以等待一段时间再次请求,并逐渐增加请求间隔,直到返回的响应头为 2xx。一般的屏蔽会在一段时间后解除。
  • 5xx 表示内容网站挂了或你的服务器网络挂了,遇到这种情况,我们能做的就只能等,等内容网站修复错误。

连续错误次数过多的网站就不要再爬取了,以便节约带宽资源

请求频率

上面提到了错误重试的问题,这一节我们聊一下调整请求频率以防止被屏蔽。

如果你的爬虫现在可以正常访问内容网站,不要玩的太 high,尽量约束一下自己的请求频率,一分钟一次已经是很多网站能够忍耐的底线了,如果内容更新不频繁,可以设置为 20 分钟一次。

此外,还能制定一些比较聪明的策略,例如发现网站内容较上次没有更新,那么下次请求间隔设置为 1.5 倍,依次递增,直到你设定的请求间隔上限。如果内容有了更新,再把请求间隔重置为最小值。这样既不影响及时得到网站的更新,也会尽量减小被屏蔽的风险。

如果你的爬虫 ip 已经被屏蔽了(怎样判断被屏蔽请参考上文),那么就消停一会儿,设定一个较长的请求间隔再尝试,直至解除屏蔽。

分布式

爬虫受制于单台服务器的带宽和请求数限制,往往在高配置的机器中也无法发挥最大的效能。所以将爬虫分布在多台低配高带宽的服务器上是比较合理的做法。至于如果分布式,这个话题聊起来就没完了,可以使用最基础的消息队列(例如 zmq, rabbitmq 或 redis)和经典的 master/worker 结构,来实现多台机器协同工作。

大数据

大数据没有以前那么火了,但是为了提高这篇文章的逼格,仍然是一个值得一提的话题。

我们知道互联网的世界是开放的,很多内容其实不需要我们亲自去爬取,搜索引擎已经帮我们收录了这些网站内容,合理利用搜索引擎的 site: 命令,有时候可以得到比亲自爬取更满意的结果。

Don’t be evil

大部分网站会在站点根目录下包含一个 robots.txt 文件,可以通过 http://域名/robots.txt 来访问,里面的内容表明了站长对于爬虫的限定,如果发现某些内容是站长不希望你爬取的,那么还是乖乖绕过吧。如果不知道 robots.txt 是什么,再去一次 wiki。

结语

以上简单介绍了一下我多年以来作为一名非大数据非分布式非社区明星开发的经验,抛砖引玉,希望能对读者有所帮助。