GO语言工程实践课后作业2 | 青训营

62 阅读7分钟

在线词典

思路:

  1. 调用第三方api
  2. 发送http
  3. 请求解析json
  4. 代码生成

百度翻译为例

首先,打开百度翻译,随便输点词语,点击翻译,在network中会一直跳出框框内的内容。

如图所示,发现v2transapi中有翻译的数据

现在来分析一下这个文件,它的请求方式为post,下图是它post时所需的data。from是你输入词语的类型,to是需要翻译成的类型,query是翻译的词语,sign是通过js文件生成的; image.png

image.png

image.png

复制响应,打开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),就会得到以下代码

image.png

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()
}


运行后,如下图所示 image.png