在 IPython 中自动重新导入包

在使用 IPython 交互性测试编写的函数的时候,可以打开自动重新导入包的功能,这样每次保存后就可以直接测试了。

In [1]: %load_ext autoreload

In [2]: %autoreload 2

其中三个数字的含义是:

  • %autoreload 0 – 关闭自动重新导入
  • %autoreload 1 – 只在 import 语句重新导入
  • %autoreload 2 – 调用的时候自动重新导入

如果想要在 IPython 中自动启用

$ ipython profile create
$ vim ~/.ipython/profile_default/ipython_config.py
c.InteractiveShellApp.extensions = ['autoreload']
c.InteractiveShellApp.exec_lines = ['%autoreload 2']

Go 语言 Map 实战

相比 Rust 中尚未实现 IndexMut 的 Hash 类型来说,Go 中的 Map 实现度可以说是非常高了。

基本用法

Map 的类型是 map[KeyType]ValueType 的。也就是由 Key 类型和 Value 类型同时决定的。声明一个 Map:

var m map[string]int

不过一般很少有人这样写,还是生命并赋值比较常见,还是使用我们的 make 函数:

m = make(map[string]int)
commits := map[string]int{
    "rsc": 3711,
    "r":   2138,
    "gri": 1908,
    "adg": 912,
}

基本上除了 slice,map 和 function 以外,都可以做 map 的键。

赋值

m["route"] = 66

获取值

i := m["route"]  // 如果 route 不存在,那么获取的就是对应的零值
j := m["non-exist"]

删除值

delete(m, "route")  // 如果不存在的话,也不会抛出异常。这里和 Python 不一样

判断是否存在

i, ok := m["route"]

遍历

for key, value := range m {
    fmt.Println("Key:", key, "Value:", value)
}

并发性

map 不是线程安全的。

本周股票复盘(2019-10-20)

上周忘了写了,没有多少操作,这周一齐补上吧。

10-11

清仓了华泰证券和中信证券,亏了不少,以后再不碰金融类股票了,看不懂的还是不能买。

人民网的内容审核业务可能有亮点,逻辑很简单,再大环境言论收紧的情况下,还有谁能比人民日报判断更准确呢?20.73 买入,21.50 卖出了。不过市场的逻辑似乎是四中全会的电子政务,21.00 又买回来了。

卖出了立讯精密,赚了一些,但是显然卖早了。

10-14

看到九月份销量还是没有触底反弹的迹象,卖出了上汽集团,稍微赚了一点。坐等汽车下乡之类的政策落实吧。三季度的 GDP 已经不能再低了,后续应该有促进汽车消费的政策出台。

75.00 买入了一些小熊电器。

10-15

72.50 的价格抄底了小熊电器,然后第二天 80.00 卖出。这一波操作可以说是很溜了。临近双十一,小熊电器应该不会很低,而且本身的业绩也是值得期待的。

18.00 加仓了歌尔股份,之前14左右买的,但是买的太少了,一直没狠下心去加仓。

最近的操作失误应该是南极电商卖早了,只吃到了 9.47 – 10.10 的一段,后续又涨到了接近12块。

踩中了海康威视被制裁的大雷,但是第三季度业绩还可以,还是坚定持有吧。

四维图新业绩也大幅度下滑,这个其实可以提前跑的,但是感觉看好长期所以没有跑。不过正确的操作应该是先卖了,等跌完再买回来。

看好韵达的业绩,上个月跌倒低的时候其实应该再吸点筹码,但是仓位比较高了,不好操作。这件事情告诉我们要保持合理的仓位,不能全仓。

Baelish: An Introspection II

完全自己一个人做了一个系统,在这个过程中有不少的收获和教训,趁还没有忘记赶快记下来。

过早优化

开始设想的服务太大了,想做一个超牛逼的大而全的东西。所以在一开始的时候就拆成了好多的repo,
每个模块都拆成了不同的微服务,中间使用 RPC 调用,并且每次打成好多不同的镜像,部署的时候也很麻烦。其实这里的问题在于不明白其中的逻辑,而是生搬硬套架构,犹如东施效颦。

分库是一个很大的问题, 最开始的时候总是想着把库拆出来做一个基础组建库,然后拆出来了好多库,甚至把代理和抽取都单独出库来,实际上没有必要保持代码的纯洁性,这是我常犯的一个错误。这方面造成了镜像打包都很麻烦,而且要在各种库之间切来切去,依赖也要重复安装好多次。当某一个组件需要被其他人复用的时候再拆出来也不迟,像是 npm 那样拆得太散也不好。

即使在拆成不同仓库比较好的时候,也没必要打成好多的镜像,如果一个人维护多个镜像的话,很容易就会忘记每个镜像的每个版本到底更新了什么。

