用Node抓站(三):防止被封

抓取如果抓取的太快太频繁会被源站封IP,本文会介绍下通过限流、限速和使用代理的方式来防止被封

上篇文章,抓取「电影天堂」最新的170部电影,在抓取首页电影list之后,会同时发出170个请求抓取电影的详情页,这样在固定时间点集中爆发式的访问页面,很容易在日志中被找出来,而且并发请求大了,很可能会中网站的防火墙之类的策略,IP被加到黑名单就悲剧了

限流&限速

先说下限流的方法,将批量的并发请求,分成多次固定请求个数,等上一次抓取结束后,再开始下一次抓取,直到全部抓取结束。

这里我使用async模块限制并发次数,async主要有:集合、流程和工具三大类方法,这里我使用eachLimit(arr, limit, iterator, [callback]),所有修改是上篇文章的fetchContents方法,该方法接受抓取到的170个文章的url list,这次通过eachLimit将170个url按照3个一组并发,依次执行,具体代码如下:

function fetchContents (urls) {
  return new Promise((resolve, reject) => {
    var results = []
    async.eachLimit(urls, 3, (url, callback) => {
      spider({url: url, decoding: 'gb2312'}, {
        url: {
          selector: '#Zoom table td a!text'
        },
        title: {
          selector: '.title_all h1!text'
        }
      }).then((d) => {
        results.push(d)
        callback()
      }, () => {
        callback()
      })
    }, () => {
      resolve(results)
    })
  })
}

限流只是控制了一次并发的请求数,并没有让抓取程序慢下来,所以还需要限速,在限流的基础上限速就变得很简单,只需要在执行eachLimitcallback的时候,加上个Timer就好了,为了方便查看限速的效果,每次抓取成功之后,都console.log显示时间,所以改完的代码如下:

