序言

去年7月刚过了日语N2,想着今年考个N1,为了加深日语文化的了解,还有学习日语,平时免不了经常上日语网站。

但是毕竟水平有限,所以不免遇到不认识的单词,日语单词的一个特点就是很多单词你知道是什么意思,但是不知道怎么读。

比如:“簡素な構造” 中的第一个词:“簡素”,很显然就是“简单,朴素的意思”,但是你肯定不知道它的读音是:“[かんそ]①”。

以前遇到这样的词的时候,就会在沪江小D网页版上面查询,但是这样特别麻烦,你要跳转到别的网站,更别提沪江每次进去弹出来的广告了。

所以呢,就想找一个能不能弄一个划词翻译的Chrome插件,能够标注出读音,怎么详细怎么来,这样便于学习。

开始折腾

因为之前写过一些过滤广告和网页辅助排序的Chrome扩展,作为一个爱折腾的开发者。当然是自给自足风衣足食啦。

百度翻译API

首先想到的是,直接使用翻译API来进行翻译,然后把翻译的结果在当前页面加一个提示框显示出来。

首先是百度翻译API:http://api.fanyi.baidu.com/api/trans/product/apidoc 官方文档很简单,只需要注册一个appid,然后HTTP请求即可。百度翻译API每个月有200W字符的免费使用资源,所以可以随便用。

注册开发者账号,申请了appid,然后照着它的文档模拟请求,因为签名方式很简单,而且官方给出了demo,很轻松就获取到了返回数据。

然而,返回的数据格式有点不对。

{
"from": "jp",
"to": "zh",
"trans_result": [
{
"src": "合格",
"dst": "合格"
}
]
}

。。。这翻译很直接,只有翻译结果,什么读音都没有,很绝望,PASS.

没办法,只能使用其他平台的API了,另外一个就是有道云的翻译API。和百度API一样,注册账号,创建应用。正打算使用的时候,发现有道云的翻译API是收费的,不过不用紧张,只要注册就送100元,够你翻译几千万字符了。

有道翻译API

有道云翻译API文档:http://ai.youdao.com/docs/doc-trans-api.s#p02,我发现一个有意思的事情,百度翻译API和有道云翻译API的调用方式几乎一模一样,两个平台给出的Demo中,什么数据签名方式,数据请求方式,几乎都是一样的。唯一需要注意的是,百度请求的应用标识参数是: appid,有道云的是: appKey,要小心一点。

很快,获取到百度翻译API的返回结果如下。

{
"tSpeakUrl": "http:\/\/openapi.youdao.com\/ttsapi?q=%E5%90%88%E6%A0%BC&langType=zh-CHS&sign=3118EBCD5EC1D0A416EF32032FE55FAF&salt=1521012839301&voice=4&format=wav&appKey=3a72ad95fe43ac83",
"query": "合格",
"translation": [
"合格"
],
"errorCode": "0",
"dict": {
"url": "yddict:\/\/m.youdao.com\/dict?le=jap&q=%E5%90%88%E6%A0%BC"
},
"webdict": {
"url": "http:\/\/m.youdao.com\/dict?le=jap&q=%E5%90%88%E6%A0%BC"
},
"l": "ja2zh-CHS",
"speakUrl": "http:\/\/openapi.youdao.com\/ttsapi?q=%E5%90%88%E6%A0%BC&langType=ja&sign=3118EBCD5EC1D0A416EF32032FE55FAF&salt=1521012839301&voice=4&format=wav&appKey=3a72ad95fe43ac83"
}

相比于百度,有道云增加单词的读音地址这一项,但是还是没有标注出片假名。

另外还有Google翻译的API没有测试,不过这个时候得转化一下思路,先去Chrome扩展商店看看是否已经有类似的插件。

Chrome应用商店

首先在Chrome扩展应用商店搜索“划词翻译”,有一些评分很高的插件。

我装了几个,这些插件确实做的比较好,我需要的划词功能都有,能中文翻译英文,能中文翻译日文,有的还有读音。

但是有一点,这些插件都没有标注出我要查询的单词的片假名读音,这可不行。

对于使用习惯了沪江小D查词的我来说,作为学习需求,查询一个单词,应该有各种读音,各种释义,各种词性和用法还有例句。

我找了很多插件,结果都是没有,就在我快要放弃的时候,我试着搜索了一下“沪江”。

居然有一个沪江小D的插件,虽然只有一个评论,还是个中评。我还是下载安装了,结果各种疯狂报错,无法使用。

迂回:挣扎

事情终于又回到了起点,我习惯于使用沪江小D的翻译结果,那么可不可以在沪江小D的翻译中寻找答案呢?

分析沪江小D

沪江小D翻译页面:https://dict.hjenglish.com/jp/jc/簡素

