Go分布式爬虫笔记(二十)_collect

最后

Python崛起并且风靡,因为优点多、应用领域广、被大牛们认可。学习 Python 门槛很低,但它的晋级路线很多,通过它你能进入机器学习、数据挖掘、大数据,CS等更加高级的领域。Python可以做网络应用,可以做科学计算,数据分析,可以做网络爬虫,可以做机器学习、自然语言处理、可以写游戏、可以做桌面应用…Python可以做的很多,你需要学好基础,再选择明确的方向。这里给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

👉Python所有方向的学习路线👈

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

👉Python必备开发工具👈

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

👉Python全套学习视频👈

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

👉实战案例👈

学python就与学数学一样,是不能只看书不做题的,直接看步骤和答案会让人误以为自己全都掌握了,但是碰到生题的时候还是会一筹莫展。

因此在学习python的过程中一定要记得多动手写代码,教程只需要看一两遍即可。

👉大厂面试真题👈

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// workerCh
var reqQueue = s.Seeds
go func() {
	for {
		var req \*collect.Request
		var ch chan \*collect.Request

		//如果任务队列 reqQueue 大于 0,意味着有爬虫任务,这时我们获取队列中第一个任务,并将其剔除出队列。

		if len(reqQueue) > 0 {
			req = reqQueue[0]
			reqQueue = reqQueue[1:]
			ch = s.workerCh
		}
		select {
		case r := <-s.requestCh:
			// 接收来自外界的请求,并将请求存储到 reqQueue 队列中
			reqQueue = append(reqQueue, r)

		case ch <- req:
			// ch <- req 会将任务发送到 workerCh 通道中,等待 worker 接收。
		}
	}
}()

}

func (s *ScheduleEngine) CreateWork() {
for {
// 接收到调度器分配的任务;
r := <-s.workerCh
// 访问服务器
body, err := s.Fetcher.Get®
if err != nil {
s.Logger.Error("can’t fetch ",
zap.Error(err),
)
continue
}
//解析服务器返回的数据
result := r.ParseFunc(body, r)
// 将返回的数据发送到 out 通道中,方便后续的处理。
s.out <- result
}
}

func (s *ScheduleEngine) HandleResult() {
for {
select {
// 接收所有 worker 解析后的数据
case result := <-s.out:
// 要进一步爬取的 Requests 列表将全部发送回 s.requestCh 通道
for _, req := range result.Requesrts {
s.requestCh <- req
}
//包含了我们实际希望得到的结果,所以我们先用日志把结果打印出来
for _, item := range result.Items {
// todo: store
s.Logger.Sugar().Info(“get result”, item)
}
}
}
}


main.go



package main

import (
“fmt”
“github.com/funbinary/go_example/example/crawler/13/collect”
“github.com/funbinary/go_example/example/crawler/13/engine”
“github.com/funbinary/go_example/example/crawler/13/log”
“github.com/funbinary/go_example/example/crawler/13/parse/doubangroup”
“github.com/funbinary/go_example/example/crawler/13/proxy”
“go.uber.org/zap/zapcore”

"time"

)

// xpath
func main() {
plugin := log.NewStdoutPlugin(zapcore.InfoLevel)
logger := log.NewLogger(plugin)
logger.Info(“log init end”)

proxyURLs := []string{"http://127.0.0.1:10809", "http://127.0.0.1:10809"}
p, err := proxy.RoundRobinProxySwitcher(proxyURLs...)
if err != nil {
	logger.Error("RoundRobinProxySwitcher failed")
}

// douban cookie
var seeds []\*collect.Request
for i := 0; i <= 0; i += 25 {
	str := fmt.Sprintf("https://www.douban.com/group/szsh/discussion?start=%d", i)
	seeds = append(seeds, &collect.Request{
		Url:       str,
		WaitTime:  1 \* time.Second,
		Cookie:    "bid=-UXUw--yL5g; dbcl2=\"214281202:q0BBm9YC2Yg\"; \_\_yadk\_uid=jigAbrEOKiwgbAaLUt0G3yPsvehXcvrs; push\_noty\_num=0; push\_doumail\_num=0; \_\_utmz=30149280.1665849857.1.1.utmcsr=accounts.douban.com|utmccn=(referral)|utmcmd=referral|utmcct=/; \_\_utmv=30149280.21428; ck=SAvm; \_pk\_ref.100001.8cb4=%5B%22%22%2C%22%22%2C1665925405%2C%22https%3A%2F%2Faccounts.douban.com%2F%22%5D; \_pk\_ses.100001.8cb4=\*; \_\_utma=30149280.2072705865.1665849857.1665849857.1665925407.2; \_\_utmc=30149280; \_\_utmt=1; \_\_utmb=30149280.23.5.1665925419338; \_pk\_id.100001.8cb4=fc1581490bf2b70c.1665849856.2.1665925421.1665849856.",
		ParseFunc: doubangroup.ParseURL,
	})
}

var f collect.Fetcher = &collect.BrowserFetch{
	Timeout: 3000 \* time.Millisecond,
	Logger:  logger,
	Proxy:   p,
}

s := engine.ScheduleEngine{
	WorkCount: 5,
	Logger:    logger,
	Fetcher:   f,
	Seeds:     seeds,
}
s.Run()

}


