# go安装

goroot: go安装目录

gopath: go代码开发目录

gobin: gopath下面的bin目录

https://golang.google.cn/dl/

将下载的二进制包解压至/usr/local目录, go安装目录。

tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz

将 /usr/local/go/bin 目录添加至 PATH 环境变量:

vim ~/.bash_profile  
// or 
vim ~/.zshrc

export GOROOT="/usr/local/go"
export GOPATH=$HOME/go
export GOBIN=$GOROOT/bin
export PATH=$PATH:$GOBIN

重载配置使其生效

source ~/.bash_profile
// or 
source ~/.zshrc

# 环境初始化

1.设置代理,快速下载第三方包

go env -w GOPROXY=https://goproxy.cn,direct

2.开启go modules (设置为auto模式,项目中有.mod文件就代表开启,没有就不开启)

go env -w GO111MODULE=auto

# 初始化一个项目

go mod init orangbus.cn/spider/web

安装包

go get github.com/antchfx/htmlquery

# 时间

time.Now().Format("2006-01-02 15:04:05") // 当前格式2022-07-05 16:21:50

# goto

跳转到指定的行

# 指针

指针是存储另一个变量的内存地址的变量

# 申明指针

var var_name *var_type

var url *string

# 举例说明

// 1、定义一个int类型的变量
a := 10
fmt.Println("a的数值是:", a)     // 10
fmt.Printf("%T\n", a)        // int
fmt.Printf("a的地址是:%p\n", &a) // 0xc00001c0f8

// 2、创建一个指针变量,用来存储a的地址
var p1 *int
fmt.Println(p1)                           // nil 空指针
p1 = &a                                   // p1 指向了a的内存地址
fmt.Println("p1的数值是:", p1)                // p1中存储的是a的地址,跟变量a的地址是一样的
fmt.Printf("p1自己的地址:%p\n", &p1)           // 0xc00000e030
fmt.Println("p1的数值是a的地址,改地址存储的数据是:", *p1) // 10 a的值

//3、更改变量数值,并不会改变地址
a = 100
fmt.Println("a修改过后的值:", a)       // 100
fmt.Printf("更改a后,a的地址:%p\n", &a) // 0xc00001c0f8

// 4、通过执行,改变变量的数值
*p1 = 200
fmt.Println("p1修改过后,a的值:", a)       // 200
fmt.Println("p1修改过后的值:", p1)        // 0xc0000b8000
fmt.Println("p1修改之后地址存储的数据是:", *p1) // 200

// 5、指针的指针
var p2 **int
fmt.Println("p2的值:", p2) // nil
p2 = &p1
fmt.Printf("%T,%T,%T\n", a, p1, p2) // int,*int,**int
fmt.Println("p2的数值:", p2)           // p1的地址
fmt.Println("p2中存储的地址,对应的数值,就是p1的地址,对应的数据:", *p2) // 0xc00001c0f8
	fmt.Println("p2中存储的地址,对应的数值,再获得对应的数值:", **p2)     // 200 也就是a的值
a -> 10
&a -> a的地址

p1 -> a的地址
&p1 -> p1自己的地址
*p1 -> 获取执行存储的地址,对应的数值

p2 -> p1的地址
&p2 -> p2 自己的地址
*p2 -> 获取指针储存的地址,对应的数值。就是p1,实际上p1中存储的数值,就是a的地址

# 数组指针

首先是一个指针,一个数组的地址

*[2]Type

# 指针数组

首先是一个数组,存储的数据类型是指针

[2]*Type

案例

// 1、创建一个普通的数组
arr1 := [4]int{1, 2, 3, 4}
fmt.Println("数组的值:", arr1) //  [1 2 3 4]

// 2、创建一个执行,存储该数组的地址 ---> 数组执行
var p1 *[4]int
p1 = &arr1
fmt.Println("p1的值:", p1)      // &[1 2 3 4] 表示一个指针的数组
fmt.Printf("p1的地址:%p\n", &p1) // 数组 arr1 的地址

