第十三章 go实现分布式网络爬虫---单机版爬虫

前端之家收集整理的这篇文章主要介绍了第十三章 go实现分布式网络爬虫---单机版爬虫前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

 

网络爬虫分为两类

1. 通用爬虫: 类似于baidu,google. 他们会把大量的数据挖下来,保存到自己的服务器上. 用户打开跳转的时候,其实先是跳转到他们自己的服务器. 

2. 聚焦爬虫: 其实就是有目标的爬虫,比如我只需要内容信息. 那我就只爬取内容信息. 

通常我们使用的爬虫都是聚焦爬虫 


 

  • 项目总体结构

 

 

 爬虫的思想很简单.

1. 写一段程序,从网络上把数据抓下来

2. 保存到我们的数据库

3. 写一个前端页面,展示数据


 

  • go语言的爬虫库/框架

 

 

 

  

以上是go语言中已经you封装好的爬虫库或者框架,但我们写爬虫的目的是为了学习. 所以.....不使用框架了


 

  • 本课程的爬虫项目

1. 不用已有的爬虫库和框架

2. 数据库使用ElasticSearch

3. 页面展示使用标准库的http

这个练习的目的,就是使用go基础.之所以选择爬虫,是因为爬虫有一定的复杂性


 

 

哈哈,要是还没有女盆友,又不想花钱的童鞋,可以自己学习一下爬虫技术


 

1. 通过http://www.zhenai.com/zhenghun页面进入. 这是一个地址列表页. 你想要找的那个她(他)是哪个城市的

2. 在用户的详情页,有推荐--猜你喜欢

 


 

  • 爬虫总体算法 

 1. 城市列表,找到一个城市

2. 城市下面有用户列表. 点击某一个用户,进去查看用户的详情信息

3. 用户详情页右侧有猜你喜欢,链接到一个新的用户详情页

需要注意的是,用户推荐,会出现重复推荐的情况. 第一个页面推荐了张三,从上三进来推荐了李四. 从李四进来有推荐到第一个页面了. 这就形成了死循环,重复推荐


 

 

 

 

我们完成爬虫,分为三个阶段

1. 单机版. 将所有功能在一个引用里完成

2. 并发版. 有多个连接同时访问,这里使用了go的协程

3. 分布式. 多并发演进就是分布式了. 削峰,减少服务器的压力. 


 

下面开始项目阶段

项目

一. 单任务版网络爬虫

目标: 抓取珍爱网中的用户信息.

1.  抓取用户所在的城市列表信息

2. 抓取某一个城市的某一个人的基本信息,把信息存到我们自己的数据库

分析: 

1. 通过url获取网站数据. 拿到我们想要的地址,以及点击地址跳转的url. 把地址信息保存到数据库.  数据量预估300

2. 通过url循环获取用户列表. 拿到页面详情url,在获取用户详情信息. 把用户信息保存到数据库. 数据量会比较大. 一个城市如果有10000个人注册了,那么就有300w的数据量.

3. 所以,数据库选择的是elasticSearch

 -------------------

抓取城市列表页,也就是目标把这个页面中我们要的内容抓取下来.

其实就两个内容,1. 城市名称,2. 点击城市名称跳转的url

 

 

第一步: 抓取页面内容

package main

import (
    "fmt"
    io/IoUtilnet/httpregexp"
)

func main() {
    // 第一步,通过url抓取页面
    resp,err := http.Get(http://www.zhenai.com/zhenghun)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return
    }

     读取出来body的所有内容
    all,err := IoUtil.ReadAll(resp.Body)
     nil {
        panic(err)
    }
    fmt.Printf("%s\n",all)
    printCityList(all)
}

 

第二步: 正则表达式,提取城市名称跳转的url

 nil {
        panic(err)
    }
    //fmt.Printf("%s\n",all)
    printCityList(all)
}

/**
 * 正则表达式提取城市名称跳转的url
 */
func printCityList(content []byte) {
    re := regexp.MustCompile(`<a href=(http://www.zhenai.com/zhenghun/[a-z1-9]+)" data-v-5e16505f>([^<]+)</a>`)
    all := re.FindAllSubmatch(content,-1for _,line := range all {
        fmt.Printf(city: %s,url: %s\n",line[2],1)">])

    }
}

 

 结果如下:

 这样第一个页面就抓取完成了. 第二个和第三个页面可以了类似处理. 但这样不好,我们需要把结构进行抽象提取. 形成一个通用的模块


再来分析我们的单机版爬虫项目

项目结构---共有三层结构:

解析器抽象

既然都是解析器,那么我们就把解析器抽象出来.

每一个解析器,都有输入参数和输出参数

输入参数: 通过url抓取的网页内容

输出参数: Request{URL,Parse}列表,Item列表

为什么输出的第一个参数是Request{URL,Parse}列表呢?

  •  城市列表解析器,我们获取到城市名称和url,点解url,要进入的是城市解析器. 所以这里的解析器应该是城市解析器. 
  • 城市解析器. 我们进入城市以后,会获取用户的姓名和用户详情页的url. 所以这里的解析器,应该传的是用户解析器.
  • 用户解析器. 用来解析用户的信息. 保存入库

项目架构

 

 1. 有一个或多个种子页面,发情请求到处理引擎. 引擎不是马上就对任务进行处理的. 他首先吧种子页面添加到队列里去

2. 处理引擎从队列中取出要处理的url,交给提取提取页面内容. 然后将页面内容返回

3. 将页面内容进行解析,返回的是Request{URL,Parse}列表和 Items列表

