Go语言基础实战项目——词典
这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
简介
需求:输入一个单词,返回单词的中文翻译和音标信息。
原理:网页上的翻译功能是通过提交表单发送HTTP请求到服务器,然后服务器向浏览器返回对应的信息,要实现需求的功能,主要可以分为以下步骤:
- 获取请求API,在Go中构建并发送HTTP请求
- 封装结构体,通过响应的JSON信息构建对应的结构体
- 将所需内容进行打印。
程序运行流程如下:
- 用户输入单词
- 将单词封装为HTTP请求
- 发出请求
- 接受响应
- 解析响应体的内容
- 输出到控制台
用到的网站:
JSON转Golang Struct - 在线工具 - OKTools
Convert curl commands to Go (curlconverter.com)
获取API
进入彩云小译 - 在线翻译 (caiyunapp.com),按F12进入
随便进行一次查询,找到Netword栏中请求方式为POST的请求,并查看一下Response,确定是否为翻译信息的请求。
在查询前可以点击左上角的Clear,清除一下无关信息的干扰,图中是Edge浏览器的布局
response中的内容:
{"rc":0,"wiki":{"known_in_laguages":35,"description":{"source":"song by Adele","target":null},"id":"Q21172725","item":{"source":"Hello","target":"\u4f60\u8fd8\u597d\u5417"},"image_url":"http://www.caiyunapp.com/imgs/link_default_img.png","is_subject":"true","sitelink":"https://www.caiyunapp.com/read_mode/?id=638c1dc20cdda67eb511c5c8"},"dictionary":{"prons":{"en-us":"[h\u0259\u02c8lo]","en":"[\u02c8he\u02c8l\u0259u]"},"explanations":["int.\u5582;\u54c8\u7f57","n.\u5f15\u4eba\u6ce8\u610f\u7684\u547c\u58f0","v.\u5411\u4eba\u547c(\u5582)"],"synonym":["greetings","salutations"],"antonym":[],"wqx_example":[["say hello to","\u5411\u67d0\u4eba\u95ee\u5019,\u548c\u67d0\u4eba\u6253\u62db\u547c"],["Say hello to him for me . ","\u4ee3\u6211\u95ee\u5019\u4ed6\u3002"]],"entry":"hello","type":"word","related":[],"source":"wenquxing"}}
确认是该请求之后,通过右击该请求,将其cURL进行复制:
如果有cmd和bash两个版本的,要复制bash版本的,cmd版本的版本的解析出来得到的结果不符合预期。
进入Convert curl commands to Go (curlconverter.com),将复制好的cURL进行粘贴,得到的代码实现的功能是,封装请求发送HTTP,并将响应的内容打印到控制台。
我们在idea中验证一下代码,测试没问题之后进行下一步。
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
func main() {
client := &http.Client{}
var data = strings.NewReader(`{"trans_type":"en2zh","source":"Hello"}`)
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,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", "")
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", `"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"`)
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/109.0.0.0 Safari/537.36 Edg/109.0.1518.55")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
运行程序,如果能返回上面response里面的内容,可以进行下一步。
自定义请求
再看一下上面生成代码,其中的请求部分:
//...
var data = strings.NewReader(`{"trans_type":"en2zh","source":"Hello"}`)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
// ...
可以看出data就是我们可以自定义的表单内容,trans_type代表翻译是从英文到中文,source就是要翻译的内容,其形式是json字符串的形式,所以我们可以构建一个和其key相同的结构体用于构造请求:
type DicReq struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
}
对代码进行修改,构造完结构体后要转换成json的格式,修改后的代码如下:
func main() {
client := &http.Client{}
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"Good"}`)
dataStruct := DicReq{TransType: "en2zh", Source: "Hello"} //构建结构体
marshal, err := json.Marshal(dataStruct) //转换为json格式
if err != nil {
log.Fatal(err)
}
data := bytes.NewReader(marshal)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
//...
fmt.Printf("%s\n", bodyText)
}
用单词Good进行测试,得到结果,虽然汉字没解析出来,但从个中单词可以看出结果是否符合预期,如果没问题,接着就要解析服务器返回的响应内容。
解析响应
服务器返回的响应内容也是JSON的格式,我们在上一步将结构体转换成了对应的JSON字符串,那么反过来我们也可以将JSON字符串转换成结构体,但是我们需要知道JSON字符串对应的结构,这时候如果要手动构建比较麻烦,所以我们要用到oktools.net/json2go这个网站,解析JSON字符串成Go语言中的结构体。
将响应的JSON字符串粘贴进去,选择嵌套,复制得到的结构体到代码中。
通过json.Unmarshal()可以将JSON字符串解析成对应的结构体,修改代码打印响应的部分:
type DicResp struct {
Rc int `json:"rc"`
Wiki struct {
KnownInLaguages int `json:"known_in_laguages"`
Description struct {
Source string `json:"source"`
Target interface{} `json:"target"`
} `json:"description"`
ID string `json:"id"`
Item struct {
Source string `json:"source"`
Target string `json:"target"`
} `json:"item"`
ImageURL string `json:"image_url"`
IsSubject string `json:"is_subject"`
Sitelink string `json:"sitelink"`
} `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 []interface{} `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"`
}
func main() {
//...
//fmt.Printf("%s\n", bodyText)
var dicresp DicResp
err = json.Unmarshal(bodyText, &dicresp)
fmt.Println(dicresp.Dictionary.Entry) //打印需要翻译的单词
fmt.Println("UK:", dicresp.Dictionary.Prons.En, "US:", dicresp.Dictionary.Prons.EnUs)//打印音标
for _, expla := range dicresp.Dictionary.Explanations {
fmt.Println(expla) //打印翻译
}
}
结尾
到此需求的功能已经完成大部分了,可以像老师一样在命令行调用,加上单词的参数来进行使用,我这里是加了个循环来读取用户输入的字符串进行翻译,所有程序如下:
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
)
type DicResp struct {
Rc int `json:"rc"`
Wiki struct {
KnownInLaguages int `json:"known_in_laguages"`
Description struct {
Source string `json:"source"`
Target interface{} `json:"target"`
} `json:"description"`
ID string `json:"id"`
Item struct {
Source string `json:"source"`
Target string `json:"target"`
} `json:"item"`
ImageURL string `json:"image_url"`
IsSubject string `json:"is_subject"`
Sitelink string `json:"sitelink"`
} `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 []interface{} `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 DicReq struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
}
func main() {
reader := bufio.NewReader(os.Stdin)
for {
client := &http.Client{}
s, err := reader.ReadString('\n')
if err != nil {
fmt.Println("input exception!")
}
dataStruct := DicReq{TransType: "en2zh", Source: strings.TrimSuffix(s, "\r\n")} //去一下换行符
data, err := json.Marshal(dataStruct)
fmt.Println(string(marshal))
if err != nil {
log.Fatal(err)
}
data := bytes.NewReader(marshal)
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,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", "")
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", `"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"`)
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/109.0.0.0 Safari/537.36 Edg/109.0.1518.52")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var dicresp DicResp
err = json.Unmarshal(bodyText, &dicresp)
fmt.Println(dicresp.Dictionary.Entry)
fmt.Println("UK:", dicresp.Dictionary.Prons.En, "US:", dicresp.Dictionary.Prons.EnUs)
for _, expla := range dicresp.Dictionary.Explanations {
fmt.Println(expla)
}
}
}
总结
这个小项目作为我Go语言的入门非常合适,有一定的趣味性同时也学到了很多东西,也是知道那些聚合翻译的实现方式了,有能力的朋友可以试试其他的翻译引擎,也许会比这个有挑战性(可能会有一些加密解密的工作)。