乐趣区

记一次翻译站经历

做这个记录之前,刚完成使用 drone 作为公司前端项目的持续交付工具的实践,打算写的教程前先把官方文档扒下来做个翻译站。在实践一番后,卡在不能频密调取 google 翻译这块上,项目无法进行下去。最后觉得经历的过程涉及的内容挺多的所以记录一下同时分享给大家。
这次经历涉及以下知识:

wget 抓取网站
使用基于 python 的翻译工具
使用 nodejs 调取命令行
使用 nodejs 读写文件
为 express 添加 jwt

基于 express 实现上传文件
在 nodejs 环境下读取并编辑 html 文件

我们一点点来说。

wget 抓取网站
最初是寻找有什么带可视化的工具来达到目的的,后来在寻找的过程中看到原来 wget 能实现整站抓取,所以怎样简单怎样来!
# 抓取整站
wget -r -p -np -k -E http://www.xxx.com
# w 抓取第一级
wget -l 1 -p -np -k http://www.xxx.com

-r 递归抓取

-k 抓取后修正链接,适合本地浏览

-e robots=off 忽略 robots 协议,强制抓取(流氓抓取)

-E 将 text/html 类型的文档保存为.html 的文件

使用基于 python 的翻译工具
这个在 github 上找了几个工具,同时也考虑过使用官方提供的 API(微软和 google 均有提供),最后得出使用 soimort/translate-shell(并不想花钱和花时间再看文档上了 >w<)
这个 trans shell 工具提供几个翻译源 (google, bing, yandex, apertium),不知道为何能用的只有 google Σ(!゚д゚)。google 也很有保证了,问题不大。
安装并不复杂,只需要安装 gawk,其他在 ubuntu 系统下默认都有包含的:
GNU Awk
gawk 安装
$ sudo apt-get install gawk
尝试:
$ gawk -f <(curl -Ls git.io/translate) — -shell
安装 trans 本体,官方文档提供三种方式,方式 1 不知道为何有 bug,方式 2 并不太熟悉,最后选择方式 3:
$ git clone https://github.com/soimort/translate-shell
$ cd translate-shell/
$ make
$ [sudo] make install
使用起来也是简单:
$ trans ‘Saluton, Mondo!’
Saluton, Mondo!

Hello, World!

Translations of Saluton, Mondo!
[Esperanto -> English]
Saluton ,
Hello,
Mondo !
World!
简短输出方式:
$ trans -brief ‘Saluton, Mondo!’
Hello, World!
翻译文件:
$ trans -b en:zh -i input.txt -o output.txt
使用 trans 调取 google translate 进行翻译不能频频调用,频频调用之后会令后续请求 503,被 google 限制请求!!
使用 nodejs 调取命令行
完成翻译的调研后,感觉本地实现翻译需要安装各种东西,不如做成 web 应用好了。用 express 快速建立网站应用,关于在 nodejs 下调用命令其实是没啥头绪的,搜索得出结果发现可以使用 Child Process 模块实现:
const util = require(‘util’)
const exec = util.promisify(require(‘child_process’).exec)
exec(`trans -V`)
.then(({stdout, stderr}) => {
if(stdout.indexOf(“not installed”) > -1) return Error(stdout)
})
.then(()=>exec(`trans -b ${language} -i ${input} -o ${output}`))
.then(({stdout, stderr})=>{
return {
input,
output,
message: stdout
}
})
使用 nodejs 读写文件
这个就不详细说明了,简单列一下用到的函数:

fs.readFileSync(path) 同步读取文件例子:
const data = fs.readFileSync(‘./test.txt’)
console.log(data.toString())
// testing!

fs.writeFileSync(path, data) 同步写入文件例子:
try{
fs.writeFileSync(‘./test.txt’, ‘testing!!’)
}catch(e){
console.error(e)
}

fs.unlinkSync(path) 同步删除文件例子:
try{
fs.unlinkSync(‘./test.txt’)
}catch(e){
console.error(e)
}

为 express 添加 jwt

先说一下 jwt,全名叫 JSON Web Tokens,是一种开放的,行业标准的 RFC 7519 方法,用于表示两端之间的应用安全。
RFC 是由 Internet Society(ISOC) 赞助发行的互联网通信协议规范,包含各种各样的协议,同时包含互联网新开发的协议及发展中所有的记录。
jwt 这种实现已经成为互联网通讯安全标准,那么在 express 怎样实现?
首先安装下面两个包:

auth0/node-jsonwebtoken
auth0/express-jwt

npm i -S express-jwt jsonwebtoken
使用:
const {router} = require(‘express’)
const decode_jwt = require(‘express-jwt’)
const jwt = require(‘jsonwebtoken’)

const secret = “your-secret” // 盐

// 登录
router.get(‘/login’, function(req, res, next) {
/**+[登录逻辑]…**/
const token = jwt.sign(user, secret)
res.status(200).send({user, token})
})

