node.js网页截图

最近有一个需求,是根据配置,用程序生成类似下面的图片:

screenshto

首先图片有一个背景图,然后有title和一个按钮,按钮里有文字。背景图和文字都是写在配置文件里的。 难点在于,文字和按钮都是有特殊效果的(渐变,阴影,描边)。

一开始,我尝试用Python+Pillow来绘制图片,但是发现非常困难,因为Pillow(PIL)提供的接口都比较底层(low level),比如写文字需要指定 (x, y) 坐标,要实现居中都得先计算出文字的宽度然后再算坐标。 渐变阴影之类的效果,就更困难了。

然后我突然想到,CSS里不是可以很简单的设置渐变、阴影吗,居中什么的更是家常便饭。于是有了一个新思路:根据配置生成HTML文件,然后对这些网页进行截图,就能得到想要的图片了。

PhantomJS/Pageres

在用jade完成了根据配置生成HTML文件后,开始了网页截图的实现。

首先想到的是 PhantomJS,之前用过PhantomJS来对网页截图,但是我不太喜欢 phantomjs-node这每步都是回调的用法,于是找了下有没有调用PhantomJS截图的库,就找到了pageres,代码如下:

function takeScreenshot(htmlPath, outPath, size, done) {
  var pageres = new Pageres()
    .src(htmlPath, [size])
    .run(function(err, items) {
      if (err) {
        done(err);
      } else {
        var f = fs.createWriteStream(outPath).on('finish', done);
        items[0].pipe(f);
      }
    });
}

webkit2png

pageres的方案试用之后,发现有时,生成的图片效果和浏览器里看到的不同(可能因为用到了 -webkit-text-stroke 这个比较新的CSS属性),怀疑是因为 PhantomJS 所用的 Webkit 的版本相对较老。折腾了一下未果之后,想到了直接调用系统里的Webkit进行截图的 webkit2png

webkit2png 是一个Python写的网页截图脚本,通过调用OSX的接口来通过Webkit对网页进行截图。

找了一下,没有找到 webkit2png 的nodejs绑定,于是只好通过child_process来执行了,简单包装了一个 webki2png.js如下:

var util = require('util');
var exec = require('child_process').exec;

require('shelljs/global');

module.exports.isSupported = function() {
  return !!which('webkit2png');
};

module.exports.capture = function (url, options, callback) {

  if (!callback && typeof options === "function") {
    callback = options;
    options = {};
  }

  var command = ['webkit2png'];
  var arg = null;

  Object.keys(options).forEach(function(key) {
    var value = options[key]
    if (value === true) {
      arg = util.format('--%s', key);
    } else {
      arg = util.format('--%s=%s', key, options[key]);
    }
    command.push(arg);
  });
  command.push(url);

  command = command.join(' ');
  var child = exec(command, function(error, stdout, stderr) {
    callback && callback(error);
  });

};

ChromeDriver

换上 webkit2png 之后,之前的问题解决了,但是又出现了新的问题:对泰文生成的图片效果不好,具体来说,相同的网页,Chrome显示很完美,Safari很差劲,webkit2png的效果呢,介于二者之间。

为了更完美的效果,开始寻找控制Chrome截图的方案,最后找到了ChromeDriver。

首先要知道什么是 WebDriver,我们知道Selenium是一个网站自动测试工具,通过它可以编程控制浏览器进行一些操作,WebDriver就是抽象出的一层控制浏览器的API,ChromeDriver则是针对Chrome的WebDriver API的实现。

首先需要在ChromeDriver的官网下载: https://sites.google.com/a/chromium.org/chromedriver/ 解压后会有一个可执行文件 chromedriver,执行它,就会在默认的9515端口启动一个Webdriver服务器。然后借助 wd 这个nodejs的WebDriver库,就可以控制Chrome进行截图了,简单封装了 chrome2png.js 如下:

var fs = require('fs');
var path = require('path');
var wd = require('wd');
var gm = require('gm');

var browser = wd.promiseChainRemote('http://localhost:9515/');

