找回密码
 立即注册
首页 业界区 安全 Go红队开发—CLI框架(二)

Go红队开发—CLI框架(二)

厂潺 2025-5-30 10:51:33
目录

  • CLI开发框架

    • cobra 集成库

      • 目录规范
      • 搭建框架

        • 根命令
        • 参数添加
        • 子命令
        • 帮助信息



  • 爬虫功能(趁热打铁)

    • Goquery处理响应
    • 编码处理
    • 收集百度热搜榜
    • 爬虫功能所有源码


CLI开发框架

师傅们久等了,为了加快进度,这章节添加了一个爬虫功能,也是后面写工具要用到的。
学习成果:能够集成一个爬虫功能到工具中
如下图所示
1.png

cobra 集成库

cobra 是一个cli程序脚手架,大是大了点,但是有规范模版,同时也很好用,代码分明(本人比较喜欢用这个)
以下仅仅是个人开发中常用到的,涉及比较浅,但是用来设计属于自己的小工具应该是足够的哈!依旧是修行靠个人。

  • 下载
  1. go get github.com/spf13/cobra/cobra
复制代码
目录规范

虽然说这个库也可以随便创建来使用,但是我十分推荐下面这个模版,清晰而且显得专业。
  1. ▾ 项目/
  2.     ▾ cmd/
  3.         cmd1.go
  4.         cmd2.go
  5.         cmd3.go
  6.         root.go
  7.     ▾其他(util)
  8.             util.go
  9.             sql.go
  10.             ...
  11.     main.go
复制代码
这里先讲一下目录结构:

  • cmd:
    就是你的命令放在该目录的root里面,子命令就是你创建的一些比如cmd1或者cmd2文件就是一些以后扩展的时候自行扩展,不是说一定要这些文件,主要文件需要一个就行,root也可以不叫root,只不过他是告诉你需要一个核心命令文件
  • main.go是必须的,一般都叫main,你也可以重命名其他,只不过代码中package main的main要换名字(小知识点在这里补充了)
搭建框架

细节:

  • 一般接收参数的变量都是放在全局
  • 重复一遍:记得go mod tidy 导入包,这是三方的,不是go默认自带,需要导入使用,就算你下载了也要导入。
2.png

根命令

root.go根命令
记住两点:

  • 结构体实现:&cobra.Command
  • Execute()函数实现,这个是写给main函数调用的,所以你起什么名字都行,不一定按照我这个名字。
  1. package cmd
  2. import (
  3.     "fmt"
  4.     "github.com/spf13/cobra"
  5. )
  6. var rootCmd = &cobra.Command{
  7.     Use:   "命令名字",
  8.     Short: "短描述",
  9.     Long:  `长描述`,
  10.     Run: func(cmd *cobra.Command, args []string) {
  11.         fmt.Println("Hello, cobra!")
  12.     },
  13. }
  14. func Execute() error {
  15.     return rootCmd.Execute()//执行命令
  16. }
复制代码
main.go

  • 调用root中给的Execute函数即可
  1. package main
  2. import (
  3.     "go_cobra/cmd"
  4. )
  5. func main() {
  6.         cmd.Execute()
  7. }
复制代码
参数添加


  • 实现init()函数,这个就是库会自动调用,我们只要负责实现,该函数即可, 这个函数主要是用来配合rootCmd的,也就是说我们的根命令,init就是来给他添加东西,比如添加参数,添加子命令(后面会讲)
  • 全局与作用在单个命令中
    单个:Flags
    全局:PersistentFlags
    单个即作用与某一个命令之下,比如root也算一个命令,但是他是根命令,假设我们有一个子命令,子命令下使用了这个Flags就是你该参数只能作用与这个子命令中
    如果你写的是PersistentFlags作用是:你当前命令下的其他子命令也能使用
我这里添加两个参数,一个是本地的,意思是只有在root根命令才能使用,另一个是全局,当后面创建了子命令的时候,子命令也能使用我这个参数
变量如下:
  1. var (
  2.     P  string //打印参数
  3.     PP string //全局打印参数
  4. )
复制代码
初始化函数init()
默认值为:nil
  1. func init() {
  2.     rootCmd.Flags().StringVarP(&P, "print", "p", "nil", "打印")              //添加参数
  3.     rootCmd.PersistentFlags().StringVarP(&PP, "Print", "P", "nil", "全局打印") //添加全局参数
  4. }
