我的第一次职业倦怠

本文是翻译,版权归原作者所有



在成为设计师之前,我在银行供职了四年。我有一份非常不错的工作,周围都是优秀的人,到现在我和他们还是朋友,很幸运。我最初做出纳,仅仅两年后,我就有个机会在职业阶梯上迈进了一步,从乡下搬迁到了大都市。我比以往更加激动、更有奔头。

我热爱我的工作,无法想象我还能做什么其它事情,因为我擅长目前的工作。每天早上 6 点起床,因为我从早上 7:30 开始上班,大约在下午 6-7 点忙完。我急急忙忙地吃午饭,让自己在 20 分钟内返回桌子前继续工作。每个月我要面对一些让人疯狂的 KPI,比如管理人员、处理客户问题、投诉、和内部银行系统一起工作(用现在设计师的话讲,它是设计糟糕的软件)、以及常常投入我 100% 的注意力,因为没有出错的空间。我不认为我曾经抱怨过它,因为它对我而言是绝对正常的。这么多年,我忙死了,甚至都没有注意到。三年半之后,发生了一些事情……

我的脾气改变很大,越来越不好。我不再有笑容、失眠、常常疲惫不堪、不再想跑步、失去了动力,我开始讨厌日复一日去上班,我觉得脱离了我的工作。我尽力对每件事、每个人掩饰这种感觉,我很厌烦。那个时候,我迎来了第三次提升,来到了新的团队,有着更可怕的 KPI,因此这些改变对我根本没有帮助。当你在新团队担任新角色时,你明白这意味着什么。即使你在公司内部有着让人尊敬的位置,但是你的团队还不了解你、或团队在保持着变化,你仍然需要向他们证明你自己,包括首先证明给自己看。我觉得我不再是过去的我了。我一直倍感压力、无助、困顿,我不知道该怎么办,在这样的精神和心态下,我坚持工作了半年。

这样过了半年之后,我突然得病了,我不得不停止了工作,在家里休息了六周,每天打点滴。这是真正的叫醒服务【注1】,最终我有了思考的时间。好吧,实际上我根本没有思考,我感到如此空洞。我记得花了数个小时却什么也没有想,如梦如幻。我非常感激我的男朋友和那两位护士,他们对我每天的照顾让我真正地振作起来。我不知道这是什么药物,但它的确是某种真正好的东东 :)

我大量阅读,并找到了很多有意思的文章。随着阅读量的增加,有一点变得清晰了,那就是我过去经历着一种工作倦怠。我几乎符合所有症状,甚至我还没到 30 岁。下面是危险的信号:

  • 疲劳
  • 挫折或降低的耐心程度
  • 压力
  • 缺乏动力
  • 无所适从、陷入困境、无助
  • 失眠
  • 突然病倒
  • 去你曾经深爱的工作场所,是因为你不得不去、而不是因为你想去

在离开工作几个星期、看了几部电影后,我觉得好多了。一旦我找回了我的思想,就决定离开了。那时候我根本不喜欢我的工作,无法想象返回工作岗位的日子,哪怕一天。如果我知道当时是职业倦怠,如果我及时得到了帮助,或许我仍然在银行当支行经理:)

我有幸能够如此容易地加入 IT 设计行业,多亏了我上面提到的男朋友。我乐于成为一名产品设计师,我深爱着设计的方方面面,但是不管多么热爱,还是有很多次,我觉得我只是满足交付日期的一条产品线,因此我开始再一次感到不开心。这是一份创新性的工作,因此和其它行业相比,设计师们有这种感受或许是非常普通的。当我感到工作负荷大、报酬太少、长期不清楚我在团队的角色定位、把我的个人性格比如完美和过度负责混淆在一起时……好吧,这不意味着有什么好处。当我涌现这些想法和感受时,我知道我需要做出一些改变,这通常意味着我需要休息,以完全从工作和同事中切换出去,因为正如我说的,我乐于成为一名设计师,我还愿意继续做一名设计师。

我给你的建议是,不要等到出问题、或你的健康状况开始恶化时才去注意。严肃看待这个问题,如果条件允许,就休息一下;如果条件不允许,就在你的生活中做出调整,以避免倦怠。


注1:这里原文是“weak-up-call”,有读者在原文问到,这里是有意写成这样,还是误写?作者对其回复是有意为之。我这里理解成“wake up call”的意思。


用 JS 复制艺术

本文是翻译,版权归原作者所有



