第2篇:基于go语言实现词典案例(彩云翻译&百度翻译)|青训营

142 阅读6分钟

0.前言

QQ截图20230810002703.png 本文通过对百度翻译彩云翻译进行抓包实验,最终实现在控制台输入单词,输出翻译;为日后完成小组件开发打下基础。

1.流程

graph LR
A(开始) --> B(发送HTTP请求)
B --> C(接收HTTP响应)
C --> D(解析响应数据)
D --> E(提取翻译结果)
E --> F(输出翻译结果到控制台)
F --> G(结束)

2.实战-彩云翻译

  1. 打开浏览器控制台,调整到“网络”,将过滤条件设置为“dic”。
  2. 在网页中输入需要翻译的单词“extraordinary”并点击翻译按钮。

network.png

请求.png 3.右键选择,以“cURL方式复制” 4.打开cURL转go代码粘贴,将生成的go代码复制到编辑器中

我们使用了Go语言的net/http包来构建HTTP请求。首先,我们创建一个http.Client对象,用于发送HTTP请求。然后,我们创建一个strings.NewReader对象,将需要翻译的文本作为请求的body数据。接下来,我们使用http.NewRequest函数创建一个POST请求,并设置请求的URL和headers。最后,我们使用client.Do方法发送请求,并通过io.ReadAll函数读取响应的body数据。最后,我们将翻译结果输出到控制台。

生成代码.png

package main


import (
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"
)

func main() {
    // 创建一个HTTP客户端
    client := &http.Client{}

    // 准备请求数据
    var data = strings.NewReader(`{"trans_type":"en2zh","source":"extraordinary"}`)

    // 创建POST请求
    req, err := http.NewRequest("POST", "https://lingocloud.caiyunapp.com/v1/dict", data)
    if err != nil {
       log.Fatal(err)
    }

    // 设置请求头部信息
    req.Header.Set("Accept", "application/json, text/plain, */*")
    req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
    req.Header.Set("Connection", "keep-alive")
    req.Header.Set("Content-Type", "application/json;charset=UTF-8")
    req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("Sec-Fetch-Dest", "empty")
    req.Header.Set("Sec-Fetch-Mode", "cors")
    req.Header.Set("Sec-Fetch-Site", "same-site")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
    req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
    req.Header.Set("app-name", "xy")
    req.Header.Set("device-id", "84821080de181d390056373e2b62e4ff")
    req.Header.Set("os-type", "web")
    req.Header.Set("os-version", "")
    req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)

    // 发送请求
    resp, err := client.Do(req)
    if err != nil {
       log.Fatal(err)
    }
    //避免资源泄漏
    defer resp.Body.Close()

    // 读取响应body数据
    bodyText, err := io.ReadAll(resp.Body)
    if err != nil {
       log.Fatal(err)
    }

    // 输出翻译结果到控制台
    fmt.Printf("%s\n", bodyText)
}

4.分析结果

目前程序已经可以发出请求并返回JSON,下面我们用JSON序列化来将固定的输入更改为变化的。

结果.png

5.定义与JSON相对应的结构体,用于序列化

type DictRequest struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
}

6.借助JSON转GO工具解析response body完成反序列化

复制响应的JSON代码

请求2.png

将AutoGenerated修改为DictResponse

QQ截图20230809213811.png 7.最终代码展示

package main

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

// DictRequest 是查询词典的请求结构体
type DictRequest struct {
    TransType string `json:"trans_type"` // 翻译类型
    Source    string `json:"source"`     // 源语言
    //UserID    string `json:"user_id"`    // 用户ID
}