复制代码
解释一下添加参数的函数,Flags为例子,PersistentFlags一样的。
这俩函数下有参数类型可以选择,所以说不仅仅是string可以作为参数值传入,当你的变量类型为bool的时候可以是:
rootCmd.Flags().BoolVarP

  • 第一个参数:接收用户传入的参数值了,用变量接收,变量类型就是要看啊刚刚说的你用什么类型的函数了。
  • 第二个参数:用户完整选项,也就是长选项
  • 第三个参数:用户短选项
  • 第四个参数:该选项的默认值
  • 第五个参数“:该参数命令描述
    3.png

运行结果
4.png

全局命令区别在下面的子命令中区分实现。
子命令

在cmd文件夹里创建一个version.go文件

  • 结构体实现:&cobra.Command,和根命令以一样的类型,所以很多东西都是可以用的,添加子命令也时添加这个类型到根命令中。
  • 依旧是实现 init() 函数,在函数里面将你的子命令添加进去即可。
这里就加一个版本命令,工具经常要写的一个子命令。
在init中使用:rootCmd.AddCommand(versionCmd),意思就是根命令中添加一个子命令versionCmd
在Run中同时也写了之前的全局参数PP的操作:也就是说全局虽然说是全局,但是在代码里面并非真的自动调用,而是需要你手动写进去,他全局的意思是全局接受,负责操作的依旧是在你当前子命令中, 他不会因为是某个命令下的全局参数而直接在你这个子命令中自动调用哈!!
5.png

代码如下:
cmd/version.go
  1. package cmd
  2. import (
  3.     "fmt"
  4.     "github.com/spf13/cobra"
  5. )
  6. var versionCmd = &cobra.Command{
  7.     Use:   "version",
  8.     Short: "显示版本",
  9.     Long:  `显示xxxx版本`,
  10.     Run: func(cmd *cobra.Command, args []string) {
  11.         fmt.Println("工具当前版本:v1.0.0")
  12.         if PP != "nil" {
  13.             fmt.Println(PP)
  14.         }
  15.     },
  16. }
  17. func init() {
  18.     //将versionCmd添加到rootCmd
  19.     //这样在执行命令时,就可以使用version这个子命令了
  20.     rootCmd.AddCommand(versionCmd)
  21. }
复制代码
帮助信息

当然cobra这么优秀的框架肯定也有自动的帮助信息哈哈,我们只需要写好自己的功能代码即可。
6.png

最后我们的目录结构是:
7.png

爬虫功能(趁热打铁)

Goquery处理响应

Goquery爬虫必备包
下载
  1. go get -u github.com/PuerkitoBio/goquery
复制代码
使用

  • goquery.NewDocumentFromReader负责解析文本内容返回*goquery.Document对象
  • *goquery.Document对象根据你给的css选择器查找元素,这个查找的元素是将你这个css选择器在内容中所有元素都查找出来,所以不必担心只查找到一个而已。
随便爬一下百度的某个元素:
这里仅仅展示爬取一个元素,因为图片中教学的是复制完整的css选择器路径,更多css选择器自行去学习。
8.png
  1. func testClimb() {
  2.     fmt.Println("测试爬取百度热点功能")
  3.     client := req.C()
  4.     data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
  5.     doc, _ := goquery.NewDocumentFromReader(data.Body)  //解析网页
  6.     doc.Find("#sanRoot > main > div.hot-wrap_1nNog > div.theme-hot.category-item_1fzJW > div.list_1EDla > a:nth-child(7) > div.normal_1fQqB > div.content-wrap_1RisM > div > div").Each(func(i int, s *goquery.Selection) { //查找类名为c-single-text-ellipsis的元素
  7.     fmt.Println(strings.TrimSpace(s.Text())) //打印标题
  8.     })
  9. }
复制代码
运行结果
9.png

编码处理

有一个单独处理某些编码的库,这个库有需要的自行学习句即可:
  1. github.com/djimenez/iconv-go
复制代码
这里仅仅展示一个比较通用的编码处理方式
  1. go get -u golang.org/x/text
复制代码
参考文章中作者已经写好了工具函数,拿来就用即可,我们写一些小工具来使用的话就不建议重复造轮子了,
使用方式:
  1. utf8Body, err := DecodeHTMLBody(res.Body, "")
