前提&介绍
在线词典是青训营课程的go项目实践之一。虽然难度不高,但是作为一个入门的工程实践,却让可以从中学习到go的web生态和http抓包。迅速从简单的小程序中走到web程序。
之前没有编写过类似的爬虫抓包的程序,go语言版的http操作更简易,而且内置的json解析库使得编写体验极为舒适。
在线词典可以通过调用第三方在线词典的api,将要查询的单词的翻译打印出来。在这个简单的程序里,了解到了许多知识:
- go语言基本语法
- go语言json解析库
- go语言网络库基本使用
- 自动化生成代码
在线词典
原理
在线词典程序本质上是通过抓包。解析发送的http请求和收到的http响应。然后由在线词典程序将要搜索的单词放入请求里。填充好正确的请求后,发送到第三方词典网站api,就可以接收到想要的翻译结果请求了。
所以,在线词典程序的关键就是在于,正确解析请求和响应的json,放入搜索的单词和翻译。
抓包
首先,我们要进行第三方词典网站的抓包。
彩云翻译:fanyi.caiyunapp.com/
打开网站后,打开浏览器的开发者工具。
搜索“perfect”的翻译
观察network中的dict包
Header:
Payload载荷:
Preview:
生成请求request
通过开发者工具进行抓包发现,请求的header十分复杂,而我们又需要在代码中构造这个请求。所以我们可以使用一种非常简单的方法生成这繁琐的代码。
header生成网站:curlconverter.com/#go
首先复制crul命令
复制到的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
我们看看生成代码里做了什么工作:
- 创建http客户端
client := &http.Client{}
- 创建请求体
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我们就可以任意定制我们想要搜索的内容,以达成我们的目的。
- 设置请求头
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")
这也是最繁琐的阶段,但是自动生成网站帮我们解决了这个麻烦。也是我们日常开发中应该掌握的技巧,只做关键的不做重复工作。
- 发起请求
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"}}
但是,我们此刻面临两个问题:
- 发送的请求json是固定的,我们要一个变量进行输入
- 得到的响应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")
}
这样就可以实现更多的功能。
结果
写在最后
虽然在线词典是一个简单的小程序,但涉及到了http程序的许多重要基础,还有json解析反序列化等等操作。是一个很好的入门程序。希望每个人都能在这个实践项目中学习到更多。