目次
CLI开发框架
师傅们久等了,为了加快进度,这章节添加了一个爬虫功能,也是背面写工具要用到的。
学习结果:能够集成一个爬虫功能到工具中
如下图所示
cobra 集成库
cobra 是一个cli步伐脚手架,大是大了点,但是有规范模版,同时也很好用,代码分明(本人比较喜欢用这个)
以下仅仅是个人开发中常用到的,涉及比较浅,但是用来筹划属于自己的小工具应该是充足的哈!依旧是修行靠个人。
- go get github.com/spf13/cobra/cobra
复制代码 目次规范
固然说这个库也可以随便创建来利用,但是我十分保举下面这个模版,清晰而且显得专业。- ▾ 项目/
- ▾ cmd/
- cmd1.go
- cmd2.go
- cmd3.go
- root.go
- ▾其他(util)
- util.go
- sql.go
- ...
- main.go
复制代码 这里先讲一下目次布局:
- cmd:
就是你的下令放在该目次的root里面,子下令就是你创建的一些比如cmd1大概cmd2文件就是一些以后扩展的时候自行扩展,不是说肯定要这些文件,重要文件需要一个就行,root也可以不叫root,只不过他是告诉你需要一个核心下令文件
- main.go是必须的,一般都叫main,你也可以重定名其他,只不过代码中package main的main要换名字(小知识点在这里补充了)
搭建框架
细节:
- 一般吸收参数的变量都是放在全局
- 重复一遍:记得go mod tidy 导入包,这是三方的,不是go默认自带,需要导入利用,就算你下载了也要导入。
根下令
root.go根下令
记住两点:
- 布局体实现:&cobra.Command
- Execute()函数实现,这个是写给main函数调用的,所以你起什么名字都行,不肯定按照我这个名字。
- package cmd
- import (
- "fmt"
- "github.com/spf13/cobra"
- )
- var rootCmd = &cobra.Command{
- Use: "命令名字",
- Short: "短描述",
- Long: `长描述`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("Hello, cobra!")
- },
- }
- func Execute() error {
- return rootCmd.Execute()//执行命令
- }
复制代码 main.go
- package main
- import (
- "go_cobra/cmd"
- )
- func main() {
- cmd.Execute()
- }
复制代码 参数添加
- 实现init()函数,这个就是库会主动调用,我们只要负责实现,该函数即可, 这个函数重要是用来配合rootCmd的,也就是说我们的根下令,init就是来给他添加东西,比如添加参数,添加子下令(背面会讲)
- 全局与作用在单个下令中
单个:Flags
全局:PersistentFlags
单个即作用与某一个下令之下,比如root也算一个下令,但是他是根下令,假设我们有一个子下令,子下令下利用了这个Flags就是你该参数只能作用与这个子下令中
假如你写的是PersistentFlags作用是:你当前下令下的其他子下令也能利用
我这里添加两个参数,一个是本地的,意思是只有在root根下令才气利用,另一个是全局,当背面创建了子下令的时候,子下令也能利用我这个参数
变量如下:- var (
- P string //打印参数
- PP string //全局打印参数
- )
复制代码 初始化函数init()
默认值为:nil- func init() {
- rootCmd.Flags().StringVarP(&P, "print", "p", "nil", "打印") //添加参数
- rootCmd.PersistentFlags().StringVarP(&PP, "Print", "P", "nil", "全局打印") //添加全局参数
- }
复制代码 表明一下添加参数的函数,Flags为例子,PersistentFlags一样的。
这俩函数下有参数类型可以选择,所以说不仅仅是string可以作为参数值传入,当你的变量类型为bool的时候可以是:
rootCmd.Flags().BoolVarP
- 第一个参数:吸收用户传入的参数值了,用变量吸收,变量类型就是要看啊刚刚说的你用什么类型的函数了。
- 第二个参数:用户完整选项,也就是长选项
- 第三个参数:用户短选项
- 第四个参数:该选项的默认值
- 第五个参数“:该参数下令形貌
运行结果
全局下令区别在下面的子下令中区分实现。
子下令
在cmd文件夹里创建一个version.go文件
- 布局体实现:&cobra.Command,和根下令以一样的类型,所以很多东西都是可以用的,添加子下令也时添加这个类型到根下令中。
- 依旧是实现 init() 函数,在函数里面将你的子下令添加进去即可。
这里就加一个版本下令,工具经常要写的一个子下令。
在init中利用:rootCmd.AddCommand(versionCmd),意思就是根下令中添加一个子下令versionCmd
在Run中同时也写了之前的全局参数PP的操作:也就是说全局固然说是全局,但是在代码里面并非真的主动调用,而是需要你手动写进去,他全局的意思是全局接受,负责操作的依旧是在你当前子下令中, 他不会由于是某个下令下的全局参数而直接在你这个子下令中主动调用哈!!
代码如下:
cmd/version.go- package cmd
- import (
- "fmt"
- "github.com/spf13/cobra"
- )
- var versionCmd = &cobra.Command{
- Use: "version",
- Short: "显示版本",
- Long: `显示xxxx版本`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("工具当前版本:v1.0.0")
- if PP != "nil" {
- fmt.Println(PP)
- }
- },
- }
- func init() {
- //将versionCmd添加到rootCmd
- //这样在执行命令时,就可以使用version这个子命令了
- rootCmd.AddCommand(versionCmd)
- }
复制代码 资助信息
当然cobra这么优秀的框架肯定也有主动的资助信息哈哈,我们只需要写好自己的功能代码即可。
最后我们的目次布局是:
爬虫功能(趁热打铁)
Goquery处置惩罚相应
Goquery爬虫必备包
下载- go get -u github.com/PuerkitoBio/goquery
复制代码 利用
- goquery.NewDocumentFromReader负责剖析文本内容返回*goquery.Document对象
- *goquery.Document对象根据你给的css选择器查找元素,这个查找的元素是将你这个css选择器在内容中所有元素都查找出来,所以不必担心只查找到一个而已。
随便爬一下百度的某个元素:
这里仅仅展示爬取一个元素,由于图片中教学的是复制完整的css选择器路径,更多css选择器自行去学习。
- func testClimb() {
- fmt.Println("测试爬取百度热点功能")
- client := req.C()
- data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
- doc, _ := goquery.NewDocumentFromReader(data.Body) //解析网页
- 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的元素
- fmt.Println(strings.TrimSpace(s.Text())) //打印标题
- })
- }
复制代码 运行结果
编码处置惩罚
有一个单独处置惩罚某些编码的库,这个库有需要的自行学习句即可:- github.com/djimenez/iconv-go
复制代码 这里仅仅展示一个比较通用的编码处置惩罚方式- go get -u golang.org/x/text
复制代码 参考文章中作者已经写好了工具函数,拿来就用即可,我们写一些小工具来利用的话就不发起重复造轮子了,
利用方式:- utf8Body, err := DecodeHTMLBody(res.Body, "")
复制代码 工具函数代码如下:- func detectContentCharset(body io.Reader) string {
- r := bufio.NewReader(body)
- if data, err := r.Peek(1024); err == nil {
- if _, name, _ := charset.DetermineEncoding(data, ""); len(name) != 0 {
- return name
- }
- }
- return "utf-8"
- }
- func DecodeHTMLBody(body io.Reader, charset string) (io.Reader, error) {
- if charset == "" {
- charset = detectContentCharset(body)
- }
- e, err := htmlindex.Get(charset)
- if err != nil {
- return nil, err
- }
- if name, _ := htmlindex.Name(e); name != "utf-8" {
- body = e.NewDecoder().Reader(body)
- }
- return body, nil
- }
复制代码 参考文章的作者还找到一篇远古gbk编码的html网页来练习:
https://news.sina.com.cn/society/netsurvival/
charset.DetermineEncoding会根据 HTML 页面中的 meta 元信息推测网页编码。
由于我的终端编码类型是gb2312,不转换就能直接剖析了,我转为utf8反而还乱码了,所以这里就不演示了
运行结果:(结尾会给出所有源码)
网络百度热搜榜
参考文章:https://darjun.github.io/2020/10/11/godailylib/goquery/
这里一样的功能,读取top榜单,但是我们读取的是榜单,所以不能直接复制单个元素了,要会一点选择器操作
三个class标签直接用.符号来取至于子元素、子女元素用空格还是>我还是简朴说一下吧(不由得)
空格:子女元素,即你孩子的孩子也能够匹配到
>:子元素,仅仅代表你的子,即你生的下一代,不代表你下一代的下一代,所以只能取到下一层的元素。
我这里的布局实在用空格还是>都行,由于布局比较简朴
运行结果
函数功能如下:- func baiduHotspot() {
- // fmt.Println("爬取百度热点功能")
- client := req.C()
- data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
- doc, _ := goquery.NewDocumentFromReader(data.Body) //解析网页
- doc.Find(".content-pos_1fT0H .name_2Px2N .c-single-text-ellipsis").Each(func(i int, s *goquery.Selection) { //查找类名为c-single-text-ellipsis的元素
- title := s.Text() //获取文本内容
- res := "\t" + strconv.Itoa(i) + ":" + strings.TrimSpace(title)
- fmt.Println(res) //打印标题
-
- })
- }
复制代码 爬虫功能所有源码
我把功能用到了cobra框架里
目次布局如下,记得创建文件
资助下令
cmd/root.go文件- package cmd
-
- import (
- "bufio"
- "fmt"
- "io"
- "strconv"
- "strings"
-
- "github.com/PuerkitoBio/goquery"
- "github.com/imroc/req/v3"
- "github.com/spf13/cobra"
- "golang.org/x/net/html/charset"
- "golang.org/x/text/encoding/htmlindex"
- )
-
- var (
- P string //打印参数
- PP string //全局打印参数
- Climb bool //爬取百度热点功能
- Testc bool //测试爬虫功能
- )
-
- func testClimb() {
- // fmt.Println("测试爬取百度热点功能")
- client := req.C()
- data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
- doc, _ := goquery.NewDocumentFromReader(data.Body) //解析网页
- 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的元素
- fmt.Println(strings.TrimSpace(s.Text())) //打印标题
-
- })
- }
- func detectContentCharset(body io.Reader) string {
- r := bufio.NewReader(body)
- if data, err := r.Peek(1024); err == nil {
- if _, name, _ := charset.DetermineEncoding(data, ""); len(name) != 0 {
- return name
- }
- }
-
- return "utf-8"
- }
-
- func DecodeHTMLBody(body io.Reader, charset string) (io.Reader, error) {
- if charset == "" {
- charset = detectContentCharset(body)
- }
-
- e, err := htmlindex.Get(charset)
- if err != nil {
- return nil, err
- }
-
- if name, _ := htmlindex.Name(e); name != "utf-8" {
- body = e.NewDecoder().Reader(body)
- }
-
- return body, nil
- }
- func testClimb2() {
- client := req.C()
- data, _ := client.R().Get("https://news.sina.com.cn/society/netsurvival/")
-
- // 将 data.Body 转换为 io.Reader
- // decodedBody, _ := DecodeHTMLBody(bytes.NewReader(data.Bytes()), "") // 解码网页
-
- doc, _ := goquery.NewDocumentFromReader(data.Body) // 解析网页
- doc.Find(".title14 li").Each(func(i int, s *goquery.Selection) { // 微博72小时网络生存测试
- fmt.Printf("%d:%s\n", i, strings.TrimSpace(s.Text())) // 打印标题
- })
- }
-
- func baiduHotspot() {
- // fmt.Println("爬取百度热点功能")
- client := req.C()
- data, _ := client.R().Get("https://top.baidu.com/board?top=realtime")
- doc, _ := goquery.NewDocumentFromReader(data.Body) //解析网页
- doc.Find(".content-pos_1fT0H > .name_2Px2N > .c-single-text-ellipsis").Each(func(i int, s *goquery.Selection) { //查找类名为c-single-text-ellipsis的元素
- title := s.Text() //获取文本内容
- res := "\t" + strconv.Itoa(i) + ":" + strings.TrimSpace(title)
- fmt.Println(res) //打印标题
-
- })
- }
-
- var rootCmd = &cobra.Command{
- Use: "命令名字",
- Short: "短描述",
- Long: `长描述`,
-
- Run: func(cmd *cobra.Command, args []string) {
- if P != "nil" {
- fmt.Println(P)
- }
- if PP != "nil" {
- fmt.Println(PP)
- }
- if Climb {
- fmt.Println("爬取百度热点功能")
- baiduHotspot()
- }
- if Testc {
- fmt.Println("测试爬虫功能")
- // testClimb()
- testClimb2()
- }
-
- },
- }
-
- func Execute() error {
- return rootCmd.Execute() //执行命令,这个是给main函数调用的
- }
-
- func init() {
- rootCmd.Flags().StringVarP(&P, "print", "p", "nil", "打印") //添加参数
- rootCmd.PersistentFlags().StringVarP(&PP, "Print", "P", "nil", "全局打印") //添加全局参数
- rootCmd.Flags().BoolVarP(&Climb, "climb", "c", false, "爬取百度热点功能") //添加爬取百度热点功能参数
- rootCmd.Flags().BoolVarP(&Testc, "testc", "t", false, "测试爬虫功能") //添加测试爬虫功能
- }
复制代码 cmd/version.go 文件- package cmd
-
- import (
- "fmt"
-
- "github.com/spf13/cobra"
- )
-
- var versionCmd = &cobra.Command{
- Use: "version",
- Short: "显示版本",
- Long: `显示xxxx版本`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("工具当前版本:v1.0.0")
- if PP != "nil" {
- fmt.Println(PP)
- }
- },
- }
-
- func init() {
- //将versionCmd添加到rootCmd
- //这样在执行命令时,就可以使用version这个子命令了
- rootCmd.AddCommand(versionCmd)
- }
复制代码 main.go 文件- package main
-
- import (
- "go_cobra/cmd"
- )
-
- func main() {
- cmd.Execute()
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |