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
