当前位置: 首页 > news >正文

Go语言基础简单了解

文章目录

  • 前言
  • 关于Go
    • 学习流程
  • 基础语法
    • 注释
    • 变量
    • 常量
    • 数据类型
    • 运算符
    • fmt库
  • 流程控制
    • if、switch、select
    • for、break、continue
    • 遍历String
  • 函数
    • 值传递和引用传递
    • defer
    • init
    • 匿名、回调、闭包函数
  • 数组和切片
  • Map
  • 结构体
  • 自定义数据类型
  • 接口
  • 协程和channel
  • 线程锁
  • 异常处理
  • 泛型
  • 文件读取
  • 文件写入
  • 反射
  • TCP网络编程
  • Http
  • websocket
  • 爬虫
    • 正则表达式
    • goquery
    • colly
    • 豆瓣250
    • 爬B站评论


前言

简单的入门一下Go、会对基础语法、网络编程、Gin开发进行简单的了解,关键是Gin开发。

关于Go

Go语言(也称为Golang)是Google开发的一种开源编程语言。它被设计用于构建高效、可靠和可扩展的软件系统。下面是Go语言的一些主要用途:

  1. 服务器端开发:Go语言提供了强大的标准库和并发模型,使其成为构建高性能网络服务器的理想选择。许多大型互联网公司正在使用Go语言来开发后端服务,以处理高负载和并发请求。
  2. 网络编程:Go语言提供了丰富的网络编程库,可用于开发各种网络应用程序,包括Web服务、API服务器、网络代理等。
  3. 分布式系统:Go语言的并发模型和原生支持的并发原语(goroutine和channel)使其非常适合构建分布式系统,例如数据处理管道、消息队列等。
  4. 命令行工具:Go语言的编译速度快,生成的可执行文件体积小,使其成为开发命令行工具的良好选择。许多开发者使用Go语言来构建工具、脚本和自动化任务。
  5. 嵌入式系统:Go语言可以用于编写嵌入式系统的控制逻辑和驱动程序。它提供了对底层硬件的访问和控制能力,并具有较小的内存消耗。

需要注意的是,Go语言具有简洁而直观的语法,易于学习和使用。它的性能非常好,可以充分利用多核处理器和并发编程来提高应用程序的性能和吞吐量。

学习流程

基础语法->Web开发->常见中间件->云平台

基础语法

注释

增强语言的可读性

  1. 单行注释

  2. 多行注释

    package mainimport "fmt"// 单行注释
    /*
    多行注释
    多行注释
    */
    func main() {fmt.Println("hello world")
    }

变量

  1. var 定义变量,var 变量名 变量类型。
  2. 简短变量声明,使用:=运算符可以在函数内部声明并初始化变量。
  3. 匿名变量,使用 _ 占位符可以声明一个匿名变量,忽略不需要的值,任何赋值給这个标识符的值都将被抛弃,并且不会导致变量的冲突
  4. 定义多个变量,可以使用()包裹,表示定义多个变量
  5. 注意点:变量名的首个字符不能为数字,全局变量可被局部变量再定义(就近原则 ),定义的变量一定要使用。

变量声明后的默认值:

  • 整数型浮点数变量默认值是0和0.0
  • 字符串变量默认值是空字符串
  • 布尔型变量默认是false
  • 切片,函数,指针变量默认是nil

Printf输出时声明的格式

  • %v:默认格式化输出,会根据变量的类型自动选择合适的格式。
  • %s:输出字符串。
  • %d%b%o%x:输出整数,分别表示十进制、二进制、八进制和十六进制。
  • %t:输出布尔值,结果为 true 或 false。
  • %f%e%g:输出浮点数,分别表示十进制表示法、科学计数法和通用格式。
  • %p:输出指针地址。
  • %c:输出字符。
  • %q:输出带引号的字符串。
  • %%:输出一个百分号。
fmt.Printf("内存地址:%p,变量类型:%T",name,&name) //打印内存地址,变量类型等

例子:

package mainimport "fmt"var name = "Lau" //全局变量(隐式定义)func fun() (int, int) {return 100, 200
}
func main() {var name string = "aiwin" //显示定义var a, b int //同时定义a,b两个变量age := 18fmt.Printf("姓名:%s,内存地址为:%p,类型为:%T,年龄:%d,年龄十六进制数为:%x\n", name, &name, name, age, age) //就近原则a, _ = fun()_, b = fun()fmt.Println("_可代替被舍弃的值,并且可被重复定义:", a, b)a, b = b, afmt.Println("类似于Python,可直接进行值交换:", a, b)
}

常量

  1. 使用const来定义常量,不可改变
  2. iota 开始是0,默认会不断的自增进行计数,相当于是一个常量的计数器,直至新的一组常量计数器出现才会恢复,可理解过const语句块的索引