另一个问题就是典型的“过早优化”。早期我把很多只是保存状态,做增删改查的部分都抽象成了单独的服务,实际上封装到一个接口中,读取 redis 就很好,在做好监控的前提下等到 redis 扛不住的再优化也不迟。实际上在项目的早期,做一个单体应用就很好,需要抽出来的地方抽出来,能不抽出来尽量不抽出来。这里的问题其实还是在于没有理解逻辑,生搬硬套架构。看过了一千篇文章,却还是做不好一个架构。

强行使用刚学会的技术

这点主要体现在 Frontier 和后来的 Scheduler 上面。在定向爬虫上,Frontier 本身就不是必须的,根本没必要多此一举。Scheduler 也没有必要使用 token bucket 算法,使用堆是最好的。token bucket 或者 leaky bucket 还是必须的。这里也考虑过多,单点部署其实就够了。单 master 多 slave 虽然看起来会有单点故障,但是确实是最简单高效的模式。

选型

选型上出的问题主要在于消息队列,日志服务和容器平台。监控上的选型倒是正确的。监控的选型完全是错误的,Prometheus 才是唯一正解。这里的问题还是在于东拼西凑概念,没有完整的理论体系。

消息队列

~~最开始混淆了缓存和队列的区别,对于爬虫的不同任务来说,需要分别放在不同的缓存,而不是直接
放到同一个队列,这样是无法调度的。~~这里在于对于消息队列的理解不够深入。

在队列的选型上,首先尝试了 rabbitmq,然而 rabbitmq 并没有一个很好的 Python 客户端,官方钦定的客户端叫做 pika,抽象层级不够,仅仅提供了非常原始的包装,而且 rabbitmq 本身的稳定性非常差,经常莫名其妙挂掉,而且没有任何一场日志,在 rabbitmq 上至少坑了半个月。

然后尝试了 redis stream,因为我本身对 kafka 的概念比较熟悉,而且redis 本身也是比较稳定的。但是还是感觉被坑的不浅。当时 redis stream 刚刚出来,Python 的客户端还没有支持这个特性,导致一些代码还需要自己解析响应,在这上面画的时间不少不说,做出来的还不太稳定。redis stream 虽然是借鉴了 kafka 的概念,但是还是有很多地方不同的,而且有一些东西也没有明确,这就导致实现起来各种小 bug 满天飞。最重要的一点是,redis 想实现 kafka 这个 API 本质上就是南辕北辙了,kafka 之所以可以做到 consumer group 能够重放这个功能,就是因为在硬盘上有比较好的消息堆积能力,而 redis 作为一个内存数据库,注定做不到好的消息堆积能力。实际上单纯模仿 kafka的 API 是没有意义的。

现在使用的是 celery,然而还是有问题。celery 提供的并发模型太少,只有 prefork 和 gevent勉强可以用,然而 gevent 又回导致严重的内存泄漏问题,而爬虫是需要大量的并发请求的,在这种情况下,celery 就成了一个瓶颈。另外一个问题是对于失败任务的 retry 机制在 celery 中也很不明确,celery 本身封装了不少层,导致捕获出异常来成了一个很大的问题,而我们又不能设置永久重试,最终结果就是有一些任务在重拾到最大次数之后被永久丢弃了。这里也是和爬虫这个业务紧密相关的,毕竟下载的失败率是很高的。

最终决定还是使用 kafka。最开始的时候,实际上还是觉得 kafka 太难搭建了,用起来的话太浪费时间了。但是实际上最开始可能用 redis 就可以,等到性能出问题了再去换到 kafka 上。使用kafka 的话,上面两个问题都可以得到解决,自己编写客户端可以任意选择并发模型,而且对于抓取失败的链接可以自定义重试策略。

日志服务

当部署多个实例的时候,实际上日志的收集是非常关键的一步,可以说必须在横向扩展之前完成,而之前忽略了这一点。在 debug 的过程中,日志非常重要,日志的缺失也就拖累了开发进度。

实际上这里的根本问题还是在于东拼西凑架构,而不是有一个统一的设计理念。

另外,阿里云的日志服务也是一个大坑,连基本的全文搜索都做不到,搞一些花里胡哨的东西也不知道有啥卵用。plain old grep 才是排查问题的利器啊。现在看来可能还是需要 loki + kafka 来做一下。

关于业务性日志和程序性日志的区别,会单独再写文章讨论。

容器平台

