Updated Jan 22, 2016
这篇文章第一版是2015年5月份写的,现在看来有点“过时”了。是的,几个月的时间是能改变很多事情的。所以我决定每隔一段时间把这篇文章更新一次。
湾区日报是一个在旧金山工作的工程师运营的个人博客。该博客每天挑选5篇他主观认为高质量的文章,针对每篇文章拟一个中文标题写一两句简单评论,通过将近10个渠道(比如网站, iOS app ,微博, 微信 , Twitter 等)推荐给读者们。
写这篇文章的“我”就是这个工程师。湾区日报不是一个公司,也不是一个创业项目。只是我的一个 side project。从 2014年8月6日发第一期 以后,运营至今。
看上去很简单,对吧?我一开始做的时候也觉得很简单,很没技术含量,任何有高中文凭具有简单电脑知识的人都能运营这种博客。但为什么同一道命题作文,每个人写的内容不一样,有人能写离题有人能写满分作文?为什么大家都会写字,有人“竟然”能成为作家并靠写字为生?
我在每天利用零散时间发现新文章,然后直觉认为是好文章的就收藏在 Pocket 里。发现新文章的渠道很多,排名不分先后: Hacker News , Medium , Quora ,我订阅的很多博客的RSS,各种社交网络上看到的文章,头脑中突然对某个主题感兴趣临时搜来的文章,与同事交谈中得知的文章等。
我也利用白天的各种零散时间读一两篇文章。但一般都是晚上回家吃完晚饭后,再开始用完整的时间专心读。每天读的文章数量不一定,有时候一天读20几篇,勉强挑出5篇;有时候一天读了5篇,但都觉得很不错,就不读其他的了。这个过程一般要花1到3个钟头。
我一般用 iPad 读文章,然后多任务打开 Slack ,在里面跟我的小机器人(wanqu-ops)对话,有点像程序员工作的时候敲命令行一样。我会告诉小机器人,这是一篇文章的链接,然后它会自动提取文章的标题,url的slug,图片等信息,最后插入到后台数据库;每5篇文章自动生成新的一期,自动生成当天的日期(北京时间)以及当天的期号(比如第502期)。总之,我只要把链接扔给这个小机器人,它就能帮我做这些繁琐的事情了 --一个普通网站的小编会怎么做?会用CMS,然后一项一项手动输入,这个过程不会花太多时间,可能每天十几分钟吧;但我实在太懒了,连每天花这十几分钟都不舍得。
下一步要写简评。我也是跟小机器人对话,比如 “wq post 2672 title 这是一篇好文章” ,就是告诉它:我要把编号 2672 的文章标题改成 “这是一篇好文章”。同理,可以录入文章简评等内容。下图是我在 iPad 上读文章然后用 Slack 写简评:
凑足5篇文章后,我最后给小机器人下达命令:把这期的内容发出。然后它就自动把内容更新到网站,消息推送给 iOS app 的用户,自动发布到微博, Twitter , Facebook 等社交平台。
总之,手动操作的部分主要是读文章与写简评。剩下的繁琐的操作,都是由程序自动完成的。同一道命题作文,程序员的写法是和普通网站小编不一样的;不同程序员的写法也会不同。
我为湾区日报这个 side project 写了一些代码。由于只是 side project,我对它的期望不高,力求用最简单的做法,花最少的时间,用最“快糙猛”的方法实现我想要的功能。
2014年8月份,湾区日报只是处于”闹着玩”的阶段,没有严肃要做的意思。所以那个时期的湾区日报网站只是一些静态网页,用 Pelican 生成的。
之后的网站换成了 Wordpress。换了几个不同的主题。甚至还花了些钱买了 Product Hunt 的主题。
到了2015年3月份的某个周末,我也不知道哪根筋接错了,就宅在家里一天,从头写了现在的这个网站。主要用 Python/Django,Celery,RabbitMQ 以及 Postgres 搭建的。
为什么用Django?为什么能在一天做一个网站?很简单,我在过去两三年自己写了大大小小10几个 Web App,都是写着玩的 side project,都是用 Django/Postgres 搭建的,只是一个套路而已。将自己以前写的代码复制粘贴,当然可以很快拼凑一个网站出来了。
所有网站基本都能简化成这个架构:Web App,Database,Async Worker,Task Queue,以及 Scheduler。其中,Web App就是跑网站代码接受用户的访问请求,所有耗时间的task(比如发邮件,发微博,数据统计等)都扔到Task Queue上,然后Async Worker从Task Queue抓task过来离线处理;而Scheduler就是定时跑程序,很多网站直接用的Cron。
对于湾区日报,Web App就是 Django App,用 uwsgi 跑N个进程,用 supervisord 来管理进程,前面挂一个 nginx 当 load balancer。Database用的是 Postgres 。Task Queue是用 RabbitMQ 。Scheduler用的是 Celery Beat 。而Async Worker是 Celery 。下图是湾区日报后台简单的架构:
湾区日报的所有这些后台的大大小小20几个进程是跑在3台 DigitalOcean 的虚拟机上(每个月花$40多)。按照访问量的增长速度,这样的架构估计至少可以支撑到2020年。当然,我在这3台虚拟机上也跑了其他的 side project,也不算浪费计算资源。
2015年5月份的时候,用了一个周末写了 iOS App。完完全全用 Swift 写的,很不错的体验。前面说了,这只是一个 side project,所以期望不高,我是可以厚着脸皮上线质量低劣的一个周末写的 app 的,反正也没什么人用,以后慢慢改进就是了。
后台的 Api 也是 Django App(抽象出一些通用的内部 api 与网站共用),也是跑了 个 uwsgi 进程然后用 nginx 做 load balancer。
在开发第一版 App 的那个周末,除了完成基本的功能(浏览文章)外,还加入:
这些都只是套路而已。尽管不是 iOS 开发人员,但至少要有这么一个意识。上线一个产品,最基本的东西都要有:收集 metrics,收集 crash 的信息,更新提醒等 --开始做 app 之前有跟同事打听了一下应该用哪些工具,少走了不少弯路。详见这篇博文:两天四夜上线一个 App
湾区日报这个博客有很多推送的渠道。现代博客与10年前的博客不同:10年前的博客就是一个网站,而现代的博客形式多样,同一份内容会以不同的形式通过不同的渠道出现在读者面前。
前面介绍到我用 Slack 与机器人对话。这机器人就是 Hubot -- 它解析我发出的命令,然后调用我在 Django App 里提供的 REST api 发布文章;当文章的状态由 pending 变成 published后,将触发一些 Celery tasks;每个 task 负责一个渠道的发布任务,比如一个task发布到微博,一个task发布到reddit等。
微博:调用微博 api 发布。凡是微博的帖子底部有 “来自 湾区日报BayArea” 字样的,都是由发布系统自动发布的。
微信 :理论上,成为认证用户后,可以调用 api 自动发消息给订阅者们。但我没有成为认证用户,只能手动发布。Hubot会生成每期需要发布的文字,我只要复制粘贴就能发出消息了。对于微信,我基本是放弃了,完全不上心;因为湾区日报这种分享链接的模式很不适合在微信这种平台做。
Twitter :调用 Twitter 的 api 发布。Twitter 的 api 做得不错。
Facebook :调用 Facebook 的 Graph API 发布。
Reddit :调用 Reddit 的 api 发布的。难道有中文用户上 Reddit?我不确定。我只是把 Reddit 当作一种 SEO 的手段而已。
Google+ :Google提供的api很难用,我不愿意花时间研究,于是就用 Buffer 自动同步 Twitter 上发的内容。
邮件订阅 :用 MailChimp 自动读取 RSS,每天定时自动发布。
iOS App 推送:没什么好说的,就是 APNS。
RSS:老旧的,但仍然很有用的 RSS。你会很惊讶,现在很多线上媒体不提供 RSS订阅。
很多读者注意到了,湾区日报在各个社交媒体上不断炒冷饭,重复发布几个月前发过的文章。我在FAQ 里有详细说明原因。简单说,半年后,湾区日报有一半的读者没有读过我以前推荐过的文章,“旧”的文章对他们而言是“新”的。
怎么炒冷饭呢?Celery Beat 每个钟头会有一定概率(不同时段的概率不同)发布一篇旧文到微博,Twitter,Facebook等社交媒体上,比如这条微博。
如果你经常使用湾区日报的 iOS app 或者网站,你就会发现我竟然公开了湾区日报的运营数字:访问量,app下载量,app打开消息推送的用户数,app的付费用户数等。下图告诉你在哪里可以找到湾区日报的各种运营数据:
湾区日报毕竟不是一个公司,我也不跟任何人合作,所以我说的算。我觉得中文的互联网上能找到运营网站的实战案例很少。一个对互联网感兴趣的人,当然希望能看到一个真实的运营案例以及真实的数据,作为一个学习材料 -- 有点像教科书上的案例分析。
这些数据分别从 App Annie 的 api,Google Analytics 的 api,以及我自己的后台数据库去抓去。Celery Beat 每小时开启一个 job 更新一下这些数据。
对于湾区日报的 iOS App,我最关心的数字是有多少用户开启了 push notification;只有开启了 push notification,用户才能及时得知当天的湾区日报的更新。每当有一个新用户开启了 push notification 或付费了,我的 Slack 就得到通知。下图是我 Watch 上来自 Slack 的通知,告诉我此刻有一个新用户下载了 App 并且打开了 push notification:
关于 Slack 与 Hubot,详见这篇博文: 湾区日报的第一个“员工”:Slack/Hubot
网站与 iOS app 上都可以关键词搜索往期的文章。我原来想用 ElasticSearch 来做一下,但想着要多管理一个ElasticSearch就不爽,这样就多了一个 failure point,运维上可能得多花点时间,不划算。
后来受到这篇文章的启发,用一个晚上的时间直接利用 Postgres 做全文索引,并实现了网站与 app 的搜索功能,真是快糙猛的做法。
我一般每星期发布一次新版本的 iOS App。每次的更新其实很有限。我每星期一般只能花不到一小时来写 app 的代码。
网站后台代码的更新主要通过 Slack。仍然是与小机器人对话:“wq deploy”。它就会 checkout 网站的 git repo 的最新 master branch,然后重启相关进程。我用 symlink 做了版本控制,所以如果发现严重的bug,要 rollback 到前一个版本的话,只需要切换一下 symlink,几秒钟的事情。
发布代码变得轻松愉快,且充满自信,这就鼓励了开发人员(我)勇于尝试各种新奇的 idea,勇于快速迭代,快速部署小更新。所以湾区日报一直在进化。湾区日报的今天没有比昨天好多少,但这个季度肯定比上个季度改进了不少。
监控网站是否挂了,我用的是 Pingdom ;如果网站挂了,我会收到短信提醒。监控服务器各种计算资源的使用情况,我用的是 Datadog(2年前去开 Dockercon 的时候,在他们摊位聊了很久,才知道有这么个东西)--理论上,monitoring 与 alerting 都可以自己用开源软件搭建,但是,湾区日报只是一个 side project 而已,有现成可以凑活着用的SaaS方案,就不必花时间自己做了;而且自己做的肯定不如人家专业的。
网站域名的 DNS 管理,我用 CloudFlare 。使用 CloudFlare 还有其他的好处,比如它帮我挡住了不少恶意请求,帮我缓存了各种静态文件。
每天都有读者来信。当然,大部分的问题都能在 FAQ 里得到解答。但也有很多来信给了我很多鼓励,知道你们从湾区日报推荐的文章里学到了东西得到了成长,我真的很高兴。
微博与微信上的读者来信,大部分都能得到关键词匹配的自动回复,算是缓解了我手动回复的压力。而花时间去写FAQ 页面则省去了回复大部分邮件的时间。
如果把这篇文章给 2014年8月6日 的我看,我肯定不会做湾区日报这个 side project 了,太花时间了 -- 竟然要做网站,要做发布系统,要学 Swift 然后写 iOS app,还要每周发布 app 新版本,还要管理好几个发布渠道,还要做客服回邮件,还要每天花1到3个钟头读文章写简评。
幸亏写这篇文章的时候,大部分困难的工作已经完成了。这就是互联网时代的产品迭代,是一个循序渐进的过程,是个人与产品共同成长的过程。你不能期望一夜成功 -- 其实,你也可以一夜成功,只是这一夜发生在第1000天甚至第3600天。
运营湾区日报是没什么经济上的回报的,尽管有一些广告收入与app内购的收入,但与投入的时间(再乘以一个哪怕是实习生的时薪)相比,相当微不足道。之所以我还能继续做下去,是看中知识的积累与个人的成长。这些运营经验以及从每天5篇文章里学到的东西,是完全可以运用到我其他的项目上的。就算最后真的什么都没做成,也是一段美好的回忆。临死的时候,回顾一生,至少我还能说,我做过一个叫湾区日报的博客,每天有那么几万个读者(我也是读者之一),我们每天都在进步。这就够了。
欢迎下载使用 湾区日报的 iOS App ,或在网站上看看往期文章。