// DictResponse 是查询词典的响应结构体
type DictResponse 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"` // 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      []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"` // 词典信息
}

// query 函数发送查询请求并打印结果
func query(word string) {
    client := &http.Client{}

    // 创建查询请求
    request := DictRequest{TransType: "en2zh", Source: word}
    buf, err := json.Marshal(request)
    if err != nil {
       log.Fatal(err)
    }
    var data = bytes.NewReader(buf)
    req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    if err != nil {
       log.Fatal(err)
    }

    // 设置请求头信息
    req.Header.Set("Connection", "keep-alive")
    req.Header.Set("DNT", "1")
    req.Header.Set("os-version", "")
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
    req.Header.Set("app-name", "xy")
    req.Header.Set("Content-Type", "application/json;charset=UTF-8")
    req.Header.Set("Accept", "application/json, text/plain, */*")
    req.Header.Set("device-id", "")
    req.Header.Set("os-type", "web")
    req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
    req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("Sec-Fetch-Site", "cross-site")
    req.Header.Set("Sec-Fetch-Mode", "cors")
    req.Header.Set("Sec-Fetch-Dest", "empty")
    req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
    req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")

    // 发送请求并获取响应
    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)
    }

    // 检查响应状态码
    if resp.StatusCode != 200 {
       log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
    }

    // 解析响应内容
    var dictResponse DictResponse
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
       log.Fatal(err)
    }

    // 打印结果
    fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
    for _, item := range dictResponse.Dictionary.Explanations {
       fmt.Println(item)
    }
}

func main() {
    // 检查命令行参数
    if len(os.Args) != 2 {
       fmt.Fprintf(os.Stderr, "请输入需要翻译的英语单词\n")
       os.Exit(1)
    }

    // 获取查询的单词
    word := os.Args[1]

    // 发起查询
    query(word)
}

运行效果展示:

完成!!.png

错误代码:

错误代码.png

3.实战-百度翻译

整体步骤同上。但需要注意的是,请求需要以"kw="开头。而JSON的键值对形式是“:”连接,所以果断放弃定义Request结构体。改用字符串拼接

1.png

package main

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

// DictResponse 定义了百度翻译API返回的数据结构
type DictResponse struct {
    Data []struct {
       V string `json:"V"`
    } `json:"data"`
}

// query 是向百度翻译API发送查询的函数
func query(word string) {
    client := &http.Client{}

    // 构造请求体,将查询单词拼接到请求体中
    //var prekey = "kw="
    //prekey += word
    //var data = strings.NewReader(prekey)
    // 使用 strings.Builder 构建请求体字符串
    var builder strings.Builder
    builder.WriteString("kw=")
    builder.WriteString(word)
    data := strings.NewReader(builder.String())

    // 创建POST请求
    req, err := http.NewRequest("POST", "https://fanyi.baidu.com/sug", data)
    if err != nil {
       log.Fatal(err)
    }

    // 设置请求头部信息
    req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
    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("Connection", "keep-alive")
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
    req.Header.Set("Cookie", `BAIDUID=1076D050F2788A25103C13DAACFB429A:FG=1; BAIDUID_BFESS=1076D050F2788A25103C13DAACFB429A:FG=1; BAIDU_WISE_UID=wapp_1689869356701_44; ZFY=6H:AIM3EUIaby4LTJPS6eAMeF0xBddPKbEVBmGUaQAyg:C; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; RT="z=1&dm=baidu.com&si=cd51613b-0c14-4f14-8cde-6e0df1dc66af&ss=lkh0z7l3&sl=1&tt=19j&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=21x&ul=12qe&hd=12r1"; newlogin=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1690289124,1690422854,1690458316,1690512464; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1690514158; ab_sr=1.0.1_ZDU0ZmZhNThmMmE3YzM2YTZjNDBkMDlkNWRlMDNkNTI5YjliMDE1N2JjNTlkYzRhMmFiZmY2MmNkM2VkNjZlNWU0YWVkZWM5ZTNmNDhiNWYxMDllODY4OWNjMmFlMDllZWQxYjg0ZGYxMDk5OTU3MzI3NzA0ZDU0MTU3NTNjY2E0ODZkY2E3OTkzYjZkMWUxMmVmZDYxNDY0NDUxNzk4MQ==`)
    req.Header.Set("Origin", "https://fanyi.baidu.com")
    req.Header.Set("Referer", "https://fanyi.baidu.com/")
    req.Header.Set("Sec-Fetch-Dest", "empty")
    req.Header.Set("Sec-Fetch-Mode", "cors")
    req.Header.Set("Sec-Fetch-Site", "same-origin")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183")
    req.Header.Set("X-Requested-With", "XMLHttpRequest")
    req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)

    // 发送请求并获取响应
    resp, err := client.Do(req)
    if err != nil {
       log.Fatal(err)
    }
    defer resp.Body.Close()

    // 读取响应体的内容
    bodyText, err := io.ReadAll(resp.Body)
    if err != nil {
       log.Fatal(err)
    }

    // 检查响应状态码是否为200
    if resp.StatusCode != 200 {
       log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
    }

    // 解析JSON响应
    var dictResponse DictResponse
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
       log.Fatal(err)
    }

    // 输出翻译结果
    explain := dictResponse.Data[0].V
    fmt.Println(word, explain)
}

func main() {
    // 检查命令行参数是否符合要求
    if len(os.Args) != 2 {
       fmt.Fprintf(os.Stderr, "请输入需要翻译的英语单词\n")
       os.Exit(1)
    }

    word := os.Args[1]
    query(word)
}

在上面给出两者字符串拼接方法,利用strings.Builder的高效性能,避免频繁地创建和拼接字符串。首先,我们实例化一个strings.Builder对象,然后使用WriteString方法逐个追加字符串片段,最后通过String方法获取完整的字符串,并使用strings.NewReader将其转换为io.Reader类型的数据,用于设置请求体。这种方式比直接使用字符串拼接操作符(+)更高效。

运行效果展示:

完成.png

4.改进方向

  • 使用无条件for循环与break语句相搭配,实现连续翻译单词
  • 改善字符串自命令行获取的输入方式,修改为程序运行后输入
  • 增加对短语,句子的翻译支持
  • 完善翻译类型,调用“自动检测语言”接口,实现中日韩英俄泰多种语言相互转化