猜谜游戏在编程语言实践都已经和 HelloWord 程序成为必不可少的新手实践环节,毕竟,它能够让我们基本熟悉 for 循环、变量定义、打印、if else 语句等等的使用,当我们基本熟悉该语言基础之后,就要学会其优势方面的程序实践,比如 Golang 所具备的爬虫及其并发优势。我们将采用彩云词典的英文单词翻译成中文的在线词典爬虫程序,及其改进版本,在并发上,我们将采用 SOCKS5 代理服务器的方式体验 Golang 语言的高并发易用性。

欢迎关注我的字节后端青训营代码仓库,更新每日课后作业及其改进代码,除此之外,还会每周发布对应笔记,欢迎一起 star 或者 contribute 代码仓库。

猜谜游戏

思路:

  1. 生成随机数
  2. 读取输入文本
  3. 删除不必要的换行符
  4. 转化文本为数字
  5. 循环判断是否猜数正确
  6. 正确退出循环
  7. 不正确则从第二步重新开始

官方版

package mainimport ("bufio""fmt""math/rand""os""strconv""strings""time"
)// 官方版本
func main() {maxNum := 100// 定义随机种子为当前时间,如果没有设定随机种子,生成数一致rand.Seed(time.Now().UnixNano())// 设置随机数最高值n,最小值默认从零开始,即生成一个值在区间 [0, n) 的 Int 数secretNumber := rand.Intn(maxNum)fmt.Println("Please input your guess")// 读取文本reader := bufio.NewReader(os.Stdin)// 输入判断,猜数正确退出循环for {input, err := reader.ReadString('\n')// nil 即为 golang 的空值if err != nil {fmt.Println("An error occured while reading input. Please try again", err)// continue 返回循环开始处continue}// windows 需要修改换行符input = strings.Trim(input, "\r\n")// 利用 string 方法转化为数字guess, err := strconv.Atoi(input)if err != nil {fmt.Println("Invalid input. Please enter an integer value")continue}fmt.Println("You guess is", guess)// 判断数字大小,及其正确与否,不正确返回循环开始处,正确则结束循环if guess > secretNumber {fmt.Println("Your guess is bigger than the secret number. Please try again")} else if guess < secretNumber {fmt.Println("Your guess is smaller than the secret number. Please try again")} else {fmt.Println("Correct, you Legend!")// 利用 break 结束循环break}}
}

简易版:


package mainimport ("fmt""math/rand""time"
)func main() {maxNum := 100rand.Seed(time.Now().UnixNano()) //  设置随机数种子secretNumber := rand.Intn(maxNum)fmt.Println("Please input your guess")for {// 采用 fmt.Scanf 则无需额外处理文本 var guess int _, err := fmt.Scanf("%d\n", &guess)if err != nil {fmt.Println("Invalid input. Please enter an integer value")continue}fmt.Println("You guess is", guess)if guess > secretNumber {fmt.Println("Your guess is bigger than the secret number. Please try again")} else if guess < secretNumber {fmt.Println("Your guess is smaller than the secret number. Please try again")} else {fmt.Println("Correct, you Legend!")break}}
}

在线词典

在 https://fanyi.caiyunapp.com/ 进行抓包,即网站加载结束后,在输入英文前打开浏览器自带的开发者工具,进行网络录制(network),输入英文,出现如下网络活动:

  • translator(重复两次)
  • dict(重复两次)

通过筛选,选择 dict 获取以下响应数据:

我们可以看出,发送数据中,source 为需要翻译的词,trans_type 为翻译类型,此处为英语翻译成汉语。响应数据中,entry 参数为需要翻译的词 test ,explanations 为翻译结果。为了方便爬取,采用代码生成的方法进行获取 go 参数。

复制为 cURL(bash),注意 edge 浏览器选择复制成 bash 格式,而不是 cmd 格式,否则,代码生成会发生错误。

