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