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
迷宮算法
廣度優先算法(應用廣泛,綜合性強)
已經發現還沒探索,放在隊列中,探索至終,然後倒著走就最短路徑
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))
}
爬取數據時需要注意,請求頭的設置
- 聲明 http.Clien{}
- 聲明
request,err:=http.NewRequest("GET",url,nil) request.Header.Add- 用
resp, err := client.Do(request)請求替換http.Get(url) defer resp.Body.Close()
到此單任務版的 查看
enginfetchermodelzhenai處個目錄的內容
併發版
Fetcher 的輸出就是 Parser 的輸入,可以抽成一個模塊
Scheduler 實現 1:所有 Worker 公用一個輸入
見分支simplechen
實現 2 有兩個隊列 request 隊列 和 worker 隊列
把 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 的方法即可,我們將採用這種方法。
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 或 postcourse/1course/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
# 先安裝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
