在线词典
思路:
- 调用第三方api
- 发送http
- 请求解析json
- 代码生成
以百度翻译为例
首先,打开百度翻译,随便输点词语,点击翻译,在network中会一直跳出框框内的内容。
如图所示,发现v2transapi中有翻译的数据
现在来分析一下这个文件,它的请求方式为post,下图是它post时所需的data。from是你输入词语的类型,to是需要翻译成的类型,query是翻译的词语,sign是通过js文件生成的;
复制响应,打开oktools.net/json2go,点击嵌套转换,复制结构体
由于结构体里面数据太多了,我只保留了自己需要的
type BaiduResponse struct {
DictResult struct {
SimpleMeans struct {
Symbols []struct {
PhEn string `json:"ph_en"`
PhAm string `json:"ph_am"`
Parts []struct {
Part string `json:"part"`
Means []string `json:"means"`
} `json:"parts"`
PhOther string `json:"ph_other"`
} `json:"symbols"`
} `json:"simple_means"`
} `json:"dict_result"`
}
如图所示,右键复制 cURL(bash),就会得到以下代码
curl 'https://fanyi.baidu.com/v2transapi?from=en&to=zh' \
-H 'Accept: */*' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Acs-Token: 1684416860205_1684417016732_lrD/EEElmTR7KbYYg5npK7L0H/f6dIRvMQ5Rbtj1FkxGZ7nOEma/l7vib4nuowoeJeAGuRroSFjFXZryndZIcv92sOPmIVoPQSF5L/e8/QzsDCGj4kT2+yBofaV+aAv06z7CJveCuyrEZITUMwnaXD+Mkb11Isk9I28Xh922XU6jfT6ebz+wnM1wC7sJl8b+LaI12RIm+Yr/Pd0FH7JU8R584lizBSejXJh0BufNQwjop6+n7f3Mv+6O98HHmpAYSOpfHmXPB9OqxWjCxcVB7EJp/ERpl6C11Kyvacsg3s5p7AgvPJ8rHITXx7lKAoKsKtBRYmhaN9ASc/PVXDpExQSBb5x6Xh9VnxAnGJmEFG9wH7qBxcIvsYh8krWcxlkBMMGbiudKcsf7fZkw+P3DNxsqPdhZhl+leCc650w7PiMM4Z9UbdPOCNORDX5eCqlC3FNv+DzVNNdum8mVQ/g4MAVGqqoD1enACJ4GvhBpeKVeJu3zxANZYNZDZOr18T/E' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
-H 'Cookie: ' \
-H 'Origin: https://fanyi.baidu.com' \
-H 'Pragma: no-cache' \
-H 'Referer: https://fanyi.baidu.com/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-origin' \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'sec-ch-ua: "Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "Windows"' \
--data-raw 'from=en&to=zh&query=good&transtype=translang&simple_means_flag=3&sign=262931.57378&token=7ea05c55081d6884108a8ecb0107d0c3&domain=common&ts=1684417015060' \
--compressed
利用curlconverter.com/go/生成代码如下
package main
import (
"fmt"
"io"
"log"
"net/http"
"strings"
)
func main() {
client := &http.Client{}
var data = strings.NewReader(`from=en&to=zh&query=good&transtype=translang&simple_means_flag=3&sign=262931.57378&token=7ea05c55081d6884108a8ecb0107d0c3&domain=common&ts=1684417015060`)
req, err := http.NewRequest("POST", "https://fanyi.baidu.com/v2transapi?from=en&to=zh", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Accept", "*/*")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Acs-Token", "1684416860205_1684417016732_lrD/EEElmTR7KbYYg5npK7L0H/f6dIRvMQ5Rbtj1FkxGZ7nOEma/l7vib4nuowoeJeAGuRroSFjFXZryndZIcv92sOPmIVoPQSF5L/e8/QzsDCGj4kT2+yBofaV+aAv06z7CJveCuyrEZITUMwnaXD+Mkb11Isk9I28Xh922XU6jfT6ebz+wnM1wC7sJl8b+LaI12RIm+Yr/Pd0FH7JU8R584lizBSejXJh0BufNQwjop6+n7f3Mv+6O98HHmpAYSOpfHmXPB9OqxWjCxcVB7EJp/ERpl6C11Kyvacsg3s5p7AgvPJ8rHITXx7lKAoKsKtBRYmhaN9ASc/PVXDpExQSBb5x6Xh9VnxAnGJmEFG9wH7qBxcIvsYh8krWcxlkBMMGbiudKcsf7fZkw+P3DNxsqPdhZhl+leCc650w7PiMM4Z9UbdPOCNORDX5eCqlC3FNv+DzVNNdum8mVQ/g4MAVGqqoD1enACJ4GvhBpeKVeJu3zxANZYNZDZOr18T/E")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("Cookie", "")
req.Header.Set("Origin", "https://fanyi.baidu.com")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Referer", "https://fanyi.baidu.com/")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "same-origin")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
req.Header.Set("sec-ch-ua", `"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
百度翻译的请求参数要用url编码,http 请求的时候将url传递的参数编码变为以&连接的字符串
而彩云翻译请求参数则需json格式
//url编码
params := url.Values{}
params.Add("from", "en")
params.Add("to", "zh")
params.Add("query", word)
params.Add("transtype", "zh")
params.Add("simple_means_flag", "3")
params.Add("sign", sign)// js逆向,sign加密
params.Add("token", "")
data := strings.NewReader(params.Encode())
//json格式
// 初始化字段名字
request := DictRequest{
TranType: "en2zh",
Source: word,
}
// 序列化,将数据编码成json字符串
buf, err := json.Marshal(request)
由于百度翻译有反爬机制,需要运行js解密获取sign,我这里用的是github.com/dop251/goja
vm := goja.New()
//js函数
const SCRIPT = `代码`
vm.RunString(SCRIPT)
//声明函数变量
var fn func(string) string
//建立引用关系
vm.ExportTo(vm.Get("a"), &fn)
fmt.Println(fn(word))
代码
一个拥有两个翻译引擎的并发的在线词典
// 在线词典 调用第三方api
// 发送http 请求解析json
// 代码生成 https://curlconverter.com/go/ 解析response body https://oktools.net/json2go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"sync"
"github.com/dop251/goja"
)
// 序列化需要构造结构体
type DictRequest struct {
TranType string `json:"trans_type"`
Source string `json:"source"`
}
type BaiduRequest struct {
From string `json:"from"`
To string `json:"to"`
Query string `json:"query"`
Transtype string `json:"transtype"`
Simple_means_flag string `json:"simple_means_flag"`
Sign string `json:"sign"`
Token string `json:"token"`
Domain string `json:"domain"`
}
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []string `json:"synonym"`
Antonym []string `json:"antonym"`
WqxExample [][]string `json:"wqx_example"`
Entry string `json:"entry"`
Type string `json:"type"`
Related []interface{} `json:"related"`
Source string `json:"source"`
} `json:"dictionary"`
}
type BaiduResponse struct {
DictResult struct {
SimpleMeans struct {
Symbols []struct {
PhEn string `json:"ph_en"`
PhAm string `json:"ph_am"`
Parts []struct {
Part string `json:"part"`
Means []string `json:"means"`
} `json:"parts"`
PhOther string `json:"ph_other"`
} `json:"symbols"`
} `json:"simple_means"`
} `json:"dict_result"`
}
func query(word string) {
client := &http.Client{}
// var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
// 初始化字段名字
request := DictRequest{
TranType: "en2zh",
Source: word,
}
// 序列化,将数据编码成json字符串
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
data := bytes.NewReader(buf)
// 创建请求头
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 设置请求头
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("cache-control", "no-cache")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "a2617f0b190922aec95e87df1dedf7e9")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("pragma", "no-cache")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发起请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
// 避免资源泄露
defer resp.Body.Close()
// 读取响应
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 判断响应是否为200
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode", resp.StatusCode, "body", string(bodyText))
}
// fmt.Printf("%s\n", bodyText)
var dictResponse DictResponse
// &才能写入结构体
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
// 最详细方式打印结构体
// fmt.Printf("%#v\n", dictResponse)
// 输出想要的
fmt.Println("彩云翻译", word, "uk:", dictResponse.Dictionary.Prons.En, "us:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
}
func baiduquery(word string) {
client := &http.Client{}
sign := Sign(word)
params := url.Values{}
params.Add("from", "en")
params.Add("to", "zh")
params.Add("query", word)
params.Add("transtype", "zh")
params.Add("simple_means_flag", "3")
params.Add("sign", sign)
params.Add("token", "")
// params.Add("domain", "domain")
data := strings.NewReader(params.Encode())
req, err := http.NewRequest("POST", "https://fanyi.baidu.com/v2transapi?", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Accept", "*/*")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Acs-Token", "")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("Cookie", "")
req.Header.Set("Origin", "https://fanyi.baidu.com")
req.Header.Set("Pragma", "no-cache")
req.Header.Set("Referer", "https://fanyi.baidu.com/")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "same-origin")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
req.Header.Set("sec-ch-ua", `"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
// 发起请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
// 避免资源泄露
defer resp.Body.Close()
// 读取响应
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode", resp.StatusCode, "body", string(bodyText))
}
var baiduResponse BaiduResponse
err = json.Unmarshal(bodyText, &baiduResponse)
if err != nil {
log.Fatal(err)
}
for _, item := range baiduResponse.DictResult.SimpleMeans.Symbols {
fmt.Println("百度翻译", word, item.PhEn, item.PhAm)
for _, item2 := range item.Parts {
fmt.Println(item2)
}
}
}
// js逆向,sign加密
func Sign(word string) string {
vm := goja.New()
//js函数
const SCRIPT = `
var i = "320305.131321201"
function a(r) {
var t = r.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
if (null === t) {
var a = r.length;
a > 30 && (r = "" + r.substr(0, 10) + r.substr(Math.floor(a / 2) - 5, 10) + r.substr(-10, 10))
} else {
for (var C = r.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), h = 0, f = C.length, u = []; f > h; h++)
"" !== C[h] && u.push.apply(u, e(C[h].split(""))),
h !== f - 1 && u.push(t[h]);
var g = u.length;
g > 30 && (r = u.slice(0, 10).join("") + u.slice(Math.floor(g / 2) - 5, Math.floor(g / 2) + 5).join("") + u.slice(-10).join(""))
}
var l = void 0
, d = "" + String.fromCharCode(103) + String.fromCharCode(116) + String.fromCharCode(107);
l = null !== i ? i : (i = o.common[d] || "") || "";
for (var m = l.split("."), S = Number(m[0]) || 0, s = Number(m[1]) || 0, c = [], v = 0, F = 0; F < r.length; F++) {
var p = r.charCodeAt(F);
128 > p ? c[v++] = p : (2048 > p ? c[v++] = p >> 6 | 192 : (55296 === (64512 & p) && F + 1 < r.length && 56320 === (64512 & r.charCodeAt(F + 1)) ? (p = 65536 + ((1023 & p) << 10) + (1023 & r.charCodeAt(++F)),
c[v++] = p >> 18 | 240,
c[v++] = p >> 12 & 63 | 128) : c[v++] = p >> 12 | 224,
c[v++] = p >> 6 & 63 | 128),
c[v++] = 63 & p | 128)
}
for (var w = S, A = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(97) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(54)), b = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(51) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(98)) + ("" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(102)), D = 0; D < c.length; D++)
w += c[D],
w = n(w, A);
return w = n(w, b),
w ^= s,
0 > w && (w = (2147483647 & w) + 2147483648),
w %= 1e6,
w.toString() + "." + (w ^ S)
}
function n(r, o) {
for (var t = 0; t < o.length - 2; t += 3) {
var e = o.charAt(t + 2);
e = e >= "a" ? e.charCodeAt(0) - 87 : Number(e),
e = "+" === o.charAt(t + 1) ? r >>> e : r << e,
r = "+" === o.charAt(t) ? r + e & 4294967295 : r ^ e
}
return r
}`
vm.RunString(SCRIPT)
//声明函数变量
var fn func(string) string
//建立引用关系
vm.ExportTo(vm.Get("a"), &fn)
return fn(word)
}
func main() {
// 后面加单词 go run main.go hello
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage:simpleDict Word`)
os.Exit(1)
}
word := os.Args[1]
// 并行
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
query(word)
fmt.Println()
}()
go func() {
defer wg.Done()
baiduquery(word)
fmt.Println()
}()
wg.Wait()
}
运行后,如下图所示