// 3、根据数组指针,操作数据
(*p1)[0] = 100
fmt.Println("修改过后的p1值", p1) //  &[100 2 3 4]
p1[1] = 200                 // 简化写法
fmt.Println(p1, arr1)       // &[100 200 3 4] [100 200 3 4]

// 4、指针数组
a := 1
b := 2
arr2 := [2]int{a, b}
arr3 := [2]*int{&a, &b}
fmt.Println(arr2) // [1 2]
fmt.Println(arr3) // [0xc00001c178 0xc00001c180]

arr2[0] = 100
fmt.Println(arr2) //[100 2]
fmt.Println(a)    // 1
fmt.Println()

*arr3[0] = 200
fmt.Println(arr3) // [0xc00001c178 0xc00001c180]
fmt.Println(a)    // 200

a = 150
fmt.Println(arr2) // [100,2]
for i := 0; i < len(arr3); i++ {
   fmt.Println(*arr3[i])
} // 150 2

指针不能做运算

// 交换两个变量的值
package main

import "testing"

func swap(a, b *int) {
	*b, *a = *a, *b
}

func swap2(a, b int) (int, int) {
	return b, a
}

func TestSwap(t *testing.T) {
	a, b := 3, 4
	swap(&a, &b)
	t.Log(a, b)

	c, d := 5, 6
	c, d = swap2(c, d)
	t.Log(c, d)
}

# 值传递: 拷贝一份数据传递

# 引用传递:传递一个地址

案例

package main

import "fmt"

func number01(number int) {
	fmt.Println("number01->number:", number) // 10
}

func number02(number *int) {
	*number = 20
	fmt.Println("number2->number:", *number) // 20
}

func main() {
	a, b := 10, 10
	number01(a)
	fmt.Println("number01:", a) // 10
	number02(&b)
	fmt.Println("number01:", b) // 20

}

# 函数指针

一个指针,指向一个函数的指针

# 指针函数

一个函数,该函数的返回值是一个指针

案例

package main

import "fmt"

func fun1() {
	fmt.Println("fun1 函数被调用了")
}

func fun2() *[3]int {
	arr := [3]int{1, 2, 3}
	return &arr
}

func main() {
	var a func()
	a = fun1
	a() // 调用函数

	arr := fun2()
	fmt.Println(arr)
}

# 数组-值类型

package main

import "testing"

func TestArray(t *testing.T) {
	var arr = [...]int{1, 2, 3, 4}
	t.Log(arr)

	for i := 0; i < len(arr); i++ {
		t.Log(arr[i])
	}
	t.Log("-----")
	for i, val := range arr {
		t.Log(i, val)
	}

	var grid [4][5]int // 四行五列
	t.Log(grid)
}

# slice-切片

package main

import "testing"

func TestSlice(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5, 6} // 定义一个数据
	arr2 := arr[3:]                   // 数组转slice
	arr2[0] = 100
	t.Log("arr2", arr2)

	// 添加
	s1 := append(arr2, 7)
	s2 := append(s1, 8)
	t.Log("s1:", s1)
	t.Log("s2:", s2)

	t.Log("arr:", arr)
}

func TestPrint(t *testing.T) {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
	s1 := arr[2:6]
	s2 := s1[3:5]
	t.Log(s1) // [2 3 4 5]
	t.Log(s2) // [5 6]
}

func TestCreate(t *testing.T) {
	var s []int
	for i := 0; i < 10; i++ {
		s = append(s, i)
	}
	t.Log("s", s)

	t.Log("=============")
	s1 := make([]int, 16)
	t.Log("s1", s1)

	t.Log("==== copy ====")
	arr2 := [...]int{11, 12, 13, 14, 15}
	copy(s, arr2[:])
	t.Log(s)
	t.Log(arr2)

	// 删除一个中间元素
	var s2 []int
	for i := 0; i < 20; i++ {
		s2 = append(s2, i)
	}
	t.Log("s2", s2, len(s2), cap(s2))
	res1 := append(s2[:9], s2[10:]...)
	t.Log("删除结果(9不见了):", res1, len(res1), cap(res1))

	// 删除头部
	heaer := s2[1:]
	t.Log("header", heaer, len(heaer), cap(heaer))
	end := s2[0 : len(s2)-1]
	t.Log("end", end, len(end), cap(end))
}

