Go程序员Golang

New一个golang爬虫

2017-09-06  本文已影响762人  aloris

刚好七八双月结束,工作整理完毕,下个双月OKR还没开始。做久了IOS开发也来扩展下领域,抽空几天学了下Golang,实现一个爬虫。

一、知识要点

1、爬虫

1.1 工作方式

传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。聚焦爬虫的工作流程较为复杂,需要根据一定的网页分析算法过滤与主题无关的链接,保留有用的链接并将其放入等待抓取的URL队列。然后,它将根据一定的搜索策略从队列中选择下一步要抓取的网页URL,并重复上述过程,直到达到系统的某一条件时停止。另外,所有被爬虫抓取的网页将会被系统存贮,进行一定的分析、过滤,并建立索引,以便之后的查询和检索;对于聚焦爬虫来说,这一过程所得到的分析结果还可能对以后的抓取过程给出反馈和指导。

1.2 分类

1.3爬虫算法

2、golang

2.1 语法学习

2.2 环境安装

1.1 在MacOSX上安装

tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz

export PATH=$PATH:/usr/local/go/bin

1.2 其他方式

参考链接

二、代码实现

先确立一个小目标,就是我们要爬取的网页的数据源是什么。一直觉得国内的大学排名争议比较有趣,TOP2的两所,但是TOP5的有8所,TOP10的有20所,哈哈,所以来爬个大学排行榜玩玩吧。

1、网页抓取

1.1 定义一个学校


type SchoolObj struct {
    rankTypeName string
    RankIndex int
    SchoolName string
    EnrollOrder string
    StarLevel string
    LocationName string
    SchoolType  string
    UrlAddress string
    SchoolTags []string
}

1.2 单页面html解析

因为有过前端开发的经验,我自然而然想到,使用CSS选择器会比直接使用遍历算法来得高效,有CSS的选择规则,我可以批量规律的获取和处理HTML的DOM结构数据。端开发中的jQuery提供了方便的操作 DOM 的 API。使用 Go 语言做服务器端开发,有时候需要解析 HTML 文件,比如抓取网站内容、写一个爬虫等。这时候如果有一个类似 jQuery 的库可以使用,操作 DOM 会很方便,而且,上手也会很快。果然,还真有这样的工具,此处推荐一个GitHub的开源框架 --- Goquery 。

A、使用介绍:

goquery定义了一个Document结构,直接对应网页Javascript的Document节点,通过一个NewDocument方法,传入参数地址为网页的url地址,直接生产一个虚拟的go语言上的dom。

type Document struct {
    *Selection
    Url      *url.URL
    rootNode *html.Node
}

func NewDocument(url string) (*Document, error) {
    // Load the URL
    res, e := http.Get(url)
    if e != nil {
        return nil, e
    }
    return NewDocumentFromResponse(res)
}


Document有定义find方法,方法的使用和JQuery里面一直,传入目标字符串的css选择器即可。通过对Document执行find查找方法,获得全部学校目标的字符串数组。

doc.Find(".bangTable table tr")

这里的选择器怎么来的呢,我们在chrome里面打开url地址,找到我们想要收集的数据排名,右键打开审查元素,可以看到HTML的选择器名称。这里需要有一点CSS基础,因为有的选择器不是直接唯一的,需要自己去判断,怎样的选择器组合才能准确的拿到想要的目标字符串。

Document有定义each方法,用于遍历数组,也就是各个大学所对应的dom节点。在each方法中继续使用查找方法,并最后获得想要的字符串。

每一个dom对应一个SchoolStruct,新建并赋值,放入数组中返回。

B、代码如下:

import (
    "github.com/PuerkitoBio/goquery"
    "SchoolReptile/struct"
    "net/http"
)

func GaokaoquanRank(urlAddress string) []SchoolStruct.SchoolObj {

    var array [] SchoolStruct.SchoolObj

    doc, err := goquery.NewDocument(urlAddress)
    if err != nil {
        log.Fatal(err)
    }

    // Find the review items
    doc.Find(".bangTable table tr").Each(func(i int, s *goquery.Selection) {
        // For each item found, get the band and title
        var obj SchoolStruct.SchoolObj
        obj.RankIndex = s.Find(".t1 span").Text()
        obj.SchoolName = s.Find(".t2 a").Text()
        obj.UrlAddress ,_ = s.Find(".t2 a").Attr("href")
        obj.LocationName = s.Find(".t3").Text()
        obj.SchoolType = s.Find(".t4").Text()
        obj.StarLevel = s.Find(".t5").Text()
        obj.EnrollOrder = "本科第一批"
        array = append(array, obj)

    })

    return array
}

2、接口请求