curl 'https://api.interpreter.caiyunai.com/v1/dict' \-H 'authority: api.interpreter.caiyunai.com' \-H 'accept: application/json, text/plain, */*' \-H 'accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6' \-H 'app-name: xy' \-H 'content-type: application/json;charset=UTF-8' \-H 'device-id: f1de93819e3bb9f68a199a51c6ee2efb' \-H 'origin: https://fanyi.caiyunapp.com' \-H 'os-type: web' \-H 'os-version;' \-H 'referer: https://fanyi.caiyunapp.com/' \-H 'sec-ch-ua: "Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"' \-H 'sec-ch-ua-mobile: ?1' \-H 'sec-ch-ua-platform: "Android"' \-H 'sec-fetch-dest: empty' \-H 'sec-fetch-mode: cors' \-H 'sec-fetch-site: cross-site' \-H 'user-agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36 Edg/113.0.1774.35' \-H 'x-authorization: token:qgemv4jr1y38jyq6vhvi' \--data-raw '{"trans_type":"en2zh","source":"test"}' \--compressed

利用 Convert curl to Go (curlconverter.com) 生成代码如下:

package mainimport ("fmt""io""log""net/http""strings"
)func main() {client := &http.Client{}var data = strings.NewReader(`{"trans_type":"en2zh","source":"test"}`)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", "f1de93819e3bb9f68a199a51c6ee2efb")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", `"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`)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", "cross-site")req.Header.Set("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36 Edg/113.0.1774.35")req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")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("%s\n", bodyText)
}

运行结束后,获取到的响应数据(未格式化展示)如下

{"rc":0,"wiki":{},"dictionary":{"prons":{"en-us":"[t\u03b5st]","en":"[test]"},"explanations":["n.,vt.\u8bd5\u9a8c,\u6d4b\u8bd5,\u68c0\u9a8c"],"synonym":["examine","question","quiz","grill","query"],"antonym":[],"wqx_example":[["take a test","\u53c2\u52a0\u6d4b\u8bd5"],["receive a test","\u63a5\u53d7\u8003\u9a8c"],["put something to the test","\u68c0\u9a8c\u67d0\u4e8b"],["We will have an English test on Monday morning . ","\u661f\u671f\u4e00\u65e9\u4e0a\u6211\u4eec\u5c06\u6709\u4e00\u6b21\u82f1\u8bed\u6d4b\u9a8c\u3002"]],"entry":"test","type":"word","related":[],"source":"wenquxing"}}

利用该响应数据,我们就能够构造一个响应数据结构体,可利用 JSON转Golang Struct - 在线工具 - OKTools 进行代码生成。生成代码如下:

// 响应数据文本,少数参数有用
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      []interface{} `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 DictRequest struct {// 翻译类型TransType string `json:"trans_type"`// 翻译文本Source string `json:"source"`// 用户idUserID string `json:"user_id"`
}

把前面生成的请求代码封装改造(把请求参数和响应 json 数据序列化)成 query 方法,如下:

func query(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("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", "f1de93819e3bb9f68a199a51c6ee2efb")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", `"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`)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", "cross-site")req.Header.Set("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36 Edg/113.0.1774.35")req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")// 发起请求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 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 函数

func main() {// 运行代码:go run dict.go hello// hello 即为要翻译的文本if len(os.Args) != 2 {fmt.Fprintf(os.Stderr, `usage: simpleDict WORD example: simpleDict hello`)os.Exit(1)}word := os.Args[1]query(word)
}

运行结果如下:

test UK: [test] US: [tεst]
n.,vt.试验,测试,检验

以上为官方版本,我自行改造了一部分内容,添加了以下功能:

  • 改变命令行运行方式,运行后输入翻译文本,而不是携带在运行命令中
  • 判断输入格式是否为英文字段,如果不是,报异常

在序列化之前(request := DictRequest{TransType: “en2zh”, Source: word} 之前)添加的判断代码如下:

