热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

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

 

网络爬虫分为两类

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

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

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


 

  • 项目总体结构

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

 

 爬虫的思想很简单.

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

2. 保存到我们的数据库中

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


 

  • go语言的爬虫库/框架

 

 

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

  

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


 

  • 本课程的爬虫项目

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

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

2. 数据库使用ElasticSearch

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

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


 

  • 爬虫的主题 

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

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


 

  • 如何发现用户

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

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

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

 


 

  • 爬虫总体算法 

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

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

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

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

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


 

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

 

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

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

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

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

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


 

下面开始项目阶段

项目

一. 单任务版网络爬虫

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

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

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

分析: 

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

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

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

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

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

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

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

 

 

第一步: 抓取页面内容

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "regexp"
)

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)
    if err != nil {
        panic(err)
    }
    //fmt.Printf("%s\n", all)
    printCityList(all)
}

 

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

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "regexp"
)

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)
    if err != nil {
        panic(err)
    }
    //fmt.Printf("%s\n", all)
    printCityList(all)
}

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

    }
}

 

 结果如下:

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

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


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

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

  • 城市列表解析器: 用来解析城市列表
  • 城市解析器: 用来解析某一个城市的页面内容, 城市里是用户列表和分页
  • 用户解析器: 从城市页面点击用户进入到用户的详情页, 解析用户的详情信息

解析器抽象

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

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

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

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

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

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

项目架构

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

 

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

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

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

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

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

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

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

项目结构 

1. 有一个提取器

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

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

4. 有一个main方法入口

第一步: Fetcher--提取器

package fetcher

import (
    "fmt"
    "io/ioutil"
    "net/http"
)
// 抓取器
func Fetch(url string) ([]byte, error) {

    // 第一步, 通过url抓取页面
    client := http.Client{}
    request, err := http.NewRequest("GET", url, nil)
    request.Header.Set("User-Agent", "Mozilla/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)
    if err != nil {
        return nil, fmt.Errorf("http get error :%s", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("http get error errCode:%d", http.StatusOK)
    }

    // 读取出来body的所有内容
    return ioutil.ReadAll(resp.Body)
}

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

package parser

import (
    "aaa/crawler/zhenai/engine"
    "regexp"
)

const cityListRegexp  = `"(http://www.zhenai.com/zhenghun/[a-z1-9]+)"[^>]*>([^<]+)`
func ParseCityList(content []byte) (engine.ParseResult) {
    re := regexp.MustCompile(cityListRegexp)
    all := re.FindAllSubmatch(content, -1)
    pr := engine.ParseResult{}
    count := 1
    for _, line := range all {
        req := engine.Request{
            Url:string(line[1]), 
            ParseFun: ParseCity,
        }
        pr.Req = append(pr.Req, req)

        pr.Items = append(pr.Items, "City: " + string(line[2]))

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

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

package engine

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

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

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

第四步: 抽象出引擎

package engine

import (
    "aaa/crawler/fetcher"
    "fmt"
    "github.com/astaxie/beego/logs"
)

func Run(seeds ...Request) {

    var que []Request

    for _, seed := range seeds {
        que = append(que, seed)
    }

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

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

        resultParse := cur.ParseFun(cont)
        que = append(que, resultParse.Req...)

        for _, item := range resultParse.Items {
            fmt.Printf("内容项: %s \n", item)
        }
    }
}

第五步: 定义程序入口

package main

import (
    "aaa/crawler/zhenai/engine"
    "aaa/crawler/zhenai/parser"
)

func main() {
    req := engine.Request{
        Url:"http://www.zhenai.com/zhenghun", 
        ParseFun: parser.ParseCityList,
    }
    engine.Run(req)

}

 第六步: 城市解析器

package parser

import (
    "aaa/crawler/zhenai/engine"
    "regexp"
)

const cityRe = `"(http://album.zhenai.com/u/[0-9]+)"[^>]*>([^<]+)`
func ParseCity(content []byte) engine.ParseResult{

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

        pr.Items = append(pr.Items, "Name: " + string(sub[2]))
    }

    return pr
}

 

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

 第七步: 用户解析器

package parser

import (
    "aaa/crawler/zhenai/engine"
    "aaa/crawler/zhenai/model"
    "regexp"
    "strconv"
    "strings"
)

// 个人基本信息
const userRegexp = `class]*class="m-btn purple"[^>]*>([^<]+)
` // 个人隐私信息 const userPrivateRegexp = `
"" class="m-btn pink">([^<]+)
` // 择偶条件 const userPartRegexp = `
"" class="m-btn">([^<]+)
` func ParseUser(content []byte, name string) engine.ParseResult { pro := model.Profile{} pro.Name = name // 获取用户的年龄 userCompile := regexp.MustCompile(userRegexp) usermatch := userCompile.FindAllSubmatch(content, -1) pr := engine.ParseResult{} for i, userInfo := range usermatch { text := string(userInfo[1]) if i == 0 { pro.Marry = text continue } if strings.Contains(text, "") { age, _ := strconv.Atoi(strings.Split(text, "")[0]) pro.Age = age continue } if strings.Contains(text, "") { pro.Xingzuo = text continue } if strings.Contains(text, "cm") { height, _ := strconv.Atoi(strings.Split(text, "cm")[0]) pro.Height = height continue } if strings.Contains(text, "kg") { weight, _ := strconv.Atoi(strings.Split(text, "kg")[0]) pro.Weight = weight continue } if strings.Contains(text, "工作地:") { salary := strings.Split(text, "工作地:")[1] pro.Salary = salary continue } if strings.Contains(text, "月收入:") { salary := strings.Split(text, "月收入:")[1] pro.Salary = salary continue } if i == 7 { pro.Occuption = text continue } if i == 8 { pro.Education = text continue } } pr.Items = append(pr.Items, pro) return pr }

看一下抓取的效果吧

抓取的城市列表

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

 

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

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

 

具体某个人的详细信息

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

 

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

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

 

二. 并发版网络爬虫

 

 

 

 

三. 分布式网络爬虫

 


推荐阅读
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 掌握远程执行Linux脚本和命令的技巧
    本文将详细介绍如何利用Python的Paramiko库实现远程执行Linux脚本和命令,帮助读者快速掌握这一实用技能。通过具体的示例和详尽的解释,让初学者也能轻松上手。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 将Web服务部署到Tomcat
    本文介绍了如何在JDeveloper 12c中创建一个Java项目,并将其打包为Web服务,然后部署到Tomcat服务器。内容涵盖从项目创建、编写Web服务代码、配置相关XML文件到最终的本地部署和验证。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • 本文探讨了 Objective-C 中的一些重要语法特性,包括 goto 语句、块(block)的使用、访问修饰符以及属性管理等。通过实例代码和详细解释,帮助开发者更好地理解和应用这些特性。 ... [详细]
  • 本文介绍了如何通过 Maven 依赖引入 SQLiteJDBC 和 HikariCP 包,从而在 Java 应用中高效地连接和操作 SQLite 数据库。文章提供了详细的代码示例,并解释了每个步骤的实现细节。 ... [详细]
  • 使用Vultr云服务器和Namesilo域名搭建个人网站
    本文详细介绍了如何通过Vultr云服务器和Namesilo域名搭建一个功能齐全的个人网站,包括购买、配置服务器以及绑定域名的具体步骤。文章还提供了详细的命令行操作指南,帮助读者顺利完成建站过程。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • andr ... [详细]
author-avatar
鑫瑜Twinkle
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有