Go资深工程师讲解(慕课) 005

005 标准库

http

  • 使用 http 客户端发送请求
  • 使用 http.Client 控制请求头
  • 使用 httputil 简化工作
package main

import (
    "fmt"
    "net/http"
    "net/http/httputil"
)

func main() {
    resp, err := http.Get("https://www.baidu.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    s, err := httputil.DumpResponse(resp, true)
    if err!=nil{
        panic(err)
    }
    fmt.Printf("%s\n",s)
}

设置手机浏览 set user-agent

package main

import (
    "fmt"
    "net/http"
    "net/http/httputil"
)

func main() {
    request, err := http.NewRequest(http.MethodGet, "https://www.baidu.com", nil)
    request.Header.Add("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Mobile Safari/537.36")

    resp, err := http.DefaultClient.Do(request) //http.Get("https://www.baidu.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    s, err := httputil.DumpResponse(resp, true)
    if err != nil {
        panic(err)
    }
      fmt.Printf("%s\n", s)
}

go tool pprof http://localhost:8888/debug/pprof/profile
或是引一个包_ "net/http/pprof"启动 web 服务 在地址栏里输入http://localhost:8888/debug/pprof

其它标准库

  • bufio
  • log
  • encoding/json
  • regexp
  • time (channel 用法)
  • strings/math/rand

看标准库的文档
godoc -http :8888 本地启一个服务
https://studygolang.com/pkgdoc

迷宫算法

广度优先算法(应用广泛,综合性强)
已经发现还没探索,放在队列中,探索至终,然后倒着走就最短路径

guangdu

package main

import (
      "fmt"
      "os"
)

//返回二维数组
func readMaze(filename string) [][]int {
      file, err := os.Open(filename)
      if err != nil {
            panic(err)
      }
      defer file.Close()
      var row, col int
      fmt.Fscanf(file, "%d %d", &row, &col)
      maze := make([][]int, row) // row行
      for i := range maze {
            maze[i] = make([]int, col)
            for j := range maze[i] {
                  fmt.Fscanf(file, "%d", &maze[i][j])
            }
      }
      return maze
}

type point struct {
      i, j int
}

func (p point) add(r point) point {
      return point{p.i + r.i, p.j + r.j}
}

func (p point) at(grid [][]int) (int, bool) { //bool代表是否越界
      if p.i < 0 || p.i >= len(grid) {
            return 0, false
      }
      if p.j < 0 || p.j >= len(grid[p.i]) {
            return 0, false
      }
      return grid[p.i][p.j], true
}

//四个方向 上左下右
var dirs = [4]point{
      {-1, 0},
      {0, -1},
      {1, 0},
      {0, 1},
}

func walk(maze [][]int, start, end point) [][]int {
      steps := make([][]int, len(maze))
      for i := range steps {
            steps[i] = make([]int, len(maze[i]))
      }
      //   队列
      Q := []point{start}
      //队列不空才去探索
      for len(Q) > 0 {
            cur := Q[0]
            Q = Q[1:]
            if (cur == end) {
                  break
            }
            for _, dir := range dirs {
                  next := cur.add(dir)
                  //      maze at next is 0
                  // and steps at next is 0 曾经到过
                  //      and next !=start
                  val, ok := next.at(maze)
                  if !ok || val == 1 {
                        continue
                  }
                  val, ok = next.at(steps)
                  if !ok || val != 0 { //走过了
                        continue
                  }
                  if next == start {
                        continue
                  }
                  //      steps 填进去
                  curSteps, _ := cur.at(steps)
                  steps[next.i][next.j] = curSteps + 1
                  Q = append(Q, next)
            }

      }
      return steps

}

func main() {
      maze := readMaze("maze/maze.in")
      for _, row := range maze {
            for _, val := range row {
                  fmt.Printf("%3d ", val)
            }
            fmt.Println()
      }
      fmt.Println()
      steps := walk(maze, point{0, 0}, point{len(maze) - 1, len(maze[0]) - 1})
      for _, row := range steps {
            for _, val := range row {
                  fmt.Printf("%3d", val)
            }
            fmt.Println()
      }
}
// 途经的点可以倒序遍历一下

简单爬虫

  • 通用爬虫 如 baidu,google
  • 聚焦爬虫 从互联网获取结构化数据
  • go 语言的爬虫库/框架
  • henrylee2cn/pholcus
  • gocrawl
  • colly
  • hu17889/go_spider

技术选型,爬虫的主题(如新闻,博客,社区,我们爬人,QQ 人人,微博,facebook,相亲网站,求职网站)

  • ElasticSearch 作为数据存储
  • Go 语言标准模板库实现 http 数据展示部分

单任务版爬虫

获取并打印所有城市的第一页用户的详细信息
转码 go get -g -v golang.org/x/text
自动识别网页的编码 go get -g -v golang.org/x/net/html

package main

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

    "golang.org/x/net/html/charset"
    "golang.org/x/text/encoding"
    "golang.org/x/text/transform"
)

