使用Go语言实现一个简单的在线词典 | 青训营笔记

293 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记

1 介绍

  • 功能:用户可以在命令行输入想要查询的单词,然后程序会通过调用第三方翻译的API查询单词的翻译内容,并将查询结果打印到终端显示出来
  • 技术要点:Go、HTTP请求的发送和响应的处理、解析JSON

2 实现过程

2.1 选择翻译引擎

在选择第三方翻译引擎时,需要选择没有反抓包功能的翻译引擎,其次最好选择没有对翻译请求信息进行加密的翻译引擎,目前可以满足这些要求的翻译引擎主要有彩云翻译、火山翻译等,这里选择了火山翻译引擎:translate.volcengine.com/translate

2.2 抓包

首先打开火山翻译引擎的网页,然后按F12或点击鼠标右键“检查”打开开发者工具:

请添加图片描述

  1. 点击开发者工具选项中的Network
  2. 在翻译网站的输入框中输入要查询的单词
  3. 在Name框中查找翻译引擎响应的含有翻译内容的文件
  4. 具体查找方法为,点击Preview选项,找到响应内容中含有可以进行处理的翻译内容的文件

2.3 代码生成

接下来需要使用Golang发送这个翻译单词的请求,这一步可以借助往网站:curlconverter.com/#go 完成:

请添加图片描述

请添加图片描述

在响应文件上点击鼠标右键,找到Copy as cURLCopy as cURL(bash)选项,然后将复制内容粘贴到代码生成网站上面的输入框,将下面生成的代码复制到本地的代码文件中,这里将这个代码文件命名为main.go

请添加图片描述

运行程序,可以看到,已经可以获取相应的响应,还需对响应内容进行进一步的格式处理:

请添加图片描述

2.4 生成request body

之前完成的是对good这个特定单词的请求构造,如果要查询任意一个单词,那么需要根据单词构造相应的请求结构,首先点击开发者工具中的Headers选项,查看good单词查询请求的Request Payload

请添加图片描述

可以看到有两个字段,text代表要查询的单词,language代表查询单词的语言,那么可以构造相应的请求结构体:

type DictRequest struct {
    Text      string `json:"text"`
    Language  string `json:"language"`
}

将原来主函数中var data = strings.NewReader({"text":"good","language":"en"})这行语句,改为:

request := DictRequest{Text: "good", Language: "en"}
buf, err := json.Marshal(request)
if err != nil {
    log.Fatal(err)
}
var data = bytes.NewReader(buf)

初始化DictRequest请求结构体后,使用json.Marshal方法获取序列化后的字符串,然后再发送查询请求,这样就构造了一个request body来存储需要查询的单词,这一步与之前一步的结果应该是一样的。

2.5 解析response body

此时得到的响应内容是JSON格式的,还需要进行进一步的处理,将response body解析出来,具体做法是构建一个结构体,将返回的JSON格式的内容反序列化到这个结构体中,然后再对内容进行解析,可以借助网站:oktools.net/json2go 进行结构体的构建:

请添加图片描述

请添加图片描述

  1. 点击开发者工具选项中的Response,复制其中的内容
  2. 在转换网站的左侧框中粘贴复制的内容
  3. 点击“转换-嵌套”按钮
  4. 将右侧框中的转换得到的response结构体复制到main.go中,并将结构体名改为DictResponse

接下来将之前读取的响应内容,使用json.Unmarshal方法反序列化到响应结构体中,即将主函数最后的fmt.Printf("%s\n", bodyText)这行语句,修改变成:

var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
    log.Fatal(err)
}

此时响应内容以JSON格式存储在了dictResponse中。

2.6 打印结果

因为要查询任意单词,所以将之前的主函数改为一个参数为单词字符串的查询函数,即将func main()改为func query(word string),将查询特定good单词的语句request := DictRequest{Text: "good", Language: "en"}改为request := DictRequest{Text: word, Language: "en"},并添加主函数:

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, `usage: simpleDict WORD example: simpleDict hello`)
        os.Exit(1)
    }
    word := os.Args[1]
    query(word)
}

此时在命令行下进入文件目录后输入go run main.go word,即可对指定单词进行查询