# map

package main

import (
	"fmt"
	"testing"
)

func TestMap(t *testing.T) {
	list := map[string]string{
		"name":  "orangbus",
		"age":   "18",
		"wight": "180",
	}
	//创建
	list2 := make(map[string]string)
	list2["name"] = "是我list2新增的"
	t.Log("list2", list2)

	// 获取
	name := list["name"]
	t.Log("获取name:", name)
	t.Log("list", list)
	t.Log(">> 添加")
	list["heght"] = "180"

	t.Log(">> 删除 age")
	delete(list, "age")
	fmt.Println(list)

	// 判断值存不存在
	if val, ok := list["age"]; ok {
		t.Log("存在age:", val)
	} else {
		t.Log("不存在age字段")
	}

	// 遍历
	for index, val := range list {
		t.Log(index, val)
	}
}

# 方法

package main

import "fmt"

// 1、 定义一个结构体
type Work struct {
	name string
	age  int
}

// 2、定义行为方法
func (w Work) run() {
	fmt.Println(w)
}

func main() {
	w := Work{
		name: "orangbus",
		age:  18,
	}
	w.run()
}

# 继承中的方法

package main

import (
	"fmt"
)

// 1、 定义一个父类
type Person struct {
	name string
	age  int
}

// 2、 定义一个子类
type Student struct {
	Person // 结构体嵌套,模拟继承性
	school string
}

// 3、方法
func (p Person) eat() {
	fmt.Println("父类的方法 -> eat")
}

func (s Student) study() {
	fmt.Println("学生学习了")
}

// 子类重写父类的方法
func (s Student) eat() {
	fmt.Println("子类重写父类的方法")
}

func main() {
	p := Person{
		name: "orangbus",
		age:  18,
	}
	fmt.Println(p)

	p.eat()

	// 创建子类对象
	s := Student{Person{name: "王二狗", age: 18}, "橙子工作室"}
	fmt.Println(s.name)
	fmt.Println(s.school)

	s.eat() // 子类访问父类的方法

	s.study()
}

# 接口

package main

import "fmt"

// 1、定义一个usb的接口
type USB interface {
	start()
	end()
}

// 2、实现类
type Mouse struct {
	name string
}

func (m Mouse) start() {
	fmt.Println(m.name, "鼠标准备就绪")
}

func (m Mouse) end() {
	fmt.Println(m.name, "鼠标停止")
}

// 测试方法
func testInterface(usb USB) {
	usb.start()
	usb.end()
}

func main() {
	m := Mouse{name: "鼠标"}
	testInterface(m)

	// 空接口的使用
	map1 := make(map[string]interface{})
	map1["name"] = "orangbus"
	map1["age"] = 25
	map1["user"] = Mouse{name: "鼠标"}
	fmt.Println(map1)

	slice1 := make([]interface{}, 0, 10)
	slice1 = append(slice1, "orangbus", Mouse{name: "鼠标"})
	fmt.Println(slice1)
}

# 接口嵌套

package main

import "fmt"

type A interface {
	test1()
}

type B interface {
	test2()
}

type C interface {
	A
	B
	test3()
}

type Cat struct {
}

func (c Cat) test1() {
	fmt.Println("test1")
}
func (c Cat) test2() {
	fmt.Println("test2")
}
func (c Cat) test3() { // 如果想实现接口C,那么需要实现AB接口
	fmt.Println("test3")
}

func main() {
	var cat Cat = Cat{}
	cat.test1()
	cat.test2()
	cat.test3()
	fmt.Println("--------")
	var a1 A = cat
	a1.test1()

	fmt.Println("--------")
	var b1 B = cat
	b1.test2()

	fmt.Println("----------")
	var c1 C = cat
	c1.test1()
	c1.test2()
	c1.test3()

	fmt.Println("----------")

	var c2 A = a1
	c2.test1()

}

