Go语言实践 在线词典 | 青训营

开头

本文简单的完成了在线词典课后作业的两个要求:

  • 增加了另一种翻译引擎的支持
  • 在上一步的基础上实现并行请求两个翻译引擎来提高响应速度。

步骤

选择翻译引擎

这里我选择的是360翻译引擎。其他的翻译引擎我都没有做出很好的解析效果,要么就是有奇奇怪怪的限制。鉴于自身实力问题,就选择了360翻译引擎。

抓包

image.png 通过翻译一个hello,找到了对应的翻译请求。

生成代码

image.png 右键->复制->复制为cURL(bash) 打开Convert curl to Go (curlconverter.com)Convert curl to Go (curlconverter.com)进行代码生成

package main

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

func main() {
    client := &http.Client{}
    req, err := http.NewRequest("POST", "https://fanyi.so.com/index/search?eng=1&validate=&ignore_trans=0&query=hello", nil)
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("authority", "fanyi.so.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("content-length", "0")
    req.Header.Set("cookie", "__huid=11wNJd%2BocFVfC2mya6DFn8FR9YfXKFnBmnHDhVe65YE1s%3D; so_huid=11wNJd%2BocFVfC2mya6DFn8FR9YfXKFnBmnHDhVe65YE1s%3D; __gid=9114931.775463156.1631263986380.1640166943863.150; QiHooGUID=97C410ED2C00E0D3A4F6B11516982081.1690537261562; Q_UDID=bb97a741-52ee-a963-d0ca-ce922256998c; __guid=144965027.578392690600855000.1692957328745.652; count=1")
    req.Header.Set("dnt", "1")
    req.Header.Set("origin", "https://fanyi.so.com")
    req.Header.Set("pro", "fanyi")
    req.Header.Set("referer", "https://fanyi.so.com/")
    req.Header.Set("sec-ch-ua", `"Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"`)
    req.Header.Set("sec-ch-ua-mobile", "?1")
    req.Header.Set("sec-ch-ua-platform", `"Android"`)
    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("sec-gpc", "1")
    req.Header.Set("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Edg/116.0.1938.54")
    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)
    }
    fmt.Printf("%sn", bodyText)
}

生成请求

http.NewRequest("POST", "https://fanyi.so.com/index/search?eng=1&validate=&ignore_trans=0&query=hello", nil)

分析这段代码可以知道,翻译的单词是直接拼接在query后面的,所以可以采用拼接的方式把要翻译的单词拼接上去即可。

http.NewRequest("POST", fmt.Sprintf("https://fanyi.so.com/index/search?eng=1&validate=&ignore_trans=0&query=%s", word), nil)

这样请求就构造好了。

生成响应体response body

image.png 右键->复制值 打开工具网站 JSON转Golang Struct - 在线工具 - OKTools

image.png 转换为嵌套结构体:

type AutoGenerated struct {
    Data struct {
        Explain struct {
            EnglishExplain []interface{} `json:"english_explain"`
            Word           string        `json:"word"`
            Caiyun         struct {
                Info struct {
                    Lbsynonym    []interface{} `json:"lbsynonym"`
                    Antonym      []interface{} `json:"antonym"`
                    WordExchange []interface{} `json:"word_exchange"`
                } `json:"info"`
            } `json:"caiyun"`
            RelatedWords []interface{} `json:"related_words"`
            WordLevel    []string      `json:"word_level"`
            Exsentence   []struct {
                Title string `json:"Title"`
                Body  string `json:"Body"`
                URL   string `json:"Url"`
            } `json:"exsentence"`
            Phonetic struct {
                NAMING_FAILED string `json:"英"`
            } `json:"phonetic"`
            WebTranslations []struct {
                Translation string `json:"translation"`
                Example     string `json:"example"`
            } `json:"web_translations"`
            Translation []string `json:"translation"`
        } `json:"explain"`
        Fanyi    string `json:"fanyi"`
        SpeakURL struct {
            SpeakURL     string `json:"speak_url"`
            TSpeakURL    string `json:"tSpeak_url"`
            WordSpeakURL string `json:"word_speak_url"`
            WordType     string `json:"word_type"`
        } `json:"speak_url"`
        Vendor string `json:"vendor"`
    } `json:"data"`
    Error int    `json:"error"`
    Msg   string `json:"msg"`
}

我们需要请求的数据就在result.Data.Explain.Phonetic.NAMING_FAILEDresult.Data.Explain.Translation里面。

完善代码

func queryBy360(word string) {
    client := &http.Client{}
    req, err := http.NewRequest("POST", fmt.Sprintf("https://fanyi.so.com/index/search?eng=1&validate=&ignore_trans=0&query=%s", word), nil)
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("authority", "fanyi.so.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("content-length", "0")
    req.Header.Set("cookie", "__huid=11wNJd%2BocFVfC2mya6DFn8FR9YfXKFnBmnHDhVe65YE1s%3D; so_huid=11wNJd%2BocFVfC2mya6DFn8FR9YfXKFnBmnHDhVe65YE1s%3D; __gid=9114931.775463156.1631263986380.1640166943863.150; __guid=65846823.4096180342024496000.1666005986099.6982; QiHooGUID=97C410ED2C00E0D3A4F6B11516982081.1690537261562; Q_UDID=bb97a741-52ee-a963-d0ca-ce922256998c; count=1")
    req.Header.Set("dnt", "1")
    req.Header.Set("origin", "https://fanyi.so.com")
    req.Header.Set("pro", "fanyi")
    req.Header.Set("referer", "https://fanyi.so.com/")
    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", "?1")
    req.Header.Set("sec-ch-ua-platform", `"Android"`)
    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("sec-gpc", "1")
    req.Header.Set("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Mobile Safari/537.36 Edg/115.0.1901.183")
    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)
    }
    var result AutoGenerated

    err = json.Unmarshal(bodyText, &result)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("---360翻译---")
    fmt.Println(word, "读音:", result.Data.Explain.Phonetic.NAMING_FAILED)
    for _, item := range result.Data.Explain.Translation {
        fmt.Println(item)
    }
}

这里把360翻译引擎的请求封装成了一个函数。 这样就可以和原有的彩云翻译分开来,提高代码可读性。 然后在main函数里分别调用彩云翻译和360翻译就可以实现串行请求翻译单词了。

改造为并行请求的方式

其实改造为并行请求的方式也很容易,就是开启协程来分别执行这两个函数。同步方式采用sync.WaitGroup来同步两个函数的调用过程,这样就可以做到两个函数并行请求翻译了。 具体代码框架就是:

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)
    go queryByCaiYun(word, &wg)
    go queryBy360(word, &wg)
    wg.Wait()
}

小结

其实这个课后作业本身就是比较容易的,按照课件的步骤也能逐步完成。难点就是并行请求的方式,这里我用的是sync.WaitGroup,当然还有其他的同步方式,这里就不介绍了。