node脚本更新详情页页面

2021-08-04  本文已影响0人  AAA前端

背景

公司项目里面资讯站里面的详情页,是php写的。由于每个站点有都有几千个页面,模板都是同一个。所有每次修改后,需要php跑脚本同步。

但是现在公司基本不怎么用php了。 php的同事也被分到其他java组去了。有其他的活。现在就是如果产品连续几天都有资讯站详情页的需求,php同事要么就推迟更新,要么就不更新(可能真的很忙)。

昨天由于php同事没有更新页面,线上页面没有变化,产品也不管了,直接拉个群,把大家拉进去,还把领导拉了进去。最后在领导协调下才同步了页面。

以前前端没有权限获取详情页更新的权限,昨天php同事给我们开通。 里面要更新的话,需要一个一个点。(用于更新一个,给产品验收,通过之后再让php同步脚本, 这个才是本次能前端跑脚本的基础)

image.png

由于不同的频道比如 新概念英语,会生成不同的cookie和pc_hash。所以我这次也没有 写登录获取用户信息等。直接在 点击 具体频道之后 ,去抓 cookie, pc_hash。

首先结构是
左侧标题列表 点击某一项专题列表,右侧会出现需要更新的详情页,分页展示,每一页20条。

脚本 逻辑就是 先获取左侧标题列表。 在通过左侧标题列表 分别递归 获取所有页面的 详情页。 再 分别调用更新接口更新。

README.md

### 用于更新 资讯站 wap 详情页
#### 需求
由于需要 php同事 写脚本,更新资讯站wap 详情页 会占用 其开发时间。 
现在php那边要求 我们去后台先生成一个详情页,让产品验收,产品验收后,再发邮件给php,让其同步更新。

如遇多次更新(产品上线后有修改),php边可能会延迟更新,或者直接不更新。。。。(本次被产品拉群,拉php领导后才更新)

所有本次抓接口,实现本次跑脚本更新 (能实现的原因也是本次php要求 生成一个详情页让产品验收的过程中, 给前端和产品 开通 `管理内部` 权限后,才得以抓接口,知道还有出了php跑脚本外,还有地方都能更新 -.- )

### 结构
|config-配置目录|
    | config-fix.js - 重试更新配置
    | config-user.js - cookie等用户信息配置
|logs-日志|
    | sderr.log - 失败日志
    | sdout.log - 更新日志
|src|
    | commong.js -公共方法
    | fix.js - 重试失败更新
    | request.js - 请求封装
    | updageAll.js - 更新方法
    | util.js - 公共方法

#### 使用
**更新所有**

由于 登录之后,切换不同站点,pc_hash, cookie也会变化。
需要去 `站点-内容-管理内容` 。点击`管理内容`后 拷贝 cookie, pc_hash到 config/config-user.js中。
运行 `npm run update`

stderr.log 错误日志
stdout.log 打印日志

如果某一页或某个接口同步失败,去stderr.log中获取  curSearch page. 把 变量 放到config/onfig-fix.js中对应地方。
运行`npm run fix`重新同步一下(在同步英语频道时,某一页同步失败了。 重新 fix.js同步对应页面,同步成功)

commons.js

const { $ajax } = require('./request.js')
const cheerio = require('cheerio')
const { pc_hash } = require('../config/config-user.js')
const { logger } = require('./util.js')

let searchList = [] // 需要刷新的左侧标题列表
let page = 1 // 当前页
let curSearch = null

// 左侧标题列表
function getLeftList() {
  const url = '?m=content&c=content&a=public_categorys&type=add&menuid=822&pc_hash=' + pc_hash;
  $ajax({ url }).then(res => {
    let $ = cheerio.load(res);

    let $list = $("img[alt='添加']").parent().next();

    if (!$list || $list.length <= 0) {
      logger.log('左侧标题列表已经更新完毕')
      return console.log('更新列表为空')
    }

    var obj = {} // 用来在日志 中展示 左侧标题列表名称 与 链接的对应关系
    $list.each(function () {
      const href = $(this).attr('href')
      searchList.push(href)
      obj[$(this).text()] = href
    })

    logger.log('获取需要更新的左侧标题列表', JSON.stringify(obj))
    curSearch = searchList.shift()
    page = 1
    getDetailList(curSearch, page)

  }).catch(err => {
    logger.error('左侧标题列表获取失败', err)
    console.log('getLeftList-err', err)
  })
}