今天我发现了一副美丽的绘画,经过一番研究,我找到了名字和作者。

jsart_infinite_dots-756x1024

  • YAYOI KUSAMA
  • Infinity-Dots [HOFS], 2014
  • Acrylic on canvas
  • 130.3 x 97 cm

(顺便说一句,可以浏览相同作品集的其它作品

看起来真的不错,更重要的是,它貌似能够用 JavaScript 复制。

我花了一些时间观察,决定使用最简单的方法——带有少许改动的、某种正弦函数。对于资源库,我打算用平常的 JS。

细节

首先,让我们放大、看下图片本身的细节。

Screen-Shot-2015-05-25-at-4.53.57-PM

我们能够注意到:

  • 稍微扭曲的圆
  • 它们有边界
  • 越小的圆,扭曲得越多

本图或许给你留下这是一副“简单的”复制品,但不尽然。看下所有这些点,它们从来没有重叠。

甚至它看起来有一些像正弦波的主风格,它更像是艺术家用大量点做的纵向条纹,然后只是耐心地填充了其余部分(我的看法,或许我完全错了)。我做了少量测试。我现在明白了,为了达到几近完美的复制,我将不得不引入某种 hit-test【注1】,或者保留事先画好的点的位置。

我不想这样做,我想做一些类似于我能做的事情,只使用简单命令和纯粹的 JS。

结果

开干,我尝试了一些简单方法,下面是结果:

刚开始,没有经验的尝试,用了简单的正弦函数。
刚开始,没有经验的尝试,用了简单的正弦函数。

很明显这行不通,我尝试不覆盖圆,但是空隙太大了:

Screen-Shot-2015-05-26-at-1.37.05-AM

在接下来的截图里,我对空间做了尝试,只是当心不让圆重叠:

更好的结果,但是仍然离原作品相差甚远。
更好的结果,但是仍然离原作品相差甚远。

最后,加上一点点扭曲,变成了:

Screen-Shot-2015-05-26-at-2.51.26-AM-1024x706
不太完美,但足以接近。全是代码的功劳。

这是真正有趣的试验,即使我做不到在细节上做好复制,但是你能够用几行代码就能搞出某些真正好看的图像,这会给人留下深刻的印象。在我们的例子中,下面是输出最终图片的所有代码:

var canvas = document.getElementById('replica');
    var ctx = canvas.getContext('2d');

    var circle = function(radius, x, y) {

        ctx.fillStyle = '#b50024';
        ctx.strokeStyle = '#771522';
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.stroke();
        ctx.fill();
        ctx.closePath();

    };

    var lastX = 0;
    var fix = 0;
    var width = 500;
    var height = 677;
    for (var i = 0; i < width; i += 2) {

        var x = i / (width / 150);
        var radius = 6 / (3 * (Math.exp(Math.sin(x))));

        lastX = lastX + radius + radius + 3;

        for (var j = 4; j < height; j += 2) {

            var randOffset = ( 0.7 * radius) * Math.random();
            var deviation = j / (height / 10 * (2 *  height / j ));
            var dOffset = Math.cos(j/(height/4)) * ((3.4 + (1.4 * (j/height)))
                    * Math.exp(Math.cos(deviation))) * (1.5 + (2 * ((3.2 * (i / width)) )
                    * Math.exp(Math.sin(deviation))));

            if (j % 12 === 0) {
                if (radius >= 3.4) {
                    circle(radius, lastX - dOffset, j + randOffset);
                }

            } else {
                if (radius < (6 * Math.random())) {
                    circle(radius, lastX - dOffset, j + randOffset);

                }
            }
        }
    }

本例的完整源代码,还可以点击在线 demo

这是一次不同凡响的尝试。下次我将挑选一些不同的(但是同样有挑战性的)的例子,并尽量做同样的事情。

更新:

用户 blackle 在评论里留下了更好的实现,她的结果更加完美!

orKSxcH

太棒了!你可以访问她的 demo


  • 注1:In computer graphics programming, hit-testing (hit detection, picking, or pick correlation) is the process of determining whether a user-controlled cursor (such as a mouse cursor or touch-point on a touch-screen interface) intersects a given graphical object (such as a shape, line, or curve) drawn on the screen. http://en.wikipedia.org/wiki/Hit-testing

译文:用 JS 复制艺术 》| 腊八粥

我们作为软件工程师要担负的责任

本文是翻译,版权归原作者所有



本周我有幸和非常优秀的 Kate Heddleston 在一起,她提到她最近就软件工程师的伦理做了大量思考,她在 PyCon Sweden 大会上只谈了一部分。这让我想起了若干年前我写的一篇文章,当时我提到:

有一种持续且惊人地传播开来的错觉,说技术多多少少是中立的,道德方面的决定是为其他人做出的。但这不是真实的。Lessig 教会了我(和一代技术专家),代码即法律【注1】

[…]

在 2008 年,世界背叛了银行家,因为很多收益通过在快速增长领域(金融工具【注2】)拓展他们的专长,这是建立在甚至连基本概念(可调利率抵押贷款)都不清楚的其他人之上的。我们软件工程师还需要多长时间,才能在类似位置找到我们的专长?做为和其它领域专家一样的、本领域专家,我们的责任是指导其他人为他们做出最好的决定,那么我们还要使我们自己逃避这个责任到什么时候呢?

好吧,我认为是时候了。

每个人都使用软件,但是极少有人理解它。对于小部分精英团体十分明显的东东,却完全让世界上大部分人感到模糊。这个缺口对我们这些软件工程精英而言,是非常非常难以看到的。举几个例子:

  • Radiolab Podcast 网站发布了一个不错的视频——《Trust Engineers》——视频里讨论了 Facebook 运作其消息来源【注3】的实验情况。对于非工程师而言,为人们提供每日信息的算法,受到一群活生生的人的、太多的控制,意识到这一点,会产生一种难以置信的、辜负了信任的感觉。(就此而言,对于那些习惯于和审查委员会打交道的研究人员来说,对于 Facebook 的所作所为也是完全震惊的。)对于大部分工程师、包括 Facebook 相当一部分优秀的、有伦理的人们在内,“这甚至算得上问题”本身就是让人惊奇的。
  • 多年前,一个朋友的朋友,他正好是世界知名的内科医生和科研人员,他问我:“Ben,工作中的系统管理员能够阅读我的电子邮件吗?即使他们没有我的密码?”答案是太对了。这对于我们工程师而言,是再明显不过了,以致于我们甚至不会去思考。对于非工程师、甚至是相当聪明的人来说,这绝对是不明显的。
  • 一个好朋友,又一个聪明人,正在和他的小孩子讨论一些事情,我无意中听到一句话“如果你不知道,就问计算机,计算机知道、而且它总是对的。”我该如何是好?

我们软件工程师拥有大部分远远无法理解的超级力量。寄希望于我们的信任社会成长如此之快,以致于看起来稍微类似的唯一地方在于,信任寄托在医生身上。除非,大部分人对他们寄托在医生身上的信任有着深入了解,然而他们几乎不清楚,每次他们安装一个 app、输入一些个人数据、或者在私密电子沟通中分享私密想法时,他们正在信任一组软件工程师,而这些工程师对于道德指导方针的形式知之甚少。

我们的希波克拉底誓词【注4】在哪里,我们的“首先,不伤害人?”在哪里?

就我自己的工作,我试着努力思考这个事情,我尽量和我指导以及打交道的每个工程师分享这种责任感。我仍然没有这个核心问题的最佳答案。然而,对于我们来说,弄清这一点正变得越来越急迫和重要。


  • 注1:框架的威力就在这里,这就是设计师可以决定什么被允许、什么本质上要禁止。如果设计师不想让某些东西发生,那么神奇的函数调用将从 API 中消失。如果设计师喜欢这种想法,那么通常会有多个函数调用以及许多支持工具。这就是哈弗法学院教授 Larry Lessig 为什么喜欢说“代码即法律”的原因。http://www.labazhou.net/2015/04/7-reasons-why-frameworks-are-the-new-programming-languages/,还可以参考 http://en.wikipedia.org/wiki/Lawrence_Lessig#.22Code_is_law.22
  • 注2:金融工具(英语:Financial instruments)是合约,在金融市场令合约的贷方持有成为资产,而令借方持有成为负债人,例如债券、股票、期权、对冲基金、存款证书等都如是,按照国际财务报告准则第39号的定义。 差别只在其合约的具体条款及性质归类,利息的支付期、保本、可换股票、现金流等之不同。http://zh.wikipedia.org/wiki/%E9%87%91%E8%9E%8D%E5%95%86%E5%93%81
  • 注3:消息来源(英语:web feed、news feed、syndicated feed又译为源料、馈送、信息提供、供稿、摘要、源、新闻订阅、网源)是一种数据格式,网站可通过它将最新信息传播给用户,用户能够订阅网站的先决条件是网站可提供持续更新的信息。消息来源受到博客及新闻网站的广泛采用,因为此类型的网站经常更新内容。http://zh.wikipedia.org/wiki/%E6%B6%88%E6%81%AF%E4%BE%86%E6%BA%90
  • 注4:希波克拉底誓词,俗称医师誓词,是西方医生传统上行医前的誓言,希波克拉底乃古希腊医者,被誉为西方“医学之父”,在希波克拉底所立的这份誓词中,列出了一些特定的伦理上的规范。http://zh.wikipedia.org/wiki/%E5%B8%8C%E6%B3%A2%E5%85%8B%E6%8B%89%E5%BA%95%E8%AA%93%E8%A9%9E

Blendle 的截图

我们是如何设计 iTunes 杂志的

本文是翻译,版权归原作者所有



通过授权超过 200 种字体,让人们为杂志付费

六个月前,我们在荷兰发布了 iTunes 杂志应用Blendle。超过 140,000 个用户(相当于我们国家每个人都在用)使用我们的服务,可以阅读到每份新闻和杂志的精美文章,而只需为他们喜欢的文章付费。

上周,我们宣布,纽约时报公司和欧洲出版商 Axel Springer 共同投资了三百万欧元,帮助我们在其它国家推广 Blendle。

我们相信,截至目前,我们相当一部分成功的原因来自于我们的设计:我们认为,文章的布局和文章本身一样重要,这就是我们为什么要投入大量时间和资金来提高付费阅读体验的原因。

在过去的一年半里,我们构思数百种草图,以期搞清楚在线付费杂志应该是什么样子。最难的部分在于,我们不得不推出一些比其它网站更有价值的东东。因为,如果一篇付费文章看起来比你能够免费阅读到的文章还要糟糕,那么你为什么要付费呢?

我们尝试了大量不同的方式,但是每一次我们都以 Flipboard 式【注1】的设计而告终:漂亮,但丢失了原生报纸或杂志的灵魂。一篇小报文章应该看起来没有多少价值,来自于流行杂志的一篇文章应该看起来,嗯,流行,而来自于纽约时报的一篇文章应该给人的感觉是,它就是一篇来自于纽约时报的文章。

因此我们决定尝试其它方式。如果 Blendle 上的文章阅读体验尽可能接近于文章的原生体验,那会怎样呢?结论让人大吃一惊:为每一份报纸和杂志使用不同的字体、颜色和布局,文章瞬间就觉得更有价值了。通过强调原生出版,我们的用户就对他们将阅读哪种类型的文章有了较好的理解(比如,无论它是一本八卦杂志,还是欧盟扩大化的分析。)。

Blendle 时间线
Blendle 时间线

模板

从这个时刻起,我们决定,为加入 Blendle 的每份报纸或杂志创建一份新模板。我们每次设计一个模板,来最好地呈现这份报纸的特征。模板包括字族【注2】、大小、粗细和颜色。我的同事 Laurens 将这些价值翻译为不同的 CSS 模板:针对时间线、针对桌面端阅读器的版本,另一种是针对移动端阅读器。

报纸的截图

Blendle 的截图
同一篇文章,前者是报纸,后者是 Blendle 阅读器

阅读器

我们针对文章阅读器使用相同策略。我们做了大胆的决定,桌面和 iPad 用户应该可以通过我们的 web 应用进行水平滚动。我们这样做,就开创了一种在线阅读体验,它更接近于印刷,它使用了分栏、不包含广告,它和其它博客或网站有着不同的感觉。尽管如此,我们还是得到了移动端的垂直读者——尤其是因为每个人已经习惯了垂直滚动。但是,人们就是喜欢这样。

新的在线阅读体验

文章的封面图片以全尺寸显示在文章左侧。文章阅读器打开了可见的图片前 300px,以确保你能够立即开始阅读。如果你点击左侧,Blendle 将显示图片的剩余部分。如果你点击右侧,Blendle 将显示更多文本。

所有内容被放在行高为 30px 的基线网格里,确保内容在任何分辨率下排列美观。这非常酷,然而,也限制了我们修改行高,比如标题 H1。行高被锁定为 30px,因此增加 H1 字体大小就意味着一个相对较小的行高。

所有内容都被排列在 30 px 的基线网格里
所有内容都被排列在 30 px 的基线网格里

影响

为我们当前的 75+ 份报纸和杂志创建新 CSS 模板的影响是,这需要超过 200 种网络字体的海量字体集。为了确保我们尽可能快速地载入,以提供完美的阅读体验,我们的开发人员开发了一套系统,它检查内容并决定哪种字体是必需的、以及应该在渲染各栏之前载入。这样,只有可见字体被载入。字体集剥去了不会被用到的外国字母。

但是更重要的是,发布商和用户都喜爱。我们甚至得到了一些发布商的说法,我们设法把他们报纸或杂志的特征翻译为网页,这比他们自己的网站还要好。对于用户,他们也买了大量文章。

1_GRPcI8vMuj1Z1gN1TRmisw1_Y0UsIEGcLbHKKuE_I3WC3Q1_mFPiBLNaS7yKBxS2muh45A1_FVJpFW_Xqp3aXLiftRcK7w

下一步

上线 Blendle 是一个巨大的冒险,不过这只是开始。既然我们有了接近 140,000 个注册用户,20% 的付费转化率 ——很高的转化率。在过去的几个月里,我们陆续增加了越来越多的功能,大部分都是基于我们的假设。有时候我们的假设是错误的,有时候它们是正确的。现在,是时候分析我们收集到的数据、并开始优化了。我们让 Blendle 更聪明、更快、更平滑、更惊艳。我们正在改善可发现性【注3】、可阅读性。我们添加了更多的动画。我们打算发布一个 iOS 应用,努力在新的国家发布。要做的事情太多,乐趣也不少!


  • 注1:Flipboard是一款社交及杂志形式的应用程序,支持Android、iOS、BlackBerry 10及Windows 8操作系统。应用程序需要用户拥有一个Flipboard账号,支持绑定包括Twitter、Facebook、新浪微博在内的多款社交网络平台,并根据喜好定制以电子杂志形式输出的信息和Google Reader的内容,通过“翻转”(Flip)的方式进行浏览和刷新。 http://zh.wikipedia.org/wiki/Flipboard
  • 注2:在 HTML 和 XHTML,font face 或 font family 是网页浏览器套用于某些文字的字型。该字型会用于在屏幕,或于打印机或其他装置显示。在 CSS,font-family (或 HTML 的 face) 包含一堆相关的字型,名为“字族(font families)”。举例来说,Times 字族包含不同的大小、styles (比如 roman 和 italic(斜体)),以及重量(weight) (比如“常规 regular 和粗体 bold)。http://zh.wikipedia.org/wiki/Font_family_(HTML)
  • 注3:可发现性(Discoverability)是某种东东、尤其是一条内容或信息被发现的能力。http://en.wikipedia.org/wiki/Discoverability

robots.txt 探索笔记

本文是翻译,版权归原作者所有



介绍

在 web 应用程序测试的侦查阶段,测试人员(或攻击者)通常使用已知的子目录列表来暴力破解服务器,以找到隐藏资源。对于这个意图,已知的子目录列表常常被用于 Skipfish 或 wfuzz。如果有额外的上下文信息被获取到,它可能会添加到该列表。最重要的是,一旦测试完成(或者命令执行已被完成),以及一个列表或服务器配置信息被揭露,该列表就会被更新,而不会缺失任何一条。

我最近丢失了我的个人列表,决定从头再建一份。这种列表是不受时间限制的。取决于某种 web 技术的理解力,它需要定期刷新。我打算把我的列表建立在网站公开的最有价值的地方:robots.txt。在这次探索中,我碰到一些有趣的发现。

免责声明:整个练习使用了互联网允许公开访问的资源。从最坏方面说,只是互联网礼仪被破坏了。

robots.txt

这些文件位于网站服务器主目录下,指示站点允许网络爬虫(比如 Google robots)访问哪个部分以进行索引。它们应该被机器分析、但具有人类可读性。例如:

User-agent: *
Disallow: /admin/
Disallow: /stats/
Disallow: /internaljobs/
Disallow: /internaljobsbyorganization/
Disallow: /internaljobsearch/

你或许看到了,Disallow 指令给攻击者提供了宝贵信息,哪些地方是值得查看的。另外,如果对于一个站点是这种情况,那么它就值得用于检查另一个站点。

抓取

为了搞到一套相关的 robots.txt 文件,有必要从主机名列表着手,如果可能的话,主机名要包含宽泛的互联网、而不要被限制为任意子集(和 主题爬虫【注2】 相反)。

大多数商业搜索引擎不再提供免费的解决方案。幸运的是,Common Crawl 项目通过 S3 提供了最近的抓取结果。

该抓取被分隔在 1GB 的文档里(解压缩后有 4GB)。(截止 2015 年 2 月份,它们超过了 33,000 条)。我写了一个脚本,从这些解压缩后的 WARC【注3】 抽取出找到的所有主机名:

require 'set'

URL_PATTERN = %r{
    (?:(?:https?)://)
    (?:
       (?:(?:[a-z0-9][a-z0-9\-]*)?[a-z0-9]+)
       (?:\.(?:[a-z0-9\-])*[a-z0-9]+)*
       (?:\.(?:[a-z]{2,}))
    )
    (?::\d{1,5})?
}xin

tlds = File.open("tlds-alpha-by-domain.txt").each_line.collect {|w| w.chomp.downcase}

s = Set.new

begin
  File.open(ARGV[0], 'rb').each_line.with_index do |line, i|
    s.merge(line.scan(URL_PATTERN).select {|f|
        # Extract TLD
        h, m, tail = f.rpartition(":")
        w = (h.include?"/") ? h : tail
        head, m, tld = w.rpartition(".")
        tlds.include? tld.downcase
      })
  end
rescue Interrupt
end
puts s.to_a.join("\n")

我们得到了一份主机名列表(TLD【注4】根据白名单来匹配以避免误报)。然后我借助 Burst 使用这个列表来下载 robots.txt:

import socket
import random
import threading

from burst.all import *
from burst.exception import BurstException
from burst.utils import chunks

nthreads = 4
slice_sz = 200
switch_session('hosts')

hosts = open("hosts").read().splitlines()
random.shuffle(hosts)

def worker(s):
  for i, h in enumerate(s):
    r = create(h + "/robots.txt")
    # Check if we have visited this host before
    for hr in history:
      if hr.hostname == r.hostname:
        break
    else:
      try:
        r()
      except (BurstException, socket.error):
        pass

for ch in chunks(hosts, slice_sz):
  slices = chunks(ch, nthreads)
  jobs = []
  for s in slices:
    t = threading.Thread(target=worker, kwargs={"s": s})
    jobs.append(t)
    t.start()
  for j in jobs:
    while j.is_alive():
      j.join(1)
  save()

所有东东被分割为 chunks。一次处理一个 chunk,然后会话被保存。这样,在抓取过程中,就能查看结果。

手动使用 Burst 接口,就可以查找特定模式了:

$ burst -rs hosts
hosts >>> history.responded().filter(lambda x: "Disallow: /admin/" in x.response.content)
{200:1563 | get.kim, rockville.wusa9.com, mars.nasa.gov, ... }

抓取的某些统计数据:59,558 个网站被抓取,59,436 个网站给我们发送了响应。这是 Common Crawl 项目结果的、极具活力的较好体现。其中,37,431 个网站响应的 HTTP 状态码为 200。其中,35,376 个网站返回了貌似合法的 robots.txt(例如,匹配至少一个标准指令)。

意料之外的指令

robots.txt 定义非常少。让我们排除已知指令,看看还有哪些指令:

RE_COMMENT = re.compile(r'(^|\n|\r)\s*#', re.I)
headers = [r'Disallow', "Allow", "User-Agent", "Noindex", "Crawl-delay",
           "Sitemap", "Request-Rate", "Host"]
all_reg = [ re.compile(r'(^|\n|\r)[ \t]*' + h + r'[ \t]*:?', re.I) for h in headers]

switch_session('hosts')

krs = history.responded().filter(
        lambda x: x.response.status == "200" and
                  len(x.response.content) > 0)

words = defaultdict(int)
for r in krs:
  # At least one directive recognised (avoid html answers)
  for reg in all_reg:
    if reg.findall(r.response.content):
      break
  else:
    continue
  # Dump anything not matching an expected regex
  for l in r.response.content.splitlines():
    for reg in all_reg + [RE_COMMENT,]:
      if reg.findall(l):
        break
    else:
      words[l] += 1

print "\n".join(
        [ str(v) + ":" + k for k,v in
            sorted(words.items(), key=lambda x: x[1], reverse=True)
        ]
      )

下面是输出结果的摘录:

35:ACAP-crawler: *
34:ACAP-disallow-crawl: /public/search/
32:ACAP-disallow-crawl: /article_email/*
11:Clean-param: filter&ref&sq&sqc&sqm&sst&s&id&pm&do&source&method&size&query&sorting
[...]
12:<!-- WP Super Cache is installed but broken. The constant WPCACHEHOME must be set in the file wp-config.php and point at the WP Super Cache plugin directory. -->
1:/* CUSTOMIZATIONS */
[...]
8:Diasllow: authcallback.aspx
1:Disalllow: /search.php
1:Disalow: /dnd/
1:Dissalow: /catalog/product/gallery/

有多个发现。首先,非标准指令的使用(比如,ACAP-*)。其次,某些 HTML 和 CSS 注释也出现了(robots.txt 注释以 # 打头)。最后,我们注意到大量拼写错误的 Disallow,和其它的指令名字。当放宽那些额外的拼写来抽出 Disallow 指令时,这就显示出用处了。

我觉得有意思的、意料之外的一条指令是,来自于 www.hanoverian.co.nz 网站混用了 Apache 配置:

User-agent: Googlebot
Disallow: /*.gif$

User-agent: Googlebot
Disallow: /*.jpg$

RewriteEngine on
#Options +FollowSymlinks

注释

修改上面的脚本,我们再看看下面的注释:

241:# block against duplicate content
241:# block MSIE from abusing cache request
[...]
26:#Disallow: /video/index.jsp*
[...]
3:#  --> fuck off.  
2:# dotbot is wreckless.
2:# KSCrawler - we don't need help from you
1:# Huge pig crawlers are eating bandwidth

我们观察到一些注释是正常的。它们貌似来自一个模板或拷贝粘贴。一些指令也被注释掉了。当抽出 Disallow 指令时,我们将重新使用它。最后,古老的网怒症爆发了。

隐藏模式

在我最初抽出 Disallow 指令时,我抽出了路径的第一部分,并做了累加,看看这样的路径被找到多少次。虽然这个列表看起来符合我的预期,但是第一条引起了我的注意:

9995:plenum
4603:search
4038:user
3398:wp-admin
3135:en
2774:admin

其它条目看起来肯定熟悉,然而我还没有听过哪个应用程序或服务器子路径中有“plenum”。查询我们的 hosts.txt 集合:

hosts >>> history.responded().filter(lambda x: "plenum" in x.response.content)
{200:1 | www.knesset.gov.il}

只有一个命中,它是以色列国会网站。为什么这个网站有这么多的 Disallow 指令呢?下面是它们的 robots.txt 摘录:

User-agent: *
Disallow: /plenum/data/5510903.doc
Disallow: /plenum/data/5697203.doc
Disallow: /plenum/data/07822203.doc
Disallow: /plenum/data/07822803.doc
Disallow: /plenum/data/7111903.doc
Disallow: /plenum/data/7714403.doc
Disallow: /plenum/data/7714903.doc
Disallow: /plenum/data/7715303.doc
Disallow: /plenum/data/7118203.doc
Disallow: /plenum/data/7118303.doc

大约 10,000 份这样的文档明确要求不要被索引。当然,这肯定有着某种厉害关系。这些文档大部分仍然可以在线访问。使用 Google 翻译服务,我们或许找到了会议记录。我这里就不引用这些文档了,不过你可以去看看。

按照同样思维,如果一个习惯在明确阻止访问某些文档上犯了错误,那么一定有其它习惯也会犯错误。另一个查询根据类似的模式被运行了,看看发生了什么。我关注于至少包含一位数字(引用次数?)的所有 Disallow 指令,并按照出现次数排序:

27016:['www.yourdictionary.com']
12651:['download.oracle.com', 'docs.oracle.com']
9995:['www.knesset.gov.il']
9381:['state.gov']

忽略前两条,重点看第四条。恩,state.gov 有着类似的 robots.txt。

User-agent: *
Disallow: /www/
Disallow: /waterfall/
[...]
Disallow: organization/193959.pdf
Disallow: organization/193960.pdf
Disallow: organization/193543.pdf
Disallow: organization/193546.pdf
Disallow: organization/193567.pdf
Disallow: organization/193719.pdf
Disallow: organization/193723.pdf
Disallow: organization/193738.pdf
Disallow: organization/193775.pdf
Disallow: organization/193779.pdf
Disallow: organization/193791.pdf

刚才的一幕又发生了,大约 9,000 个文档明确要求不要被索引。然而这一次,它们不能再从服务器上找到了。幸运的是,互联网从来不会忘掉。

web 历史博物馆截图

涉及的大部分文档已经被存档了。但是仔细看下时间线,虽然它们通常是在不同的日期添加的,但是所有文档都在同一个日期消失了,2013 年 4 月。Edward Snowden 当时所做的某些工作,或许是这次大规模删除背后的原因。重申,我自己不会深入这些文档细节,但是你可以自便。

回到 robots.txt 列表里次数较多的条目,我们发现了 www.createspace.com。Create Space 是一家 Amazon 公司,它帮助不知名的作者出版他们的书:

Disallow: /en/community/thread/12819
Disallow: /en/community/message/91211
Disallow: /en/community/message/92213
Disallow: /en/community/message/92216
Disallow: /en/community/message/92222

大约 100 条这样的信息对于网络爬虫是隐藏的。如果我们专门看它们的内容:

Create Space 的 message 截图

我不打算深入该讨论的细节,但请记住,Create Space 明确要求这些信息不要被查询。

Disallow

现在,我们回头关注下最初目标,抽出每个路径的第一部分。每次匹配将被累加,一个文件算一次。这类似于文件频率【注5】。下面是前十个:

5806:wp-admin
5398:search
3793:admin
3206:includes
2805:cgi-bin
2663:modules
2389:xmlrpc
2384:scripts
2333:user
2153:cron

这份额外结果取决于在你的正则表达式和目标(比如仅抽出字典里的名字)里包含了哪些字符。

列表质量

同样的实验使用 Common Crawl 的另一个子集实现。抽出主机名之后,对其它子集的对比就完成了。这两份列表的相似度用 Jaccard index【注6】测量。系数值介于 0 和 1 之间,其中,1 代表高相似度(相同),0 代表不共享任何元素(不相交)。对于这两个主机名文件列表,0.27 的系数值被计算出来了。这是合理的,它们有着大量不同的网站,但是一些大型网址是共享的(正则表达式捕捉了所有被找到的 URL,包括响应内容中的 URL,而不仅仅只是被抓取的 URL)。

同样的相似度被测量了,这次是根据从这两个集合中抽出的 Disallow 词语清单。下面是结果图表:

Disallow词语分布图

两份列表中的前 N 个词语根据 Jaccard index 被标示出来了。刚开始,前 100 个词语有着高相似度(~0.88)。随着子集规模的增大,相似度开始减少。三条竖线被标出来了,它们代表了第一个集合内的出现次数的门槛。当至少出现 10 次的一个词被找到时,两个列表仍然有着相对较高的相似度。

保留多少词?

根据上面的结果,只有一定数量的、排名靠前的词语被保留到了我们的最终列表。实际上,在能发送多少请求(时间、网络限制)和找到隐藏资源所得好处之间,有一个权衡。

个人看来,我宁愿在所有时间里运行差不多没有限制的列表,也不愿意错失一个资源(如果你的 SSH keys 可以访问到 ~huang 目录,那么我不想掠过它们)。

限制

下面是这次练习中值得注意的一些限制。首先,用到的算法根本没有优化。明显的改进是存在的,但是本文的目标是,这种实验需要的脚本少于一百行,主要复用了已有工具。

其次,被频繁访问的主机名取决于 Common Crawl 的抓取策略。既然基于开源项目 Nutch,那么已知的偏见就不要有了。我没有阅读 Nutch 的源代码来证实这个假设。

第三,一些不相关的重复或许出现了,它们人为地增强了某些词语的分数。这对于有着多个子域名的域名,是尤为正确的。在此,我只是基于最大的域名(*.blogspot.*,*.wordpress.com 等)实现了一份黑名单。

最后,只有 Disallow 指令被分析了。其它有用的信息或许产生于其它指令的聚合中。

结论

正如我们所见,robots.txt 的使用不是没有影响。在最简单的情况下,它将泄露受限制的路径和你的服务器所使用的技术。不过,经过进一步的调查,有人或许发现,一些内容不应该出现在那里。

从防卫者的角度看,两个常见谬误仍然存在。第一,robots.txt 或多或少扮演着访问控制的机制。第二,它们的内容只会被搜索引擎看到、而不会被人看到。我希望这篇文章向你展示了为什么这两点是错误的,以及这种假设可能产生什么影响。