func main() {
    //所有城市第一页用户
    resp, err := http.Get("http://www.zhenai.com/zhenghun")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        fmt.Println("Error: ", resp.StatusCode)
        //panic("Status Code:")
        return
    }
    //自动转码
    e := determineEncoding(resp.Body)
    utf8Reader := transform.NewReader(resp.Body, e.NewDecoder())
    all, err := ioutil.ReadAll(utf8Reader)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", all)

}

func determineEncoding(r io.Reader) encoding.Encoding {
    bytes, err := bufio.NewReader(r).Peek(1024)
    if err != nil {
        panic(err)
    }
    e, _, _ := charset.DetermineEncoding(bytes, "")
    return e
}

正则表达式

package main

import (
    "fmt"
    "regexp"
)

const text = "My email is ccmouse@gmail.com"

func main() {
    //re := regexp.MustCompile("ccmouse@gmail.com")
    //用反引号,正则的特殊符号不用再转意了
    re := regexp.MustCompile(`[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+`)
    match := re.FindString(text)
    fmt.Println(match)
}

读取 json 文件并 parse 成对应的对象

package main

import (
    "encoding/json"
    "fmt"
    "os"

)

type CityObj struct {
    LinkContent string `json:"linkContent"`
    LinkURL string
}

type CityGroup struct {
    CityList []CityObj
    Order string
}

type RtnData struct {
    CityData []CityGroup
}

func main() {
    path,_:=os.Getwd()
    fmt.Println(path)
    //var jObj interface{}
    var jObj RtnData
    file, err := os.Open("./cities.json")
    if err != nil {
        panic(err)
    }
    //file stat
    fi, _ := file.Stat()
    buffer := make([]byte, fi.Size())
    _, err = file.Read(buffer)
    if err != nil {
        panic(err)
    }
    err = json.Unmarshal(buffer, &jObj)
    fmt.Println(jObj.CityData[0].CityList[0])
}

通过正则来爬取数据

package main

import (
    "bufio"
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "regexp"

    "golang.org/x/net/html/charset"
    "golang.org/x/text/encoding"
    "golang.org/x/text/encoding/unicode"
    "golang.org/x/text/transform"
)

func main() {
    //所有城市第一页用户
    url := "http://www.zhenai.com/zhenghun"
    resp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        fmt.Println("Error: ", resp.StatusCode)
        panic("Status Code:")

    }
    //自动转码
    e := determineEncoding(resp.Body)
    utf8Reader := transform.NewReader(resp.Body, e.NewDecoder())
    all, err := ioutil.ReadAll(utf8Reader)
    if err != nil {
        //panic(err)
        fmt.Printf("%v\n", err)
        return
    }

    //fmt.Printf("%s\n", all)
    printCityList(all)

}

func determineEncoding(r io.Reader) encoding.Encoding {
    bytes, err := bufio.NewReader(r).Peek(1024)
    if err != nil {
        //panic(err)
        return unicode.UTF8
    }
    e, _, _ := charset.DetermineEncoding(bytes, "")
    return e
}

func printCityList(contents []byte) {
    //re := regexp.MustCompile(`<a target="_blank" href="http://www.zhenai.com/zhenghun/[0-9a-z]+"[^>]*>[^<]+</a>`)
    re := regexp.MustCompile(`<a href="http://www.zhenai.com/zhenghun/[0-9a-z]+"[^>]*>[^<]+</a>`) // 470个城市
    matches := re.FindAll(contents, -1)
    for _,m:=range matches{
        fmt.Printf("%s\n",m)
    }
    fmt.Printf("Matches found: %d\n",len(matches))
}