# goroutine

# 临界资源

package main

import (
	"fmt"
	"math/rand"
	"time"
)

var ticket = 10

func saleTickets(name string) {
	for {
		if ticket > 0 {
			time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
			fmt.Println(name, "售出:", ticket)
			ticket--
		} else {
			fmt.Println(name, "没有票了")
			break // 结束循环
		}
	}
}

/**
临界资源
*/
func main() {
	go saleTickets("售票口1")
	go saleTickets("售票口2")
	go saleTickets("售票口3")
	go saleTickets("售票口4")
	time.Sleep(time.Second * 3)
}
// 出现了负数的情况
售票口3 售出: 10
售票口1 售出: 9
售票口3 售出: 8
售票口1 售出: 7
售票口3 售出: 6
售票口4 售出: 5
售票口2 售出: 4
售票口1 售出: 3
售票口3 售出: 2
售票口4 售出: 1
售票口4 没有票了
售票口3 售出: 0
售票口3 没有票了
售票口1 售出: -1
售票口1 没有票了
售票口2 售出: -2
售票口2 没有票了

# 解决临界资源

# 1、sync.waitGroup 等待同步组

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{} // 创建同步等待组

func saleTickets(name string) {
	for i := 0; i < 10; i++ {
		fmt.Println(name, "->", i)
	}
	// 结束
	wg.Done()
}

/**
临界资源
*/
func main() {
	wg.Add(2) // 添加到等待组
    
	go saleTickets("售票口1")
	go saleTickets("售票口2")
	fmt.Println("main函数进入阻塞,等待 goroutien 结束")
	wg.Wait()
	fmt.Println("main函数结束")
}

# 2、sync.Mutex 加锁

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

var ticket = 10

var wg = sync.WaitGroup{}
var mutex = sync.Mutex{} // 创建一个锁头

func saleTickets(name string) {
	defer wg.Done()
	for {
		mutex.Lock() // 上锁
		if ticket > 0 {
			time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
			fmt.Println(name, "售出:", ticket)
			ticket--
		} else {
			fmt.Println(name, "没有票了")
			mutex.Unlock() // 条件不满足也要解锁
			break          // 结束循环
		}
		mutex.Unlock() // 解锁
	}
}

/**
临界资源
*/
func main() {
	wg.Add(4)
	go saleTickets("售票口1")
	go saleTickets("售票口2")
	go saleTickets("售票口3")
	go saleTickets("售票口4")
	wg.Wait()
	fmt.Println("main 结束了")
}

# 读写锁

package main

import (
	"fmt"
	"sync"
	"time"
)

var rwMutex sync.RWMutex // 读写锁
var wg sync.WaitGroup    // 同步等待组

func readData(i int) {
	defer wg.Done()
	fmt.Println("开始读取数据", i)
	rwMutex.RLock() // 读操作上锁
	fmt.Println("正在读取数据。。。->", i)
	time.Sleep(time.Second * 1)
	rwMutex.RUnlock()
	fmt.Println(i, "数据读取完毕")
}

func writeData(i int) {
	defer wg.Done()
	fmt.Println("开始写->", i)
	rwMutex.Lock()
	fmt.Println("正在写数据->", i)
	time.Sleep(time.Second * 1)
	rwMutex.Unlock()
	fmt.Println(i, "->写结束")
}

func main() {
	//rwMutex = new(sync.RWMutex)
	//wg = new(sync.WaitGroup)

	//wg.Add(2)
	//go readData(1)
	//go readData(2)

	wg.Add(3)
	go writeData(1)
	go writeData(2)
	go writeData(3)

	wg.Wait()
	fmt.Println("main结束")
}

1、可以随便读,多个goroutine 同时读

2、写的时候,啥也不能干,不能读也不能写

# 通道

1、用户goroutine,传递消息的

2、通道,每个都有相关联的数据类型,nil chan 不能使用

3、使用通道传递数据:<-