复制代码
工具函数代码如下:
  1. func detectContentCharset(body io.Reader) string {
  2.   r := bufio.NewReader(body)
  3.   if data, err := r.Peek(1024); err == nil {
  4.     if _, name, _ := charset.DetermineEncoding(data, ""); len(name) != 0 {
  5.       return name
  6.     }
  7.   }
  8.   return "utf-8"
  9. }
  10. func DecodeHTMLBody(body io.Reader, charset string) (io.Reader, error) {
  11.   if charset == "" {
  12.     charset = detectContentCharset(body)
  13.   }
  14.   e, err := htmlindex.Get(charset)
  15.   if err != nil {
  16.     return nil, err
  17.   }
  18.   if name, _ := htmlindex.Name(e); name != "utf-8" {
  19.     body = e.NewDecoder().Reader(body)
  20.   }
  21.   return body, nil
  22. }
复制代码
参考文章的作者还找到一篇远古gbk编码的html网页来练习:
https://news.sina.com.cn/society/netsurvival/
charset.DetermineEncoding会根据 HTML 页面中的 meta 元信息猜测网页编码。
由于我的终端编码类型是gb2312,不转换就能直接解析了,我转为utf8反而还乱码了,所以这里就不演示了
运行结果:(结尾会给出所有源码)
10.png

收集百度热搜榜

参考文章:https://darjun.github.io/2020/10/11/godailylib/goquery/
这里一样的功能,读取top榜单,但是我们读取的是榜单,所以不能直接复制单个元素了,要会一点选择器操作
三个class标签直接用.符号来取至于子元素、后代元素用空格还是>我还是简单说一下吧(忍不住)
空格:后代元素,即你孩子的孩子也能够匹配到
>:子元素,仅仅代表你的子,即你生的下一代,不代表你下一代的下一代,所以只能取到下一层的元素。
我这里的结构其实用空格还是>都行,因为结构比较简单
11.png

运行结果
12.png

函数功能如下:
  1. func baiduHotspot() {
  2.     // fmt.Println("爬取百度热点功能")
  3.     client := req.C()
  4.     data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
  5.     doc, _ := goquery.NewDocumentFromReader(data.Body)                                                          //解析网页
  6.     doc.Find(".content-pos_1fT0H .name_2Px2N .c-single-text-ellipsis").Each(func(i int, s *goquery.Selection) { //查找类名为c-single-text-ellipsis的元素
  7.         title := s.Text() //获取文本内容
  8.         res := "\t" + strconv.Itoa(i) + ":" + strings.TrimSpace(title)
  9.         fmt.Println(res) //打印标题
  10.   
  11.     })
  12. }
复制代码
爬虫功能所有源码

我把功能用到了cobra框架里
目录结构如下,记得创建文件
13.png

帮助命令
14.png