## 通道


特性


* 我们往 nil 通道中写入数据会陷入到堵塞的状态。因此,如果 reqQueue 为空,这时 req 和 ch 都是 nil,当前协程就会陷入到堵塞的状态,直到接收到新的请求才会被唤醒。

 

package main

import (
“fmt”
)

func main() {
var ch chan *int
go func() {
<-ch
}()
select {
case ch <- nil:
fmt.Println(“it’s time”)
}

}
//fatal error: all goroutines are asleep - deadlock!



## 函数选项模式


* 原由: 在实践中函数可能有几十个参数等着我们赋值,不同参数的灵活组合可能会带来不同的调度器类型。在实践中为了方便使用,开发者可能会创建非常多的 API 来满足不同场景的需要,随着参数的不断增多,这种 API 会变得越来越多,这就增加了开发者的心理负担。

 

// 基本调度器
func NewBaseSchedule() *Schedule {
return &Schedule{
WorkCount: 1,
Fetcher:baseFetch,
}
}
// 多worker调度器
func NewMultiWorkSchedule(workCount int) *Schedule {
return &Schedule{
WorkCount: workCount,
Fetcher:baseFetch,
}
}

// 代理调度器
func NewProxySchedule(proxy string) *Schedule {
return &Schedule{
WorkCount: 1,
Fetcher:proxyFetch(proxy),
}
}

* 另一种使用方式就是传递一个统一的 Config 配置结构,如下所示。这种方式只需要创建单个 API,但是需要在内部对所有的变量进行判断,繁琐且不优雅。对于使用者来说,也很难确定自己需要使用哪一个字段。

 

type Config struct {
WorkCount int
Fetcher collect.Fetcher
Logger *zap.Logger
Seeds []*collect.Request
}

func NewSchedule(c *Config) *Schedule {
var s = &Schedule{}
if c.Seeds != nil {
s.Seeds = c.Seeds
}
if c.Fetcher != nil {
s.Fetcher = c.Fetcher
}

if c.Logger != nil {
s.Logger = c.Logger
}

return s
}