最后在query方法中添加对响应是否成功的判断,并对响应内容存储进的JSON结构体进行分析,更好地输出需要的翻译内容,比如可以看到:

请添加图片描述

英式发音的音标所处的位置为dictResponse.Words[0].PosList[0].Phonetics[0].Text,美式发音的音标所处的位置为dictResponse.Words[0].PosList[0].Phonetics[1].Text,而需要的翻译结果在dictResponse.Words[0].PosList中,利用range对其进行遍历,打印每一项的Explanations[0].Text,即可获得翻译内容,最后代码:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
)

type DictRequest struct {
    Text     string `json:"text"`
    Language string `json:"language"`
}

type DictResponse struct {
    Words []struct {
        Source  int    `json:"source"`
        Text    string `json:"text"`
        PosList []struct {
            Type      int `json:"type"`
            Phonetics []struct {
                Type int    `json:"type"`
                Text string `json:"text"`
            } `json:"phonetics"`
            Explanations []struct {
                Text     string `json:"text"`
                Examples []struct {
                    Type      int `json:"type"`
                    Sentences []struct {
                        Text      string `json:"text"`
                        TransText string `json:"trans_text"`
                    } `json:"sentences"`
                } `json:"examples"`
                Synonyms []interface{} `json:"synonyms"`
            } `json:"explanations"`
            Relevancys []interface{} `json:"relevancys"`
        } `json:"pos_list"`
    } `json:"words"`
    Phrases  []interface{} `json:"phrases"`
    BaseResp struct {
        StatusCode    int    `json:"status_code"`
        StatusMessage string `json:"status_message"`
    } `json:"base_resp"`
}

func query(word string) {
    // 创建客户端
    client := &http.Client{}
    request := DictRequest{Text: word, Language: "en"}
    buf, err := json.Marshal(request)
    if err != nil {
        log.Fatal(err)
    }
    var data = bytes.NewReader(buf)
    // 创建请求
    req, err := http.NewRequest("POST", "https://translate.volcengine.com/web/dict/match/v1/?msToken=&X-Bogus=DFSzswVLQDc7EEGDSWt7ldCr9FuZ&_signature=_02B4Z6wo00001l4QzPQAAIDBlqwF6vei3lJeEMhAAPYOuzarBrmXaa6uYwNRblftdnJ6RWiBMA2AMxALHB6M3g4C9XW-JqScAK.Vq-5KZWzKetsthBs8vr-AM201Y9KO7PpDzFI2FYqvx9lJ88", data)
    if err != nil {
        log.Fatal(err)
    }
    // 设置请求头
    req.Header.Set("authority", "translate.volcengine.com")
    req.Header.Set("sec-ch-ua", `"Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"`)
    req.Header.Set("accept", "application/json, text/plain, */*")
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
    req.Header.Set("content-type", "application/json")
    req.Header.Set("origin", "https://translate.volcengine.com")
    req.Header.Set("sec-fetch-site", "same-origin")
    req.Header.Set("sec-fetch-mode", "cors")
    req.Header.Set("sec-fetch-dest", "empty")
    req.Header.Set("referer", "https://translate.volcengine.com/translate?category=&home_language=zh&source_language=detect&target_language=zh&text=good")
    req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
    // 发起请求,并获取响应
    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 dictResponse DictResponse
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(word, "UK:", dictResponse.Words[0].PosList[0].Phonetics[0].Text, "US:", dictResponse.Words[0].PosList[0].Phonetics[1].Text)
    for i, item := range dictResponse.Words[0].PosList {
        fmt.Println(i+1, item.Explanations[0].Text)
    }
}

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, `usage: simpleDict WORD example: simpleDict hello`)
        os.Exit(1)
    }
    word := os.Args[1]
    query(word)
}

打印结果:

请添加图片描述

3 个人思考

在入门了Go语言,了解了一些Go语言的基本语法后,尝试进行了一次项目的实战。这个项目整体并不算复杂,并使用了一些网站工具辅助完成,使我对Go语言进行网络编程的过程有了更深的了解,使用Go语言的能力也有了很大的提高。