Go語言網路爬蟲分析器介面

2020-07-16 10:04:51
分析器的介面包含兩個額外的方法 RespParsers 和 Analyze,其中前者會返回當前分析器使用的 HTTP 響應解析函數(以下簡稱解析函數)的列表因此,分析器的實現型別有用於儲存此列表的欄位。另外,與下載器的實現型別相同,它也有一個 stub.ModuleInternal 型別的匿名欄位。相關程式碼如下:
//分析器的實現型別
type myAnalyzer struct {
    //元件基礎範例
    stub.ModuleInternal
    //響應解析器列表
    respParsers []module.ParseResponse
}
該型別及其方法存放在 gopcp.v2/chapter6/webcrawler/module/local/analyzer 程式碼包 中。大家可以從我的網路硬碟(連結:https://pan.baidu.com/s/1yzWHnK1t2jLDIcTPFMLPCA 提取碼:slm5)中下載相關的程式碼包。當然,還有 New 函數:
//用於建立一個分析器範例
func New(
    mid module.MID,
    respParsers []module.ParseResponse,
    scoreCalculator module.CalculateScore) (module.Analyzer, error) {
    moduleBase, err := stub.NewModuleInternal(mid, scoreCalculator)
    if err != nil {
        return nil, err
    }
    if respParsers == nil {
        return nil, genParameterError("nil response parsers")
    }
    if len(respParsers) == 0 {
        return nil, genParameterError("empty response parser list")
    }
    var innerParsers []module.ParseResponse
    for i, parser := range respParsers {
        if parser == nil {
            return nil, genParameterError(fmt.Sprintf("nil response parser[%d]", i))
        }
        innerParsers = append(innerParsers, parser)
    }
    return &myAnalyzer{
        ModuleInternal: moduleBase,
        respParsers:    innerParsers,
    }, nil
}
該函數中的大部分程式碼都用於引數檢查。對引數 respParsers 的檢査要尤為仔細,因為它們一定是網路爬蟲框架的使用方提供的,屬於外來程式碼。

分析器的 Analyze 方法的功能是,先接收響應並檢查,再把 HTTP 響應依次交給它持有的若干解析函數處理,最後彙總並返回從解析函數那裡獲得的資料列表和錯誤列表。

由於 Analyze 方法的實現比較長,這裡分段講解。先來看看檢查響應的程式碼:
func (analyzer *myAnalyzer) Analyze(
    resp *module.Response) (dataList []module.Data, errorList []error) {
    analyzer.ModuleInternal.IncrHandlingNumber()
    defer analyzer.ModuleInternal.DecrHandlingNumber()
    analyzer.ModuleInternal.IncrCalledCount()
    if resp == nil {
        errorList = append(errorList,
            genParameterError("nil response"))
        return
    }
    httpResp := resp.HTTPResp()
    if httpResp == nil {
        errorList = append(errorList,
            genParameterError("nil HTTP response"))
        return
    }
    httpReq := httpResp.Request
    if httpReq == nil {
        errorList = append(errorList,
            genParameterError("nil HTTP request"))
        return
    }
    var reqURL = httpReq.URL
    if reqURL == nil {
        errorList = append(errorList,
            genParameterError("nil HTTP request URL"))
        return
    }
    analyzer.ModuleInternal.IncrAcceptedCount()
    respDepth := resp.Depth()
    logger.Infof("Parse the response (URL: %s, depth: %d)... n",
        reqURL, respDepth)
    //省略部分程式碼
}
這裡的檢查非常細,要像庖丁解牛一樣檢查引數值的內裡。因為任何異常都有可能造成解析函數執行失敗。我們一定不要給它們造成額外的困擾。一旦檢查通過,就可以遞增接受計數了。然後列印出一行紀錄檔,代表分析器已經開始解析某個響應了。

還記得前面講的多重讀取器嗎?現在該用到它了:
func (analyzer *myAnalyzer) Analyze(
    resp *module.Response) (dataList []module.Data, errorList []error) {
    //省略部分程式碼
    //解析HTTP響應
    if httpResp.Body != nil {
        defer httpResp.Body.Close()
    }
    multipleReader, err := reader.NewMultipleReader(httpResp.Body)
    if err != nil {
        errorList = append(errorList, genError(err.Error()))
        return
    }
    dataList = []module.Data{}
    for respParser := range analyzer.respParsers {
        httpResp.Body = multipleReader.Reader()
        pDataList, pErrorList := respParser(httpResp, respDepth)
        if pDataList != nil {
            for _, pData := range pDataList {
                if pData == nil {
                    continue
                }
                dataList = appendDataList(dataList, pData, respDepth)
            }
        }
        if pErrorList I- nil {
            for _, pError := range pErrorList {
                if pError == nil {
                    continue
                }
                errorList = append(errorList, pError)
            }
        }
    }
    if len(errorList) == 0 {
        analyzer.ModuleInternal.IncrCompletedCount()
    }
    return dataList, errorList
}
這裡先依據 HTTP 響應的 Body 欄位初始化一個多重讀取器,然後在每次呼叫解析函數之前先從多重讀取器那裡獲取一個新的讀取器並對 HTTP 響應的 Body 欄位重新賦值,這樣就解決了 Body 欄位值的底層資料只能讀取一遍的問題。

每個解析函數都可以順利讀出 HTTP 響應體。在所有解析都完成之後,如果錯誤列表為空,就遞增成功完成計數。最後,我會返回收集到的資料列表和錯誤列表。

由於我們把解析 HTTP 響應的任務都交給了解析函數,所以 Analyze 方法的實現還是比較簡單的,程式碼邏輯也很清晰。