// 获取 当前页 所有需要更新的数据
function getDetailList(searchParams, curPage, fixFlag) {

  if (!searchParams) {
    !fixFlag && logger.log('更新结束,已经无curSearch')
    return console.log('更新结束');
  }

  !fixFlag && logger.log(`开始获取需要更新的一组列表,curSearch: ${searchParams}, page:${curPage}`)

  $ajax({ url: `${searchParams}&pc_hash=${pc_hash}&page=${curPage}` }).then(res => {
    let $ = cheerio.load(res);
    const url = $('input[value="批量生成HTML"]').attr('onclick').match(/^myform.action=\'(.+)\'/)[1]
    let trs = $('form[name="myform"]').find('tbody tr')
    const len = trs.length

    if (!trs || len <= 0) {
      if (!fixFlag) {
        logger.log(`已经更新完了一个列表,---${searchParams},共更新${curPage}页`)
        page = 1
        curSearch = searchList.shift()
        getDetailList(curSearch, page)
      }
      return console.log('没有更多需要更新的数据了')
    }
    for (let i = 0; i < len; i++) {
      let form = {
        pc_hash,
        recommend_content_flag: 0
      }
      let $q1 = $(trs[i]).children().eq(0).find('input')
      let $q2 = $(trs[i]).children().eq(1).find('input')
      form[$q1.attr('name')] = $q1.attr('value')
      form[$q2.attr('name')] = $q2.attr('value')
      formDataFn(url, form, fixFlag)
    }

    // 开始请求下一页
    !fixFlag && setTimeout(() => {
      console.log(`当前列表更新完第${page}页,还有${searchList.length}个列表等待更新`)
      !fixFlag && logger.log(`更新完当前${page}页,准备更新下一页`)
      page += 1
      getDetailList(curSearch, page)
    }, 1000);
  }).catch(err => {
    !fixFlag && logger.error(`获取需要更新的一组列表失败,还有${searchList.length}个列表等待更新---err:${err}, 当前curSearch:${searchParams}, page:${page}`)
    console.log('getDetailList-err', err)
  })
}

// 实际更新 某一条数据
function formDataFn(url, formData, fixFlag) {
  $ajax({ url, method: "POST", formData }).then(res => {
    if (/操作成功/.test(res)) {
      fixFlag && console.log(`更新成功一条数据`);
      !fixFlag && logger.log(`更新成功一条数据:url:${url}, formData:${JSON.stringify(formData)}`)
    } else {
      console.log(`更新失败一条数据-curSearch:${curSearch}, page:${page}`)
      !fixFlag && logger.error(`更新失败一条数据:url:${url}, formData:${JSON.stringify(formData)},curSearch:${curSearch}, page:${page}`)
    }
  }).catch(err => {
    !fixFlag && logger.error(`更新某一项数据接口失败:还有${searchList.length}个列表等待更新。 url:${url}, formData:${JSON.stringify(formData)},curSearch:${curSearch}, page:${page},err:${err}`)
    console.log('formData-err', err)
  })
}


module.exports = { getDetailList, getLeftList }

fix.js

const {getDetailList} = require('./common.js')
const {curSearch, page} = require('../config/config-fix.js')

getDetailList(curSearch, page, true)

request.js

const request = require('request')
const { Cookie } = require('../config/config-user.js')
const baseUrl = 'http://phpcms.koolearn.com/index.php'

exports.$ajax = function ({ url, method = "GET", formData = {} }) {
  return new Promise((resolve, reject) => {
    request({
      url: baseUrl + url,   // 请求的URL
      method,                   // 请求方法
      headers: {
        "Content-Type": "text/html; charset=utf-8",                  // 指定请求头
        'Cookie': Cookie // 指定 Cookie
      },
      formData
    }, function (error, response, body) {
      if (!error && response.statusCode == 200) {
        if(/请先登录后台管理/.test(body)){
          reject('登录cookie过期')
        }
        resolve(body)
      }
      reject(error)
    });
  })
}


updateAll.js

const fs = require('fs')
const path = require('path')

// 如果存在日志。在 更新所有的时候,删除日志。
const stdoutPath = path.resolve(__dirname, '../logs/stdout.log')
const stderrPath = path.resolve(__dirname, '../logs/stderr.log')

if(fs.existsSync(stdoutPath)){
  fs.unlinkSync(stdoutPath)
}
if(fs.existsSync(stderrPath)){
  fs.unlinkSync(stderrPath)
}

const { getLeftList } = require('./common.js');
getLeftList()


util.js


const fs = require('fs');
const path = require('path')
const options = {
  flags: 'a',     // append模式。 // 不能用w 模式,不然fix的时候也会删除掉日志。在updateAll 中手动删除
  encoding: 'utf8',  // utf8编码
};

const stdout = fs.createWriteStream(path.resolve(__dirname, '../logs/stdout.log'), options);
const stderr = fs.createWriteStream(path.resolve(__dirname, '../logs/stderr.log'), options);

// 创建logger
const logger = new console.Console(stdout, stderr);
module.exports = { logger }

package.json

{
 "name": "cms",
 "version": "1.0.0",
 "main": "index.js",
 "license": "MIT",
 "scripts": {
   "update": "node ./src/updateAll.js",
   "fix": "node ./src/fix.js"
 },
 "dependencies": {
   "cheerio": "^1.0.0-rc.10",
   "request": "^2.88.2"
 }
}


上一篇 下一篇

猜你喜欢

热点阅读