golang实现一个简化英中词典 | 青训营笔记

266 阅读7分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记,今天差不多半天都在整这个,可算是弄明白了

一.彩云小译版本

1.抓包

如果是第一次见到这个次,那么你一定会想问:什么是抓包? 百度百科是这样解释的:

抓包(packet capture)就是将网络传输发送与接收的数据包进行截获、重发、编辑、转存等操作,也用来检查网络安全。抓包也经常被用来进行数据截取等。 了解这个概念之后,现在我们来进行实操,首先打开彩云小译在线翻译,进入网页后,在页面中点击右键,点击检查查看页面元素,然后点击network显示网络面板,如果你也如我一样第一次接触这个东西,看到这个面板之后可能会很懵,在进行我们抓包操作之前,我们先来详细介绍下这个面板

image.png 一共五部分,首先介绍一下控制器

image.png 第二,过滤器,在搜索框中输入关键词,请求列表会过滤只显示响应请求,也可以点击右侧关键词进行过滤

第三,在概览中可以框出时间来查看响应时间内的请求

第四,请求列表,最上面是一些字段名

  1. Name:资源的名称
  2. Status:HTTP状态码
  3. Type:请求的资源的MIME类型
  4. Initiator:发起请求的对象或进程。它可能有以下几种值:
  5. Size:服务器返回的响应大小(包括头部和包体),可显示解压后大小
  6. Time:总持续时间,从请求的开始到接受响应中的最后一个字节
  7. Waterfall:各请求相关活动的直观分析图 单击一个请求,我们会看到如下画面

image.png 从图上可以看到如下信息: 查看头部,包括请求头,响应头 预览响应正文:查看图像用 查看响应正文 发起请求的对象或进程 时间详细分布

第五,概要,统计了请求的数量以及流量

接下来,进行我们的操作,在翻译框中输入一个单词点击翻译,我们会看到出现dict请求,右键单击请求,以cURL命令形式复制该请求,进入Convert curl commands to code,生成GO语言代码

package main

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

func main() {
	client := &http.Client{}
	var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.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,en-GB;q=0.8,en-US;q=0.7,en;q=0.6")
	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", "cross-site")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36")
	req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
	req.Header.Set("app-name", "xy")
	req.Header.Set("os-type", "web")
	req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"`)
	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 := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", bodyText)
}

2.修改代码--添加结构体

我们再回到刚才的彩云小译页面,右键点击Request Method为post的dict,复制其response,进入json转Golang结构体,嵌套生成我们需要的结构体

type AutoGenerated 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 []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"`
}

大致结构已经完整了,接下来我们开始修改代码 首先添加DictRequest结构体

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

然后将我们刚才生成的结构体名字改为DictResponse 在看到我们第一步抓包时生成的代码,改名为query方法 在该方法中,创建一个请求实例,编码成json格式,并转化为io流

request := DictRequest{TransType: "en2zh",Source: word}//创建一个实例,word为我们要查的单词
buf,err := json.Marshal(request)//将该请求实例解析为json格式
if err != nil {
   log.Fatal(err)
}
var data = bytes.NewReader(buf)//将我们的实例转化为ioReader流

其次,如果服务器处理了该请求会返回状态码200,将响应内容解码创建一个dictResponse实例,并输出我们想要的内容

if resp.StatusCode != 200 {
   log.Fatal("bad status code:", resp.StatusCode, "body", string(bodyText))
}
//声明一个响应结构体并把响应体通过json解码初始化
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)
}

3.main函数实现

命令行参数,os.Args[0]为程序路径,os.Arg[1]为我们要查的单词

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)
}

嘿嘿嘿,经过这么一番折腾,看到运行结果出来的我感觉一下都释然了,如下;

image.png

二.有道智云版本

进入有道智云,输入一个单词翻译,需要注意的是,这里我们的是名为trans的请求,大致步骤同第一个版本一样,复制其cURL命令,转化成查询代码,复制response转化为响应结构体,值得注意的是,该版本不需要声明请求结构体,而是直接将word格式化为json格式输出作为创建流的参数

client := &http.Client{}
var data = strings.NewReader(fmt.Sprintf(`q=%s&from=en&to=zh-CHS`, word))
req, err := http.NewRequest("POST", "https://aidemo.youdao.com/trans", data)
if err != nil {
   log.Fatal(err)
}

三.实现并行请求两个翻译引擎来提高响应速度

合并上面两个版本,主要修改main函数,通过waitgroup优雅地实现阻塞,开启两个协程,完整代码如下

