序言
去年 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=合格&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=合格"
- },
- "webdict": {
- "url": "http:\/\/m.youdao.com\/dict?le=jap&q=合格"
- },
- "l": "ja2zh-CHS",
- "speakUrl": "http:\/\/openapi.youdao.com\/ttsapi?q=合格&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/簡素' 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 样式和布局
我就是一个傻逼 (私馬鹿人)
来源: https://www.cnblogs.com/youyoui/p/8569362.html