4. 我们将Request添加到任务队列中. 然后下一次依然从任务队列中取出一条记录. 这样就循环往复下去了

5. 队列什么时候结束呢? 有可能不会结束,比如循环推荐,也可能可以结束. 

这样,结构都有了,入参出参也定义好了,接下来就是编码实现

我们先来改写上面的抓取城市列表

项目结构 

1. 有一个提取

2. 有一个解析器. 解析器里应该有三种类型的解析器

3. 有一个引擎来触发操作

4. 有一个main方法入口

第一步: Fetcher--提取

package fetcher

import (
    
)
 抓取器
func Fetch(url string) ([],error) {

    页面
    client := http.Client{}
    request,err := http.NewRequest(GETUser-AgentMozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/66.0.3359.181 Safari/537.36)
    resp,err := client.Do(request)
    resp,err := http.Get(url)
     nil {
        return nil,fmt.Errorf(http get error :%shttp get error errCode:%d 读取出来body的所有内容
     IoUtil.ReadAll(resp.Body)
}

 第二步: 有一个城市解析器

package parser

import (
    aaa/crawler/zhenai/engine
)

const cityListRegexp  = `<a[^href]*href="[^>]*>([^<]+)</a>`
func ParseCityList(content []) (engine.ParseResult) {
    re := regexp.MustCompile(cityListRegexp)
    all := re.FindAllSubmatch(content,1)">)
    pr := engine.ParseResult{}
    count := 1
     range all {
        req := engine.Request{
            Url:string(line[]),ParseFun: ParseCity,}
        pr.Req = append(pr.Req,req)

        pr.Items = append(pr.Items,1)">City: " + 2]))

        count --
        if count <=0 {
            break
        }
    }
     pr
}

第三步:定义引擎需要使用的结构体

package engine

type Request struct {
    Url string
    ParseFun func(content []) ParseResult
}

type ParseResult  {
    Req []Request
    Items []interface{}
}

func NilParse(content []) ParseResult{
     ParseResult{}
}

第四步: 抽象出引擎

package engine

import (
    aaa/crawler/fetchergithub.com/astaxie/beego/logs
)

func Run(seeds ...Request) {

    var que []Request

     range seeds {
        que = append(que,seed)
    }

    for len(que) >  {
        cur := que[]
        que = que[:]

        logs.Info(fetch url: fetcher.Fetch(cur.Url)
        if e != nil {
            logs.Info(解析页面异常 url:continue
        }

        resultParse := cur.ParseFun(cont)
        que = range resultParse.Items {
            fmt.Printf(内容项: %s \n
aaa/crawler/zhenai/parser
)

func main() {
    req := engine.Request{
        Url:
const cityRe = `<a[^href]*href=(http://album.zhenai.com/u/[0-9]+)`
func ParseCity(content []) engine.ParseResult{

    cityRegexp:= regexp.MustCompile(cityRe)
    subs := cityRegexp.FindAllSubmatch(content,1)"> engine.ParseResult{}
     range subs {
        name := string(sub[])
         获取用户的详细地址
        re := 注意,这里定义了一个函数来传递,这样可以吧name也传递过去
            ParseFun: func(content []) engine.ParseResult {
                 ParseUser(content,name)
            },re)

        pr.Items = append(pr.Items,1)">Name: ]))
    }

     pr
}

 

城市解析器和城市列表解析器基本类似. 返回的数据是request和用户名

 第七步: 用户解析器

aaa/crawler/zhenai/modelstrconvstrings 个人基本信息
const userRegexp = `<div[^class]*class=m-btn purple"[^>]*>([^<]+)</div>`
 个人隐私信息
const userPrivateRegexp = `<div data-v-8b1eac0c="" m-btn pink">([^<]+)</div> 择偶条件
const userPartRegexp = `<div data-v-8b1eac0c=m-btn`

func ParseUser(content []byte,name ) engine.ParseResult {
    pro := model.Profile{}
    pro.Name = name
     获取用户的年龄
    userCompile := regexp.MustCompile(userRegexp)
    usermatch := userCompile.FindAllSubmatch(content,1)">)

    pr :=for i,userInfo := range usermatch {
        text := string(userInfo[if i ==  {
            pro.Marry = text
            
        }
        if strings.Contains(text,1)">岁) {
            age,_ := strconv.Atoi(strings.Split(text,1)">")[])
            pro.Age = age
            ) {
            pro.Xingzuo =cm) {
            height,1)">])
            pro.Height = height
            
        }

        kg) {
            weight,1)">])
            pro.Weight = weight
            工作地:) {
            salary := strings.Split(text,1)">]
            pro.Salary = salary
            月收入:7 {
            pro.Occuption =8 {
            pro.Education =
        }
    }
    pr.Items = append(pr.Items,pro)

     pr
}

看一下抓取的效果

抓取的城市列表

 

 抓取的某个城市的用户列表

 

具体某个人的详细信息

 

 至此,完成了单机版爬虫. 再来回顾一下. 

做完了感觉,这个爬虫其实很简单,之前用java都实现过.只不过这次是用go实现的

  • 有一个种子页面,从这个页面进来,会获取到源源不断的用户信息
  • 遇到一个403的问题. 需要使用自定义的http请求,设置header 的User-agent,否则服务器请求被拒绝
  • 使用函数式编程. 函数的特点就是灵活. 灵活多变. 想怎么封装都行. 这里是在cityParse解析出user信息的时候,使用了函数式编程.把用户名传递过去了

 

二. 并发版网络爬虫

 

 

 

 

三. 分布式网络爬虫

 

猜你在找的Go相关文章