module.exports.capture = function(htmlPath, outPath, size, callback) {
  var url = 'file://' + path.resolve(htmlPath);
  browser
    .init({browserName:'chrome'})
    .get(url)
    .takeScreenshot()
    .then(function(base64Data) {
      fs.writeFileSync(outPath, base64Data, 'base64');
      gm(outPath).crop(size.width, size.height, 0, 0).write(outPath, callback);
    })
    .fail(callback)
    .fin(function() {
      return browser.quit();
    })
    .done();
};

生成的效果非常完美。唯一的缺点是,在截图过程中,浏览器窗口会弹出,不过在我这个需求下是可以忍受的。

ack

ack是一个几乎每天都在用但是从没学习过的东西,刚才看了一遍文档,这里记录一下有用的几点。

ack [OPTION]... PATTERN [PATH]

常用参数

-i 忽略大小写

ack -i note 也会匹配 Note

-Q literal

ack -Q 'note.url()' 不需要对点和括号进行转义了

-C [NUM] 输出上下文

ack -C 5 note 会在上下各多输出5行

-f 查找文件

ack -f --css 列出所有的css文件

注意,ack的参数列表里,--x 等价于 --type=x

-g 匹配文件名

ack -g note 会匹配所有文件名里有note的文件

-n 不递归查找子目录

ack -n note 只在当前目录下的文件里查找

--type 只从指定的文件类型里找

ack --type=css note 只在css文件里找

自定义类型

之前遇到过的一个问题是,在css目录里用ack搜索某个词,发现结果总是空,而明明是有包含这个词的文件的,很是不解。今天看了文档才知道,ack是按文件类型来搜索的,预定义的文件类型列表可以通过ack --help-types看到,我们的css是用scss写的,ack并不支持这个类型,所以无法搜索。

怎么解决呢?也很简单,只需要增加一种文件类型就可以了。方法是在配置文件 ~/.ackrc 里增加一行:

--type-add css=.scss

就可以了~

Doora

当我第N次面临“把电脑上的文件传到手机”的困境的时候,我决定解决这个问题。

想了想,我需要的是一个很简单的网站,能快速的打开,拖拽上传,生成一个二维码,手机扫描下载。但是好像还没看到完全符合这个条件的网站(虽然我觉得肯定有...),于是就开始自己写了。

第二天,有了 Doora

名字

Doora,哆啦A梦,任意门,传送。。。感受一下。

七牛

上传后的文件是存在七牛上的,主要是为了提供比较好的下载速度,而且之前七牛搞活动的时候充过一些钱,不用白不用。 不过七牛的文档...,半天都找不到想找的内容,Python SDK的接口也不太友好。

过期

不过七牛没有提供设定文件上传后多久删除的功能,只能自己实现了。 现在的方案是通过rq-scheduler,上传后30分钟后,把文件的key放入删除队列里,然后由rqworker来消费删除。

速度

为了加快页面显示速度,尽可能减少了阻塞的css和js文件,现在只会加载一个css,由内嵌在HTML里的load.js来异步加载其它js,引用的静态都是放在http://www.staticfile.org/ CDN上的。

源码

https://github.com/wong2/doora

夜拍

没什么长进...

KFC 背吉他的姑娘

顺便试试七牛

Hello

看,我又又又搞了个新博客。

这次是为什么呢?说来也很无聊。

前几天我的一个.me的域名要到期了,于是去Godaddy看看价格,顺手就查了下wong2.me,然后顺手就买了,然后顺手就开始搭新博客了... 放着该做的事情不做,搞一些旁枝末节,嗯,我老这样。

看,我实在懒的搞备案了,终于换掉了心爱的wong2.cn域名。为了纪念,写一下我为什么这么喜欢它吧: 因为我觉得它很有对称美。

我还是喜欢写点东西的,特别是编故事。 抽风的时候,我就会根据QQ群里的一句话展开,编一个荒唐的故事。在QQ上一句一句的写,大有刷屏之势。 基本我一开始写,群里面就没人说话了,更有些没良心的直接把群屏蔽掉了,这样没有反馈的写着写着,也就兴致寡然,突然收尾了。 然后大家回来继续扯淡。

- “给你们讲个故事啊。”
- “88”

耶,扯完了第一篇。

push......