我最开始的想法:打开调试窗口,然后观察它查询单词的Ajax请求,模拟查询。然而,沪江小D查询单词并不是使用Ajax异步查询结果,(失望,不能看到接口)。

很容易发现,沪江小D查单词就是请求一个页面,把单词拼接在类似于:https://dict.hjenglish.com/jp/jc/ 这样的页面后面,所以我就想在插件里面构造这个url,然后使用ajax请求获取HTML数据,解析HTML数据。然而这个页面是禁止跨域请求的,也就是说服务端设置了禁止跨域请求,option不通过。我又把这个页面放在一个 iframe 里面,然而

Refused to display 'https://dict.hjenglish.com/jp/jc/%E7%B0%A1%E7%B4%A0' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

该网页禁止iframe访问。崩溃!

这个时候我猛然想到沪江小D不是有手机APP吗,那肯定会请求服务端API啊!所以我立马使用Fiddler抓包,我激动地输入单词,点击查询按钮,Fiddler中果然出现了查询请求。是压缩过的,解压之后是json格式的返回值,data里面是一大堆乱七八糟的鬼东西。呵呵哒,加密过的。

我这儿不得不佩服沪江做得真是好!

使用服务器转发

然而我是不会放弃的,不就是跨域的问题,你再怎么禁止跨域,也是浏览器能访问的嘛!对吧!在Postman中模拟请求不需要额外的cookie信息后。我在服务器写了一个转发器,请求页面数据,然后返回给插件客户端。由于我的服务器主要是PHP环境,所以用了PHP写的转发器。主要代码如下:

// 允许跨域>_<
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:POST,GET');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header('Content-Type:application/json;charset=UTF-8'); class HujianTranslate
{ protected $base_api = [
'Japanese_Chinese' => 'https://dict.hjenglish.com/jp/jc/', // from Japanese to Chinese
'Chinese_Japanese' => 'https://dict.hjenglish.com/jp/cj/', // from Chinese to Japanese
'Chinese_English' => 'https://dict.hjenglish.com/w/', // from Chinese to English
'English_Chinese' => 'https://dict.hjenglish.com/w/', // from English to Chinese
]; // 根据查询语言转换url
protected function buildUrl($query, $from, $to)
{
$url = $this->base_api[$from . '_' . $to];
$url = $url . rawurlencode($query);
return $url;
} public function translate($query, $from, $to)
{
$url = $this->buildUrl($query, $from, $to);
$response = $this->curlCall($url, '', 'GET');
return $response;
} // curl模拟发送http请求
public function curlCall($url, $params = null, $method="post", $withCookie = false, $timeout = CURL_TIMEOUT, $headers=array())
{
$ch = curl_init();
$data = '';
$params && $data = http_build_query($params);
if($method == "post" || $method == "POST")
{
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_POST, 1);
}
else
{
if ($data) {
stripos($url, "?") > 0 ? $url .= "&$data" : $url .= "?$data";
}
} curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // UserAgent 模拟
$useragent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36';
curl_setopt($ch, CURLOPT_USERAGENT, $useragent); // 绕过SSL认证
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 设置请求头部
$headers && curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// 设置cookie
$withCookie && curl_setopt($ch, CURLOPT_COOKIEJAR, $_COOKIE);
$response = curl_exec($ch);
if (false === $response) {
die(curl_error($ch));
}
curl_close($ch);
return $response;
}
} $translator = new HujianTranslate();
echo $translator->translate($_GET['query'], $_GET['from'], $_GET['to']);
exit();

页面修改

终于能够成功获取了页面数据,但是这个时候我遇到了一个抉择,在哪儿解析

  • 在服务端解析好页面数据,然后把结果转发给插件客户端
  • 服务端不做任何数据处理,在客户端解析页面数据

开始我是想在服务端解析的,PHP的自带解析器用于XML,用来解析HTML的时候出现了一大堆报警,虽然可以正常跑,但是很不爽,又不太想重新用Python写一遍,而且服务器资源有限。后来就把这个解析的任务交给客户端了,毕竟JavaScript解析HTML很方便!

下面是对查询url:https://dict.hjenglish.com/jp/jc/上 的HTML内容解析函数

