Go语言爬虫实战(线程池)

Go语言爬虫实战

目标

  • 利用go语言爬取指定网站的图片。
  • 实现爬取网站任意页面所有所需的图片。
  • 实现使用go语言线程池开启多个线程爬取图片内容。
  • 最后实现创建多个文件夹存储图片。

爬取网站图片

步骤

  • 对指定URL发去GET请求,获取对应的响应。

    • resp, err := http.Get(url)
  • 通过返回的响应获取网站的Html文本内容

    • BodyData, err := io.ReadAll(resp.Body)
  • 通过观察Html文本中图片的地址,并写出对应的正则表达式,匹配所有符合的图片信息。

    • 细节:通过浏览器的开发者模式,可以更快找到图片的地址

    • reImg := `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
      
  • 保存正则表达式的匹配结果,并对其发起GET请求获取图片资源信息。

    • //创建正则表达式的对象
      compile, err := regexp.Compile(reImg)
      //根据网站得Html内容匹配符合条件的结果,-1的意思是匹配所有结果。正数则表示匹配对应数字的结果
      allResult := compile.FindAllString(string(BodyData), -1)
      //获取图片资源
      for i, resultUrl := range allResult {...}
      
  • 保存图片到指定的文件夹

    • //获取图片信息
      data, err := io.ReadAll(get.Body)
      //创建指定文件夹
      mkdirPath := "./img/" + "img_" + strconv.Itoa(num) + "/"
      os.MkdirAll(mkdirPath, os.ModePerm)
      //创建文件保存图片信息
      file, err := os.OpenFile(mkdirPath+"cutImg_"+name, os.O_CREATE|os.O_RDWR, os.ModePerm)
      //将图片信息写入文件
      _, err = file.Write(data)
      

实现爬取网站任意页面

思路

  • 可以通过对网站的观察我们可以发现网站各个页面之间微小的变化,然后将需要爬取的网页存储在一个切片当中,之后重复第一步即可。

  • 例如:https://desk.3gbizhi.com/deskFJ/该网站的网页信息,通过点击翻页可以发现一些规律

    • https://desk.3gbizhi.com/deskFJ/index_1.html 第一页
    • https://desk.3gbizhi.com/deskFJ/index_2.html 第二页
    • https://desk.3gbizhi.com/deskFJ/index_3.html 第三页
    • 所以这里我们只需改变后面的数字即可获取对应页数的网页信息,并开始爬取图片信息。
  • 代码

    var start int
    var end int
    fmt.Printf("请输入爬取的开始页数:")
    fmt.Scanf("%d\n", &start)
    fmt.Printf("请输入爬取的结束页数:")
    fmt.Scanf("%d\n", &end)
    reImg := `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
    for i := 0; i < end-start+1; i++ {
        ...
    	urls[i].Url = "https://desk.3gbizhi.com/deskFJ/index_" + strconv.Itoa(urls[i].Id) + ".html"
        ...
    }
    

线程池开启多个线程

  • 可以查看往期文章
    • https://editor.csdn.net/md/?articleId=137082930

创建多个文件夹存储图片

  • 创建文件夹

    • os.MkdirAll(mkdirPath, os.ModePerm)
  • 在存储图片的时候,获取图片的后缀以及获取图片原名称来命名图片

    • //截取名字和后缀
      index := strings.LastIndex(resultUrl, "/")
      name := resultUrl[index+1:]
      
      

项目结构图片

在这里插入图片描述


项目代码

//pool.go
package worker

import (
	"log"
	"math"
	"sync"
)

// Args 参数结构体
type Args struct {
	Url   string
	ReImg string
	Id    int
}

// Task 定义任务函数类型
type Task func(num int, url string, reImg string) interface{}

//type Task func() interface{}

type Pool struct {
	worker  int
	tasks   *Queue
	events  chan struct{}
	results chan interface{}
	wg      sync.WaitGroup
}

// NewPool 创建pool
func NewPool(worker int) *Pool {
	return &Pool{
		worker:  worker,
		tasks:   NewQueue(-1),
		events:  make(chan struct{}, math.MaxInt),
		results: make(chan interface{}, worker*2),
		wg:      sync.WaitGroup{},
	}
}