我们再爬去数据的时候,一般都能直接抓取网页数据,但是有的数据在第一页炳辉展示出来,需要有点击操作,比如加载更多。此处的大学排行有200位,第一页请求只有20位,这时候就会发现,接口请求的方便。
有的网页在接口上做了cookie校验,摸清别人的请求规则,才能正确模拟出请求获得返回数据。

我们此处拿乐学高考作文例子,获取各个类型的大学排行榜。通过charles代理,我们获得请求的各类参数。

url := LexueHost+"/college/ranking?page="+pageStr+"&rank_type="+rankObj.RankType+"&page_size=15"

网络请求返回的是一个字符串结构的数据,我们需要把它映射成map结构好获取key对应的value值。

这里推荐一个go语言在json解析上的一个开源库Simplejson,将返回的数据进行JSON结构化,然后通过get方法可以直接获得对应的参数值。

defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)

jsonBody,err := simplejson.NewJson(data)

schoolJsonArray,err := jsonBody.Get("schools").Array()
    
var nextArray [] SchoolStruct.SchoolObj
nextArray = LexueRankEachList(rankObj,pageIndex)

B、代码如下:

import (
    "SchoolReptile/struct"
    "net/http"
    "io/ioutil"
    "fmt"
    "bytes"
    "encoding/json"
    "strings"
    "github.com/bitly/go-simplejson"
    "strconv"
)

func LexueRankEachList(rankObj SchoolStruct.RankTypeObj,pageIndex int ) []SchoolStruct.SchoolObj {

    pageStr := strconv.Itoa(pageIndex)

    url := LexueHost+"/college/ranking?page="+pageStr+"&rank_type="+rankObj.RankType+"&page_size=15"

    resp, err := http.Get(url)
    if err != nil {
        // handle error
    }

    defer resp.Body.Close()

    data, err := ioutil.ReadAll(resp.Body)

    jsonBody,err := simplejson.NewJson(data)

    schoolJsonArray,err := jsonBody.Get("schools").Array()

    var array [] SchoolStruct.SchoolObj

    if len(schoolJsonArray) <= 0 {
        println("请求到头了")
        return array
    }

    for i,_ := range schoolJsonArray {
        schoolJson := jsonBody.Get("schools").GetIndex(i)
        var obj SchoolStruct.SchoolObj
        obj.RankIndex = strconv.Itoa(schoolJson.Get("school_rank").MustInt())
        obj.SchoolName = schoolJson.Get("school_name").MustString()
        obj.SchoolTags = schoolJson.Get("school_tags").MustStringArray()
        array = append(array, obj)
        println(obj.RankIndex,obj.SchoolName,obj.SchoolTags)
    }

    pageIndex++
    var nextArray [] SchoolStruct.SchoolObj
    nextArray = LexueRankEachList(rankObj,pageIndex)
    if len(nextArray) > 0 {
        for _,obj := range nextArray {
            array = append(array,obj)
        }
    }

    return array

}

3、保存到Excel

前两部获得了网络数据,并解析生成了对应的SchoolStruct数组,这个时候我们只需要创建excel边。遍历数组,把数组里面的数据字段都存入表格即可,git开源库xlsx能够让我们轻松的创建、查找、赋值Excel表。

代码如下:

func SaveSchoolRank(schoolArray [] SchoolStruct.SchoolObj,excelName string,sheetName string)  {

    var file *xlsx.File
    var sheet *xlsx.Sheet
    var row *xlsx.Row
    var cell *xlsx.Cell
    var err error

    file,err = xlsx.OpenFile(excelName + ".xlsx")

    if err != nil {
        file = xlsx.NewFile()
        sheet,err = file.AddSheet(sheetName)
    } else {
       sheet = file.Sheet[sheetName]
    }

    if err == nil {

        for i := 0; i < len(schoolArray); i++ {
            obj := schoolArray[i]

            row = sheet.AddRow()
            cell = row.AddCell()
            cell.Value = obj.RankIndex

            cell = row.AddCell()
            cell.Value = obj.SchoolName

            cell = row.AddCell()
            cell.Value = obj.StarLevel

            cell = row.AddCell()
            cell.Value = obj.LocationName

            cell = row.AddCell()
            cell.Value = obj.EnrollOrder

            cell = row.AddCell()
            cell.Value = obj.SchoolType

            cell = row.AddCell()
            cell.Value = obj.UrlAddress


            var tagStr string
            for _,value := range obj.SchoolTags {
                tagStr += "+" + value
            }
            cell = row.AddCell()
            cell.Value = tagStr


            if err != nil {
                fmt.Printf(err.Error())
            }
        }

    }

    err = file.Save(excelName + ".xlsx")
    if err != nil {
        fmt.Printf(err.Error())
    }
}

上一篇 下一篇

猜你喜欢

热点阅读