package mainimport "fmt"func main() {const (a = iotabc		d = "aiwin"e		//未被定义所以用上一个的值,但是iota还是会一直计数f = 100g 		//未被定义所以用上一个的值h = iotaj)const (k = iota	//新的const出现,新iota被重新从0开始计数l)fmt.Println(a, b, c, d, e, f, g, h, j, k, l) //0 1 2 aiwin aiwin 100 100 7 8 0 1}

数据类型

  1. 布尔型bool,默认值是false

  2. 数字类型,分为intfloat,并且支持复数,位运算采用补码

    序号类型和描述
    1uint8无符号8位整型(0~255)
    2uint16无符号16位整型(0~65535)
    3uint32无符号32位整型(0~4294967295)
    4uint64无符号64位整型(0~18446744073709551615)
    5int8有符号8位整型(-128~127)i
    6int16符号16位整型(-32768~32767)
    7int32有符号32位整型(-2147483648~2147483647)
    8int64有符号64位整型(-9223372036854775808~9223372036854775807)
  3. float浮点型,默认是64位,保留6位小数,保留小数会丢失精度,采取四舍五入的原则

    序号类型和描述
    1float32 IEEE-754 32位浮点型数
    2float64 IEEE-754 64位浮点型数
    3complex64 32 位实数和虚数
    4complex128 64 位实数和虚数
  4. 类型别名Go语言会有一些类型的别名

    序号类型和描述
    1byte类似uint8
    2intuint一样大小
    3rune类似int32
    7uintptr无符号整型,用于存放指针
  5. 字符类型 和**"** 和双引号包裹的字符是有差别的, 默认是int32 类型,会自动转换成Unicode 编码的值,字符可以直接使用**+** 连接

  6. 类型转换Go语言不存在隐式类型转换,所有的类型转换都必须是显式的声明

package mainimport ("fmt"
)func main() {var age byte = 18 //相当于uint8//超过范围,报错 age = 9223372036854775808var num1 float32 = -123.0000901var num2 float64 = -123.0000901fmt.Println("num1=", num1, "num2=", num2) //精度缺失fmt.Println("转换后导致的精度丢失:num=", float32(num2))var num3 float64 = 3.19fmt.Printf("num3=%.1f\n", num3) //四舍五入,输出3.2fmt.Printf("age的类型为%T,数值为%d", age, age)str := "Hello"str1 := '中' //使用Unicode编码表,会自动认为是int32类型str2 := "World"fmt.Printf("%T,%s\n", str, str)fmt.Printf("%T,%d\n", str1, str1) //默认是int32类型,转换成数字fmt.Println(str + "," + str2)var b uint16 = 256fmt.Println("b=", uint8(b)) //变成了0/*flag := 2fmt.Println(bool(flag)) 整型不能转换成bool类型*/}

运算符

运算符描述
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。
||逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。
!逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。
运算符描述
&按位与运算符"&"是双目运算符。都是1结果为1,否则为0
|按位或运算符"|"是双目运算符。 都是0结果为0,否则为1
^按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。
<<左移运算符"<<“是双目运算符。左移n位就是乘以2的n次方。 其功能把”<<“左边的运算数的各二进位全部左移若干位,由”<<"右边的数指定移动的位数,高位丢弃,低位补0。
>>右移运算符">>“是双目运算符。右移n位就是除以2的n次方。 其功能是把”>>“左边的运算数的各二进位全部右移若干位,”>>"右边的数指定移动的位数。
&^位清空,a&^b,对于b上的每个数值,如果为0,则取a对应位上的数,如果为1,则取0
运算符描述
&返回变量存储地址
*指针变量。
package mainimport "fmt"func main() {var a uint = 13     //0011 1100var b uint = 60     //0000 1101fmt.Println(a & b)  //0000 1100fmt.Println(a | b)  //0011 1101fmt.Println(a ^ b)  //0011 0001fmt.Println(a &^ b) // 0000 0001fmt.Println(b >> a) //60右移60位,结果为0
}

fmt库

常用的一些函数

  1. Print / Println / Printf:格式化并输出到标准输出。
  2. Println:类似于 Print,但在输出后添加换行符。
  3. Printf:使用格式化字符串进行输出(类似于 C 语言中的 printf 函数)。
  4. Sprint / Sprintln / Sprintf:将格式化的结果以字符串形式返回,而不是输出到标准输出。
  5. Fprint / Fprintln / Fprintf:将格式化的结果输出到指定的文件(io.Writer)。
  6. Errorf:生成一个格式化的错误字符串。
  7. Scan / Scanln / Scanf:从标准输入读取并格式化输入。
  8. Sscan / Sscanln / Sscanf:从给定的字符串中读取并格式化输入。
  9. Fscan / Fscanln / Fscanf:从指定的文件(io.Reader)中读取并格式化输入。
package mainimport ("fmt""os"
)func main() {name := "Alice"age := 30height := 1.68// 格式化并输出到标准输出fmt.Print("Hello, ")fmt.Print(name)fmt.Println("!")// 使用格式化字符串进行输出fmt.Printf("%s is %d years old.\n", name, age)// 输出到指定文件file, _ := os.Create("user.gob")defer file.Close()fmt.Fprintln(file, name)fmt.Fprintf(file, "%d", age)//标准化读取文件输入file, _ = os.Open("user.gob")defer file.Close()var ReadName stringvar ReadAge intfmt.Fscanln(file, &ReadName) //以行为单位来读取fmt.Fscanln(file, &ReadAge)fmt.Printf("读取到的数据为,Name: %s,Age: %d\n", ReadName, ReadAge)// 将格式化的结果以字符串形式返回info := fmt.Sprintf("Name: %s, Age: %d, Height: %.2f", name, age, height)fmt.Println(info)// 从标准输入读取并格式化输入var input stringfmt.Print("Enter your name: ")fmt.Scanln(&input)fmt.Printf("Hello, %s!\n", input)
}

流程控制

if、switch、select

语句描述
if语句if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if elseif 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
else if你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。
switchswitch 语句用于基于不同条件执行不同动作。
fallthrough当使用swich语句时,可以使用fallthrough 进行case穿透,下面的条件一定会执行
selectselect 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
package mainimport ("fmt""time"
)func main() {//if语句var password stringvar username stringfmt.Print("请输入账号:")fmt.Scan(&username)fmt.Print("请输入密码:")fmt.Scan(&password)if username == "admin" {if password == "Qyx@Sxf715" {fmt.Println("登录成功")} else {fmt.Println("密码错误")}} else {fmt.Println("用户名错误") //注意这里的else一定要接在if的}后面,不能进行换行}//switchvar score int = 88switch {case score >= 90:fmt.Println("成绩为A级")case score >= 80 && score < 90:fmt.Println("成绩为B级")//fallthrough 一定会把下面的一个case也穿透掉case score >= 70 && score < 80:fmt.Println("成绩为C级")case score >= 60 && score < 70:fmt.Println("成绩为D级")default:fmt.Println("成绩为不及格")}//select语句的使用ch1 := make(chan string) //创建了两个通道 ch1 和 ch2ch2 := make(chan string)//两个匿名的 goroutine 分别向这两个通道发送值go func() {time.Sleep(2 * time.Second)ch1 <- "Hello"}()go func() {time.Sleep(3 * time.Second)ch2 <- "World"}()/*select 会同时监听多个通道的操作,当任何一个 case 中的操作就绪时,该 case 就会被执行。如果同时有多个 case 就绪,select 随机选择一个可执行的 case 来执行。如果没有任何 case 就绪,并且存在 default 分支,那么执行 default 分支。*/select {case msg1 := <-ch1:fmt.Println("Received from ch1:", msg1)case msg2 := <-ch2:fmt.Println("Received from ch2:", msg2)case <-time.After(5 * time.Second):fmt.Println("Timed out")}}

for、break、continue

Go语言的for循环也需要三个参数,起始位,最终位,间距 ,但是三个参数都可以省略掉。

package mainimport "fmt"func main() {//9*9乘法表for j := 1; j <= 9; j++ {for i := 1; i <= j; i++ {fmt.Printf("%dx%d=%d \t", i, j, i*j)}fmt.Println()}for j := 1; j <= 9; j++ {if j == 5 {//结束掉整个循环break}fmt.Print(j)}fmt.Println()for j := 1; j <= 9; j++ {if j == 5 {//结束当次循环continue}fmt.Print(j)}
}

遍历String

package mainimport "fmt"func main() {var str string = "Hello,Aiwin"fmt.Println(str)fmt.Printf("字符串的长度为%d\n", len(str))fmt.Printf("第二个字符是%c\n", str[1])//遍历字符串for i := 0; i < len(str); i++ {fmt.Printf("%c", str[i])}//for rangefmt.Println("\n")for i, v := range str {fmt.Printf("%d%c\n", i, v)}
}

函数

  1. 函数是一个基本代码块,用于执行一个任务
  2. Go语言最少有一个main函数
  3. 函数声明告诉编译器函数的名称,返回类型,参数
  4. 函数本身也是一个变量,也可以进行赋值
function 函数名(参数,参数类型)(返回类型){}
  • 形式参数:定义函数时,用于接收外部传入数据的参数
  • 实际参数:调用函数时,传给形参的实际数据是实际参数
  • 可变参数:参数类型确定,但是数量不确定,可以用**…,可变参数前面可以继续定义参数,后面不能再定义参数,一个函数列表只能有一个**可变参数
package mainimport ("fmt"
)func main() {fmt.Println("1+2的结果为", add(1, 2))x, y := swap("你好", "Go语言")fmt.Println(x, y)printMessage("一个参数的函数")printStatic()fmt.Println("求和函数:", getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))}// 有多个返回值函数
func swap(x, y string) (string, string) {return y, x
}// 有两个参数的函数
func add(a, b int) int {result := a + breturn result
}// 一个参数函数
func printMessage(msg string) {fmt.Println(msg)
}// 无参数函数
func printStatic() {fmt.Println("无参数函数")
}func getSum(number ...int) int {sum := 0for i := 0; i < len(number); i++ {sum += number[i]}return sum
}

值传递和引用传递

  • 值类型数据:操作的是数据本身,如intstringboolarray,改变值不会改变数据本身,地址是不一样的
  • 引用类型数据,操作的是数据的地址,如slicemapchanel,改变的时候会一起改变,函数地址是一样的。

defer

可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会逆序执行,可以用于在函数返回前关闭相应的资源等操作

package mainimport "fmt"func main() {//值传递arr := [4]int{1, 2, 3, 4}updateArr(arr)fmt.Println(arr)//引用传递sli := []int{1, 2, 3, 4, 5}updateSlice(sli)fmt.Println(sli)//defer函数a := 10defer MyPrint(a) //输出10,已经传递进去了,一切准备就绪a++fmt.Println(a)
}
func updateArr(arr [4]int) {arr[1] = 100fmt.Println(arr)
}
func updateSlice(sli []int) {sli[1] = 100fmt.Println(sli)
}
func MyPrint(number int) {fmt.Println(number)
}

init

  1. **init()**函数不能被其它函数调用,而是在main函数执行之前自动被调用

  2. init 函数不能作为参数传入,不能有传入参数和返回值

  3. 当一个main有多个init函数,谁在前谁就先执行

匿名、回调、闭包函数

  1. 也叫闭包函数(closures),允许临时创建一个没有指定名称的函数
  2. 回调函数就是一个函数作为另一个函数的参数
  3. 闭包结构,一个外层函数中有内层函数,该内层函数中可以操作外层函数的局部变量,并且外层函数的返回值是内层函数,这种结构是闭包结构。
  4. 正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁,但是在闭包结构中的外层函数的局部变量并不会随着外层函数的结果而销毁,因为内层函数还在堆栈中使用
package mainimport "fmt"func main() {r1 := operator(1, 2, add)fmt.Println(r1)//匿名函数r2 := operator(3, 2, func(a, b int) int {if b == 0 {return 0}return a / b})fmt.Println(r2)//闭包结构f1 := increment()fmt.Println(f1())fmt.Println(f1())f2 := increment()fmt.Println(f2())fmt.Println(f1())//输出3,f1没有被销毁
}// 回调函数,fun是告诫函数,operator是回调函数
func operator(a, b int, fun func(int, int) int) int {return fun(a, b)
}
func add(a, b int) int {return a + b
}func increment() func() int {i := 0fun := func() int {i++return i}return fun
}

数组和切片

数组的数量在创建的时候就定义的,不可再进行改变。

数组的定义语法:

var 数组名 [数量]数据类型=[数量]数据类型{数据}

切片相对于数组来说更加灵活,切片的长度是可变的,相当于可切的数组

package mainimport "fmt"func main() {var nameArr [3]string = [3]string{"Hello", "Wor", "ld"}fmt.Println(nameArr)var nameList []stringnameList = append(nameList, "Hello")fmt.Println(nameList)
}

Map

Map就是Python中的字典(键值对),Map创建的时候一定需要初始化

var 变量名 map[键类型]值类型{}
package mainimport "fmt"func main() {var userMap map[int]string = map[int]string{1: "Aiwin",2: "Lau",3: "",}fmt.Println(userMap[1])value, ok := userMap[3] //3存在,ok是truefmt.Println(value, ok)userMap[1] = "LauAiwin"delete(userMap, 3)fmt.Println(userMap)
}

结构体

结构体定义,可以类比为对象

type 结构体名称 struct{名称 类型
}
package mainimport ("encoding/json""fmt"
)type Parent struct {Name stringAge  int
}
type Children struct {ParentName stringAge  int
}func (children *Children) setChildernName(name string, age int) { //引用传递children.Name = namechildren.Age = age
}type User struct {Username string `json:"username"`      //转换结果为usernamePassword string `json:"-"`             //不显示Age      int    `json:"age,omitempty"` //抛弃空值
}func main() {parent := Parent{Name: "Lau", Age: 40}childern := Children{parent, "Aiwin", 20}fmt.Printf("%s的父亲是:%s,年龄是:%d\n", childern.Name, childern.Parent.Name, childern.Parent.Age)childern.setChildernName("LauAiwin", 19)fmt.Println(childern)user := User{"Aiwin", "123456", 0}byteData, _ := json.Marshal(user)fmt.Println(string(byteData))}

自定义数据类型

自定义类型的本意就是为了代码更简化、易于理解、方便维护。

类型别名(就是将类型赋值给一个type)

  1. 不能绑定方法
  2. 打印类型还是原始类型
  3. 类型别名不用转换
package mainimport "fmt"type Code intconst (SuccessCode Code = 1ErrorCode   Code = 2
)func (code Code) getMessage() (message string) {switch code {case SuccessCode:return "请求成功"case ErrorCode:return "请求失败"}return ""
}
func (code Code) result() (result Code, message string) {return code, code.getMessage()
}func Request(name string) (code Code, message string) {if name == "admin" {return SuccessCode.result()} else {return ErrorCode.result()}
}
func main() {fmt.Println(Request("admin"))}

接口

接口是一组仅包含方法名、参数、返回值的为具体实现的方法的集合,同样接口也不能绑定方法

package mainimport "fmt"type Student struct {name string
}
type Name interface {getName() string
}func (student Student) getName() string {return student.name
}type Teacher struct {name string
}func (teacher Teacher) getName() string {return teacher.name
}// 接口,可以统一传入其它的类型
func getName(name Name) string {//name.(Teacher)switch types := name.(type) { //类型断言case Teacher:fmt.Println(types)case Student:fmt.Println(types)}return name.getName()
}func MyPrint(val interface{}) { //空接口fmt.Println(val)
}func main() {student := Student{"Aiwin"}teacher := Teacher{"Xd"}fmt.Println(getName(student))fmt.Println(getName(teacher))MyPrint(1)
}

协程和channel

协程可以理解为轻量级线程,一个线程可以拥有多个协程,与线程相比,协程不受操作系统调度,协程调度器按照调度策略把协程调度到线程中执行,协程调度器由应用程序的runtime包提供,用户使用go关键字即可创建协程,这也就是GO在语言层面直接支持协程的特色

package mainimport ("fmt""sync""time"
)func shopping(name string, group *sync.WaitGroup) {fmt.Printf("%s 开始攻击\n", name)time.Sleep(1 * time.Second)fmt.Printf("%s 停止攻击\n", name)group.Done()
}
func main() {var group sync.WaitGroupStartTime := time.Now()group.Add(3)go shopping("张三", &group)go shopping("李四", &group)go shopping("王五", &group)group.Wait()fmt.Println(time.Since(StartTime))
}

那么协程里面产生的数据,怎么传递给主线程,Go 官方使用channel 来传递

package mainimport ("fmt""sync""time"
)var moneyChanel chan int = make(chan int)
var nameChanel chan string = make(chan string)
var DoneChanel chan struct{} = make(chan struct{})func shopping(name string, money int, group *sync.WaitGroup) {fmt.Printf("%s 开始攻击\n", name)time.Sleep(1 * time.Second)fmt.Printf("%s 停止攻击\n", name)moneyChanel <- moneynameChanel <- namegroup.Done()
}
func main() {var group sync.WaitGroupStartTime := time.Now()group.Add(3)go shopping("张三", 100, &group)go shopping("李四", 150, &group)go shopping("王五", 160, &group)var moneyList []intvar nameList []stringgo func() { //解决moneyChanel一直死循环的问题defer close(moneyChanel)defer close(nameChanel)defer close(DoneChanel)group.Wait()}()//go func(){//	for money := range moneyChanel {//		moneyList = append(moneyList, money) //解决moneyChannel被一直输数据问题//	}//}()//for name := range nameChanel {//	nameList = append(nameList, name)//}event := func() {for {select {case names := <-nameChanel:nameList = append(nameList, names)case money := <-moneyChanel:moneyList = append(moneyList, money)case <-DoneChanel://解决当协程全部完事,退出循环的问题return}}}event()fmt.Println(moneyList)fmt.Println(nameList)fmt.Println(time.Since(StartTime))
}

超时

package mainimport ("fmt""time"
)var doneChanel = make(chan struct{})func timeOut() {fmt.Println("开始")time.Sleep(3 * time.Second)fmt.Println("结束")close(doneChanel)
}
func main() {go timeOut()select {case <-doneChanel:fmt.Printf("执行完成")case <-time.After(4 * time.Second):fmt.Println("超时")return}
}

线程锁

package mainimport ("fmt""sync"
)var sum int
var wait sync.WaitGroupvar lock sync.Mutexfunc add() {lock.Lock() //线程锁,不然会线程紊乱for i := 0; i < 10000; i++ {sum++}lock.Unlock()wait.Done()}
func sub() {lock.Lock() for i := 0; i < 10000; i++ {sum--}lock.Unlock()wait.Done()
}
func main() {wait.Add(2)go add()go sub()wait.Wait()fmt.Println(sum)var maps = sync.Map{}//Map的协程紊乱,要使用这种创建方式go func() {for {maps.Store(1, "Aiwin")}}()go func() {for {fmt.Println(maps.Load(1))}}()select {}
}

异常处理

Go语言没有捕获异常的机制,每次都要接error ,这是Go语言的一个诟病,异常处理可分为三种,分别是中断、恢复、从上一级返回处理。

例子:

package mainimport ("errors""fmt"
)// 中断仅适用于init开始时
//
//	func init() {
//		_, err := os.ReadFile("aaa")
//		if err != nil {
//			panic("中断报错了")
//		}
//	}
func div(a, b int) (res int, err error) {if b == 0 {err = errors.New("除数不能为0")return 0, err}res = a / breturn res, nil
}func zhixing() (res int, err error) {res, err = div(2, 0)if err != nil {return 0, err}res += 2return res, nil}func recovery() {defer func() {recover()}()var lists []int = []int{1, 2, 3}fmt.Println(lists[4])}
func main() {/*错误向上处理res, err := zhixing()if err != nil {fmt.Println(err)return}fmt.Println(res)*/recovery()fmt.Println("恢复正常逻辑")}

泛型

Go语言的泛型是指在定义函数、数据结构或接口时,可以不指定具体数据类型,而是以一种通用的方式编写代码,以便在不同的数据类型上有效地进行操作。泛型使得代码更具有通用性和可复用性,因为它可以适用于多种不同类型的数据而无需重复编写相似的代码。

比如说结构的泛型:

package mainimport ("encoding/json""fmt"
)type Response[A any] struct {Code int    `json:"code"`Msg  string `json:"msg"`Data A      `json:"data"`
}
type User struct {Name string `json:"name"`
}
type User1 struct {Name string `json:"name"`Age  int    `json:"age"`
}func main() {//UserInfo := Response[User]{//	Code: 1,//	Msg:  "反序列化",//	Data: User{//		Name: "Aiwin",//	},//}//marshal, _ := json.Marshal(UserInfo)//fmt.Println(string(marshal))var UnMarRes Response[User]json.Unmarshal([]byte(`{"code":1,"msg":"反序列化","data":{"name":"Aiwin"}}`), &UnMarRes) //通过泛型可以识别是属于哪一个Userfmt.Println(UnMarRes.Data.Name)}

文件读取

  1. os.ReadFile()一次性读取整个文件的内容。

  2. os.Open()分片读取。

  3. bufio依赖来读取,指定分割符,换行符等

    package mainimport ("bufio""fmt""os"
    )func main() {file, err := os.Open("text.txt")if err != nil {panic("文件读取错误")}//buf := bufio.NewReader(file)//for {//	line, _, err := buf.ReadLine()//	if err == io.EOF {//		break//	}//	fmt.Println(string(line))//}//指定分割符scanner := bufio.NewScanner(file)scanner.Split(bufio.ScanLines)for scanner.Scan() {fmt.Println(scanner.Text())}
    }

文件写入

  1. os.openFile()中flag的类型:

    const (O_RDONLY int = syscall.O_RDONLY // open the file read-only.O_WRONLY int = syscall.O_WRONLY // open the file write-only.O_RDWR   int = syscall.O_RDWR   // open the file read-write.O_APPEND int = syscall.O_APPEND // append data to the file when writing.O_CREATE int = syscall.O_CREAT  // create a new file if none exists.O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
    )
    

    例子:

    package mainimport ("fmt""io""os"
    )
    func main() {//文件写入file, err := os.OpenFile("text.txt", os.O_CREATE|os.O_RDWR, 0777) //模式和权限,权限仅针对linux系统if err != nil {panic("文件写入失败")}defer file.Close()file.Write([]byte("hello"))//文件写入err1 := os.WriteFile("text.txt", []byte("password"), 0777) //全部写入,会全覆盖fmt.Println(err1)//文件复制rFile, err2 := os.Open("text.txt")if err2 != nil {panic("文件不可取")}wFile, err3 := os.OpenFile("copy.txt", os.O_CREATE|os.O_WRONLY, 0777)if err3 != nil {panic("文件错误")}defer wFile.Close()io.Copy(wFile, rFile) //文件复制dir, err := os.ReadDir("基础语法")if err != nil {panic("error")}for _, entry := range dir {info, _ := entry.Info()fmt.Println(entry.IsDir(), entry.Name(), info.Size())}
    }
    

    反射

    1. reflect.typeof 获取类型
    2. reflect.Valueof 获取值
    3. setInt、setString 重新设置值

demo

package mainimport ("fmt""reflect"
)func getType(obj any) {v := reflect.TypeOf(obj)switch v.Kind() {case reflect.Int:fmt.Println("获取到Int类型")case reflect.String:fmt.Println("获取到String类型")}
}
//注意要更改值,需要使用指针的形式更改
func setValue(obj any, value any) {v1 := reflect.ValueOf(obj)v2 := reflect.ValueOf(value)if v1.Elem().Kind() != v2.Kind() { //获取一个值的指针所指向的元素值return}switch v1.Elem().Kind() {case reflect.Int:v1.Elem().SetInt(v2.Int())case reflect.String:v1.Elem().SetString(value.(string))}
}func main() {var name = "张三"var age = 24getType(name)getType(age)setValue(&name, "李四")setValue(&age, 25)fmt.Println(name, age)
}
package mainimport ("fmt""reflect"
)type User struct {UserName string `json:"userName"`Password string `json:"password"`
}func demo(obj any) {t := reflect.TypeOf(obj)v := reflect.ValueOf(obj)for i := 0; i < t.NumField(); i++ {value := v.Field(i)fmt.Println(value)}for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)if m.Name != "Call" { //方法名必须为大写continue}method := v.Method(i)method.Call([]reflect.Value{reflect.ValueOf("Aiwin"),})}
}func (User) Call(name string) {fmt.Println("我的名字是", name)
}func main() {user := User{UserName: "Aiwin", Password: "123456"}demo(user)
}

反射转ord小案例:

package mainimport ("errors""fmt""reflect""strings"
)type Users struct {Name string `orm:"name"`Id   int    `orm:"id"`
}func Find(obj any, query ...any) (sql string, err error) {t := reflect.TypeOf(obj)if t.Kind() != reflect.Struct {err = errors.New("非结构体")return}//获取到whereif len(query) > 0 {// 有第二个参数,校验第二个参数中的?个数,是不是和后面的个数一样q := query[0] //取第一个参数if strings.Count(q.(string), "?")+1 != len(query) {err = errors.New("参数个数不对")return}var where stringfor _, a := range query[1:] {at := reflect.TypeOf(a)switch at.Kind() {case reflect.Int:q = strings.Replace(q.(string), "?", fmt.Sprintf("%d", a.(int)), 1)//将?替换成数值case reflect.String:q = strings.Replace(q.(string), "?", fmt.Sprintf("'%s'", a.(string)), 1)}}where += "where " + q.(string)//获取到字段var columns []stringfor i := 0; i < t.NumField(); i++ {field := t.Field(i)f := field.Tag.Get("orm")columns = append(columns, f)}//获取表名称table := strings.ToLower(t.Name())sql = fmt.Sprintf("select %s from %s %s", strings.Join(columns, ","), table, where)}return
}func main() {sql, err := Find(Users{}, "name= ? and id = ?", "Aiwin", 1)//select name,id from Users where name='Aiwin' and id=1fmt.Println(sql, err)sql, err = Find(Users{}, "id = ?", 1)//select name,id from Users where id=1fmt.Println(sql, err)
}

TCP网络编程

主要通过net依赖包来完成,比如以下方法:

  • net.Dial(network, address string) (Conn, error):通过指定的网络协议和地址连接到远程主机,返回一个Conn接口类型的实例和可能的错误。
  • net.Listen(network, address string) (Listener, error):在指定的网络协议和地址上监听连接,返回一个Listener接口类型的实例和可能的错误。
  • net.DialTimeout(network, address string, timeout time.Duration) (Conn, error):在指定的超时时间内,通过指定的网络协议和地址连接到远程主机,返回一个Conn接口类型的实例和可能的错误。
  • net.ListenPacket(network, address string) (PacketConn, error):在指定的网络协议和地址上监听数据包的到达,返回一个PacketConn接口类型的实例和可能的错误。
  • net.ResolveTCPAddr(network, address string) (*TCPAddr, error):将字符串形式的TCP地址解析为TCPAddr类型的实例,包括IP地址和端口号等信息。
  • net.ResolveUDPAddr(network, address string) (*UDPAddr, error):将字符串形式的UDP地址解析为UDPAddr类型的实例,包括IP地址和端口号等信息。
  • net.LookupHost(host string) ([]string, error):通过主机名查询对应的IP地址列表,并返回一个字符串切片和可能的错误。
  • net.LookupPort(network, service string) (port int, err error):通过网络协议和服务名查询对应的端口号,并返回端口号和可能的错误。

demo:

package mainimport ("fmt""io""net"
)func main() {tcp, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:81") //将一个字符串形式的 TCP 地址解析为一个 TCPAddr 类型的实例listen, _ := net.ListenTCP("tcp", tcp)              //进行监听for {//接收连接fmt.Println("开始监听....")con, err := listen.Accept()if err != nil {break}fmt.Println(con.RemoteAddr().String() + "进来了")for {var buf []byte = make([]byte, 1024)n, err := con.Read(buf)//客户端退出if err == io.EOF {fmt.Println(con.RemoteAddr().String() + "退出了")break}fmt.Println(string(buf[0:n]))}}}
package mainimport ("fmt""net"
)func main() {conn, _ := net.Dial("tcp", "127.0.0.1:81")var s stringfor {fmt.Scanln(&s)if s == "quit" {break}conn.Write([]byte(s))}conn.Close()
}

Http

  • http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)):注册一个处理函数,用于指定URL模式的请求处理。
  • http.Handle(pattern string, handler http.Handler):注册一个处理器对象,该对象实现了http.Handler接口,用于指定URL模式的请求处理。
  • http.ListenAndServe(addr string, handler http.Handler):启动一个HTTP服务器,监听指定地址,并使用指定的处理器对象处理接收到的请求。
  • http.Get(url string) (resp *http.Response, err error):向指定URL发起GET请求,并返回响应结果。
  • http.Post(url string, bodyType string, body io.Reader) (resp *http.Response, err error):向指定URL发起POST请求,携带指定类型的请求体,并返回响应结果。
  • http.NewRequest(method, url string, body io.Reader) (*Request, error):创建一个自定义的HTTP请求对象,可以指定请求方法、URL和请求体。
  • http.DefaultServeMux:默认的ServeMux多路复用器,可以通过它来注册处理函数,用于处理HTTP请求。
  • http.HandlerFunc:将一个函数转换为http.Handler接口的实现,用于处理HTTP请求。
  • http.Error(w ResponseWriter, error string, code int):向客户端发送指定状态码的错误响应。
  • http.Redirect(w ResponseWriter, r *Request, url string, code int):向客户端发送重定向指令,使其跳转到指定的URL。

demo

package mainimport ("crypto/md5""fmt""net/http"
)func HashUsingMD5(input string) string {hasher := md5.New()hasher.Write([]byte(input))return fmt.Sprintf("%x", hasher.Sum(nil))
}
func LoginHandler(res http.ResponseWriter, req *http.Request) {if req.Method == "POST" {username := req.FormValue("username")password := req.FormValue("password")if username == "admin" && password == "123456" {Cookie := HashUsingMD5(username + password)cookie := http.Cookie{Name:  "Value",Value: Cookie,}http.SetCookie(res, &cookie)http.Redirect(res, req, "/success", http.StatusFound)} else {http.Redirect(res, req, "/", http.StatusFound)}} else {res.WriteHeader(405)res.Write([]byte("Method Not Allow"))}
}func SuccessHandler(res http.ResponseWriter, req *http.Request) {cookie, err := req.Cookie("Value")if err != nil {http.Redirect(res, req, "/", http.StatusFound)return}cookie_value := HashUsingMD5("admin123456")if cookie.Value != cookie_value {http.Redirect(res, req, "/", http.StatusFound)} else {res.Write([]byte("登录成功"))}
}func main() {//创建一个文件服务器来处理静态文件fs := http.FileServer(http.Dir("C:\\Users\\25018\\GolandProjects\\Project\\网络编程\\http"))http.Handle("/", http.StripPrefix("/", fs))http.HandleFunc("/login", LoginHandler)http.HandleFunc("/success", SuccessHandler)fmt.Println("HTTP server running at http://127.0.0.1:7000")err := http.ListenAndServe("127.0.0.1:7000", nil)if err != nil {fmt.Println(err)}}
package mainimport ("fmt""net/http""net/url"
)func main() {client := &http.Client{CheckRedirect: func(req *http.Request, via []*http.Request) error {return http.ErrUseLastResponse},}res, err := client.PostForm("http://127.0.0.1:7000/login", url.Values{"username": {"admin"}, "password": {"123456"}})if err != nil {fmt.Println("请求失败")}if res.StatusCode == http.StatusFound {fmt.Println(res.Header)fmt.Println("重定向成功")}}

websocket

go get github.com/gorilla/websocket

websocket是socket连接和http协议的结合体,可以实现网页和服务端的长连接

demo

package mainimport ("fmt""github.com/gorilla/websocket""net/http"
)var UP = websocket.Upgrader{ReadBufferSize:  1024,WriteBufferSize: 1024,
}func handler(res http.ResponseWriter, req *http.Request) {conn, err := UP.Upgrade(res, req, nil)if err != nil {fmt.Println(err)return}for {//消息类型,消息,错误types, message, err := conn.ReadMessage()if err != nil {break}conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("你说的是:%s吗?", string(message))))fmt.Println(types, string(message))}defer conn.Close()fmt.Println("服务关闭")
}func main() {http.HandleFunc("/", handler)http.ListenAndServe("127.0.0.1:7000", nil)}
package mainimport ("bufio""fmt""github.com/gorilla/websocket""os"
)func send(conn *websocket.Conn) {reader := bufio.NewReader(os.Stdin)l, _, _ := reader.ReadLine()conn.WriteMessage(websocket.TextMessage, l)
}func main() {dl := websocket.Dialer{}conn, _, err := dl.Dial("ws://127.0.0.1:7000", nil)if err != nil {fmt.Println(err)return}for {go send(conn)t, p, err := conn.ReadMessage()if err != nil {break}fmt.Println(t, string(p)) //1代表TextMessage}}

爬虫

正则表达式

package mainimport ("fmt"_ "github.com/go-sql-driver/mysql""io""net/http""os""regexp""strconv""strings""time""xorm.io/xorm"
)type Page struct {Id          int64 `xorm:"pk autoincr"` //Id自增Title       stringContent     string    `xorm:"text"`CreateTime  time.Time `xorm:"created"`UpdatedTime time.Time `xorm:"updated"`
}var engine *xorm.Enginefunc init() {dbType := "mysql"dbHost := "localhost"dbPort := "3306"dbUser := "root"dbPassword := "root"dbName := "test_xorm"// 创建引擎var err errorengine, err = xorm.NewEngine(dbType, dbUser+":"+dbPassword+"@tcp("+dbHost+":"+dbPort+")/"+dbName) //连接mysql数据库//engine, err = xorm.NewEngine("mysql", "root:root@/test_xorm?charset=utf-8") //连接mysql数据库if err != nil {fmt.Println(err)panic("初始化不成功")} else {err2 := engine.Ping()if err2 != nil {panic("连接不成功")} else {fmt.Println("连接成功!")}}
}
func fetch(url string) string {client := &http.Client{}req, _ := http.NewRequest("GET", url, nil)req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36")req.Header.Add("Cookie", "_gid=GA1.2.1321863478.1703508772; _ga_YXBYDX14GJ=GS1.1.1703508771.1.1.1703510246.58.0.0; _ga=GA1.2.1396264026.1703508772")resp, err := client.Do(req)if err != nil {fmt.Println("Http error", err)return ""}if resp.StatusCode != 200 {fmt.Println("Http Status Code", resp.StatusCode)return ""}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {fmt.Println("Read error", err)return ""}return string(body)}func parse(html string) {html = strings.Replace(html, "\n", "", -1)re_sidebar := regexp.MustCompile(`<aside id="sidebar" role="navigation">(.*?)</aside>`)sidebar := re_sidebar.FindString(html)re_link := regexp.MustCompile(`href="(.*?)"`)links := re_link.FindAllString(sidebar, -1)url := "https://gorm.io/zh_CN/docs/"for _, value := range links {fmt.Println(value)href := value[6 : len(value)-1]url := url + hreffmt.Println("url\n", url)body := fetch(url)go parse2(body)}
}
func parse2(body string) {body = strings.Replace(body, "\n", "", -1)re_title := regexp.MustCompile(`<h1 class="article-title" itemprop="name">(.*?)</h1>`)title := re_title.FindString(body)title = title[42 : len(title)-5]fmt.Println("title", title)//save(title, body)saveToDB(title, body)
}
func save(title string, content string) {err := os.WriteFile("网络编程/Go爬虫/pages/"+title+".html", []byte(content), 0644)if err != nil {panic("保存出现错误")}
}func saveToDB(title string, content string) {err := engine.Sync(new(Page))if err != nil {fmt.Println("Failed to sync database: %v", err)}page := Page{Title:   title,Content: content,}affected, err := engine.Insert(&page)if err != nil {fmt.Println("插入出现错误", err)}fmt.Println("save:" + strconv.FormatInt(affected, 10))
}func main() {url := "https://gorm.io/zh_CN/docs/"html := fetch(url)parse(html)
}

goquery

通过goquery可以快速的对HTMLXML进行解析,提供简单的API提取一个HTMLXML页面中的节点。

常用元素:

  1. Find: 通过CSS选择器查找元素,例如 doc.Find("div.content") 将返回所有class为content的div元素。
  2. Each: 遍历匹配的元素集合,并对每个元素执行指定的函数。
  3. Text: 获取元素的文本内容。
  4. Attr: 获取元素的属性值。
  5. Html: 获取匹配元素的HTML内容。
  6. Parent, Children, Next, Prev: 获取父元素、子元素、相邻的后一个元素、相邻的前一个元素等。
  7. Filter, Not, HasClass, Is: 根据特定条件对元素集合进行过滤和判断。
  8. AddClass, RemoveClass, ToggleClass: 添加、移除、切换元素的类名。
  9. Serialize: 将匹配的表单元素序列化为URL编码的字符串。
  10. Each: 遍历匹配的元素集合,并对每个元素执行指定的函数。
package mainimport ("fmt""github.com/PuerkitoBio/goquery""net/http"
)func main() {url := "https://gorm.io/zh_CN/docs/"//goquery.NewDocument(url)已过时弃用res, err := http.Get(url)if err != nil {panic("http请求出现错误")}defer res.Body.Close()doc, err := goquery.NewDocumentFromReader(res.Body)if err != nil {panic("goquery解析失败")}doc.Find(".sidebar-link").Each(func(i int, s *goquery.Selection) {href, _ := s.Attr("href")base_url := "https://gorm.io/zh_CN/docs/"detail_url := base_url + hrefresp, _ := http.Get(detail_url)detail_doc, _ := goquery.NewDocumentFromReader(resp.Body)title := detail_doc.Find(".article-title").Text()content, _ := detail_doc.Find(".article").Html()fmt.Println("title:\n", title)fmt.Println("content\n", content)})
}

colly

Colly 是一个用于爬取 Web 数据的 Golang 框架,具有以下特点:

  1. 简单易用:Colly 提供了一个简洁、直观的 API,易于使用和理解。
  2. 高度灵活:Colly 允许你自定义请求头、回调函数、处理方法等,以满足各种爬取需求。
  3. 并发支持:Colly 提供了并发请求的支持,可以同时处理多个请求,提高爬取效率。
  4. 支持动态网页:Colly 集成了 PhantomJS,可以处理 JavaScript 渲染的动态网页。
  5. 内置的选择器引擎:Colly 使用自己的选择器引擎,类似于 CSS 选择器,可以轻松地从 HTML 中提取所需的数据。
  6. 自动处理重试和错误:Colly 可以自动处理请求的重试和错误,提供了一种简化错误处理的方法。
  7. 支持代理:Colly 允许你使用代理服务器进行请求,以帮助隐藏真实 IP 地址和绕过访问限制。
  8. 自定义数据存储:Colly 提供了灵活的机制,允许你自定义数据的存储方式,比如输出到文件、存储到数据库等。
  9. 事件驱动:Colly 采用事件驱动的方式,通过注册回调函数处理请求和提取数据。
  10. 广泛的社区支持:Colly 作为一个流行的爬虫框架,有着活跃的社区,你可以轻松找到相关的文档、教程和示例代码。
package mainimport ("fmt""github.com/gocolly/colly"
)func main() {c := colly.NewCollector()c.OnHTML(".sidebar-link", func(element *colly.HTMLElement) {href := element.Attr("href")if href != "index.html" {c.Visit(element.Request.AbsoluteURL(href))}})c.OnHTML(".article-title", func(element *colly.HTMLElement) {title := element.Textfmt.Println("title:", title)})c.OnHTML(".article", func(element *colly.HTMLElement) {content, _ := element.DOM.Html()fmt.Println("content:", content)})c.OnRequest(func(request *colly.Request) {fmt.Println(request.URL.String())})url := "https://gorm.io/zh_CN/docs/"c.Visit(url)
}

豆瓣250

package mainimport ("fmt""github.com/PuerkitoBio/goquery"_ "github.com/go-sql-driver/mysql""net/http""regexp""strconv""xorm.io/xorm"
)type MovieData struct {Id       int64 `xorm:"pk autoincr"`Title    stringYear     stringScore    stringDirector stringActor    string `xorm:"text"`Quote    string `xorm:"text"`Picture  string
}var engine *xorm.Enginefunc init() {dbType := "mysql"dbHost := "localhost"dbPort := "3306"dbUser := "root"dbPassword := "root"dbName := "test_xorm"var err errorengine, err = xorm.NewEngine(dbType, dbUser+":"+dbPassword+"@tcp("+dbHost+":"+dbPort+")/"+dbName)if err != nil {fmt.Println(err)panic("初始化失败")} else {err2 := engine.Ping()if err2 != nil {panic("连接不成功")} else {fmt.Println("连接成功!")}}
}func main() {for i := 0; i < 10; i++ {fmt.Printf("正常爬取第 %d 页信息\n", i)Spider(strconv.Itoa(i * 25))}
}
func Spider(page string) {url := "https://movie.douban.com/top250" + "?start=" + pageclient := http.Client{}req, _ := http.NewRequest("GET", url, nil)req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36")req.Header.Set("Accept-Encoding", "zh-CN,zh;q=0.9")req.Header.Set("Cache-Control", "max-age=0")resp, _ := client.Do(req)doc, _ := goquery.NewDocumentFromReader(resp.Body)//#content > div > div.article > ol > li:nth-child(1)//#content > div > div.article > ol > li:nth-child(2)doc.Find("#content > div > div.article > ol > li").Each(func(i int, selection *goquery.Selection) {title := selection.Find("div > div.info > div.hd > a > span:nth-child(1)").Text()img := selection.Find("div > div.pic > a > img")imgUrl, ok := img.Attr("src")quote := selection.Find("div > div.info > div.bd > p.quote > span").Text()score := selection.Find("div > div.info > div.bd > div > span.rating_num").Text()info := selection.Find("div > div.info > div.bd > p:nth-child(1)").Text()if ok {year, director, actor := InfoHandler(info)saveToDb(title, imgUrl, year, score, quote, director, actor)}})
}func saveToDb(title, imgUrl, year, score, quote, director, actor string) {err := engine.Sync(new(MovieData))if err != nil {fmt.Println("保存数据出现错误")}moviedata := MovieData{Title:    title,Year:     year,Score:    score,Director: director,Actor:    actor,Quote:    quote,Picture:  imgUrl,}affected, err := engine.Insert(&moviedata)if err != nil {fmt.Println("插入出现错误!")}fmt.Println("保存:", strconv.FormatInt(affected, 10))}func InfoHandler(info string) (year, director, actor string) {year_re, _ := regexp.Compile(`(\d+)`)year = string(year_re.Find([]byte(info)))director_re, _ := regexp.Compile(`导演:(.*?)\s*(主演:|$)`)director_result := director_re.FindStringSubmatch(info)if len(director_result) > 1 {director = director_result[1]}actor_re, _ := regexp.Compile(`主演:(.*)`)actor_result := actor_re.FindStringSubmatch(info)if len(actor_result) > 1 {actor = actor_result[1]}return year, director, actor}

在这里插入图片描述

爬B站评论

Go语言爬虫还是挺累人,它在解析json数据要使用结构体的形式,这里可以使用 json2struct.mervine.net网站来转换成结构体,并提取出评论部分。

package mainimport ("fmt""github.com/goccy/go-json""io""log""net/http"
)type KingRankResp struct {Code int64 `json:"code"`Data struct {Replies []struct {Content struct {Device  string        `json:"device"`JumpURL struct{}      `json:"jump_url"`MaxLine int64         `json:"max_line"`Members []interface{} `json:"members"`Message string        `json:"message"`Plat    int64         `json:"plat"`} `json:"content"`Count  int64 `json:"count"`Folder struct {HasFolded bool   `json:"has_folded"`IsFolded  bool   `json:"is_folded"`Rule      string `json:"rule"`} `json:"folder"`Like    int64 `json:"like"`Replies []struct {Action  int64 `json:"action"`Assist  int64 `json:"assist"`Attr    int64 `json:"attr"`Content struct {Device  string   `json:"device"`JumpURL struct{} `json:"jump_url"`MaxLine int64    `json:"max_line"`Message string   `json:"message"`Plat    int64    `json:"plat"`} `json:"content"`Rcount  int64       `json:"rcount"`Replies interface{} `json:"replies"`} `json:"replies"`Type int64 `json:"type"`} `json:"replies"`} `json:"data"`Message string `json:"message"`
}func main() {client := &http.Client{}req, err := http.NewRequest("GET", "https://api.bilibili.com/x/v2/reply/wbi/main?oid=338159898&type=1&mode=3&pagination_str={\"offset\":\"\"}&plat=1&seek_rpid=&web_location=1315875&w_rid=1b7a0abc9704055facaafb28b36d864e&wts=1704203337", nil)if err != nil {log.Fatal(err)}req.Header.Set("authority", "api.bilibili.com")req.Header.Set("sec-ch-ua-mobile", "?0")req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36")req.Header.Set("accept", "*/*")req.Header.Set("accept-language", "zh-CN,zh;q=0.9")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)}var resultList KingRankResp_ = json.Unmarshal(bodyText, &resultList)for _, result := range resultList.Data.Replies {fmt.Println("一级评论:", result.Content.Message)for _, reply := range result.Replies {fmt.Println("二级评论:", reply.Content.Message)}}
}

相关文章:

Go语言基础简单了解

文章目录 前言关于Go学习流程 基础语法注释变量常量数据类型运算符fmt库 流程控制if、switch、selectfor、break、continue遍历String 函数值传递和引用传递deferinit匿名、回调、闭包函数 数组和切片Map结构体自定义数据类型接口协程和channel线程锁异常处理泛型文件读取文件写…...

kafka重平衡经验总结

文章目录 概要背景解决方法技术细节小结 概要 关于kafka重平衡问题在实践工作的应用 背景 重平衡包括以下几种场景: 消费者组内成员发生变更&#xff0c;这个变更包括了增加和减少消费者。注意这里的减少有很大的可能是被动的&#xff0c;就是某个消费者崩溃退出了主题的分…...

Py之jupyter_client:jupyter_client的简介、安装、使用方法之详细攻略

Py之jupyter_client:jupyter_client的简介、安装、使用方法之详细攻略 目录 jupyter_client的简介 jupyter_client的安装 jupyter_client的使用方法 1、基础用法 (1)、获取内核信息 (2)、执行代码块 (3)、远程执行代码 jupyter_client的简介 jupyter_client 包含 Jupyter 协…...

61.网游逆向分析与插件开发-游戏增加自动化助手接口-游戏红字公告功能的逆向分析

内容来源于&#xff1a;易道云信息技术研究院VIP课 上一节内容&#xff1a;游戏公告功能的逆向分析与测试-CSDN博客 码云地址&#xff08;master分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;63e04cc40f649d10ba2f4f…...

neo4j查询语言Cypher详解(五)--apoc

APOC (Awesome Procedures on Cypher)是一个Neo4j库&#xff0c;它提供了对其他过程和函数的访问&#xff0c;扩展了Cypher查询语言的使用。 apoc MATCH (n:Movie) CALL apoc.create.addLabels( n, [ n.genre ] ) YIELD node REMOVE node.genre RETURN node;附录 参考 apoc…...

odoo17 | 视图操作按钮

前言 到目前为止&#xff0c;我们主要通过声明字段和视图来构建我们的模块。在上一章中&#xff0c;我们刚刚通过计算字段和onchanges引入了业务逻辑。在任何真实的业务场景中&#xff0c;我们都会希望将一些业务逻辑链接到操作按钮。在我们的房地产示例中&#xff0c;我们希望…...

KBDPL.DLL文件丢失,软件游戏无法启动,修复方法

不少小伙伴&#xff0c;求助说遇到Windows弹窗提示“KBDPL.DLL文件丢失&#xff0c;应用无法启动的问题”&#xff0c;不知道应该怎么修复&#xff1f; 首先&#xff0c;先来了解“KBDPL.DLL文件”是什么&#xff1f; kbdpl.dll是Windows操作系统的一部分&#xff0c;是一个动…...

Webpack5 常用优化总结

本文主要总结经常用到的一些代码性能优化、减小代码体积、提升webpack打包构建速度等内容的方法。具体的实现可参考webpack官网查看相关示例。 注&#xff1a;如果读者还未接触过webpack&#xff0c;请先了解webpack的基本使用。 正文&#xff1a; SourceMap ---- 提升开发体…...

Oracle-视图与索引

视图 简介 视图是一种虚表 视图建立在已有表的基础上&#xff0c;视图赖以建立的的这些表成为基表 向视图提供的数据的内容的语句的select 语句&#xff0c;可以将视图理解为存储起来的select 语句 视图向用户提供基表数据的另外一种表现形式 视图的好处 控制数据访问 …...

在Linux写自己的第一个程序“hello Linux”

01.nano指令 我们在Windows中有很多的编译环境&#xff0c;大家应该都很熟悉&#xff0c;但是在Linux中&#xff0c;我们怎么写代码呢&#xff1f; 这里&#xff0c;我介绍一个非常简单的指令->nano 这个指令就类似于我们Windows中的记事本&#xff0c;使用方法也很简单 …...

【AI视野·今日Robot 机器人论文速览 第六十八期】Tue, 2 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Tue, 2 Jan 2024 Totally 12 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Edge Computing based Human-Robot Cognitive Fusion: A Medical Case Study in the Autism Spectrum Disorder Therapy Author…...

图像识别快速实现

文本的跑通了&#xff0c;接下来玩玩图片场景 1. 引入模型 再另起类test_qdrant_img.py&#xff0c;转化图片用到的模型和文本不太一样&#xff0c;我们这里使用ResNet-50模型 import unittest from qdrant_client.http.models import Distance, VectorParams from qdrant_cl…...

一文详解动态 Schema

在数据库中&#xff0c;Schema 常有&#xff0c;而动态 Schema 不常有。 例如&#xff0c;SQL 数据库有预定义的 Schema&#xff0c;但这些 Schema 通常都不能修改&#xff0c;用户只有在创建时才能定义 Schema。Schema 的作用是告诉数据库使用者所希望的表结构&#xff0c;确保…...

Web网页开发-总结笔记2

28.为什么会出现浮动&#xff1f;浮动会带来哪些问题&#xff1f; 1)为什么会出现浮动&#xff1a; 为了页面排版时块元素同行显示 2)浮动带来的问题&#xff1a; 父元素高度崩塌29.清除浮动的方法 (额外标签法、父级overflow、after伪元素、双伪元素&#xff09; &#xff08…...

C#的StringBuilder方法

一、StringBuilder方法 StringBuilder方法Append()向此实例追加指定对象的字符串表示形式。AppendFormat()向此实例追加通过处理复合格式字符串&#xff08;包含零个或更多格式项&#xff09;而返回的字符串。 每个格式项都由相应的对象自变量的字符串表示形式替换。AppendJoi…...

美格智能5G RedCap模组SRM813Q通过广东联通5G创新实验室测试认证

近日&#xff0c;美格智能5G RedCap轻量化模组SRM813Q正式通过广东联通5G创新实验室端到端的测试验收&#xff0c;获颁测评证书。美格智能已连续通过业内两家权威实验室的测试认证&#xff0c;充分验证SRM813Q系列模组已经具备了成熟的商用能力&#xff0c;将为智慧工业、安防监…...

MVCC 并发控制原理-源码解析(非常详细)

基础概念 并发事务带来的问题 1&#xff09;脏读&#xff1a;一个事务读取到另一个事务更新但还未提交的数据&#xff0c;如果另一个事务出现回滚或者进一步更新&#xff0c;则会出现问题。 2&#xff09;不可重复读&#xff1a;在一个事务中两次次读取同一个数据时&#xff0c…...

通过国家网络风险管理方法提供安全的网络环境

印度尼西亚通过讨论网络安全法草案启动了其战略举措。不过&#xff0c;政府和议会尚未就该法案的多项内容达成一致。另一方面&#xff0c;制定战略性、全面的网络安全方法的紧迫性从未像今天这样重要。 其政府官方网站遭受了多起网络攻击&#xff0c;引发了人们对国家网络安全…...

input中typedate的属性都有那些

自我扩展‘ type 中date属性 自我 控制编辑区域的 ::-webkit-datetime-edit { padding: 1px; background: url(…/selection.gif); }控制年月日这个区域的 ::-webkit-datetime-edit-fields-wrapper { background-color: #eee; }这是控制年月日之间的斜线或短横线的 ::-webki…...

将PPT4页并排成1页

将PPT4页并排成1页打印 解决方法: 方法一 在打印时选择&#xff1a; 打开 PPT&#xff0c;点击文件选项点击打印点击整页幻灯片点击4张水平放置的幻灯平页面就会显示4张PPT显示在一张纸上 方法二 另存为PDF&#xff1a; 打开电脑上的目标PPT文件&#xff0c;点击文件点击…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...