// AddTasks 任务添加
func (p *Pool) AddTasks(task Task) {
	err := p.tasks.Push(task)
	if err != nil {
		log.Println(err)
		return
	}
	p.events <- struct{}{}
}

// Start 启动工作池
func (p *Pool) Start(urls []Args) chan interface{} {
	var index = -1
	var IndexLock sync.Mutex
	for i := 0; i < p.worker; i++ {
		p.wg.Add(1)
		go func() {
			for range p.events {
				task, err := p.tasks.Pop()
				if err != nil {
					log.Println(err)
					continue
				}
				IndexLock.Lock()
				index++
				IndexLock.Unlock()
				if task, ok := task.(Task); ok {
					p.results <- task(urls[index].Id, urls[index].Url, urls[index].ReImg)
				}
			}
			p.wg.Done()
		}()
	}
	return p.results
}

// Wait 关闭池子
func (p *Pool) Wait() {
	close(p.events)
	p.wg.Wait()
	close(p.results)
}

//queue.go
package worker

import (
	"fmt"
	"sync"
)

type Queue struct {
	elements []interface{}
	lock     sync.Mutex
	limit    int
}

// NewQueue 创建队列
func NewQueue(limit int) *Queue {
	return &Queue{
		elements: make([]interface{}, 0, 1024),
		lock:     sync.Mutex{},
		limit:    limit,
	}
}

// Push 入队
func (q *Queue) Push(task interface{}) error {
	if q.limit != -1 && len(q.elements) >= q.limit {
		return fmt.Errorf("队列已满,请等待")
	}
	q.lock.Lock()
	defer q.lock.Unlock()
	q.elements = append(q.elements, task)
	return nil
}

// Pop 出队
func (q *Queue) Pop() (interface{}, error) {
	if len(q.elements) == 0 {
		return nil, fmt.Errorf("队列以空,等带任务入队")
	}
	task := q.elements[0]
	q.elements = q.elements[1:]
	return task, nil
}

//main.go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"src/worker"
	"strconv"
	"strings"
	"time"
)

// 爬虫流程
// 1.对网站发送Get请求
// 2.读取网站信息
// 3.提取图片路径
// 4.下载图片,保存起来

func main() {
	var start int
	var end int
	fmt.Printf("请输入爬取的开始页数:")
	fmt.Scanf("%d\n", &start)
	fmt.Printf("请输入爬取的结束页数:")
	fmt.Scanf("%d\n", &end)
	pool := worker.NewPool(5)
	reImg := `https?://[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
	urls := make([]worker.Args, end-start+1)
	for i := 0; i < end-start+1; i++ {
		urls[i].ReImg = reImg
		urls[i].Id = start + i
		urls[i].Url = "https://desk.3gbizhi.com/deskFJ/index_" + strconv.Itoa(urls[i].Id) + ".html"
		pool.AddTasks(GetUrlImage)
	}

	pool.Start(urls)
	pool.Wait()

	fmt.Printf("爬取结束!")
}

func GetUrlImage(num int, url string, reImg string) interface{} {
	resp, err := http.Get(url)
	HandleError(err)
	//读取
	BodyData, err := io.ReadAll(resp.Body)
	HandleError(err)
	//解析数据
	compile, err := regexp.Compile(reImg)
	HandleError(err)
	allResult := compile.FindAllString(string(BodyData), -1)
	if allResult == nil {
		fmt.Printf("%d号线程找不到对应数据\n", num)
		return nil
	}
	fmt.Printf("%d号线程已经获取到%d个图片路径,准备下载\n", num, len(allResult))
	for i, resultUrl := range allResult {
		//截取名字
		index := strings.LastIndex(resultUrl, "/")
		name := resultUrl[index+1:]
		get, err := http.Get(resultUrl)
		HandleError(err)
		//下载图片
		now := time.Now()
		data, err := io.ReadAll(get.Body)
		HandleError(err)
		fmt.Println("图片耗时:", time.Now().Sub(now))
		mkdirPath := "./img/" + "img_" + strconv.Itoa(num) + "/"
		os.MkdirAll(mkdirPath, os.ModePerm)
		file, err := os.OpenFile(mkdirPath+"cutImg_"+name, os.O_CREATE|os.O_RDWR, os.ModePerm)
		HandleError(err)
		_, err = file.Write(data)
		HandleError(err)
		fmt.Printf("成功下载图片%d\n", i)
	}
	return nil
}

// HandleError 错误
func HandleError(err error) {
	if err != nil {
		log.Println(err)
		return
	}
}


(mkdirPath, os.ModePerm)
file, err := os.OpenFile(mkdirPath+“cutImg_”+name, os.O_CREATE|os.O_RDWR, os.ModePerm)
HandleError(err)
_, err = file.Write(data)
HandleError(err)
fmt.Printf(“成功下载图片%d\n”, i)
}
return nil
}

// HandleError 错误
func HandleError(err error) {
if err != nil {
log.Println(err)
return
}
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/500493.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【STM32嵌入式系统设计与开发】——13WWDG(窗口看门狗应用)

这里写目录标题 一、任务描述二、任务实施1、WWDG工程文件夹创建2、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;USART1初始化函数(usart1_init())&#xff08;3&#xff09;USART数据发送函数&#xff08; USART1_Send_Data&#xff08;&#xff09…

单词频次-第12届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第44讲。 单词频次&#xf…

大模型重塑电商,淘宝、百度、京东讲出新故事

配图来自Canva可画 随着AI技术日渐成熟&#xff0c;大模型在各个领域的应用也越来越深入&#xff0c;国内互联网行业也随之进入了大模型竞赛的后半场&#xff0c;开始从“百模大战”转向了实际应用。大模型从通用到细分垂直领域的跨越&#xff0c;也让更多行业迎来了新的商机。…

Python学习:lambda(匿名函数)、装饰器、数据结构

Python Lambda匿名函数 Lambda函数&#xff08;或称为匿名函数&#xff09;是Python中的一种特殊函数&#xff0c;它可以用一行代码来创建简单的函数。Lambda函数通常用于需要一个函数作为输入的函数&#xff08;比如map()&#xff0c;filter()&#xff0c;sort()等&#xff0…

boost::asio::ip::tcp/udp::socket::release 函数为什么限制 Windows 8.1 才可以调用?

如本文题目所示&#xff0c;这是因为只有在 Windows 8.1&#xff08;Windows Server 2012 RC&#xff09;及以上 Windows 操作版本才提供了运行时&#xff0c;修改/删除完成端口关联的ABI接口。 boost::asio 在 release 函数底层实现之中是调用了 FileReplaceCompletionInform…

(完结)Java项目实战笔记--基于SpringBoot3.0开发仿12306高并发售票系统--(三)项目优化

本文参考自 Springboot3微服务实战12306高性能售票系统 - 慕课网 (imooc.com) 本文是仿12306项目实战第&#xff08;三&#xff09;章——项目优化&#xff0c;本篇将讲解该项目最后的优化部分以及一些压测知识点 本章目录 一、压力测试-高并发优化前后的性能对比1.压力测试相关…

Modelsim手动仿真实例

目录 1. 软件链接 2. 为什么要使用Modelsim 3. Modelsim仿真工程由几部分组成&#xff1f; 4. 上手实例 4.1. 新建文件夹 4.2. 指定目录 4.3. 新建工程 4.4. 新建设计文件&#xff08;Design Files&#xff09; 4.5. 新建测试平台文件&#xff08;Testbench Files&…

H7310 线性恒流调光芯片 支持24V30V48V60V100V转3.3V5V12V1.5A 外围简单 性价比高

线性恒流调光芯片是一种能够将输入电压稳定转换为恒定电流输出的电子设备&#xff0c;同时支持调光功能。这种芯片通常具有较高的效率和稳定性&#xff0c;适用于LED照明、显示屏等领域。 针对您提到的支持24V、30V、48V、60V、100V转3.3V、5V、12V&#xff0c;并且能够提供1.…

二十九 超级数据查看器 讲解稿 查询复用

二十九 超级数据查看器 讲解稿 查询复用 ​点击此处 以新页面 打开B站 播放当前教学视频 点击访问app下载页面 百度手机助手 下载地址 大家好&#xff0c;今天我们讲一下超级数据查看器的查询复用功能&#xff0c;这是新版本要增加的功能&#xff0c;这讲是预告。 先介绍…

数据可视化Grafana Windows 安装使用教程(中文版)

1.跳转连接 天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/site?url 2.下载应用程序 官网地址&#xff1a;Grafana get started | Cloud, Self-managed, Enterprisehttps://grafana.com/get/ 3.修改配置文件 grafana\conf\defaults 4.启动\bin\目录下serve应用程序 浏…

机器学习——降维算法-主成分分析(PCA)

机器学习——降维算法-主成分分析&#xff08;PCA&#xff09; 在机器学习领域&#xff0c;主成分分析&#xff08;Principal Component Analysis&#xff0c;简称PCA&#xff09;是一种常用的降维技术&#xff0c;用于减少数据集中特征的数量&#xff0c;同时保留数据集的主要…

尾矿库在线安全监测:提升矿山安全水平

在矿山安全领域&#xff0c;尾矿库的安全管理尤为关键。尾矿库作为矿山生产链条的重要环节&#xff0c;其稳定性不仅关系到生产活动的持续进行&#xff0c;更直接影响着周边环境和人民群众的生命财产安全。因此&#xff0c;尾矿库的安全监测显得尤为重要。近年来&#xff0c;随…

YOLOv9改进策略 : C2f改进 | 引入YOLOv8 C2f结构

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a;应订阅者需求&#xff0c;如何将YOLOv8 C2f结构引入到YOLOv9 &#x1f4a1;&#x1f4a1;&#x1f4a1;C2f层是一种特殊的卷积层&#xff0c;用于将不同尺度的特征图融合在一起&#xff0c;以提高目标检测的准…

XXE漏洞知识及ctfshow例题

XXE漏洞相关知识 XXE全称为XML Enternal Entity Injection 中文叫xml外部实体注入 什么是xml 简单了解XML&#xff1a; &#xff08;xml和html的区别可以简易的理解成&#xff1a;xml是用来储存数据和传输数据的而html是用来将数据展现出来&#xff09; XML 指可扩展标记语…

UE5数字孪生系列笔记(三)

C创建Pawn类玩家 创建一个GameMode蓝图用来加载我们自定义的游戏Mode新建一个Pawn的C&#xff0c;MyCharacter类作为玩家&#xff0c;新建一个相机组件与相机臂组件&#xff0c;box组件作为根组件 // Fill out your copyright notice in the Description page of Project Set…

【力扣】300. 最长递增子序列(DFS+DP两种方法实现)

目录 题目传送最长递增子序列[DFS 方法]DFS方法思路图思路简述代码大家可以自行考虑有没有优化的方法 最长递增子序列[DP]方法DP方法思路图思路简述代码方案 题目传送 原题目链接 最长递增子序列[DFS 方法] DFS方法思路图 思路简述 对于序列中的每一个数字只有选择和不选择两…

C语言查找-----------BF算法KMP算法

1.问题引入 有一个主字符串&#xff0c;有一个子字符串&#xff0c;要求我们寻找子字符串在主字符串里面开始出现的位置&#xff1b; 2.BF算法 BF算法就是暴力算法&#xff0c;这个做法虽然效率不高&#xff0c;但是按照我们传统的思路依然能够得到结果&#xff0c;接下来我们…

LeetCode 523. 连续的子数组和

解题思路 相关代码 class Solution {public boolean checkSubarraySum(int[] nums, int k) {int s[] new int[nums.length1];for(int i1;i<nums.length;i) s[i]s[i-1]nums[i-1];Set<Integer> set new HashSet<>(); for(int i2;i<nums.length;i){set.ad…

filebox在线文件管理工具V1.11.1.1查分吧修改自用版免费分享[PHP]

* 基于:https://down.chinaz.com/soft/35899.htm * 查分吧 修改自用版今日对外分享(自2016年1.10版本以来一直用他云开发:Web环境即时看效果) * 也可以用于本人很多txt/csv通用查询系统的在线管理后台管理数据 * 默认登陆账号filebox密码nidemima * 修改账号密码:21-22行;获取…

Java八股文(K8S)

Java八股文のK8S K8S K8S 请解释什么是Kubernetes&#xff1f; Kubernetes是一个开源的容器编排和管理工具&#xff0c;用于自动化部署、扩展和管理容器化应用程序。 请解释Kubernetes中的Pod、Deployment和Service之间的关系。 ● Pod是Kubernetes的最小部署单元&#xff0c;…