single_task

爬取数据时需要注意,请求头的设置

  1. 声明 http.Clien{}
  2. 声明 request,err:=http.NewRequest("GET",url,nil)
  3. request.Header.Add
  4. resp, err := client.Do(request)请求替换http.Get(url)
  5. defer resp.Body.Close()

到此单任务版的 查看engin fetcher model zhenai 处个目录的内容

并发版

Fetcher 的输出就是 Parser 的输入,可以抽成一个模块

bingfa_worker.jpg

Scheduler 实现 1:所有 Worker 公用一个输入

见分支simplechen

实现 2 有两个队列 request 队列 和 worker 队列

bingfa_worker_v2

把 request 放到 request 的队列中,为每一个 request 创建一个 go routine,然后让所有的 workder 抢一个 chanel。

其它页面

重复率很高,抓取一个城市,比如上海

e.Run(engine.Request{
    Url:"http://www.zhenai.com/zhenghun/shanghai",
    ParserFunc: parser.ParseCity,
})

url 去重

  • 哈希表(真接存储,占空间--本课使用)
  • 计算 md5 等哈希,再存哈希表
  • 使用 bloom filter 多重哈希结构
  • 使用 redis 等 key-value 存储系统实现分布式去重

profile 的保存

抽象出 Task 的概念 FetchTask,PersistTask 共用一个 Engine,Scheduler,需要创建 FetchWorker,PersistWorker;本项目中显得过重。为每个 Item 创建 goroutine,提交给 ItemSaver;ItemSaver 的速度比 Fetcher 快,类似 SimpleScheduler 的方法即可,我们将采用这种方法。

saver

elastic 与 docker

elastic 全文搜索引擎
本教程使用 docker 集成 elastic install doc

docker pull docker.elastic.co/elasticsearch/elasticsearch:6.7.2

 docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 -v /home/soft/ES/config/es1.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /home/soft/ES/data1:/usr/share/elasticsearch/data --name ES01 elasticsearch:5.6.8

 docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9201:9201 -p 9301:9301 -v /home/soft/ES/config/es2.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /home/soft/ES/data2:/usr/share/elasticsearch/data --name ES02 elasticsearch:5.6.8

 docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9202:9202 -p 9302:9302 -v /home/soft/ES/config/es3.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /home/soft/ES/data3:/usr/share/elasticsearch/data --name ES03 elasticsearch:5.6.8

#   本例运行如下
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 -v /Users/willzhao/Documents/Develop/docker/elasticsearch/6.8/data:/usr/share/elasticsearch/data --name ES_6.8 elasticsearch:6.8.0
# http://localhost:9200/ 可以访问

比如我们创建 course 库
添加记录我们可以用 put 或 post course/1 course/2 添加 json 数据,可以用 get 获取全部数据,或用 get 加参数course/_search?,
不加 id 要用 post

  • <server>:9200/index/type/id
  • index 相当于 database
  • type 相当于 table
  • 我们不需要预先创建 index 和 type (IK 插件 有时间了解一下)
  • 使用 REST 接口
  • PUT/POST 创建/修改数据,使用 post 可以省略 id
  • GET 获取数据
  • GET <index>/<type>/_search?q='参数'

本课中的 save()使用elasticsearch client
go 社区版
https://gopkg.in/olivere/elastic.v6

elastic_client.jpg

# 先安装v6(我认为是基础
go get -v -u github.com/olivere/elastic
# 引这个的包 本例中用的是v6 在查询全
# 这个是v6的client https://olivere.github.io/elastic/ 请仔细阅读这个文档
go get gopkg.in/olivere/elastic.v6

html/template

分布式系统

  • 多个结点
  • 容错
  • 可扩展性(性能)
  • 固有分布性
  • 消息传递
  • 节点具有私有存储
  • 易于开发
  • 可扩展性(功能)
  • 对比:并行计算
  • 消息传递的方法
    • RESt
    • RPC
    • 中间件(messageQ)
  • 使用场景
    • 对外:Rest
    • 模块内部:RPC
    • 模块之间:中间件,REST
  • 完成特定需求

分布式架构 vs 微服务架构

分布式:指导节点之间如何通信
微服务:鼓励按业务划分模块

多层架构 vs 微服务架构

