这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
在线词典实现
1. 简述
在青训营第一节课程中,学习了在线词典的实现,该实战项目通过爬取第三方接口,根据抓包获取接口的请求参数,拼接相应的Json数据后发送请求,再获取到返回的响应体数据并进行解析,最终打印出需要的数据,最终实现了彩云翻译和有道翻译,其中有道翻译中加入了反爬取机制,还需要动态去生成相应参数
2. 彩云翻译
2.1 抓包
进入彩云翻译网站,找到用来查询单词的请求,这是一个POST请求,请求的header比较复杂,请求头是JSON格式,里面包含两个字段,trans_type代表要从什么语言转换成什么语言,source代表要查询的单词,在返回结果里面需要用到的是explanations翻译内容字段和prons音标字段
2.2 代码生成
在Golang里需要实现发送这个请求,可以通过右键浏览器copy as curl(bash)获取到一串Json,访问curlconverter可以生成相应的请求代码
分析代码可以看到首先创建了一个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,获取发送请求的参数
由于有道翻译加入了反爬机制,需要注意三个参数
通过搜索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<s=%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"翻译结果
5. 总结
青训营的这次项目实战还是干货满满的,学习到抓包,用golang去实现发起请求和结果解析,作业部分做的有道翻译因为反爬的原因折腾了不少时间,还好最后解决了QAQ。