// 判断是否为英文dictionary := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"for _, v := range word {if !strings.Contains(dictionary, string(v)) {log.Fatal("Translation error, please enter English!")}}

当然,我们可以

main 函数改造如下

func main() {fmt.Printf("请输入您想翻译的单词:")var word string_, err := fmt.Scanf("%v", &word)if err != nil {fmt.Println(err)return}query(word)return
}

SOCKS5 代理

建立简单 tcp 服务器,以方便验证代理服务器结果,实现效果:往 tcp 服务器发送什么数据,就会返回打印什么数据,可用 netcat 进行验证,先安装 netcat ,步骤如下:

  1. 下载官网压缩包:netcat 1.11 for Win32/Win64 (eternallybored.org)
  2. 关闭自带杀毒软件,以防误删文件
  3. 解压并将解压后文件复制到 C:\Windows\System32 中即可(管理员权限)
  4. 打开 cmd 命令行即可使用 netcat 命令,即 nc 命令

tcp 服务器代码如下:

func main() {// 运行命令:go run tcp.go// windows 安装 netcat 之后,解压缩到 C:\Windows\System32 便可以使用 nc 命令// 测试命令:nc 127.0.0.1 1080// 监听发送给该端口的请求server, err := net.Listen("tcp", "127.0.0.1:1080")if err != nil {panic(err)}for {client, err := server.Accept()if err != nil {log.Printf("Accept failed %v", err)continue}// 创建一个新线程执行该方法go process(client)}
}func process(conn net.Conn) {// 执行方法结束,关闭defer conn.Close()reader := bufio.NewReader(conn)// 把发送的数据打印出来for {b, err := reader.ReadByte()if err != nil {break}_, err = conn.Write([]byte{b})if err != nil {break}}
}

接下来就是建立代理服务器的步骤了

从上图,我们可以知道 SOCKS5 的实现步骤分为以下三步:

  1. 认证阶段
  2. 请求阶段
  3. relay (回复)阶段

认证阶段包括以下三个字段

VER NMETHODS METHODS
1 1 1 to 255
  1. VER: 协议版本,socks5为0x05

  2. NMETHODS: 支持认证的方法数量

  3. METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:

    • 0x00:不需要认证

    • 0x02 :用户密码认证

认证阶段逻辑步骤如下:

  1. 浏览器会给代理服务器发送一个请求参数包,以便通过认证,然后服务端得选择一种认证方式,告诉客户端:VER 0x05,METHOD 可为如下

    • 如果是无需认证的话,methods 为 0x01,无需携带其他参数
    • 如果是用户密码认证的话,methods 为 0x02,需要验证用户密码
  2. 代理服务器读取请求参数,并利用 io.ReadFull 读满一个缓冲区。

func auth(reader *bufio.Reader, conn net.Conn) (err error) {// 读取字段信息ver, err := reader.ReadByte()if err != nil {return fmt.Errorf("read ver failed:%w", err)}if ver != socks5Ver {return fmt.Errorf("not supported ver:%v", ver)}methodSize, err := reader.ReadByte()if err != nil {return fmt.Errorf("read methodSize failed:%w", err)}// 创建缓冲区method := make([]byte, methodSize)_, err = io.ReadFull(reader, method)if err != nil {return fmt.Errorf("read method failed:%w", err)}// 设置为无需认证_, err = conn.Write([]byte{socks5Ver, 0x00})// 代理服务器还需要返回一个 response,返回包包括两个字段,// 一个是 version 一个是 method,if err != nil {return fmt.Errorf("write failed:%w", err)}return nil
}

请求阶段:在完成认证以后,客户端需要告知服务端它的目标地址,需要包括以下请求参数包