function fetchContents (urls) {
  return new Promise((resolve, reject) => {
    var results = []
    async.eachLimit(urls, 3, (url, callback) => {
      spider({url: url, decoding: 'gb2312'}, {
        url: {
          selector: '#Zoom table td a!text'
        },
        title: {
          selector: '.title_all h1!text'
        }
      }).then((d) => {
        var time = moment().format(‘HH:MM:ss')
        console.log(`${url}===>success, ${time}`)
        results.push(d)
        setTimeout(callback, 2e3)
      }, () => {
        callback()
      })
    }, () => {
      resolve(results)
    })
  })
}

效果如下:

避免重复抓取

因为一些网站更新比较慢,我们写的抓取程序在定时脚本任务(crontab)跑的时候,可能网站还没有更新,如果不做处理会造成资源的浪费,尤其国内不少VPS都是有流量限制的,不做控制,真金白银就打水漂了。。

用Node抓站(二):Promise使代码更优雅

本文主要目的是通过抓取「电影天堂」的最新电影名称和下载地址,展现如何抓取列表之后,继续抓取正文内容

使用《用Node抓站(一)》(没看过的可以翻看下本公众号的历史文章)当中写的spider.js 代码可以直接用下面的代码把列表抓出来:

var spider = require('../lib/spider')

spider({
  url: 'http://www.dytt8.net/index.htm',
  decoding: 'gb2312'
}, (err, data, body, req) => {
  if (!err) {
    console.log(data)
  }
}, {
  items: {
    selector: '.co_area2 .co_content2 ul a!attr:href'
  }
})

这里不同的是涉及到一个编码问题,「电影天堂」用的是gb2312编码,需要转成utf8,不然抓的内容会乱码。我扩展了request模块的参数增加了decoding:因为encoding被占用了,而且为了转码方便,我将encoding设为null,这样出来的数据就是Buffer,可以直接用iconv-lite之类的进行转码,涉及到编码问题不是本文讨论内容,就不多说了。

抓取列表后,发现title是被截断的,也要在正文页面抓取一下;继续写抓取下载地址和电影title的代码:

spider({
  url: 'http://www.dytt8.net/index.htm',
  decoding: 'gb2312'
}, (err, data, body, req) => {
  if (!err) {
    if (data && data.items) {
      var urls = data.items
      urls.forEach(function (url) {
        url = 'http://www.dytt8.net' + url
        spider({url: url, decoding: 'gb2312'}, (e, d) => {
          if (!e) {
            console.log(d)
          }
        }, {
          url: {
            selector: '#Zoom table td a!text'
          },
          title: {
            selector: '.title_all h1!text'
          }
        })
      })
    }
  }
}, {
  items: {
    selector: '.co_area2 .co_content2 ul a!attr:href'
  }
})

看上去挺简单的,但是回调好多啊。。。

处理这种异步回调可以使用Promise!

记一次Vue项目的重构

上周没有更新原创技术文章,原因是忙着重构一个新接手的项目,此项目因为项目技术负责人离职,虽然投入人力持续增多,前端达到4人,后端3人,但因为新参与的童鞋对代码结构和业务的理解,导致项目开发了一个多月,还有一堆问题,达不到上线要求,接手项目之后,对项目业务场景和代码进行简单的了梳理,决定重构。重构不是个人冲动,而是的的确确存在各种大大小小的问题:

  1. 接口太碎。项目本身按照vue组件化开发,但是页面每个组件独自请求自己的数据,比如:首页由轮播图、各种列表和用户信息展现组成,导致首页从上到下7~8个模块,每个模块都各自发自己的请求,访问首页需要同时发出8个ajax请求
  2. vuex的store太乱,大家按照组件去开发,各自跟后端兑接口、联调,但是没有人来统筹安排,导致大量重复工作,而且存在接口重复开发问题;接口多了store的命名就太乱,action和mutation中的业务逻辑代码太多,而不同页面需要不同的数据格式,则导致:1. 或者在mutation当中对数据进行重新整理,2. 或者新开个接口,这样就造成接口越来越多,mutation部分代码越来越重。
  3. 一开始设计或者沟通有误。比如:用户信息相关的接口,需要传入用户id(uid),而不是通过登录cookie从passport获取;第三方接口需要用户信息,竟然请求的时候将cookie发给对方(幸好cookie是http only的,没有调通被我及时发现)
  4. 重复代码太多,抽象能力太差。一份代码在多个地方复制,导致代码改来改去最后都不知道哪里改了哪里没改
  5. 命名太乱,包括url、方法名之类,还有错别字,getAdcontent(用户地址信息),getmaildetail(用户地址信息)
  6. 研发人员缺乏全局意识,只管自己的代码,而不关心整个流程。由于前后投入人较多,没人对整个项目有把控,只能面向自己的需求编程。比如:积分获取页面,获取成功之后,联调成功,但是实际在积分获取列表页面却没有相关的记录信息;在比如:任何用户都可以领走别人的奖品,原因后端没做奖品是否是当前登录用户获取的校验
  7. 问题定位能力不够,遇见问题一调就是半天,找不到根本问题

介绍下项目背景:

此项目是一个积分项目,一些页面需要用户登录,页面主要包括:首页、任务+列表、商品+列表、个人信息和记录以及其他类(说明和规则等)
项目用Vue+yog2编写,ajax请求部分使用vue resource

Vue SSR从入门到Case Study

最近两个项目同时开发,使用了Vue2的SSR,这样后端渲染页面首屏可以加快页面呈现,增加SEO和用户体验,但是项目上线后却发现了严重的性能问题,于是在三天内两次重大调整,最后只能放弃Vue SSR,本文从Vue SSR实现开始,逐渐复盘整个事件。

两周前就预告了要写一篇Vue SSR的文章,但是没想到上周四上线之后,周六放量之后发现性能问题,这周一到周三,做了两次重大调整,最终还是放弃了SSR,并且做了这次事件复盘。

技术选型

调研Vue已经很久了,随着Vue2正式发布,使用Vue来做项目又燃起了希望,不是为了一时的技术理想和情怀(了解我的人都知道,我不是这样的人),主要是出于下面几方面考虑:

  1. 用artTemplate+Sass+JS做的components方案已经做了很久了,沉淀了很多组件,随着Node服务开始上线,一直想在此基础上做同构,而公司Node框架Yog2的view层选择偏向于Smarty模板的Swig,修改比较麻烦
  2. 既然改不了,那么要换不如直接选择新的components方案,这次最强烈需求是:组件化和支持SSR,而Vue2之后支持SSR
  3. 这次两个项目同时进行,而且仅仅给两周的开发时间,组件化有效提高工作效率,可以把通用的组件抽象出来,多个页面之间业务组件复用率也很高,而且业务组件在后续的运营活动也可以直接复用
  4. 手百产品形态复杂,页面即在手百内使用又有手百外使用,手百内页面被多个Webview隔开,不适合SPA形式,而手百外适合SPA形式,所以一套代码需要适配两种情况,Vue 可以适应这两种方式
  5. Vue的SPA形式可以方便进行PWA和Hybrid改造(继续关注本公众号Hybrid系列)

所以,最后决定:上Vue!技术栈:Vue2+Yog2

再介绍下两个项目:

  • 项目A是老项目进行重构,产品需求要跟功能全部保留,架构跑通使用的是Vue2.1,所以A项目代码相对复杂,一直没有使用Vue2.2
  • 项目B是新项目,开始使用Vue2.1,上线后发现已经有Vue2.2,于是升级Vue2.2,并且把项目目录结构调整一番,Webpack config等都可配

Vue SSR入门到上线

先看下Vue SSR的实现流程图:

简单解释一下:

  1. app.js是Server和Client公用的
  2. webpack会根据server-entry.js和client-entry.js打包出来两个文件:server-bundle和client-bundle
  3. server-bundle用于后端渲染(2.1是js文件,2.2变成json,引入更加方便)

但是这张图没有说明在调用API接口方面,前后端是怎么公用代码的。前端走的是Ajax请求,后端走的是http请求(百度内部是RAL接口服务管理),结合上图补充完整的代码执行流程图如下: