Go工程实践-在线词典 | 青训营

105 阅读6分钟

前提&介绍


在线词典是青训营课程的go项目实践之一。虽然难度不高,但是作为一个入门的工程实践,却让可以从中学习到go的web生态和http抓包。迅速从简单的小程序中走到web程序。

之前没有编写过类似的爬虫抓包的程序,go语言版的http操作更简易,而且内置的json解析库使得编写体验极为舒适。

在线词典可以通过调用第三方在线词典的api,将要查询的单词的翻译打印出来。在这个简单的程序里,了解到了许多知识:

  1. go语言基本语法
  2. go语言json解析库
  3. go语言网络库基本使用
  4. 自动化生成代码

在线词典

原理

在线词典程序本质上是通过抓包。解析发送的http请求和收到的http响应。然后由在线词典程序将要搜索的单词放入请求里。填充好正确的请求后,发送到第三方词典网站api,就可以接收到想要的翻译结果请求了。

所以,在线词典程序的关键就是在于,正确解析请求和响应的json,放入搜索的单词和翻译。

抓包

首先,我们要进行第三方词典网站的抓包。

彩云翻译:fanyi.caiyunapp.com/

打开网站后,打开浏览器的开发者工具。

搜索“perfect”的翻译

image.png

观察network中的dict包

Header: image.png

Payload载荷:

image.png

Preview:

image.png

生成请求request

通过开发者工具进行抓包发现,请求的header十分复杂,而我们又需要在代码中构造这个请求。所以我们可以使用一种非常简单的方法生成这繁琐的代码。

header生成网站:curlconverter.com/#go

首先复制crul命令

image.png

复制到的crul命令:(如果在命令行运行可以收到一个json的返回)

curl 'https://api.interpreter.caiyunai.com/v1/dict' \
  -H 'authority: api.interpreter.caiyunai.com' \
  -H 'accept: application/json, text/plain, */*' \
  -H 'accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6' \
  -H 'app-name: xy' \
  -H 'content-type: application/json;charset=UTF-8' \
  -H 'device-id: b853b35f5fdbe919a916106cbfd9b748' \
  -H 'origin: https://fanyi.caiyunapp.com' \
  -H 'os-type: web' \
  -H 'os-version;' \
  -H 'referer: https://fanyi.caiyunapp.com/' \
  -H 'sec-ch-ua: "Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "Windows"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: cross-site' \
  -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.54' \
  -H 'x-authorization: token:qgemv4jr1y38jyq6vhvi' \
  --data-raw '{"trans_type":"en2zh","source":"perfect"}' \
  --compressed

生成请求头requset header

image.png

我们看看生成代码里做了什么工作:

  1. 创建http客户端
client := &http.Client{}
  1. 创建请求体
var data = strings.NewReader(`{"trans_type":"en2zh","source":"perfect"}`) 
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)

原始字符串

go语言中,""可以包含一条标准的string字符串。 而''指的是原始字符串,里面包含的转义字符将不会生效。比如说'123\n'就不会换行。

这是一个post请求,将会发送到第三方api上。观察可以发现包含一个json请求体:{"trans_type":"en2zh","source":"perfect"}

其中,trans_type的值为en2zh 英译中;source的值为"perfect"即为我们要搜索的单词。通过这个json我们就可以任意定制我们想要搜索的内容,以达成我们的目的。

  1. 设置请求头
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,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") req.Header.Set("app-name", "xy") 
req.Header.Set("content-type", "application/json;charset=UTF-8") 
req.Header.Set("device-id", "b853b35f5fdbe919a916106cbfd9b748") 
req.Header.Set("origin", "https://fanyi.caiyunapp.com") 
req.Header.Set("os-type", "web") req.Header.Set("os-version", "") 
req.Header.Set("referer", "https://fanyi.caiyunapp.com/") 
req.Header.Set("sec-ch-ua", `"Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"`) 
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/116.0.0.0 Safari/537.36 Edg/116.0.1938.54") req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")

这也是最繁琐的阶段,但是自动生成网站帮我们解决了这个麻烦。也是我们日常开发中应该掌握的技巧,只做关键的不做重复工作。

  1. 发起请求
resp, err := client.Do(req)

发起请求并接收响应到resp 5. 读取响应

bodyText, err := io.ReadAll(resp.Body) 
if err != nil { log.Fatal(err) } fmt.Printf("%s\n", bodyText)

成功运行之后就会获得返回的响应json

❯ go run online_dict perfect
{"rc":0,"wiki":{},"dictionary":{"prons":{"en-us":"[\u02c8p\u025df\u026akt]","en":"[\u02c8p\u0259\u02d0fikt]"},"explanations":["a.\u6781\u597d\u7684;\u5706\u6ee1\u7684;\u5b8c\u6574\u7684;\u5b8c\u5584\u7684;\u7cbe\u6e5b\u7684","vt.\u4f7f\u2026\u8d8b\u4e8e\u5b8c\u7f8e;\u4f7f\u5b8c\u5168"],"synonym":["faultless","flawless","ideal","correct","accurate"],"antonym":["imperfect","defective","faulty","incomplete","impaired"],"wqx_example":[["He is perfectly right . ","\u4ed6\u5b8c\u5168\u6b63\u786e\u3002"]],"entry":"perfect","type":"word","related":[],"source":"wenquxing"}}

但是,我们此刻面临两个问题:

  1. 发送的请求json是固定的,我们要一个变量进行输入
  2. 得到的响应json有许多内容,我们要从中获得需要的翻译。

为了解决这两个问题,我们需要结构体来存放请求和响应json 这个结构体与请求响应字段一一对应。

但是与请求json不同的是,响应json明显更复杂。所以我们可以像请求头一样,借助自动生成代码来帮我们生成结构体。

json转Struct网站:0kt001s.net/json2go

DictRequest 结构体
type DictRequest struct{
	TransType string `json:"trans_type"`
	Source string `json:"source"`
	UserID string `json:"user_id"`
}
DictResponse 结构体
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"`
}

进行反序列化操作将json响应反序列化到结构体内。

  var dictResponse DictResponse
	err=json.Unmarshal(bodyText,&dictResponse)
	if err!=nil{
		log.Fatal(err)
	}
	fmt.Printf("%#v\n\n\n",dictResponse)

然后将我们需要的字段筛选出来并打印

var dictResponse DictResponse
	err=json.Unmarshal(bodyText,&dictResponse)
	if err!=nil{
		log.Fatal(err)
	}
	// fmt.Printf("%#v\n\n\n",dictResponse)

	if resp.StatusCode!=200{
		log.Fatal("bad StatusCode:",resp.StatusCode,"body",string(bodyText))
	}

	fmt.Println(word,"\n|音标|: ","UK",dictResponse.Dictionary.Prons.En,"US:",dictResponse.Dictionary.Prons.EnUs)
	

经过一些拓展,我们不仅仅可以打印音标,还有近义词、反义词和例句

for _,item := range dictResponse.Dictionary.Explanations{
		fmt.Println(item)
	}
        fmt.Println("|近义词|:")
	for _,item:=range dictResponse.Dictionary.Synonym{
		fmt.Println(item)
	}
	fmt.Println("|反义词|:")
	for _,item:=range dictResponse.Dictionary.Antonym{
		fmt.Println(item)
	}
	fmt.Println("|例句|:")
	for _,item:=range dictResponse.Dictionary.WqxExample{
		fmt.Println(item[0])
		fmt.Println(item[1],"\n")

	}

这样就可以实现更多的功能。

结果

image.png

写在最后

虽然在线词典是一个简单的小程序,但涉及到了http程序的许多重要基础,还有json解析反序列化等等操作。是一个很好的入门程序。希望每个人都能在这个实践项目中学习到更多。