VER CMD RSV ATYP DST.ADDR DST.PORT
1 1 X’00’ 1 Variable 2
  1. VER:0x05,socks5的值为0x05

  2. CMD:连接方式,0x01=CONNECT, 0x02=BIND, 0x03=UDP ASSOCIATE

  3. RSV:保留字段,现在没什么用

  4. ATYP:地址类型,0x01=IPv4,0x03=域名,0x04=IPv6

  5. DST.ADDR

    • 目标地址

    • 目标地址类型,DST.ADDR的数据对应这个字段的类型。

      0x01表示IPv4地址,DST.ADDR为4个字节

      0x03表示域名,DST.ADDR是一个可变长度的域名

  6. DST.PORT:目标端口,2字节,网络字节序(network octec order)

func connect(reader *bufio.Reader, conn net.Conn) (err error) {buf := make([]byte, 4)_, err = io.ReadFull(reader, buf)if err != nil {return fmt.Errorf("read header failed:%w", err)}ver, cmd, atyp := buf[0], buf[1], buf[3]// 读取 socks5Verif ver != socks5Ver {return fmt.Errorf("not supported ver:%v", ver)}// 读取 cmdif cmd != cmdBind {return fmt.Errorf("not supported cmd:%v", cmd)}addr := ""// 处理 atypswitch atyp {case atypeIPV4:_, err = io.ReadFull(reader, buf)if err != nil {return fmt.Errorf("read atyp failed:%w", err)}addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])case atypeHOST:hostSize, err := reader.ReadByte()if err != nil {return fmt.Errorf("read hostSize failed:%w", err)}host := make([]byte, hostSize)_, err = io.ReadFull(reader, host)if err != nil {return fmt.Errorf("read host failed:%w", err)}addr = string(host)case atypeIPV6:return errors.New("IPv6: no supported yet")default:return errors.New("invalid atyp")}_, err = io.ReadFull(reader, buf[:2])if err != nil {return fmt.Errorf("read port failed:%w", err)}// BigEndian:“network octec order” 网络字节序port := binary.BigEndian.Uint16(buf[:2])dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))if err != nil {return fmt.Errorf("dial dst failed:%w", err)}defer dest.Close()}

回复阶段:返回参数,告诉客户端已经准备好了!

log.Println("dial", addr, port)_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})if err != nil {return fmt.Errorf("write failed: %w", err)}ctx, cancel := context.WithCancel(context.Background())defer cancel()go func() {_, _ = io.Copy(dest, reader)cancel()}()go func() {_, _ = io.Copy(conn, dest)cancel()}()<-ctx.Done()return nil

最后照例简单总结下:

  • Go语言非常适合实现网络服务,代码短小精悍,性能强大
  • Socks 5 是一个简单的二进制网络代理协议
  • 网络字节序实际上就是 BigEndian,大端存储

欢迎关注我的字节后端青训营代码仓库,更新每日课后作业及其改进代码,除此之外,还会每周发布对应笔记,欢迎一起 star 或者 contribute 代码仓库。