微服务架构具有更多的“服务”
微服务通常需要配合自动化测试,部署,服务发现

分部式爬虫

  • 限流问题
  • 单节点能够承受的流量有限
  • 将 worker 放到不同的节点
  • 去重问题
  • 分布式去重将去重放到 worker 上
  • 数据存储问题

jsonRPC

package rpcdemo

import "errors"

// Service.Method

type DemoService struct {}

type Args struct {
    A,B int
}

func (DemoService) Dive(args Args,result *float64) error  {
    if args.B==0 {
        return errors.New("division by zero.")
    }
     *result = float64(args.A)/float64(args.B)
     return nil
}
// server
package main

import (
    rpcdemo "gobasic/rpc"
    "log"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

func main() {
    rpc.Register(rpcdemo.DemoService{})

    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        panic(err)
    }
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Printf("accept error: %v", err)
            continue
        }

        go jsonrpc.ServeConn(conn)
    }
}

可以通过 telnet localhost 1234 来测试一下,发 json 数据如下

{"method":"DemoService.Dive","params":[{"A":3,"B":4}],"id":1}
// 返回结果
{"id":1,"result":0.75,"error":null}

创建一个 client 来调用

// client/main.go
package main

import (
    "fmt"
    rpcdemo "gobasic/rpc"
    "net"
    "net/rpc/jsonrpc"
)

func main() {
    conn, err := net.Dial("tcp", ":1234")

    if err != nil {
        panic(err)
    }
    client := jsonrpc.NewClient(conn)
    var result float64
    err = client.Call("DemoService.Dive", rpcdemo.Args{10, 3}, &result)
    fmt.Println(result, err)

    err = client.Call("DemoService.Dive", rpcdemo.Args{10, 0}, &result)
    fmt.Println(result, err)
}

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6742

(0)
Walker的头像Walker
上一篇 12小时前
下一篇 2025年11月25日 12:00

相关推荐

  • 编程基础 0013_Go企业实践案例精华

    Go 企业实践案例精华 知识来源:基于以下电子书资料整理- 《Go在百度BFE的应用 for Gopher China》- 《Go在分布式数据库中的应用》- 《Go在猎豹移动的应用》- 《Golang与高性能DSP竞价系统》- 《Go at Google: Language Design in the Service of Software Engineer…

    后端开发 20小时前
    100
  • Go工程师体系课 001

    转型 想在短时间系统转到Go工程理由 提高CRUD,无自研框架经验 拔高技术深度,做专、做精需求的同学 进阶工程化,拥有良好开发规范和管理能力的 工程化的重要性 高级开的期望 良好的代码规范 深入底层原理 熟悉架构 熟悉k8s的基础架构 扩展知识广度,知识的深度,规范的开发体系 四个大的阶段 go语言基础 微服务开发的(电商项目实战) 自研微服务 自研然后重…

  • Go工程师体系课 004

    需求分析 后台管理系统 商品管理 商品列表 商品分类 品牌管理 品牌分类 订单管理 订单列表 用户信息管理 用户列表 用户地址 用户留言 轮播图管理 电商系统 登录页面 首页 商品搜索 商品分类导航 轮播图展示 推荐商品展示 商品详情页 商品图片展示 商品描述 商品规格选择 加入购物车 购物车 商品列表 数量调整 删除商品 结算功能 用户中心 订单中心 我的…

    1天前
    100
  • Go工程师体系课 007

    商品微服务 实体结构说明 本模块包含以下核心实体: 商品(Goods) 商品分类(Category) 品牌(Brands) 轮播图(Banner) 品牌分类(GoodsCategoryBrand) 1. 商品(Goods) 描述平台中实际展示和销售的商品信息。 字段说明 字段名 类型 说明 name String 商品名称,必填 brand Pointer …

  • Go工程师体系课 002

    GOPATH 与 Go Modules 的区别 1. 概念 GOPATH 是 Go 的早期依赖管理机制。 所有的 Go 项目和依赖包必须放在 GOPATH 目录中(默认是 ~/go)。 一定要设置 GO111MODULE=off 项目路径必须按照 src/包名 的结构组织。 不支持版本控制,依赖管理需要手动处理(例如 go get)。 查找依赖包的顺序是 g…

    1天前
    100
简体中文 繁体中文 English