* Rob Pike 在 2014 年的[一篇博客](https://bbs.csdn.net/topics/618317507)中提到了一种优雅的处理方法叫做函数式选项模式 (Functional Options)。这种模式展示了闭包函数的有趣用途,目前在很多开源库中都能看到它的身影,我们项目中使用的日志库 Zap 也使用了这种模式。


1. 我们要对 schedule 结构进行改造,把可以配置的参数放入到options 结构中:



type Schedule struct {
requestCh chan *collect.Request
workerCh chan *collect.Request
out chan collect.ParseResult
options
}

type options struct {
WorkCount int
Fetcher collect.Fetcher
Logger *zap.Logger
Seeds []*collect.Request
}


2. 我们需要书写一系列的闭包函数,这些函数的返回值是一个参数为 options 的函数:



type Option func(opts *options)

func WithLogger(logger *zap.Logger) Option {
return func(opts *options) {
opts.Logger = logger
}
}
func WithFetcher(fetcher collect.Fetcher) Option {
return func(opts *options) {
opts.Fetcher = fetcher
}
}

func WithWorkCount(workCount int) Option {
return func(opts *options) {
opts.WorkCount = workCount
}
}

func WithSeeds(seed []*collect.Request) Option {
return func(opts *options) {
opts.Seeds = seed
}
}


3. 创建一个生成 schedule 的新函数,函数参数为 Option 的可变参数列表。defaultOptions 为默认的 Option,代表默认的参数列表,然后循环遍历可变函数参数列表并执行。



func NewSchedule(opts …Option) *Schedule {
options := defaultOptions
for _, opt := range opts {
opt(&options)
}
s := &Schedule{}
s.options = options
return s
}


4. 在 main 函数中调用 NewSchedule。让我们来看看函数式选项模式的效果:



func main(){
s := engine.NewSchedule(
engine.WithFetcher(f),
engine.WithLogger(logger),
engine.WithWorkCount(5),
engine.WithSeeds(seeds),
)
s.Run()
}


### 函数式选项模式的好处


* API 具有可扩展性,高度可配置化,新增参数不会破坏现有代码;
* 参数列表非常简洁,并且可以使用默认的参数;
* option 函数使参数的含义非常清晰,易于开发者理解和使用;
* 如果将 options 结构中的参数设置为小写,还可以限制这些参数的权限,防止这些参数在 package 外部使用。


## 通道底层原理


通道的实现并没有想象中复杂。它利用互斥锁实现了并发安全,只不过 Go 运行时为我们屏蔽了底层的细节。通道包括两种类型,一种是无缓冲的通道,另一种是带缓冲区的通道。通道的结构如下:


​![image](https://img-blog.csdnimg.cn/img_convert/ff3e26ecb75af9e29a20d37d42802020.png)​


可以看到,通道中包含了数据的类型、大小、数量,堵塞协程队列,以及用于缓存区的诸多字段。


### 无缓冲区的通道


通道需要有多个协程分别完成读和写的功能,这样才能保证数据传输是顺畅的。对于无缓冲区的通道来说,如果有一个协程正在将数据写入通道,但是当前没有协程读取数据,那么写入协程将立即陷入到休眠状态。写入协程堵塞之前协程会被封装到 sudog 结构中,并存储到写入的堵塞队列 sendq 中,之后协程陷入休眠。


之前我们介绍过,协程的堵塞是位于用户态的,协程切换时,运行时会保存当前协程的状态、并调用 gopark 函数切换到 g0 完成新一轮的调度。如果之后有协程读取数据,那么读取协程会立即读取 sendq 队列中第一个等待的协程,并将该协程对应的元素拷贝到读取协程中,同时调用 goready 唤醒写入协程,将写入协程放入到可运行队列中等待被调度器调度。


​![image](https://img-blog.csdnimg.cn/img_convert/446afcb0572bc2e8791d460d652e117d.png)​


### 带缓冲区的通道



**一、Python所有方向的学习路线**

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

![img](https://img-blog.csdnimg.cn/1d40facda2b84990b8e1743f5487d455.png)  
![img](https://img-blog.csdnimg.cn/0fc11d4a31bd431dbf124f67f1749046.png)

**二、Python必备开发工具**

工具都帮大家整理好了,安装就可直接上手!![img](https://img-blog.csdnimg.cn/ff266f529c6a46c4bc28e5f895dec647.gif#pic_center)

**三、最新Python学习笔记**

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

![img](https://img-blog.csdnimg.cn/6d414e9f494742db8bcc3fa312200539.png)

**四、Python视频合集**

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

![img](https://img-blog.csdnimg.cn/a806d9b941c645858c61d161aec43789.png)

**五、实战案例**

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。![img](https://img-blog.csdnimg.cn/a353983317b14d3c8856824a0d6186c1.png)

**六、面试宝典**

![在这里插入图片描述](https://img-blog.csdnimg.cn/97c454a3e5b4439b8600b50011cc8fe4.png)

![在这里插入图片描述](https://img-blog.csdnimg.cn/111f5462e7df433b981dc2430bb9ad39.png)

###### **简历模板**![在这里插入图片描述](https://img-blog.csdnimg.cn/646863996ac44da8af500c049bb72fbd.png#pic_center)




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618317507)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值