chan <- data // 发送数据到通道,向同道中人写数据
data <- chan //从同道中人获取数据,向通道中读取数据

4、阻塞,

发送数据:chan <- data 是阻塞的,知道另外一条 goroutine 读取数据来解除阻塞

读取数据:data <- chan 也是阻塞的,知道另外一条 goroutine 写入数据解除阻塞。

5、channel本身是同步的,意味着同一时间,只能有一个goroutine来操作。

package main

import "fmt"

func main() {
	var ch1 chan bool
	ch1 = make(chan bool)

	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println("goroutine i:", i)
		}
		// 循环结束后,想通信中写数据,表示要结束了
		ch1 <- true
		fmt.Println("goroutine 循环结束")
	}()

	data := <-ch1
	fmt.Println("data: ", data)
	fmt.Println("main end ...")

}

# 关闭通道

close(chanel)
package main

import (
	"fmt"
	"time"
)

func seedData(ch chan int) {
	for i := 0; i < 10; i++ {
		ch <- i
	}
	close(ch)
}

func main() {
	ch1 := make(chan int)
	go seedData(ch1)

	// 读取通道的数据
	for {
		time.Sleep(time.Second)
		v, ok := <-ch1
		if !ok {
			fmt.Println("已读取所有数据", ok)
			break
		}
		fmt.Println("读取的数据是:", v, ok)
	}
}

package main

import (
	"fmt"
	"time"
)

func seedData(ch chan int) {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		ch <- i
	}
	close(ch)
}

func main() {
	ch1 := make(chan int)
	go seedData(ch1)

	// 读取通道的数据
	for v := range ch1 {
		fmt.Println("读取的数据是:", v)
	}
	fmt.Println("main end ...")
}

# 缓冲通道

非缓冲通道:make(chan T)

​ 一次发送,一次接受,都是阻塞的

缓冲通道: make(chann T , capacity)

​ 发送:缓冲取的数据满了,才会阻塞

​ 接受:缓冲器的的数据空了,才会阻塞

# jwt

# elasticsearch

# 数据转化

# json转struct

请求第三方接口返回的 json 数据(举个例子)

{
    code: 200
    msg: "ok",
    data:[
        { id:1,title: "golang入门"},
        { id:2,title: "golang入门到放弃"},
    ]
}
package main

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"testing"
)

var url = "https://demo.com"

// 定义一个列表结构
type classList struct {
	Type_id   string `json:"type_id"`
	Type_name string `json:"type_name"`
}

// json 字段 (注意:开头大写,否则转化失败)
type respData struct {
	Code      int         `json:"code"`
	Page      int         `json:"page"`
	Pagecount int         `json:"pagecount"`
	Limit     int         `json:"limit"`
	Total     int         `json:"total"`
	ClassList []classList `json:"class"` // 这是一个列表数据
}

func TestFuzz(t *testing.T) {
	resp, error := http.Get(url)
	if error != nil {
		t.Log(error)
	}
	defer resp.Body.Close()

    // 将字节转化为 json字符串
	body, _ := ioutil.ReadAll(resp.Body)
	t.Log(string(body))
    
    // 将json字符串转化为 struct
	item := respData{}
	if jsonErr := json.Unmarshal(body, &item); jsonErr == nil {
		t.Log(item)
	} else {
		t.Log(jsonErr)
	}
	for k, v := range item.ClassList {
		t.Log(k, ":", v)
	}
}

# struct 转 json

encoding/json
package main

import (
	"encoding/json"
	"fmt"
	"testing"
)

type user struct {
	Id   int    `json:"id"`
	Age  int    `json:"age"`
	Name string `json:"name"`
}

func TestStructToJson(t *testing.T) {
	user := user{
		Id:   1,
		Name: "orangbus",
		Age:  18,
	}
	fmt.Println(user) // {1 18 orangbus}

	//转化为json
	data, _ := json.Marshal(user)
	fmt.Println(string(data)) // {"id":1,"age":18,"name":"orangbus"}
}

# 字符串操作

# 截取

