基于Go1.19的站点模板爬虫

昨天研究了一下,关于基于Go1.19的站点模板爬虫,分享一下代码和记录,希望可以帮助大家:

package main

import (
	"fmt"
	"net/http"
	"io/ioutil"
	"log"
	"golang.org/x/net/html"
	"strings"
	"sync"
)

// URLVisited是一个包含已访问URL的集合,防止重复访问
var URLVisited = make(map[string]bool)
var mu sync.Mutex

// FetchURL 负责从给定的URL获取内容
func FetchURL(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

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

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(bodyBytes), nil
}

// ParseHTML 解析HTML内容并提取所有链接
func ParseHTML(body string) ([]string, error) {
	doc, err := html.Parse(strings.NewReader(body))
	if err != nil {
		return nil, err
	}

	var links []string
	var f func(*html.Node)
	f = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "a" {
			for _, attr := range n.Attr {
				if attr.Key == "href" {
					link := attr.Val
					if strings.HasPrefix(link, "http") {
						links = append(links, link)
					}
				}
			}
		}
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			f(c)
		}
	}
	f(doc)
	return links, nil
}

// Crawl 开始爬取给定的URL,并递归爬取所有发现的链接
func Crawl(url string, wg *sync.WaitGroup) {
	defer wg.Done()

	mu.Lock()
	if URLVisited[url] {
		mu.Unlock()
		return
	}
	URLVisited[url] = true
	mu.Unlock()

	fmt.Println("Crawling:", url)
	body, err := FetchURL(url)
	if err != nil {
		log.Println("Error fetching URL:", err)
		return
	}

	links, err := ParseHTML(body)
	if err != nil {
		log.Println("Error parsing HTML:", err)
		return
	}

	for _, link := range links {
		wg.Add(1)
		go Crawl(link, wg)
	}
}

func main() {
	var wg sync.WaitGroup
	startURL := "https://example.com"

	wg.Add(1)
	go Crawl(startURL, &wg)
	wg.Wait()

	fmt.Println("Crawling finished.")
}

代码解释

主函数(main)

func main() {
	var wg sync.WaitGroup
	startURL := "https://example.com"

	wg.Add(1)
	go Crawl(startURL, &wg)
	wg.Wait()

	fmt.Println("Crawling finished.")
}

主函数定义了一个起始URL,并使用sync.WaitGroup来跟踪并发的爬取任务。wg.Add(1)用于增加等待组的计数,go Crawl(startURL, &wg)则启动了一个新的goroutine来爬取起始URL。wg.Wait()等待所有爬取任务完成后,打印“Crawling finished.”消息。

Crawl函数

func Crawl(url string, wg *sync.WaitGroup) {
	defer wg.Done()

	mu.Lock()
	if URLVisited[url] {
		mu.Unlock()
		return
	}
	URLVisited[url] = true
	mu.Unlock()

	fmt.Println("Crawling:", url)
	body, err := FetchURL(url)
	if err != nil {
		log.Println("Error fetching URL:", err)
		return
	}

	links, err := ParseHTML(body)
	if err != nil {
		log.Println("Error parsing HTML:", err)
		return
	}

	for _, link := range links {
		wg.Add(1)
		go Crawl(link, wg)
	}
}

Crawl函数是递归爬虫的核心。它接收一个URL和一个等待组指针。首先,它检查URL是否已经被访问过,如果是则直接返回。否则,它将URL标记为已访问,并获取页面内容。接着,解析HTML并提取所有链接,为每个链接启动新的goroutine来继续爬取。

FetchURL函数

func FetchURL(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

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

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	return string(bodyBytes), nil
}

FetchURL函数负责获取给定URL的内容。它使用http.Get发送HTTP请求,并检查响应状态码是否为200(OK)。如果响应状态码不是200,则返回错误。然后读取响应体并将其转换为字符串。

ParseHTML函数

func ParseHTML(body string) ([]string, error) {
	doc, err := html.Parse(strings.NewReader(body))
	if err != nil {
		return nil, err
	}

	var links []string
	var f func(*html.Node)
	f = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "a" {
			for _, attr := range n.Attr {
				if attr.Key == "href" {
					link := attr.Val
					if strings.HasPrefix(link, "http") {
						links = append(links, link)
					}
				}
			}
		}
		for c := n.FirstChild; c != nil; c = n.FirstChild {
			f(c)
		}
	}
	f(doc)
	return links, nil
}

ParseHTML函数解析给定的HTML内容并提取所有链接。它使用golang.org/x/net/html包来解析HTML,并使用递归函数遍历节点树。当发现<a>元素时,提取其href属性中的链接并将其添加到链接列表中。

互斥锁

为了确保多线程访问URLVisited集合时的数据一致性,我们使用sync.Mutex。在访问和修改URLVisited时,我们使用mu.Lock()mu.Unlock()来锁定和解锁集合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值