// 受限的接口
router.get(‘/user/star’, decode_jwt({secret: secret}), (req, res)=>{
const {user} = req
const stars = []
/**+[获取用户 star 列表]**/
res.status(200).send(stars)
})
解释一下,jsonwebtoken 包为加密作用,secret 作为盐用来混淆内容(出于安全是不能对客户端公开),然后经过 express-jwt 解密处理 http header 里带有的 authorization: Bearer [token] 中的 token 来获得 user 信息。这样在 /user/star 接口中就能获取到用户资料做后续的业务处理了。
基于 express 实现上传文件
忘了说明这里提及的 express 版本为 4,那么在新版的 express 4 文档中提及了这么一段关于上传文件的处理说明:
In Express 4, req.files is no longer available on the req object by default. To access uploaded files on the req.files object, use multipart-handling middleware like busboy, multer, formidable, multiparty, connect-multiparty, or pez.
意思是:express 4 版本 req.files 字段不在有效,需要使用上面提及的中间件提供支持才能实现读取上传来的文件。
看了一番文档,最后选择了 multer。
下面讲一下如何使用:
安装
npm i -S multer
使用
const multer = require(‘multer’)
const limits = {fieldSize: 1024*1024*3}
const extname = ‘html’
// 创建本地储存
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, ‘./uploads’);
},
// 储存文件时自定义文件名称
filename: function (req, file, cb) {
cb(null, file.fieldname + ‘-‘ + Date.now());
}
})
// 创建上传处理
const uploader = require(‘multer’)({
storage,
limits,
fileFilter(req, file, cb){
if(path.extname(file.originalname) === `.${extname}`) cb(null, true)
else cb(new Error(`upload file extname must be ${extname}`))
}
})
/**
* 上传接口
* 只接受 http 头是 `content-type: multipart/form-data` 的数据
* 这里设定获取字段是 `file` 的内容储存成文件来完成文件上传工作
**/
router.post(‘/trans_on_upload’, uploader.single(‘file’), (req, res)=>{
const {file} = req
const fileData = fs.readFileSync(file.path)
console.log(fileData.toString())
res.status(200)
})
multer 接受多种文件上传方式:

uploader.single(fieldname) 接受一个以 fieldname 命名的文件。这个文件的信息保存在 req.file

uploader.array(fieldname[, maxCount]) 接受一个以 fieldname 命名的文件数组。可以配置 maxCount 来限制上传的最大数量。这些文件的信息保存在 req.files。

uploader.fields(fields) 接受指定 fields 的混合文件。这些文件的信息保存在 req.files。fields 应该是一个对象数组,应该具有 name 和可选的 maxCount 属性。例子:
[
{name: ‘avatar’, maxCount: 1},
{name: ‘gallery’, maxCount: 8}
]

uploader.none() 只接受文本域。如果任何文件上传到这个模式,将发生 “LIMIT_UNEXPECTED_FILE” 错误。这和 upload.fields([]) 的效果一样。

uploader.any() 接受一切上传的文件。文件数组将保存在 req.files。警告: 确保你总是处理了用户的文件上传。永远不要将 multer 作为全局中间件使用,因为恶意用户可以上传文件到一个你没有预料到的路由,应该只在你需要处理上传文件的路由上使用。

multer 使用起来有一定限制,并不是所有项目合适使用,所以不做深入说明。
在 nodejs 环境下读取并编辑 html 文件
这里处理的流程是使用 fs.readFileSync(path) 读取 html 文件内容后,希望能以 dom 方式编辑内容,使用 jsdom/jsdom 能像在浏览器一样的方式处理 DOM,是非常好用的工具。
比如我的需求是获取所有 TextNode 提取内容进行翻译并替换原来内容,最后导出 html 内容:
const {JSDOM} = require(“jsdom”)
const {minify} = require(“html-minifier”)

// 递归获得所有 TextNode
const getAllTextNode = (node)=>{
var all = [];
for (node=node.firstChild;node;node=node.nextSibling){
const {parentNode} = node
if (node.nodeType==3){
all.push(node)
}
else all = all.concat(getAllTextNode(node));
}
return all;
}

const html = “”
/**+[获取 html 内容]**/
const vbrows = new JSDOM(minify(html, {
collapseWhitespace: true
}))
const {document} = vbrows.window
const textNodes = getAllTextNode(document.body)
textNodes.forEach(textNodes=>{
const transStr = textNodes.textContent
/** 翻译处理 **/
textNodes.textContent = transStr
})
// 翻译结果
console.log(‘trans result’, vbrows.serialize())
总结
完成一个应用涉及的范围其实挺广的,内容如果再深入讨论应该能写更多内容吧。由于手上还有其他工作,只能大致记录关键词和使用的方式,原理方向等有时间再深入研究。

退出移动版