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"
}
}