function parseHTML(data) {
var parser = new DOMParser(),
doc = parser.parseFromString(data, 'text/html'),
details = [], items,
word_details = doc.getElementsByClassName('word-details-pane'); // 遍历每种解释含义
for (var i = 0; i < word_details.length; i++) {
details[i] = {}; var word = word_details[i],
detail = {},
pronounces = word.getElementsByClassName('pronounces')[0],
word_text = word.getElementsByClassName('word-text')[0],
simple = word.getElementsByClassName('simple')[0],
detail_group = word.getElementsByClassName('detail-groups')[0]; // word text 解析
detail.text = word_text.getElementsByTagName('h2')[0].innerHTML; // pronounces 发音解析
detail.pronounce = {};
items = pronounces.getElementsByTagName('span');
detail.pronounce.kana = items[0].innerHTML;
detail.pronounce.roma = items[1].innerHTML;
detail.pronounce.accent = items[2].innerHTML;
detail.pronounce.audio = items[3].getAttribute('data-src'); // simple 词意解析
detail.meaning = {};
detail.meaning.pos = []; // 词性
detail.meaning.means = []; // 词义
var poses = simple.getElementsByTagName('h2');
for (var j = 0; j < poses.length; j++) {
detail.meaning.pos[j] = poses[j].innerHTML; // 词性
// 词义li列表
var lis = poses[i].nextSibling.nextSibling.getElementsByTagName('li');
for (var k = 0; k < lis.length; k++) {
detail.meaning.means[j] = [];
detail.meaning.means[j][k] = lis[k].textContent; // 带序号的词义
}
} // 解析详细释义
detail.meaning.detail = detail_group;
}
return detail;
}

弄好解析函数之后,犯难了,我该怎么展示这么多的数据呢?回到沪江小D的查询展示页面,最后的展示效果不也就和这个差不多嘛!

所以最后,我放弃了使用解析函数,而是直接截取页面中的翻译结果节点,然后重新调整了样式。

function parseResponse(data) {
var parser = new DOMParser(),
doc = parser.parseFromString(data, 'text/html'),
content = doc.getElementsByClassName('word-details')[0];
// content 为主要内容区域
document.body.appendChild(content);
}

对内容的重新修改包括:

  • 调整间距,重新设置样式
  • 去掉广告内容
  • 因为一个词有多种读音,添加tab
  • 限制内容显示区域,隐藏进度条
  • 高亮标记例句中的查询单词
  • 完善音频播放

放一下音频播放器的代码:

// 音频播放器
function AudioPlayer() {
var audio = document.createElement('audio');
audio.setAttribute('controls', 'controls');
audio.style.display = 'none';
// audio.setAttribute('src', src);
document.body.appendChild(audio); this.play = function(src) {
audio.setAttribute('src', src);
audio.play();
return this;
};
this.stop = function() {
audio.pause();
return this;
};
// 播放结束回调
this.end = function(callback) {
var repeat = setInterval(function() {
if (audio.ended) {
clearInterval(repeat);
callback && callback();
}
}, 100);
setTimeout(function() {
clearInterval(repeat);
}, 5000);
}; return this;
}

最后如愿了,将沪江翻译的内容成功改装到一个小窗口中。接下来就是将内容封装到Chrome插件中就可以了。最后的效果如下:



可以下拉显示例句,点击小喇叭可以发音,点击开始的词条可以切换不同发音。

对比沪江小D页面可以点击这儿

最后:放弃

封装成插件之后,只能我自己用啊,毕竟版权是沪江的,那么可不可以联系沪江的官方,获得授权什么的呢?

所以我就联系了沪江的客服,了解了几个事情

  • 之前那个在Chrome上面的插件不是官方的
  • 我如果要共享插件代码的话,得和他们合作部门谈
  • 沪江已经在制定计划开发Chrome插件
  • 沪江的客服态度很好

而且,我还发现了一个很恶心的事情,在沪江翻译的页面里面,自带划词翻译,而且效果挺好的,标注了读音。效果图如下,双击单词会在单词下面出现翻译按钮,双击翻译会在小窗口显示读音和释义。这个过程是一个ajax请求,不过呢,需要appid和签名。



所以,我所做的工作根本没有什么用处。

所以我泪流满面的。。。久久不能言语。

相信沪江小D能够使用自己的API的话,开发的插件一定类似于这个小D划词释义一样,可以看假名。

完败收获

  • PHP 有相关的库可以像jQuery一样解析HTML,可用于爬虫
  • 服务端解析时,正则表达式可以用平衡组类似于堆栈匹配标签
  • CSS中使用counter等函数可以为li列表自动添加序号
  • 服务端跨域的破解方法是请求转发
  • 复习了一波CSS样式和布局
  • 我就是一个傻逼(ばか,死ね!)

个人博客原文:不想说

