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)}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/316737.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

进制的计算方法

目录 一、什么是进制 二、进制是干什么的 三、进制的计数规则 四、进制相互转换 五、用计算器计算进制 六、进制前缀 一、什么是进制 生活中进制无处不在&#xff0c;最常用的就是十进制。如果你没有接触过计算机或者是通信相关的知识&#xff0c;那么大概率你只知道十进…

09、docker 安装nacos并配置mysql存储配置信息

docker 安装nacos并配置mysql存储配置信息 1、docker启动nacos的各种方式2、Docker安装nacos3、MySQL中新建nacos的数据库4、挂载数据or配置目录5、运行 1、docker启动nacos的各种方式 内嵌derby数据源 docker run -d \ -e PREFER_HOST_MODEhostname \ -e SPRING_DATASOURCE_…

云原生容器编排问题盘点,总结分享年度使用Kubernetes的坑和陷阱

云原生容器编排问题盘点&#xff0c;总结分享年度使用Kubernetes的坑和陷阱 Kubernetes与云原生性能问题&#xff1a;忽略节点选择器导致调度效率低下问题排查和分析解决方案案例介绍 配置问题&#xff1a;应用服务端口与Service&#xff08;KubectlProxy&#xff09;控制的端口…

深度生成模型之GAN优化目标设计与改进 ->(个人学习记录笔记)

文章目录 深度生成模型之GAN优化目标设计与改进原始GAN优化目标的问题1. JS散度度量问题2. 梯度问题 优化目标的设计与改进1. 最小二乘损失GAN2. Energy-based GAN(EBGAN)3. Wasserstein GAN4. WGAN-GP5. Boundary Equilibrium GAN(BEGAN)6. Loss Sensitive GAN7. Relativeisti…

java servlet 学生管理系统myeclipse开发oracle数据库BS模式java编程网

一、源码特点 java servlet 学生管理系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助servletbeandao (mvc模式开发)&#xff0c;系统具有完整的源代码和数据库&#xff0c;开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Oracle 10g…

Java异常篇----第二篇

系列文章目录 文章目录 系列文章目录前言一、 Excption与Error包结构二、Thow与thorws区别三、Error与Exception区别?四、error和exception有什么区别前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女…

VUE 若依框架,当页面设置了keepAlive=true,v-if和v-hasPermi作用在统一个按钮上时v-hasPermi失效,出现按钮显示异常问题

当前列表页设置了缓存keepAlivetrue&#xff0c;同时&#xff0c;在同一个按钮上使用v-if判断数据状态、用v-hasPermi判断按钮权限 当v-if的数据状态改变&#xff0c;由 1 变成 2 的时候&#xff0c;后面的v-hasPermi判断失效 原因&#xff1a; 是因为一开始页面初始化时&#…

跟cherno手搓游戏引擎【1】:配置与入口点

环境配置&#xff1a; 编译环境&#xff1a;VS2019 创建两个项目&#xff1a; 设置Sandbox为启动项&#xff1a; 设置sandbox的配置属性-常规-输出目录\中间目录为如下&#xff1a; 预处理定义&#xff1a;为了配置一些只有windows才能用的函数。 设置YOTOEngin&#xff08;我…

图像的腐蚀与膨胀

图像的腐蚀与膨胀 设集合 B B B的反射为 B ^ \hat{B} B^&#xff0c;其定义如下 B ^ { w ∣ w − b , b ∈ B } \hat{B}\begin{Bmatrix}w|w-b,b\in B\end{Bmatrix} B^{w∣w−b,b∈B​} 设集合 B B B按照点 z ( z 1 , z 2 ) z(z_1,z_2) z(z1​,z2​)平移得到集合 ( B ) z (…

IDEA2023 最新版详细图文安装教程(安装+运行测试+汉化+背景图设置)

IDEA2023 最新版详细图文安装教程 名人说&#xff1a;工欲善其事&#xff0c;必先利其器。——《论语》 作者&#xff1a;Code_流苏(CSDN) o(‐&#xff3e;▽&#xff3e;‐)o很高兴你打开了这篇博客&#xff0c;跟着教程去一步步尝试安装吧。 目录 IDEA2023 最新版详细图文安…

【AIGC摄影构图prompt】与重不同的绘制效果,解构主义+优美连拍提示效果

提取关键词构图&#xff1a; 激进解构主义 在prompt中&#xff0c;激进解构主义的画面效果可能是一种颠覆传统和权威的视觉呈现。这种画面可能以一种极端或激烈的方式表达对现有社会结构和观念体系的批判和质疑。 具体来说&#xff0c;这种画面效果可能包括&#xff1a; 破…

ssrf之gopher协议的使用和配置,以及需要注意的细节

gopher协议 目录 gopher协议 &#xff08;1&#xff09;安装一个cn &#xff08;2&#xff09;使用Gopher协议发送一个请求&#xff0c;环境为&#xff1a;nc起一个监听&#xff0c;curl发送gopher请求 &#xff08;3&#xff09;使用curl发送http请求&#xff0c;命令为 …