func TestStrSplice(t *testing.T) {
	var s = "https://v.qq.com/x/cover/mzc00200t1tvb7d/g0   04252hyh1.html"
	res := s[:16]
	t.Log(res) // https://v.qq.com
}

# 分隔-split

strings.split(str,",")

# 去除空格

func TestStrTrim(t *testing.T) {
	var s = "https://v.qq.com/x/cover/mzc00200t1tvb7d/g0   04252hyh1.html"
	res := strings.Replace(s, " ", "", -1)
	t.Log(res)
}

# 字符串拼接

next_url := fmt.Sprintf("%s?page=%d", url, i+1)

# 方法错误返回处理

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func getHtml(url string) (data string, error error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	// 读取内容,resp.Body() 获取到的是字节
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func main() {
	html, err := getHtml("https://imooc.com")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(html)
}

# Gorm

安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

案例

package main

import (
	"database/sql"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
	"time"
)

type MovieCate struct {
	Id        uint32    `json:"id"`
	ApiId     int       `json:"api_id"`
	TypeName  string    `json:"name"`
	TypeId    int       `json:"type_id"`
	Status    int       `json:"status"`
	Sort      int       `json:"sort"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

var drive = "mysql"
var database = "golang"
var host = "127.0.0.1"
var port = 3306
var username = "root"
var password = "root"

// 获取当前时间
func getTime() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// 全局数据库对象
var db = &gorm.DB{}

func init() {

	// 连接数据库
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, database)
	mysqlDb, err := sql.Open(drive, dsn)
	if err != nil {
		panic(err)
	}
	db, err = gorm.Open(mysql.New(mysql.Config{
		Conn: mysqlDb,
	}), &gorm.Config{
		// 日志配置
		Logger: logger.New(
			log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
			logger.Config{
				SlowThreshold:             time.Second, // 慢 SQL 阈值
				LogLevel:                  logger.Info, // 日志级别
				IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
				Colorful:                  false,       // 禁用彩色打印
			}),
	})
	if err != nil {
		panic("数据库连接失败")
	}
	fmt.Println("数据库连接成功!")
}

// 插入数据
func Create() {
	cate := MovieCate{
		ApiId:     1,
		TypeId:    1,
		TypeName:  "demo",
		Status:    1,
		Sort:      10,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	result := db.Create(&cate)
	fmt.Println(result)
}

// 批量插入
func BatchInsert() {
	var cateList = []MovieCate{}
	for i := 0; i < 10; i++ {
		cateList = append(cateList, MovieCate{
			ApiId:     i + 1,
			TypeId:    i + 1,
			TypeName:  "demo",
			Status:    1,
			Sort:      i,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		})
	}
	res := db.Create(&cateList)
	fmt.Println(res)
}

// 查询数据
func getDataById(id int) {
	//cate := new(MovieCate)
	//db.First(cate)
	//fmt.Println(cate)

	cate := db.Find(&MovieCate{}, id)
	fmt.Println(cate)
}

// 条件查询
func getCateByApiId(api_id int) {
	res := db.Where("api_id =?", api_id).Order("id desc").First(&MovieCate{})
	fmt.Println(res)
}

// 更新数据
func update() {
	var cate MovieCate
	//db.First(&cate, 723)
	//cate.TypeName = "我被更新了"
	//db.Save(&cate)

	db.Model(&cate).Where("id = ?", 724).Updates(MovieCate{
		TypeName: "我又被更新了",
		Status:   0,
	})
}

// 删除数据
func delete(id int) {
	db.Delete(&MovieCate{}, id)
}

func main() {
	delete(723)
}

# 爬虫案例

package main

import (
	"fmt"
	"github.com/antchfx/htmlquery"
	"time"
)

// 段子结构体
type Jokes struct {
	Id         int
	Cate_id    int
	Content    string
	Image_url  string
	Image_gif  string
	Status     int
	Like_count int
	Form       string
	Created_at string
	Updated_ed string
}

// 获取段子
func getJoke(url string, page int) {
	// 解析html
	doc, err := htmlquery.LoadURL(fmt.Sprintf("%s?page=%d", url, page))
	if err != nil {
		fmt.Println(err)
	}
	list := htmlquery.Find(doc, "//div[@class='one-cont']")

	var jokeList []Jokes
	for _, val := range list {
		a := htmlquery.FindOne(val, "//p/a")
		if a != nil {
			joke := Jokes{
				Content:    htmlquery.InnerText(a),
				Form:       url + htmlquery.SelectAttr(a, "href"),
				Created_at: time.Now().Format("2006-01-02 15:04:05"),
				Updated_ed: time.Now().Format("2006-01-02 15:04:05"),
			}
			jokeList = append(jokeList, joke)
		}
	}

	// 判断最后一页
	if len(list) == 0 {
		fmt.Println("最后一页了:", url)
		return
	}
	page += 1
	next_url := fmt.Sprintf("%s?page=%d", url, page)
	fmt.Println("next_url:", next_url)
	getJoke(url, page)
}

func main() {
	var url = "https://www.xiaohua.com"
	getJoke(url+"/duanzi", 1)
}

# 保存到数据库中-协程

package main

import (
	"database/sql"
	"fmt"
	"github.com/antchfx/htmlquery"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
	"sync"
	"time"
)

// 全局数据库对象
var db = &gorm.DB{}

// 数据库配置信息
var drive = "mysql"
var database = "golang"
var host = "127.0.0.1"
var port = 3306
var username = "root"
var password = "root"

// 协程
var wg sync.WaitGroup

func init() {

	// 连接数据库
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, database)
	mysqlDb, err := sql.Open(drive, dsn)
	if err != nil {
		panic(err)
	}
	db, err = gorm.Open(mysql.New(mysql.Config{
		Conn: mysqlDb,
	}), &gorm.Config{
		// 日志配置
		Logger: logger.New(
			log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
			logger.Config{
				SlowThreshold:             time.Second, // 慢 SQL 阈值
				LogLevel:                  logger.Info, // 日志级别
				IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
				Colorful:                  false,       // 禁用彩色打印
			}),
	})
	if err != nil {
		panic("数据库连接失败")
	}
	fmt.Println("数据库连接成功!")
}

// 段子结构体
type Jokes struct {
	Id        int       `json:"id"`
	CateId    int       `json:"cate_id"`
	Content   string    `json:"content"`
	ImageUrl  string    `json:"image_url"`
	ImageGif  string    `json:"image_gif"`
	Status    int       `json:"status"`
	LikeCount int       `json:"like_count"`
	Form      string    `json:"form"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAT time.Time `json:"updated_at"`
}

// 获取段子
func getJoke(url string, page int) {
	// 解析html
	doc, err := htmlquery.LoadURL(fmt.Sprintf("%s?page=%d", url, page))
	if err != nil {
		fmt.Println(err)
	}
	list := htmlquery.Find(doc, "//div[@class='one-cont']")

	var jokeList []Jokes
	for _, val := range list {
		a := htmlquery.FindOne(val, "//p/a")
		if a != nil {
			joke := Jokes{
				Content:   htmlquery.InnerText(a),
				Form:      url + htmlquery.SelectAttr(a, "href"),
				CreatedAt: time.Now(),
				UpdatedAT: time.Now(),
			}
			jokeList = append(jokeList, joke)
		}
	}

	// 保存到数据库中
	db.Create(&jokeList)

	// 判断最后一页
	if len(list) == 0 {
		fmt.Println("最后一页了:", url)
		return
	}
	page += 1
	next_url := fmt.Sprintf("%s?page=%d", url, page)
	fmt.Println("next_url:", next_url)
	//getJoke(url, page)
	// 协程结束
	wg.Done()
}

// 获取列表

// 保存数据库

func main() {
	var url = "https://www.xiaohua.com"
	for i := 1; i < 1073; i++ {
		wg.Add(1)
		go getJoke(url+"/duanzi", i)
	}
	wg.Wait() // 等待协程结束
	fmt.Println("爬取完成。。。")
}

# 保存到elasticsearch中