文章目录
- 程序介绍
- 抓包
- 代码生成
- 生成request body
- 解析respond body
- 完整代码
字节青训营基础班学习记录。
程序介绍
在运行程序的时候以命令行的形式输入要查询的单词,然后程序返回单词的音标、释义等信息。
示例如下:
抓包
我们选择与网站https://fanyi.caiyunapp.com/
建立连接。
首先我们打开网站,Windows
下直接按f12
进入开发者工具,
然后输入一个单词并点击翻译按钮,在网络tag
部分找到请求方法为POST
的dict
:
这里有部分请求表头,是我们后续需要在代码中添加设置的。
然后去负载tag
可以看到这次请求的source
也就是要翻译的单词,
也能看到要翻译的类型,这里是en2zh
,也就是英文到中文:
这个请求是JSON
类型的,我们后续存储会用结构体去存储这个数据,所以传请求信息的时候要先对结构体进行序列化。
预览tag
就是这次请求返回的信息:
返回信息也是JSON
,为了方便打印我们后续也会将返回得到的JSON
反序列化存放到对应的结构体变量中。
代码生成
我们要实现一个在线词典,所以我们运行程序后需要程序发送对应的请求。
上面也看了,部分代码会特别复杂,
所以这里用到一个代码生成工具Convert curl to Go (curlconverter.com)帮我们完成部分代码工作。
鼠标指向刚刚的请求tag
-> 右键点击 -> 选择复制 -> 选择复制为cURL(cmd)
:
然后将我们复制后的信息粘贴到网站的curl command
下面的代码框内得到生成的go
语言代码:
此时可能会出现这样的报错信息:
这是因为我们复制的command
的每一行是以^
结尾的,我们需要手动改成\
,这样就没问题了:
然后把代码复制到我们的代码编辑器中就OK了。
下面添加了部分注释帮助理解代码:
package main
import (
"fmt"
"io"
"log"
"net/http"
"strings"
)
func main() {
//创建http客户端
client := &http.Client{}
//data是请求参数,也就是翻译类型和要翻译的单词
//由于创建请求信息的时候请求参数是io.Reader流类型
//所以需要用strings.NewReader()将JSON转化为io.Reader类型
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("authority", "api.interpreter.caiyunai.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("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "dde0d0432ca66c7fe6175b7bf940b7f2")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.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", "?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", "cross-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 Edg/115.0.1901.183")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
//发送请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
//成功拿到请求后关闭请求,如果不关闭请求可能会造成资源泄漏
//defer关键字会在函数结束时调用resp.Body.Close()方法
defer resp.Body.Close()
//读取请求返回的数据
//这里使用ioutil.ReadAll()方法读取,返回的是[]byte类型的数据
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//打印返回的信息
fmt.Printf("%s\n", bodyText)
}
生成request body
上面的代码已经能够成功发送请求并接收序列化的返回信息。
但是现在有一个缺点,请求信息是一个写死的JSON
转成的流,
而我们需要从命令行自己输入单词,而单词又是请求信息的一部分,
所以我们需要定义一个能存放请求信息的结构体,结构体核心字段为trans_type
和source
:
我们是要从命令行中获取要翻译的单词,
所以可以从命令行参数中读取单词并用其初始化我们要创建的请求信息结构体变量,
trans_type
字段是我们要翻译的类型,需要初始化为en2zh
。
这样就可以用结构体来表示我们的请求信息。
但是发送请求时请求信息需要转为io.Reader
类型,
所以我们还需要先将结构体序列化,
然后使用func bytes.NewReader(b []byte) *bytes.Reader
将序列化后的JSON转成io.Reader
类型,
之后就可以创建请求。
// DictRequest 请求参数
// 因为需要序列化,所以结构体每个字段的首字母都要大写
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
//读取命令行参数,也就是我们要翻译的单词
word := os.Args[1]
//用我们读到的单词初始化请求信息结构体
request := DictRequest{TransType: "en2zh", Source: word}
//将结构体序列化为json字符串
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
//将json字符串转换为io.Reader类型,因为NewRequest()方法需要传入io.Reader类型的参数
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
解析respond body
此时我们的程序已经可以翻译单个从命令行输入的单词,
并接收全部的返回信息:
请求返回的信息是JSON
序列,
而我们需要把这些信息反序列到一个结构体中,
然后打印出结构体中我们想要的部分信息。
所以我们还需要定义一个结构体用以接收返回信息反序列化的结果。
而返回信息中有太多字段了,一一去定义这些字段不仅低效并且容易出错,
所以这里使用到了另一款工具:JSON转Golang Struct - 在线工具 - OKTools。
首先还是打开开发者工具,找到POST
请求,点击预览:
然后单击鼠标右键选择复制值。
随后进入上面那个网站,将复制的内容 粘贴在左边一栏,然后点击转换嵌套:
这样就得到了对应的接收返回信息的结构体类型。
现在就可以把这段代码改个结构体的名添加到我们的代码中:
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `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"`
}
此前代码生成部分接收的返回数据是以[]byte
类型在bodyTest
中存储的,
此时我们就可以将其反序列化为结构体对象,
然后选择性地打印我们所需要的字段:
//将返回的数据反序列化解析到结构体中
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)
}
完整代码
此时将上面的代码整合一下就得到了完整的代码,
这里我们对代码进行了部分封装,
从main
函数中读取待翻译的单词,
然后将发送请求到接受返回信息并打印这一部分封装成query
函数,
将待翻译的单词作为参数,
如果有多个待翻译的单词可以在main
函数中一个个读取并调用query
函数 :
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"`
}
// DictResponse 返回参数
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `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"`
}
func query(word string) {
//创建http客户端
client := &http.Client{}
//创建请求
//POST请求,需要传入请求方法,请求地址,请求参数
//这里的地址是我自己搭建的一个测试接口,大家可以自行搭建或者使用其他接口
//data是请求参数,需要使用strings.NewReader()方法转换为io.Reader类型
request := DictRequest{TransType: "en2zh", Source: word}
//将结构体序列化为json字符串
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
//将json字符串转换为io.Reader类型,因为NewRequest()方法需要传入io.Reader类型的参数
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()方法
defer resp.Body.Close()
//读取请求返回的数据
//这里使用ioutil.ReadAll()方法读取,返回的是[]byte类型的数据
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 os.Args == nil || len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "usage: simpleDict WORDexample: simpleDict hello")
os.Exit(1)
}
for _, word := range os.Args[1:] {
query(word)
fmt.Println() //每打印完一个单词的信息用空行隔开
}
}
至此就完成了一个 简单的命令行在线词典。