猜谜游戏、彩云词典爬虫、SOCKS5代理的 Go(Golang) 小实践,附带全代码解释相关推荐

  1. Python爬虫爬取某小说网的教程(含全代码)#大佬勿喷

    1.主要使用三个库 import parsel import requests import os 2.利用parsel解析网页 3.利用os库保存一本小说 4.链接上 小说专题: https://w ...

  2. python爬虫短片_Python爬虫练习:爬取全民小视频(附代码,过程)

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 本次目标 爬取全民小视频 https://quanmin.baidu.com ...

  3. python 爬小视频_Python爬虫练习:爬取全民小视频(附代码,过程)

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 本次目标 爬取全民小视频 https://quanmin.baidu.com ...

  4. 使用Socks5代理加速爬虫访问的方法

    在进行网络爬虫开发的过程中,我们常常需要使用代理来模拟不同地区.不同网络环境的访问情况.而Socks5代理相比HTTP代理,更具有一些优势,例如可以实现UDP流量的转发,支持用户名密码认证等功能.本文 ...

  5. 加速代理游戏,让指定进程走代理网络,驱动级SOCKS5代理使用方法。

    大家都遇到一种情况,在国内玩国外的游戏,就是网速好卡是吧. 一般来说,为了兼容性,最好用驱动SOCKS5来做,驱动负责拦截网络数据,R3负责转发数据.当然LSP或者API HOOK都可以办到,但是现在 ...

  6. GO语言的实战学习(猜谜游戏和在线词典)| 青训营笔记

    一.GO语言的实战学习 1.1 前言 在上文我们急速学习了Go语言的入门,今天我们来学习一下Go语言的实战 本专栏代码,源码打包下载地址如下: https://download.csdn.net/do ...

  7. 使用Socks5代理加强爬虫的网络安全

    网络爬虫是获取互联网上数据的重要手段,但也会因为访问频率过高或其他不当行为,导致被网站封禁或IP被屏蔽.此时,使用代理服务器是解决这一问题的常用方法.本文介绍了Socks5代理的使用方式,以及如何将其 ...

  8. [神器]Windows平台本地socks5代理客户端-ProxyCap

    ProxyCap这款软件是用来辅助应用程序(譬如游戏)使用socks5代理网络的客户端工具. 玩外服的人一般都是挂VPN来玩游戏,但是如今VPN政策收紧,好的vpn资源可遇不可求. 那么用什么方式替代 ...

  9. SOCKS5代理的四大应用场景

    SOCKS5协议使用隧道通过网络传输数据.它通过发送数据包来做到这一点.数据包是为传输而分解的数据位,然后在到达目的地后重新组合.隧道可以建立有效的连接.绕过防火墙并在网络上启用不受支持的协议.SOC ...

最新文章

  1. 十字星编程c语言,C语言求完整的代码
  2. C++ STL swap_range
  3. keil生成hex文件找不到_骚操作!用Python把公众号文章打包成pdf文件,再也不怕找不到了...
  4. .NET Core整合log4net以及全局异常捕获实现2
  5. 什么是高并发,如何避免高并发
  6. AspNetCore 基于AOP实现Polly的使用
  7. 第3步 (请先看第2步再看第3步) 新建完spring+springmvc+mybatis项目 需要推送gitee仓库进行管理 巨详细
  8. java中实现多线程的两种基本方法
  9. vmware中nat模式上网
  10. 【独家】一种手机上实现屏幕录制成gif的方案
  11. Hadoop经典面试题
  12. php爬虫框架使用案例QueryList,将数据爬到mysql数据库
  13. 官方通知:考研国家线预计4月中旬公布
  14. 【转】Java线程系列:Callable和Future
  15. 什么是表示学习(representation learning)表征学习 表达学习
  16. 用GEPHI绘制的 我的微博 好友 关系 与 好友的好友关系图
  17. python爬虫系列(一):爬虫简介
  18. 微软最走运和最倒霉的十个瞬间
  19. 第二次作业-产品分析
  20. 前端ui框架layui——layer弹出层-弹出框方法

热门文章

  1. Python数据分析 1-5 NumPy中ufunc函数(一)主要介绍NumPy中常用的通用函数:基本数学函数、三角函数和反三角函数和统计类函数
  2. 金融IC卡PBOC规范摘录
  3. 【Alpha】“北航社团帮”小程序v1.0项目展示
  4. ios非常全的库iOS开发 非常全的三方库、插件、大牛博客等等
  5. C语言的图形界面?——简单外挂程序
  6. 浅谈数据库并发控制 - 锁和 MVCC
  7. python开发工程师工资_Python开发工程师工资一般多少钱
  8. I/O口输入时的上拉、下拉、三态
  9. tensorflow学习笔记九:将 TensorFlow 移植到 Android手机,实现物体识别、行人检测和图像风格迁移详细教程
  10. 车间生产如何实现数字化管理,答案全在这里