package main

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

type DictRequestCaiYun struct {
   TransType string `json:"trans_type"`
   Source    string `json:"source"`
   UserID    string `json:"user_id"`
}

type DictResponseCaiYun 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      []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"`
}

type DictResponseYouDao struct {
   ReturnPhrase []string `json:"returnPhrase"`
   Query        string   `json:"query"`
   ErrorCode    string   `json:"errorCode"`
   L            string   `json:"l"`
   TSpeakURL    string   `json:"tSpeakUrl"`
   Web          []struct {
      Value []string `json:"value"`
      Key   string   `json:"key"`
   } `json:"web"`
   RequestID   string   `json:"requestId"`
   Translation []string `json:"translation"`
   Dict        struct {
      URL string `json:"url"`
   } `json:"dict"`
   Webdict struct {
      URL string `json:"url"`
   } `json:"webdict"`
   Basic struct {
      ExamType   []string `json:"exam_type"`
      UsPhonetic string   `json:"us-phonetic"`
      Phonetic   string   `json:"phonetic"`
      UkPhonetic string   `json:"uk-phonetic"`
      Wfs        []struct {
         Wf struct {
            Name  string `json:"name"`
            Value string `json:"value"`
         } `json:"wf"`
      } `json:"wfs"`
      UkSpeech string   `json:"uk-speech"`
      Explains []string `json:"explains"`
      UsSpeech string   `json:"us-speech"`
   } `json:"basic"`
   IsWord   bool   `json:"isWord"`
   SpeakURL string `json:"speakUrl"`
}

func queryByCaiYun(word string) {
   client := &http.Client{}                                       //创建一个客户端
   request := DictRequestCaiYun{TransType: "en2zh", Source: word} //创建一个实例
   buf, err := json.Marshal(request)                              //将该请求实例解析为json格式
   if err != nil {
      log.Fatal(err)
   }
   var data = bytes.NewReader(buf) //将我们的实例转化为ioReader流
   req, err := http.NewRequest("POST",
      "https://api.interpreter.caiyunai.com/v1/dict",
      data) //向该网址以post方式发出我们的实例请求
   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,en-GB;q=0.8,en-US;q=0.7,en;q=0.6")
   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", "cross-site")
   req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36")
   req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
   req.Header.Set("app-name", "xy")
   req.Header.Set("os-type", "web")
   req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"`)
   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 := ioutil.ReadAll(resp.Body) //读取响应体中的全部内容并以字节数组形式返回
   if err != nil {
      log.Fatal(err)
   }
   if resp.StatusCode != 200 {
      log.Fatal("bad status code:", resp.StatusCode, "body", string(bodyText))
   }
   //声明一个响应结构体并把响应体通过json解码初始化
   var dictResponse DictResponseCaiYun
   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 queryByYouDao(word string) {
   client := &http.Client{}
   var data = strings.NewReader(fmt.Sprintf(`q=%s&from=en&to=zh-CHS`, word))
   req, err := http.NewRequest("POST", "https://aidemo.youdao.com/trans", data)
   if err != nil {
      log.Fatal(err)
   }
   req.Header.Set("authority", "aidemo.youdao.com")
   req.Header.Set("accept", "application/json, text/javascript, */*; q=0.01")
   req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6")
   req.Header.Set("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
   req.Header.Set("origin", "https://ai.youdao.com")
   req.Header.Set("referer", "https://ai.youdao.com/")
   req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"`)
   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", "same-site")
   req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36")
   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 status code:", resp.StatusCode, "body", string(bodyText))
   }
   var dictResponse DictResponseYouDao
   err = json.Unmarshal(bodyText, &dictResponse)
   if err != nil {
      log.Fatal(err)
   }
   fmt.Println(word, "UK", dictResponse.Basic.UkPhonetic, "US", dictResponse.Basic.UsPhonetic)
   for _, item := range dictResponse.Basic.Explains {
      fmt.Println(item)
   }
}

func main() {
   if len(os.Args) != 2 {
      fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
      `)
      os.Exit(1)
   }
   word := os.Args[1]
   var wg sync.WaitGroup //维护一个计数器
   wg.Add(2)             //开两个协程,计数器为2
   go func() {
      defer wg.Done() //该协程结束,计数器减1
      queryByCaiYun(word)
   }()
   go func() {
      defer wg.Done()
      queryByYouDao(word)
   }()
   wg.Wait() //等待计数器减为0,主函数即可结束
}