当前位置: 首页 > 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;点击文件点击…...

iPhone 恢复出厂设置后如何恢复数据

如果您在 iPhone 上执行了恢复出厂设置&#xff0c;您会发现所有旧数据都被清除了。这对于清理混乱和提高设备性能非常有用&#xff0c;但如果您忘记保存重要文件&#xff0c;那就是坏消息了。 恢复出厂设置后可以恢复数据吗&#xff1f;是的&#xff01;幸运的是&#xff0c;…...

欧洲最好的AI大模型:Mistral 7B!(开源、全面超越Llama 2)

你可能已经听说过Meta&#xff08;原Facebook&#xff09;的Llama 2&#xff0c;这是一款拥有13亿参数的语言模型&#xff0c;能够生成文本、代码、图像等多种内容。 但是你知道吗&#xff0c;有一家法国的创业公司Mistral AI&#xff0c;推出了一款只有7.3亿参数的语言模型&am…...

Python | 诞生、解析器的分类版本及安装

1. python的诞生 Python是一门由Guido van Rossum&#xff08;龟叔&#xff09;于1991年创造的高级编程语言。 下图是TIOBE指数&#xff08;TIOBE Index&#xff09;的官方网站的截图&#xff0c;TIOBE指数是衡量编程语言流行度的指标之一&#xff0c;截止到目前python排名第…...

vim学习记录

目录 历史记录前言相关资料配置windows互换ESC和Caps Lock按键 基本操作替换字符串 历史记录 2024年1月2日, 搭建好框架,开始学习; 前言 vim使用很久了,但是都是一些基本用法,主要是用于配置Linux,进行一些简单的编写文档和程序.没有进行过大型程序开发,没有达到熟练使用的程…...

bat脚本:将ini文件两行值转json格式

原文件 .ini&#xff1a;目标转换第2行和第三行成下方json [info] listKeykey1^key2^key3 listNameA大^B最小^c最好 ccc1^2^3^ ddd0^1^9目标格式 生成同名json文件&#xff0c;并删除原ini文件 [ { "value":"key1", "text":"A大" …...

scratch绘制小正方形 2023年12月中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析

目录 scratch绘制小正方形 一、题目要求 1、准备工作 2、功能实现 二、案例分析...

【产品应用】一体化伺服电机在管道检测机器人中的应用

一体化伺服电机在管道检测机器人的应用正日益受到关注。管道检测机器人是一种能够在管道内部进行检测和维护的智能化设备&#xff0c;它可以检测管道的内部结构、泄漏、腐蚀等问题&#xff0c;以确保管道的安全和稳定运行。而一体化伺服电机作为机器人的动力源&#xff0c;对于…...

Django在urls.py利用函数path()配置路由时传递参数给调用的视图函数的方法

01-单个参数的传递 问&#xff1a;在urls.py利用函数path()配置路由时能不能传递一些参数给调用的视图函数&#xff1f;因为我有很多路由调用的其实是同一个视图函数&#xff0c;所以希望能传递一些额外的参数。比如路由的PATH信息如果能传递就好了。 答&#xff1a;在Django中…...

Ubuntu20 编译 Android 12源码

1.安装基础库 推荐使用 Ubuntu 20.04 及以上版本编译&#xff0c;会少不少麻烦&#xff0c;以下是我的虚拟机配置 执行命令安装依赖库 // 第一步执行 update sudo apt-get update//安装相关依赖sudo apt-get install -y libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-de…...

RFID传感器|识读器CNS-RFID-01/1S在AGV小车|搬运机器人领域的安装与配置方法

AGV 在运行时候需要根据预设地标点来执行指令&#xff0c;在需要 AGV 在路径线上位置执行某个指令时候&#xff0c;则需要在这个点设置 命令地标点&#xff0c;AGV 通过读取不同地标点编号信息&#xff0c;来执行规定的指令。读取地标点设备为寻址传感器&#xff0c;目前&#…...

网站左边logo图标怎么做/苏州网站维护

一、jQuery概述 宗旨: Write Less, Do More. 基础知识&#xff1a; 1.符号$代替document.getElementById()函数 2.使用CssXpath来查询页面元素 3.适当的混用jQuery、Dom和JavaScript能够提升程序的执行效率。 如&#xff1a;Offset、App…...

网站建设及推广方案/深圳做网站的公司有哪些

首发于我的博客网站(prajna.top)&#xff1a; http://prajna.top/doc/4/278 apk文件 apk实际上就是一个zip文件&#xff0c;可以直接使用zip解压&#xff0c;它包含 classes.dex, 资源文件&#xff0c;证书&#xff0c;动态链接库等。 classes.dex: 代码文件&#xff0c;包含…...

网站做乘法表/手机百度免费下载

在 Rss Bandit 和 SharpDevelop 之间的权衡 在 Rss Bandit 和 SharpDevelop 之间的权衡 &#xff08;心理变化过程&#xff0c;没有技术含量&#xff0c;只是胡诌&#xff0c;呵呵&#xff09; 距离上次的WebLog居然过了一个月有半&#xff0c;其实心理是一直希望可以经常写点…...

网页登陆微信可以下载wordpress/如何自己创建网址

有上线的Android应用&#xff08;需要有投入市场的产品&#xff0c;最好附带作品演示&#xff09;。 有安卓系统完整产品开发经验。 有流媒体、音视频Codec开发经验者优先&#xff08;媒体应用开发经验&#xff09;&#xff1b;熟悉Android多媒体(音频,视频)处理编程&#xff0…...

杭州市做外贸网站的公司/微信营销模式有哪些

本文整理自2017云栖大会-成都峰会上阿里云高级技术专家许玲的分享讲义。讲义主要分享了阿里云在智能客服方面给出的解决方案&#xff0c;并分享了其智能服务机器人“云博士”及其工作思路&#xff0c;并分享了关于阿里云旗下的智能对话分析服务的内容。...

怎么做网赌网站/网络营销的方式和方法

前言 Javascript是一门基于对象的动态语言&#xff0c;也就是说&#xff0c;所有东西都是对象&#xff0c;一个很典型的例子就是函数也被视为普通的对象。Javascript可以通过一定的设计模式来实现面向对象的编程&#xff0c;其中this “指针”就是实现面向对象的一个很重…...