最开始的时候没有多想,直接使用 ansible 上线部署,这样的不好是在同一台机器上只能部署一个实例。但是爬虫需要扩展的时候,需要在一台机器上部署多个实例,这时候就需要容器的编排平台了,另外就是日志也需要收集。首先考虑了 kubernetes,但是还是觉得太复杂了,概念有点多,感觉用不然,然后就选择了 nomad,结果证明又是一个大坑。nomad 的编排经常无法看到运行中的容器,迷之找不到 container。nomad 的日志收集也有问题,没有好的解决方案。最重要的问题还是,nomad 的生态太小众了,遇到问题无法查找到社区提供的解决方案。最终还是上了 kubernetes,其实过了入门的坎,再看 k8s 还是很简单的。另外一点就是 k8s 通过 cluster IP 这个功能很好地解决了服务发现的问题,完全不用再去手工注册服务,代码量节省了不少,也省去了维护 consul 的工作。

RPC

在 RPC 框架的选择上,主要纠结在 thrfit 和 gRPC 之间,虽然花了一些时间学习和比较两个框架,但是最终感觉还是值得的。不过也还是使用地太早了,在最开始的时候完全没有使用 RPC 的必要性。

监控

监控使用了 influxdb 现在看来是一个比较正确的选择,但是没能及早发现 statsd 还是走了一些弯路,不过学习了下时序数据库的相关东西也算没有浪费时间吧。

influxdb 和 statsd 实际上是两个大坑。influxdb 好多关于时序性数据的特点和要求没有在文档中提及,需要自己试错才知道。而 statsd 基本完全没考虑标签,导致聚合结果完全是错的。

数据库

数据库的选择和使用上其实暴露了我对于 mysql 性能的无知了。最开始没有考虑到连接数问题,导致 MySQL 被锁死。之后又没有如何批量插入的问题,导致数据插入的丢失问题也很严重。当然这个问题也不完全是我的个人问题,把半结构化的数据存入 MySQL 本来就是一个比较奇葩的选择。

总体来看,主要原因就在于两个:

  1. 知识不足,确实需要学习
  2. 选型过于小众,坑太多

其实核心还是没有自己的逻辑,东拼西凑。这一点在读完 Facebook 员工的一篇文章后有了极大改善。

业务逻辑

从业务逻辑上来说,也有不少可以优化的地方。

规则变动

从我自身而言,对于整个业务逻辑的梳理不是很明确,排期预计也不准确。最终导致的结果就是,爬虫要执行的规则变来变去,导致做了好多次返工。比如抽取的规则,最开始定义了页面的字段,最后才统一到必须是行的字段上。最开始觉得直接写 yaml 就可以了,最终还是回到做了一个 GUI 上。

混乱管理

CXO 们除了FBJ有做通用爬虫的想法之外,其他人还停留在线性增长的思路上。只是关心短线结果,不考虑长远的规划,对于爬虫的开发也产生了一些不良影响。实际上,作为科技公司,不论是否直接参与代码的编写,对于其中的好奇心和敬畏感是都要有的,如果只是关心结果,很难做到高效。

CEO 最大的问题在于在公司呆的时间太短,对于公司发生的事情掌控力太差,频繁见客户不一定有用,耐心打磨产品才是正途。

同事水平过低

很简单的东西,没有人能明白我的思路,反复说了,他们还是按照低效的方法来做,实际上最终还是要返工。

比如说对于监控问题,很明显很清晰的一个问题,利用现有工具也可以做得很好,非要自己写一通,最后的结果也是很差的。

对于缓存的问题,有很成熟的思路可以直接使用,但是由于大家水平问题,竟然理解不了,也抽象不到这个层级,最终竟然重抽问题还是没有解决。

过于倚重阿里云和其他第三方服务,缺乏自研和探索精神。实际上诸如灵犀和 jumpserver 之类的服务是非常难用的,而开源的工具可以做到很好,把时间花在这些 trivial 的东西上最终产出也不是很好。阿里云的日志服务,k8s 服务,es 服务等等都不是非常地好用,甚至可以说非常难用了。而整体研发的思路,尤其是F很信任的CD方面则是能用阿里云尽量用阿里云,没有一点探索精神。

战略的迷失

盲目追求数据的大而全,但是又不能保证数据质量,没有做精做细某一块。举个例子来说:

  1. 电商数据。最基础的抓取问题没有解决,或者说这个数据根本就是不可能获得的,阿里的风控团队是吃素的吗?更何况其中还有法律风险问题。

  2. 招投标数据。这里面可以做的点非常非常多。而且作为一手的数据来源,政府网站永远不可能屏蔽爬虫。而去爬二手数据来源,需要繁杂的反爬措施。

后端数据清洗方面,整个公司对于数据的治理还停留在线性叠加的水平上,而不是打造平台,从而能够横向拓展。比如说对于研报、新闻、招投标公告需要一套底层的文章库,而现在每一套的处理流程都是单独的,而且效率很低,没有人有整合的想法。相比之下,头条很早就有打造推荐引擎的想法。