cmd/root.go文件
  1. package cmd
  2.   
  3. import (
  4.     "bufio"
  5.     "fmt"
  6.     "io"
  7.     "strconv"
  8.     "strings"
  9.   
  10.     "github.com/PuerkitoBio/goquery"
  11.     "github.com/imroc/req/v3"
  12.     "github.com/spf13/cobra"
  13.     "golang.org/x/net/html/charset"
  14.     "golang.org/x/text/encoding/htmlindex"
  15. )
  16.   
  17. var (
  18.     P     string //打印参数
  19.     PP    string //全局打印参数
  20.     Climb bool   //爬取百度热点功能
  21.     Testc bool   //测试爬虫功能
  22. )
  23.   
  24. func testClimb() {
  25.     // fmt.Println("测试爬取百度热点功能")
  26.     client := req.C()
  27.     data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
  28.     doc, _ := goquery.NewDocumentFromReader(data.Body)                                                                                                                                                                      //解析网页
  29.     doc.Find("#sanRoot > main > div.hot-wrap_1nNog > div.theme-hot.category-item_1fzJW > div.list_1EDla > a:nth-child(7) > div.normal_1fQqB > div.content-wrap_1RisM > div > div").Each(func(i int, s *goquery.Selection) { //查找类名为c-single-text-ellipsis的元素
  30.         fmt.Println(strings.TrimSpace(s.Text())) //打印标题
  31.   
  32.     })
  33. }
  34. func detectContentCharset(body io.Reader) string {
  35.     r := bufio.NewReader(body)
  36.     if data, err := r.Peek(1024); err == nil {
  37.         if _, name, _ := charset.DetermineEncoding(data, ""); len(name) != 0 {
  38.             return name
  39.         }
  40.     }
  41.   
  42.     return "utf-8"
  43. }
  44.   
  45. func DecodeHTMLBody(body io.Reader, charset string) (io.Reader, error) {
  46.     if charset == "" {
  47.         charset = detectContentCharset(body)
  48.     }
  49.   
  50.     e, err := htmlindex.Get(charset)
  51.     if err != nil {
  52.         return nil, err
  53.     }
  54.   
  55.     if name, _ := htmlindex.Name(e); name != "utf-8" {
  56.         body = e.NewDecoder().Reader(body)
  57.     }
  58.   
  59.     return body, nil
  60. }
  61. func testClimb2() {
  62.     client := req.C()
  63.     data, _ := client.R().Get("https://news.sina.com.cn/society/netsurvival/")
  64.   
  65.     // 将 data.Body 转换为 io.Reader
  66.     // decodedBody, _ := DecodeHTMLBody(bytes.NewReader(data.Bytes()), "") // 解码网页
  67.   
  68.     doc, _ := goquery.NewDocumentFromReader(data.Body)               // 解析网页
  69.     doc.Find(".title14 li").Each(func(i int, s *goquery.Selection) { // 微博72小时网络生存测试
  70.         fmt.Printf("%d:%s\n", i, strings.TrimSpace(s.Text())) // 打印标题
  71.     })
  72. }
  73.   
  74. func baiduHotspot() {
  75.     // fmt.Println("爬取百度热点功能")
  76.     client := req.C()
  77.     data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
  78.     doc, _ := goquery.NewDocumentFromReader(data.Body)                                                              //解析网页
  79.     doc.Find(".content-pos_1fT0H > .name_2Px2N > .c-single-text-ellipsis").Each(func(i int, s *goquery.Selection) { //查找类名为c-single-text-ellipsis的元素
  80.         title := s.Text() //获取文本内容
  81.         res := "\t" + strconv.Itoa(i) + ":" + strings.TrimSpace(title)
  82.         fmt.Println(res) //打印标题
  83.   
  84.     })
  85. }
  86.   
  87. var rootCmd = &cobra.Command{
  88.     Use:   "命令名字",
  89.     Short: "短描述",
  90.     Long:  `长描述`,
  91.   
  92.     Run: func(cmd *cobra.Command, args []string) {
  93.         if P != "nil" {
  94.             fmt.Println(P)
  95.         }
  96.         if PP != "nil" {
  97.             fmt.Println(PP)
  98.         }
  99.         if Climb {
  100.             fmt.Println("爬取百度热点功能")
  101.             baiduHotspot()
  102.         }
  103.         if Testc {
  104.             fmt.Println("测试爬虫功能")
  105.             // testClimb()
  106.             testClimb2()
  107.         }
  108.   
  109.     },
  110. }
  111.   
  112. func Execute() error {
  113.     return rootCmd.Execute() //执行命令,这个是给main函数调用的
  114. }
  115.   
  116. func init() {
  117.     rootCmd.Flags().StringVarP(&P, "print", "p", "nil", "打印")              //添加参数
  118.     rootCmd.PersistentFlags().StringVarP(&PP, "Print", "P", "nil", "全局打印") //添加全局参数
  119.     rootCmd.Flags().BoolVarP(&Climb, "climb", "c", false, "爬取百度热点功能")      //添加爬取百度热点功能参数
  120.     rootCmd.Flags().BoolVarP(&Testc, "testc", "t", false, "测试爬虫功能")        //添加测试爬虫功能
  121. }
复制代码
cmd/version.go 文件
  1. package cmd
  2.   
  3. import (
  4.     "fmt"
  5.   
  6.     "github.com/spf13/cobra"
  7. )
  8.   
  9. var versionCmd = &cobra.Command{
  10.     Use:   "version",
  11.     Short: "显示版本",
  12.     Long:  `显示xxxx版本`,
  13.     Run: func(cmd *cobra.Command, args []string) {
  14.         fmt.Println("工具当前版本:v1.0.0")
  15.         if PP != "nil" {
  16.             fmt.Println(PP)
  17.         }
  18.     },
  19. }
  20.   
  21. func init() {
  22.     //将versionCmd添加到rootCmd
  23.     //这样在执行命令时,就可以使用version这个子命令了
  24.     rootCmd.AddCommand(versionCmd)
  25. }
复制代码
main.go 文件
  1. package main
  2.   
  3. import (
  4.     "go_cobra/cmd"
  5. )
  6.   
  7. func main() {
  8.     cmd.Execute()
  9. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册