基于golang的文章信息抓取

基于golang的文章信息抓取

学习golang爬虫,实现广度爬取,抓取特定的网页地址:测试站点新笔趣阁(https://www.xsbiquge.com/)
主要学习golang的goroutine和channel之间的协作,无限爬取站点小说的地址仅限书目录地址,不进行文章内容爬取

在学习中遇到了一些问题:例如共享变量map写入竞争、连接提前关闭问题等等

福利彩蛋:没有好玩的 API 接口?上百款免费接口等你来,免费 API,免费 API 大全

为了解决map类型共享变量竞争问题,封装map类型添加读写锁限制,防止不同的goroutine之间出现写入竞争(虽然只有goroutine比较多的时候才会出现)

go 复制代码
package util

import "sync"

//封装一个map结构体,主要用来过滤相同的url地址
type VisitMap struct {
    sync.RWMutex
    visited map[string]bool
}

func (vis *VisitMap) ReadMap(url string) bool {
    vis.RLock()
    value := vis.visited[url]
    vis.RUnlock()
    return value
}

func (vis *VisitMap) WriteMap(url string) {
    vis.Lock()
    vis.visited[url] = true
    vis.Unlock()
}

//channel封装
type Pool struct {
    VisitMap *VisitMap
    queue    chan string
}

func New(size int) *Pool {
    if size < 1 {
        size = 1
    }
    visitMap := new(VisitMap)
    visitMap.visited = make(map[string]bool)

    return &Pool{
        VisitMap: visitMap,
        queue:    make(chan string, size),
        wg:       &sync.WaitGroup{},
    }
}

接下来是main函数处理

go 复制代码
package main

import (
    "fmt"
    "github.com/thinkeridea/go-extend/exstrings"
    "golang.org/x/net/html"
    "net/http"
    "net/url"
    "regexp"
    "reptile/demo/queue/dao"
    "reptile/demo/queue/model"
    "reptile/tools"
    "strings"
    "time"
)

func main() {

    //1,初始化Orm
    _, err := tools.OrmEngine()
    if err != nil {
        fmt.Println(err)
        panic(err)
    }

    bookUrl := "https://www.xsbiquge.com/"
    //bookUrl = "https://www.xsbiquge.com/68_68470/"
    pool := New(100)
    pool.queue <- bookUrl
    //根据主页面爬取子页面
    u, err := url.Parse(bookUrl)

    if err != nil {
        fmt.Println(err)
        return
    }
    hostName := u.Hostname()
    for uri := range pool.queue {
        go DownLoad(hostName, uri, pool)
        fmt.Println("range pool.queue  : " + uri)
    }

    fmt.Println("结束啦:" + bookUrl)

}

html抓取和信息处理,匹配小说列表链接

go 复制代码
/**
下载
*/
func DownLoad(host, bookUrl string, pool *Pool) {

    showTime("download start")

    pool.VisitMap.WriteMap(bookUrl)
    //http 客户端
    client := http.Client{}
    //创建请求
    req, err := http.NewRequest("GET", bookUrl, nil)
    if err != nil {
        fmt.Println(err)
    }

    //设置请求header
    req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")

    //执行请求
    showTime("client.Do start")
    resp, err := client.Do(req)
    showTime("client.Do end")

    if err != nil {
        fmt.Println(err)
    }
    //这里判断下,如果响应关闭。则直接返回,实测是存在这种情况的
    if resp == nil || resp.Close {
        return
    }
    defer resp.Body.Close()

    showTime("links start")
    body, err := html.Parse(resp.Body)
    links := visit(nil, body)
    showTime("links end")

    for _, link := range links {
        absolute := urlJoin(link, bookUrl)
        //fmt.Println(runtime.NumGoroutine())

        //匹配是否是文章页面
        rh, _ := regexp.Compile(host)
        host := rh.MatchString(absolute)
        //匹配是否是文章页面
        r, _ := regexp.Compile(`\.html`)
        html := r.MatchString(absolute)

        if bookUrl != " " && host && !html {

            len := strings.Index(absolute, "#")

            if len != -1 {
                absolute = exstrings.SubString(absolute, 0, len)
            }
            fmt.Println("current url: " + absolute)

            if !pool.VisitMap.ReadMap(absolute) {
                fmt.Println("add url: " + absolute)
                go urlQueue(absolute, pool)
            }
        }
    }
}

func urlQueue(url string, pool *Pool) {
    pool.queue <- url
}


func urlJoin(href, base string) string {
    uri, err := url.Parse(href)
    if err != nil {
        return " "
    }
    baseUrl, err := url.Parse(base)
    if err != nil {
        return " "
    }
    return baseUrl.ResolveReference(uri).String()
}


func showTime(action string) {
    //fmt.Println(fmt.Sprintf("%s :%s", action, time.Now().String()))
}

在页面链接处理过程中遇到了空指针问题:所以在处理之前先判断下 *html.Node指针是否是空指针

go 复制代码
//文档链接处理
func visit(links []string, n *html.Node) []string {
    if n != nil {
        if n.Type == html.ElementNode && n.Data == "a" {
            for _, a := range n.Attr {
                if a.Key == "href" {
                    links = append(links, a.Val)
                }
            }
        }
        for c := n.FirstChild; c != nil; c = c.NextSibling {
            links = visit(links, c)
        }
    }
    return links
}

学习收货:

读写锁的使用、不同goroutine之间通过channel通讯、url地址解析、html解析、html内容提去链接地址、通过map进行链接地址去重、go-extend扩展工具包使用

但是有一个问题,通过range来循环通道,如果通道内没有消息了就会处于等待状态,要如果退出?

福利彩蛋:没有好玩的 API 接口?上百款免费接口等你来,免费 API,免费 API 大全

相关推荐
开源技术14 小时前
如何将本地LLM模型与Ollama和Python集成
开发语言·python
Hello World . .14 小时前
数据结构:队列
c语言·开发语言·数据结构·vim
clever10114 小时前
在QtCreator 4.10.2中调试qt程序qDebug()输出中文为乱码问题的解决
开发语言·qt
测试开发Kevin14 小时前
小tip:换行符CRLF 和 LF 的区别以及二者在实际项目中的影响
java·开发语言·python
松☆15 小时前
Dart 核心语法精讲:从空安全到流程控制(3)
android·java·开发语言
编码者卢布15 小时前
【Azure Storage Account】Azure Table Storage 跨区批量迁移方案
后端·python·flask
编码者卢布15 小时前
【App Service】Java应用上传文件功能部署在App Service Windows上报错 413 Payload Too Large
java·开发语言·windows
kaikaile199515 小时前
结构风荷载理论与Matlab计算
开发语言·matlab
切糕师学AI15 小时前
ARM 汇编器中的伪指令(Assembler Directives)
开发语言·arm开发·c#
吕司16 小时前
Qt的信号与槽
开发语言·qt