反爬虫实战之text转canvas

@songhlc 2018-07-17 13:55:42发表于 yonyouyc/blog

记录分享一种text转canvas的实践

最近遇到线上页面被千里马爬取的数据的问题,问题网址
看了一下爬取的结果发现,是直接把我们报价页面的渲染完的结构给爬取了,然后把div的样式和style都去掉了,同时span也进行了合并。
于是猜测有可能是用类似chrome的headless之类的功能,对我们的页面数据进行了爬取(因为报价接口做过反爬虫的拦截)。

所以萌生了将关键信息由text转成canvas让爬虫爬不到关键信息的想法。

之前刘旭写过类似的代码,这里直接把源代码拿过来

let canvas = document.getElementsByClassName('canvas-box')[this.index]
// 简单地检测当前浏览器是否支持Canvas对象,以免在一些不支持html5的浏览器中提示语法错误
if (canvas.getContext && this.text) {
  // 获取对应的CanvasRenderingContext2D对象(画笔)
  let ctx = canvas.getContext('2d')
  // 设置字体样式
  ctx.font = this.fontStyle
  // 设置字体填充颜色
  ctx.fillStyle = this.color
  // 从坐标点(x,y)开始绘制文字
  ctx.fillText(this.text, this.positionX, this.positionY)
}

代码的核心就是创建一个canvas然后调用fillText方法,写入文字。

试验之后发现两个问题。

  • 1.文字在屏幕上显示很不清晰
  • 2.canvas宽度是写死的 如果文字短则宽度会显得太长,如果文字比较长,那么又会出现宽度不够的问题

百度之后发现问题所在

  • 1.我使用的mac是高清屏幕(即: window.devicePixelRatio的值是2,普通屏幕是1),canvas绘制在高清屏幕下的问题具体可以参见https://www.html5rocks.com/en/tutorials/canvas/hidpi/ 译文:https://www.jianshu.com/p/2cd5143cf9aa
    ,简单描述就是,当css设置100px,实际上屏幕使用200px的物理像素来显示,所以自然会出现模糊的情况。为了解决这个问题,我们需要通过放大devicePixelRatio 倍canvas的宽高,然后通过用css将canvas缩小回原像素(如:canvas.width=100px * 2(这里的2是devicePixelRatio的值) ; canvas.style.width = 100px)
  • 2.canvas支持ctx.measureText(val).width方法,我们可以根据传入的字符串长度来动态计算文本的尺寸,从而来动态设置canvas的宽度

直接上关键代码

function init (params) {
    var canvas = params.$el.firstElementChild
    // 屏幕像素比
    var ratio = window.devicePixelRatio || 1;
    // canvas style的高度宽度
    this.styleWidth = ko.observable(params.width || 200)
    this.styleHeight = ko.observable(params.height || 20)
    this.styleWidthPx = ko.computed(function () {
      return this.styleWidth() + 'px'
    }.bind(this))
    this.styleHeightPx = ko.computed(function () {
      return this.styleHeight() + 'px'
    }.bind(this))
    // canvas实际的高度宽度 需要乘以当前的ratio
    this.width = ko.computed(function () {
      return this.styleWidth() * ratio
    }.bind(this))
    this.height = ko.computed(function () {
      return this.styleHeight() * ratio
    }.bind(this))
    
    this.drawCanvas = function (val) {
      if (canvas.getContext && val) {
        // 获取对应的CanvasRenderingContext2D对象(画笔)
        var ctx = canvas.getContext('2d')
        clearCanvas(ctx)
    
        // 设置字体填充颜色
        ctx.fillStyle = '#333'
        var stringWidth = ctx.measureText(val).width * ratio
        // 宽度计算不准确防止被遮住需要放大宽度1.5倍
        stringWidth = stringWidth / ratio * 1.5
        this.styleWidth(stringWidth)
        ctx.width = this.width()
        ctx.height = this.height()
        ctx.scale(ratio, ratio);
        // 设置字体样式
        ctx.font = '13px Arial'
        ctx.fillText(val, 0, 13)
      }
    }.bind(this)
    if (ko.isObservable(params.text)) {
      params.text.subscribe(function (val) {
        this.drawCanvas(val)
      }.bind(this))
      setTimeout(function () {
        this.drawCanvas(params.text())
      }.bind(this))
    } else {
      setTimeout(function () {
        this.drawCanvas(params.text)
      }.bind(this))
    }
}

具体可以参见代码 cpu-portal-fe/js/widgets/kocomponent/text2canvas