Go语言网络爬虫内部基础接口

首先要做的是,先为组件通用功能定义一个内部接口,这里把它叫做组件的内部基础接口。内部基础接口及其实现类型存放在了代码包 gopcp.v2/chapter6/webcrawler/module/stub 中,代码包可以在我的网盘中下载(链接:https://pan.baidu.com/s/1yzWHnK1t2jLDIcTPFMLPCA 提取码:slm5)。

该接口内嵌了之前讲过的 Module 接口,其声明如下:
//组件的内部基础接口的类型
type ModuleInternal interface {
    module.Module
    //把调用计数增 1
    IncrCalledCount()
    //把接受计数增1
    IncrAcceptedCount()
    //把成功完成计数增 1
    IncrCompletedCount()
    //把实时处理数增 1
    IncrHandlingNumber()
    //把实时处理数减 1
    DecrHandlingNumber()
    //用于清空所有计数
    Clear()
}
Module 接口中声明的更多的是获取内部状态的方法,比如:获取组件 ID、组件地址、各种计数值,等等。而在 ModuleInternal 接口中,添加的方法都是改变内部状态的方法。

由于通常情况下外部不应该宜接改变组件的内部状态,所以该接口的名字才以 "Internal" 为后缀,以起到提示的作用。并且,在 gopcp.v2/chapter6/webcrawler/module 包中公开的程序实体并没有涉及该接口。

ModuleInternal 接口及其实现类型只是为了方便自行编写组件的人而准备的。在编写组件时也用到了它们。

ModuleInternal 接口是 Module 接口的扩展,前者的实现类型自然也是后者的实现类型。这个实现类型命名为 myModule,它的基本结构如下:
//组件内部基曲接口的实现类型
type myModule struct {
    //组件ID
    mid module.MID
    //组件的网络地址
    addr string
    //组件评分
    score uint64
    //评分计算器
    scoreCalculator module.CalculateScore
    //调用计数
    calledCount uint64
    //接受计数
    acceptedCount uint64
    //成功完成计数
    completedCount uint64
    //实时处理数
    handlingNumber uint64
}
这些字段都是理所应当存在的,它们分别与 Module 接口(以及 ModuleInternal 口)中声明的方法有直接的对应关系。按照惯例, NewModuleInternal 用于新建一个 ModuleInternal 类型的实例,它的声明如下。
//创建一个组件内部基础类型的实例
func NewModuleInternal(
    mid module.MID,
    scoreCalculator module.CalculateScore) (ModuleInternal, error) {
    parts, err := module.SplitMID(mid)
    if err != nil {
        return nil, errors.NewIllegalParameterError(
            fmt.Sprintf("illegal ID %q: %s", mid, err))
    }
    return &myModule{
        mid: mid,
        addr: parts[2],
        scoreCalculator: scoreCalculator
    }, nil
}
myModule 类型中的字段有几个是需要显式初始化的,包括:组件 ID、组件的网络地址(下面简称组件地址)和组件评分计算器。参数 mid 提供了组件 ID,同时也提供了组件地址。因为组件 ID 中可以包含组件地址。

如果组件地址为空,就说明该组件与网络爬虫程序同处在一个进程中。这时的 addr 字段自然就是 ""。module 包的 SplitMID 函数用于分离出组件 ID 的各个部分,并在组件 ID 不符合规范时报错,它是 module 包中众多工具类函数中的一个。

与之相对应,module 包中还有一个 GenMID 函数,用它可以生成组件 ID。调用 GenMID 函数时,需要给定一个序列号。大家可以通过调用 module 包中的 NewSNGenertor 函数创建出一个序列号生成器。强烈建议把序列号生成器的实例赋给一个全局变量。

组件评分计算器理应由外部提供,并且一般会为同一类组件实例设置同一种组件评分计算器,而且一旦设置就不允许更改。所以,即使是 ModuleInternal 接口也没有提供改变它的方法。

再强调一下,NewIllegalParameterError 是 gopcp.v2/chapter6/webcrawler/errors 包中的函数。该包中还有 NewCrawlerError 和 NewCrawlerErrorBy 函数,用于生成爬虫程序运作过程中抛出的错误值。

有了上述的那些字段,实现 ModuleInternal 接口的方法就相当简单了,唯一要注意的就是充分利用原子操作保证它们的并发安全。这里就不展示了。或许大家可以试着编写出来,然后对比看看。