在线词典项目实战 | 青训营笔记

206 阅读4分钟

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

在线词典实现

1. 简述

在青训营第一节课程中,学习了在线词典的实现,该实战项目通过爬取第三方接口,根据抓包获取接口的请求参数,拼接相应的Json数据后发送请求,再获取到返回的响应体数据并进行解析,最终打印出需要的数据,最终实现了彩云翻译和有道翻译,其中有道翻译中加入了反爬取机制,还需要动态去生成相应参数

2. 彩云翻译

2.1 抓包

进入彩云翻译网站,找到用来查询单词的请求,这是一个POST请求,请求的header比较复杂,请求头是JSON格式,里面包含两个字段,trans_type代表要从什么语言转换成什么语言,source代表要查询的单词,在返回结果里面需要用到的是explanations翻译内容字段和prons音标字段

14bba616bcb337fb02f373f03e69f90.png

2.2 代码生成

在Golang里需要实现发送这个请求,可以通过右键浏览器copy as curl(bash)获取到一串Json,访问curlconverter可以生成相应的请求代码

284234de83671202870f04245d09d01.png

分析代码可以看到首先创建了一个HTTP client请求,创建的时候可以指定很多参数,接下来构造一个HTTP请求,调http.NewRequest,传入的一个参数是“POST”,表示是一个post请求,第二个参数是请求的url,最后一个参数是body,为了支持流式发送,可以创建strings.NewReader或者bytes.NewReader,接下来是设置一系列的header参数,接下来调用client.do.request,得到response,如果请求失败的话,那么这个error会返回非空,那么应该打印错误信息以及结束进程,返回的数据是同样是一个流,为了避免资源泄漏,通过加入defer来保证函数结束后手动关闭这个流接下来就是调用json.Unmarshal()解析出我们想要得到的数据结构

func queryCaiYun(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()
    //...
    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 DictResponseCaiYun
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("####彩云翻译####")
    fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
    for _, item := range dictResponse.Dictionary.Explanations {
        fmt.Println(item)
    }
}

在golang里面生成json,需要先定义一个结构体,结构体应该与json结构对应,由于请求返回的结果比较复杂,也可以通过代码生成的方法快速生成对应的结构体,访问json2go

type AutoGenerated struct {
    TranslateResult [][]struct {
        Tgt string `json:"tgt"`
        Src string `json:"src"`
    } `json:"translateResult"`
    ErrorCode int `json:"errorCode"`
    Type string `json:"type"`
    SmartResult struct {
        Entries []string `json:"entries"`
        Type int `json:"type"`
    } `json:"smartResult"`
}

2.3 主函数

从命令行读取参数,如果参数不为2,则终止程序,如果参数为2,os.Args[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)
}

3. 有道翻译

3.1 抓包

与上述抓包过程一致,找到请求的url,获取发送请求的参数

28038afdf7a35e002cc7c07063b511f.png

由于有道翻译加入了反爬机制,需要注意三个参数

3255aab240f63d97575aa21a22f37dd.png

通过搜索salt,打上断点,分析出三个参数的具体实现

sign = "fanyideskweb" + 输入的单词(i) + salt + "Tbh5E8=q6U3EXe+&L[4c@"  //再用md5加密
lts   //时间戳
salt  //时间戳 + 0-9随机整数

3.2 代码生成

与上述代码生成步骤相同,右键浏览器copy as curl(bash)获取到一串Json,访问curlconverter生成相应的请求代码

并对salt,lts,sign实现动态生成

func queryYouDao(word string) {
    client := &http.Client{}
    //lts根据时间戳生成
    lts := strconv.Itoa(int(time.Now().UnixNano() / 1e6))
    rand.Seed(time.Now().UnixNano())
    //时间戳 + 0~9随机数
    salt := lts + strconv.Itoa(rand.Intn(10))
    //拼接多个参数
    sign := "fanyideskweb" + word + salt + "Ygy_4c=r#e#4EX^NUGUc5"
    //MD5加密sign
    sign = fmt.Sprintf("%x", md5.Sum([]byte(sign)))
    str := fmt.Sprintf(`i=%s&from=AUTO&to=AUTO&smartresult=dict&client=fanyideskweb&salt=%s&sign=%s&lts=%s
    &bv=ac3968199d18b7367b2479d1f4938ac2&version=2.1&keyfrom=fanyi.web&doctype=json&action=FY_BY_REALTlME`,     word, salt, sign, lts)
    data := strings.NewReader(str)
    req, err := http.NewRequest("POST", "https://fanyi.youdao.com/translate_o?smartresult=dict
                                &smartresult=rule", data)
    if err != nil {
        log.Fatal(err)
    }
    //req.Header.Set()
    //...   
    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 DictResponseYouDao
    err = json.Unmarshal(bodyText, &dictResponse)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("####有道翻译####")
    for _, item := range dictResponse.SmartResult.Entries {
        fmt.Printf("%s", item)
    }
}

3.3 修改主函数

现在已经实现了两个单词翻译函数,分别是有道翻译queryYouDao()和彩云翻译queryCaiYun()函数,为了提高响应速度,可以创建两个goroutine分别调用两个单词翻译函数,实现并行执行

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
        `)
        os.Exit(1)
    }
    word := os.Args[1]
    wg := sync.WaitGroup{}
    wg.Add(2)
    go func() {
        queryYouDao(word)
        wg.Done()
    }()
    go func() {
        queryCaiYun(word)
        wg.Done()
    }()
    wg.Wait()
}

4. 测试结果

通过命令行输入go run main.go monster启动,分别打印了有道翻译和彩云翻译的单词"monster"翻译结果

bd79c5a63f3b398aa4c32d2b67b13a4.png

5. 总结

青训营的这次项目实战还是干货满满的,学习到抓包,用golang去实现发起请求和结果解析,作业部分做的有道翻译因为反爬的原因折腾了不少时间,还好最后解决了QAQ。