项目介绍
运用Golang来构建一个在线字典,用户可以在命令行里面查询一个字典。程序能够通过调用第三方的API查询到单词的信息(翻译信息、音标和例句等),并且能够将这些信息打印出来。
实现方法
(1)抓包
以彩云小译这款在线翻译软件为例,先打开彩云小译的网页"fanyi.caiyunapp.com/" ,然后右键点击检查选项,打开浏览器的开发者工具。
在彩云小译的单词输入框内输入一个单词,然后点击翻译按钮,浏览器会发送一系列的请求,找到dict选项卡,然后找到查询单词的请求。这是一个 HTTP 的 post 的请求,请求的 header 是一个json,里面有两个字段,一个代表用户是要从什么语言转到什么语言,source代表用户查询的单词。API 的返回结果里面会有 Wiki 和 dictionary 两个字段。需要用的结果主要在dictionary.Explanations 字段里面,其他字段里面还包括音标等信息。
(2)代码生成
需要在 Golang 里面去发送这个请求。但是这个请求比较复杂,用代码构造很麻烦。可以用一种简单的方式来生成代码,右键浏览器里面的 copy as curl,在终端粘贴一下 curl 命令,可以返回一大串 json。打开代码生成网站"curlconverter.com/go/" ,粘贴 curl 请求,即可自动生成 Golang 代码。
client := &http.Client{}
var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
req, err := http.NewRequest("POST", "https://lingocloud.caiyunapp.com/v1/dict", data)
......
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
......
resp, err := client.Do(req)
......
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
http.Client{}表示程序创建了一个 HTTP 客户端对象,该客户端用于发送 HTTP 请求。strings.NewReader({"trans_type":"en2zh","source":"good"})表示使用strings.NewReader创建了一个包含请求体内容的io.Reader对象,以便发送 POST 请求时携带数据。http.NewRequest("POST", "https://lingocloud.caiyunapp.com/v1/dict", data)表示使用http.NewRequest创建一个新的 HTTP 请求。指定了请求的方法(POST)和目标 URL。- 程序使用
req.Header.Set方法设置了请求的各个头部信息,包括Accept、Content-Type、User-Agent等。这些头部信息指定了请求的参数、格式和用户代理信息等。 client.Do(req)表示使用创建的客户端对象,通过client.Do(req)发送请求并接收响应。defer resp.Body.Close()表示通过defer关键字,确保在函数结束时关闭响应体。io.ReadAll(resp.Body)表示使用io.ReadAll从响应体中读取数据。
(3)生成request body
在 Golang 里面,需要生成一段 JSON ,常用的方式是先构造出来一个结构体,这个结构体和需要生成的 JSON 的结构是一一对应的。
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
这里用type DictRequest struct { ... }定义了一个名为 DictRequest 的结构体类型。其中,TransType stringjson:"trans_type" 表示结构体字段TransType表示翻译类型,使用了json:"trans_type"标签,这会在 JSON 编码和解码时将字段名映射为trans_type。
var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
替换成
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
首先,初始化每个结构体成员,在调用json.Marshal来得到这个序列化之后的字符串,不同于之前那个字符串,这里是一个字符数组,所以需要把strings.NewReader改成bytes.NewReader来构造结构体上的主体部分,最后成功的进入一个变量来发送http请求。
(4)解析response body
接下来把这个 response body 来解析出来。在 js/Python 这些脚本语言里面,body 是一个字典或者 map 的结构, 可以直接从里面取值,但是 golang 是个强类型语言,这种做法并不是最佳实践。更常用的方式是和 request 的一样,写一个结构体,把返回的 JSON 反序列化到结构体里面。但是在浏览器里面可以看到这个 API 返回的结构非常复杂,如果要一一定义结构体字段,非常繁琐并且容易出错。
我们打开一个代码生成工具网站"oktools.net/json2go" ,把彩云小译上的json字符串粘贴上去,这样我们就能生成对应的结构体。
先定义一个dictResponse结构体对象,然后用json.Unmarshal把bodyText反序列化到这个定义了的结构体内。
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"`
}
......
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
(5)打印结果
观察那个 json 可以看出需要的结果是在 Dictionary.explanations 里的,用 for range 循环来迭代它,然后直接打印结构,参照一些词典的显示方式,可以在前面打印出这个单词和它的音标。
总结
在上面这个在线字典项目中,我学习到了一下go语言的知识点:
- HTTP 请求:
- 使用
http.Client{}创建 HTTP 客户端对象。 - 使用
http.NewRequest创建一个新的 HTTP 请求。 - 设置请求头部信息,如
req.Header.Set。
- JSON 编码与解码:
- 定义了两个结构体
DictRequest和DictResponse,并使用了json标签指定字段的 JSON 名称。 - 使用
json.Marshal将结构体编码为 JSON 格式的字节切片。 - 使用
json.Unmarshal将 JSON 数据解码为结构体。
- 命令行参数处理:
- 使用
os.Args获取命令行参数。 - 检查命令行参数的数量,根据需求进行处理。
- 文件操作:
- 使用
os.Exit终止程序。 - 使用
fmt.Fprintf打印帮助信息。
- 循环:
- 在
main函数中使用for循环遍历dictResponse.Dictionary.Explanations中的每个元素。
- 错误处理:
- 在许多地方使用
err进行错误检查,并使用log.Fatal打印错误信息并退出程序。