当前位置: 首页 > article >正文

A tour of Go - Web Crawler

Version1

UrlsStatue 使用普通map,不能保证并发安全,所以要加一个互斥锁

package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch 返回 URL 所指向页面的 body 内容,
	// 并将该页面上找到的所有 URL 放到一个切片中。
	Fetch(url string) (body string, urls []string, err error)
}

var url_statue = UrlsStatue{vis: make(map[string]bool)}

// Crawl 用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, wg *sync.WaitGroup) {
	// TODO: 并行地爬取 URL。
	// TODO: 不重复爬取页面。
	// 下面并没有实现上面两种情况:
	defer wg.Done()
	if depth <= 0 {
		return
	}

	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		flag := false

		url_statue.mu.Lock() // 加锁查看该网站是否遍历过
		if url_statue.vis[u] == false {
			flag = true
			url_statue.vis[u] = true
		}
		url_statue.mu.Unlock()

		if flag == true {
			wg.Add(1)
			go func(u string) {
				//fmt.Println(url)
				Crawl(u, depth-1, fetcher, wg)
			}(u)
		} // 没遍历过则递归遍历
	}
	return
}

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	start := "https://golang.org/"
	url_statue.vis[start] = true
	Crawl(start, 4, fetcher, &wg)
	wg.Wait()
}

// fakeFetcher 是待填充结果的 Fetcher。
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

// 创建记录每个url是否遍历过的结构体,里面加锁是为了让并发的线程互斥地使用map
type UrlsStatue struct {
	mu  sync.Mutex
	vis map[string]bool
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

Version2

使用sync.map,该结构体本身是并发安全的。

package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch 返回 URL 所指向页面的 body 内容,
	// 并将该页面上找到的所有 URL 放到一个切片中。
	Fetch(url string) (body string, urls []string, err error)
}

var url_statue = UrlsStatue{}

// Crawl 用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, wg *sync.WaitGroup) {
	// TODO: 并行地爬取 URL。
	// TODO: 不重复爬取页面。
	// 下面并没有实现上面两种情况:
	defer wg.Done()
	if depth <= 0 {
		return
	}

	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	for _, u := range urls {
		flag := false

		//url_statue.mu.Lock() // 加锁查看该网站是否遍历过
		if _, ok := url_statue.vis.Load(u); !ok {
			flag = true
			url_statue.vis.Store(u, true)
		}
		//url_statue.mu.Unlock()

		if flag == true { // 没遍历过则递归遍历
			wg.Add(1)
			go func(u string) {
				//fmt.Println(url)
				Crawl(u, depth-1, fetcher, wg)
			}(u)
		}
	}
	return
}

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	start := "https://golang.org/"
	url_statue.vis.Store(start, true)
	Crawl(start, 4, fetcher, &wg)
	wg.Wait()
}

// fakeFetcher 是待填充结果的 Fetcher。
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

type UrlsStatue struct {
	mu sync.Mutex
	//vis map[string]bool
	vis sync.Map
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
	"https://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"https://golang.org/pkg/",
			"https://golang.org/cmd/",
		},
	},
	"https://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"https://golang.org/",
			"https://golang.org/cmd/",
			"https://golang.org/pkg/fmt/",
			"https://golang.org/pkg/os/",
		},
	},
	"https://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
	"https://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"https://golang.org/",
			"https://golang.org/pkg/",
		},
	},
}

sync.Map对外暴露的方法

// 根据一个key读取值,返回值会返回对应的值和该值是否存在
func (m *Map) Load(key any) (value any, ok bool)

// 存储一个键值对
func (m *Map) Store(key, value any)

// 删除一个键值对
func (m *Map) Delete(key any)

// 如果该key已存在,就返回原有的值,否则将新的值存入并返回,当成功读取到值时,loaded为true,否则为false
func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)

// 删除一个键值对,并返回其原有的值,loaded的值取决于key是否存在
func (m *Map) LoadAndDelete(key any) (value any, loaded bool)

// 遍历Map,当f()返回false时,就会停止遍历
func (m *Map) Range(f func(key, value any) bool) 


http://www.kler.cn/a/394851.html

相关文章:

  • 轮转数组
  • 【JavaEE初阶 — 多线程】死锁的产生原因和解决方法
  • 计算机网络 (1)互联网的组成
  • Ps:OpenColorIO 设置
  • Dolby TrueHD和Dolby Digital Plus (E-AC-3)编码介绍
  • 文件夹被占用了无法删除怎么办?强制粉碎文件夹你可以这样操作
  • tomcat配合geoserver安装及使用
  • 【Elasticsearch】Elasticsearch集成Spring Boot
  • 7天用Go从零实现分布式缓存GeeCache(学习)(5)
  • 巧妙注入的奥秘:在 Spring 中优雅地使用 List 和 Map
  • 【SpringBoot】20 同步调用、异步调用、异步回调
  • 【学习】【HTML】块级元素,行内元素,行内块级元素
  • macos 搭建自己的git pages
  • awk是一种在 Linux 和 Unix 系统中非常强大且常用的文本处理工具
  • Linux( 权限+特殊权限 图片+大白话)
  • macOS系统下使用SQLark连接达梦数据库
  • flutter插件:录制系统播放的声音
  • Docker compose部署portainer
  • 【Python进阶】Python中的数据库交互:ORM技术与SQLAlchemy
  • 国产系统给在线的Word文件创建表格
  • unity3d————接口基础知识点
  • STM32 创建一个工程文件(寄存器、标准库)
  • Android在使用RecycylerView开发中,设置item单选效果,并且设置默认选中第一个
  • fastadmin多个表crud连表操作步骤
  • 《鸿蒙生态:开发者的机遇与挑战》
  • ddl/dml/dcl