一次日语翻译的Chrome插件开发经历的更多相关文章

  1. [Chrome插件开发]001.入门

    Chrome插件开发入门 Chrome扩展文件 Browser Actions(扩展图标) Page Actions(地址栏图标) popup弹出窗口 Background Pages后台页面 实战讲 ...

  2. chrome插件开发-消息机制中的bug与解决方案

    序言 最近开发chrome插件,涉及到消息传递机时按照教程去敲代码,结果总是不对.研究了大半天终于找到原因,现在记录下. 程序 插件程序参考官网 chrome官网之消息传递机制, 不能FQ的同事也可以 ...

  3. Chrome插件开发入门(二)——消息传递机制

    Chrome插件开发入门(二)——消息传递机制   由于插件的js运行环境有区别,所以消息传递机制是一个重要内容.阅读了很多博文,大家已经说得很清楚了,直接转一篇@姬小光 的博文,总结的挺好.后面附一 ...

  4. vue.js 初体验— Chrome 插件开发实录

    欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者:陈纬杰 背景 对于经常和动画开发打交道的开发者对于Animate.css这个动画库不会陌生,它把一些常见 ...

  5. Chrome插件开发,美化网页上的文件列表。chrome-extension,background

    上一篇文章 通过“content-scripts”的方式向页面注入js和css来美化页面,但是有一个弊端:一旦配置好需要注入的页面,之后如果这个页面地址以后发生变化,或者要新加一些URL进来,那么得修 ...

  6. Chrome插件开发,美化网页上的文件列表。chrome-extension,content-scripts

    趁着2018年还剩最后几天,发几篇博客,荒废太久了,惭愧. 最近也是需求驱动,研究了下Chrome插件开发.来看一下我们公司运维提供的日志查看页面 所有项目的日志都参杂在一起,每次去找都很痛苦.慢慢发 ...

  7. chrome插件开发学习(一)

    两个不错的网址: 360chrome插件开发文档:http://open.chrome.360.cn/extension_dev/manifest.html 图灵 chrome插件开发于应用 电子书: ...

  8. [小工具] chrome上日语翻译工具

    rikaikun -> 日语 "理解君" 下载地址: https://chrome.google.com/webstore/detail/rikaikun/jipdnfibh ...

  9. [No000080]右键解锁增强Chrome插件开发,破除防复制

    昨天用360极速(虽然我不喜欢360.)浏览器,登陆知乎查阅一些东西,突然感觉有些观点很赞同,想copy转载一下,我了个去,它丫的居然不让我复制. 地址:https://www.zhihu.com/q ...

随机推荐

  1. MySql主键自动生成,表、实体、C#调用方法

    1.表:mysql建表语句 DROP TABLE IF EXISTS `keycode`; CREATE TABLE `keycode` ( `Id` ) NOT NULL AUTO_INCREMEN ...

  2. esxi 改变虚拟机磁盘格式为精简存储

    最近在部署虚拟机,导入几个之前保存的ovf模板,发现存储已经被耗费的差不多了.检查了下磁盘存储格式 存储类型是 后置备延迟置零 占用空间 简单了解下 三种存储类型 1.厚置备延迟置零: 默认的创建格式 ...

  3. SVN高级

    #查找有关svn关键字的目录及文件 find / -name "*svn*" find / -name "*Svn*" find / -name "* ...

  4. Yii如何使用数据库

    1.Yii如何使用数据库 Yii通过数据库访问对象(Database Access Objects,简称DAO)来使用数据库的. DAO建立在"PHP数据对象(PDO)之上,并提供一套面向对 ...

  5. ElasticSearch 5.0.0 集群安装部署文档

    1.  搭建环境 3台物理机 操作系统 centos7 es1   192.168.31.141   4g内存   2核 es2   192.168.31.142   4g内存   2核 es3    ...

  6. css渲染(二) 文本

    一.文本样式  首行缩进 text-indent 首行缩进是将段落的第一行缩进,这是常用的文本格式化效果.一般地,中文写作时开头空两格.[注意]该属性可以为负值:应用于: 块级元素(包括block和i ...

  7. Java--JDBC连接与Django--DATABASES设置

    JDBC 简介 JDBC(Java Data Base Connectivity,java 数据库连接)是一种用于执行 SQL 语句的 JavaAPI,可以为多种关系 数据库提供统一访问,它由一组用 ...

  8. java网络编程(3)——UDP

    UDP在java中主要使用DatagramSocket来实现通讯,数据一般是通过DatagramPacket来封装: 发送方只需指定接受方的地址和端口,然后通过send()方法就可以把封装在Datag ...

  9. hibernate中的缓存问题与快照机制

    1.  什么是缓存 数据存储到数据库里面,数据库本身是一个文件系统,使用流方式操作文件(效率不高) 改进方式:把数据存到内存中,不需要使用流方式,可以直接读取内存中的数据 缓存:内存中的临时数据,当内 ...

  10. 网络基础tcp/ip协议三

    数据链路层:(位于网络层与物理层之间) 数据链路层的功能: 数据链路的建立,维护. 帧包装,帧传输,帧同步. 帧的差错恢复. 流量的控制. 以太网:(工作在数据链路层) CSMA/CD(带冲突检测的载 ...