抓取上,更是“脚本小子”的思路,每个项目都单独编写爬虫,主要精力竟然是放在了不同站点的反爬策略上,这一点是非常匪夷所思的。出了重点抓取的电商数据外,不应该有任何网站存在很复杂的反爬逻辑才对。另外就是单独编写的爬虫可维护性太差,其实就相当于内包给某个员工,业务的风险性太大。甚至经常出现某个人的脚本由于写得太差,把整个集群打挂的情形。

总结

要有自己的逻辑。科技公司还是要技术驱动的,那些“非技术驱动论”的鼓吹者可以休矣!

为什么说小公司的沟通效率反而是低下的?

人们普遍的观点是:大公司环节冗长导致沟通效率低下,小公司人少好传达效率更高。其实有时候,小公司的沟通效率反而是低下的。

最核心的逻辑:大公司遇到的好多问题不会因为公司变小就不存在,这些问题也不只是因为公司大了就必然产生的,还有部分是因为公司大了招的人质量下降了才产生的。如果小公司招的人质量不行,那么一开始就会有各种问题。

小公司的人水平不足是一个很重要的原因。在大公司虽然环节众多,但是因为大家对问题都有比较深入的研究,所以简单的问题也可以很快定下来,马上执行。但是在小公司由于好多人经验不足,反而需要反复讨论,谁也不能拿定一个主意,导致迟迟不能执行。

小公司也并不一定是更快的成长之路,在小公司你会被各种烦事儿纠缠,以至于无法深入思考。人的成长最好是十字形人才,根深才能叶茂,有在一方面的内功很重要。

大公司也不只是螺丝钉,而是站在巨人的肩膀上,在更高的平台,思考更抽象的问题,做更有挑战的事儿。

小公司的另一个陷阱是创始人成长太慢。最开始的时候可能创始人还能够独当一面,但是当业务开始开展以后,创始人不一定能够跟得上这个节奏,反而成了拖后腿的。我们可能已经习惯了比尔盖茨和扎克伯格的故事,但是这样能够随着公司成长的 CEO 是可遇而不可求的。

小公司同样可以犯大公司的病。本来可以顺畅流动的空气也可能被人为阻断,不管是管理层好心学习大公司的制度还是恶意过一把当领导的瘾,他们可能因此在公司内部制造各种障碍。创业公司最好还是能做到 Context, not Control。如果没有给足 Context,即使好心问大家意见,大家也不知道该说啥。但是又总是不够乾纲独断,还非要考虑大家的意见,做决定总是犹犹豫豫,最终效率低下。

总之,同等条件下,小公司肯定是效率更高的。但是现实情况可能是小公司因为能力问题,反而效率更低。

使用 ssh 反向隧道登录没有 IP 的服务器

假设我们家里的服务器叫做 homeserver,没有公网 IP。然后我们有一台服务器叫做 relayserver,拥有公网 IP。

在家里执行

homeserver~$ ssh -fN -R 10022:localhost:22 relayserver_user@1.1.1.1

在服务器上就可以登陆啦

relayserver~$ ssh -p 10022 homeserver_user@localhost

参考

http://xmodulo.com/access-linux-server-behind-nat-reverse-ssh-tunnel.html

Sequel Pro cannot create a JSON value from a string with CHARACTER SET ‘binary’

I had this problem dealing with exports made by Sequel Pro. I unchecked the Output BLOB fields as hex option and the problem went away. Visually inspecting the export showed legible JSON instead of binary.

导出数据的时候把 “Output BLOB fields as hex” 这个选项取消就可以了。

参考:https://stackoverflow.com/questions/38078119/mysql-5-7-12-import-cannot-create-a-json-value-from-a-string-with-character-set

如何导出 Docker 镜像

可以使用 docker save 和 docker export 导出 docker 镜像。那么两者有什么区别呢?

  • export 是用来导出一个容器的,也就是运行中的镜像。
  • save 是用来导出镜像的,也就是还没有运行的镜像。

这里我们需要用的显然是 docker save。

语法是:

docker save [OPTIONS] IMAGE [IMAGE...]

其中的 options 也就一个参数 -o 文件名。如果不指定 -o 的话直接输出到 stdout,方便使用管道串联。

如果需要压缩的话还可以这样

docker save myimage:latest | gzip > myimage_latest.tar.gz

导出之后,可以使用 docker load 导入镜像。不使用 -i 的话直接从 stdin 读取。

docker load -i FILE

macOS 中如何正确安装 pycurl

Reinstall the curl libraries

brew install curl --with-openssl

Install pycurl with correct environment and paths

export PYCURL_SSL_LIBRARY=openssl
pip uninstall pycurl 
pip install --no-cache-dir --global-option=build_ext --global-option="-L/usr/local/opt/openssl/lib" --global-option="-I/usr/local/opt/openssl/include"  pycurl