Go使用代理采集数据实践

简介: 今天这篇分享:使用Go语言做爬虫的实践,包括对接代理和不对接代理的情况。

今天这篇分享:使用Go语言做爬虫的实践,包括对接代理和不对接代理的情况。


需求分析


  1. 允许用户指定关键词去获得数据
  2. 允许用户输入代理ip,如果不输入代理ip,则默认使用本机ip
  3. 把采集结果输出到文件中
  4. 把不可用的代理ip输出到文件中,方便用户更新。


说明


本教程仅供学习研究GO语言技术使用,如果大家要采集数据,请通过正常渠道和官方对接,或者对接聚合API等数据平台。


知识点


下面介绍一下涉及到的知识点,让大家有个系统的认识:


  1. 首先有和用户交互的文字输入和文件输出:flag.StringVar()os
  2. ip池的管理:gcache的使用
  3. 使用代理ip请求数据:http客户端的使用
  4. 正则匹配:处理目标数据


代码


说明:下面所有的函数都可以放到同一个文件中,为了方便给大家讲解,我按照业务拆分成了多个子目录。


主程序及main()函数


  1. 根据是否输入代理ip判断是否通过代理ip采集
  2. 注意os文件操作的权限
  3. 管理ip池的思路是使用用户本地的内存做缓存。


package main
import (
 "flag"
 "fmt"
 "github.com/gogf/gf/frame/g"
 "github.com/gogf/gf/net/ghttp"
 "github.com/gogf/gf/os/gcache"
 "io"
 "io/ioutil"
 "math/rand"
 "os"
 "regexp"
 "strconv"
 "strings"
 "time"
)
var proxyIps string
var IDS []string
var keyword string
var wq string
var filePath string
var fp *os.File
var PriceStart int
var Page int
var fileUnUseIP *os.File
var useProxy bool
const (
 SleepTime   = 3 //每次请求休眠时间
 UnuseIpFile = "不可用ip记录.txt"
 MaxPage     = 100
 MaxPrice    = 2000
)
func main() {
 flag.StringVar(&keyword, "keyword", "", "url关键词")
 flag.StringVar(&proxyIps, "ips", "", "代理ip,多个英文逗号分隔")
 //默认存储到当前文件件下
 flag.StringVar(&filePath, "file", "test.txt", "指定保存数据的文件路径及名称,如 c:/test.txt")
 flag.Parse()
 if "" == keyword {
  fmt.Printf("必须传递keyword")
  return
 }
 var err error
 fp, err = os.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) //0666表示:创建了一个普通文件,所有人拥有对该文件的读、写权限,但是都不可执行
 if nil != err {
  fmt.Printf("打开文件失败,请检查文件路径是否正确,或者您的电脑是否设置了权限,无法读写文件")
  return
 }
 defer fp.Close()
 //失效ip写入文件
 var errUnUseIP error
 fileUnUseIP, errUnUseIP = os.OpenFile(UnuseIpFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
 if nil != errUnUseIP {
  fmt.Printf("打开" + UnuseIpFile + "失败,请检查您的电脑是否设置了权限,无法读写文件")
 }
 defer fileUnUseIP.Close()
 if "" != proxyIps {
  useProxy = true
  //初始化ip池
  InitIpPool()
  ips, _ := gcache.Keys()
  g.Dump("代理ip池:", ips)
 } else {
  useProxy = false
  g.Dump("未使用代理ip")
 }
fetchList(useProxy)
}


fetchList()函数


  1. 合理的休眠,减轻源站压力
  2. 区分是否使用代理
  3. 请求超时或者返回的数据为空,则认为ip被封禁,不再可用,从ip池中移除,获得新的代理ip


func fetchList(useProxy bool) (isSkip bool) {
  isSkip = false
  url := "https://search.xxxx.com/search?keyword=" + keyword
  time.Sleep(SleepTime * time.Second)
  var randIp string
  //区分是否使用代理
  if useProxy {
    ips, _ := gcache.Values()
    if len(ips) == 0 {
      isSkip = true
      g.Dump("ip均不可用,程序退出。")
      return
    }
    randIp = GetRandIp()
    g.Dump("当前代理ip:", randIp)
    if randIp == "" {
      g.Dump("代理ip为空")
      return
    }
  }
  client := ProxyClient(randIp, useProxy)
  resp, err := client.Get(url)
  if err != nil {
    fmt.Println(err.Error())
    fmt.Printf("网络连接超时,切换ip重新请求")
    //移除请求超时的代理ip 重新抓取
    if useProxy {
      RemoveIP(randIp)
    }
    fetchList(useProxy)
    return
  }
  defer resp.Body.Close()
  isSkip = WriteFile(resp.Body)
  if isSkip && !useProxy {
    g.Dump("一直采集不到数据,可能本地ip被封禁,请使用代理ip")
  }
  return
}


定义代理客户端


  1. 设置authority为源码域名
  2. 根据是否使用代理决定是否设置client.SetProxy(ip)
  3. 返回http客户端对象


//代理客户端
func ProxyClient(ip string, useProxy bool) (client *ghttp.Client) {
  client = g.Client()
  client.SetHeader("authority", "search.xxx.com")
  client.SetHeader("cache-control", "max-age=0")
  client.SetHeader("sec-ch-ua", "\"Microsoft Edge\";v=\"95\", \"Chromium\";v=\"95\", \";Not A Brand\";v=\"99\"")
  client.SetHeader("sec-ch-ua-mobile", "?0")
  client.SetHeader("sec-ch-ua-platform", "\"Windows\"")
  client.SetHeader("upgrade-insecure-requests", "1")
  client.SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30")
  client.SetHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
  client.SetHeader("sec-fetch-site", "none")
  client.SetHeader("sec-fetch-mode", "navigate")
  client.SetHeader("sec-fetch-user", "?1")
  client.SetHeader("sec-fetch-dest", "document")
  client.SetHeader("accept-language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7")
  client.SetTimeout(3 * time.Second)
  if useProxy {
    client.SetProxy(ip)
  }
  return
}


维护ip池的方法


  1. 思路非常简单:我使用了gcache来管理ip池
  2. 失效的时候就从ip池中移除
  3. 客户端需要代理ip时从ip池中随机返回一个代理ip


//初始化ip池 维护ip池
func InitIpPool() (ipCount int) {
  ips := proxyIps
  splitStr := strings.Split(ips, ",")
  ipCount = len(splitStr)
  for i := 0; i < ipCount; i++ {
    gcache.Set(splitStr[i], splitStr[i], 0)
  }
  return ipCount
}
//随机获得ip
func GetRandIp() (ip string) {
  ips, _ := gcache.Values()
  rand.Seed(time.Now().Unix())
  randIndex := rand.Intn(len(ips))
  ip = ips[randIndex].(string) //转成string
  return
}
//移除ip
func RemoveIP(ip string) {
  gcache.Remove(ip)
  //失效ip统计
  _, err := fileUnUseIP.WriteString(ip)
  if nil != err {
    fmt.Println("不可用ip写入文件失败:", err)
  }
  _, _ = fileUnUseIP.WriteString("\r\n")
}


输出结果到文件


  1. 获得的数据如何和我们预期的数据不完全一致,可以通过使用正则匹配处理数据re := regexp.MustCompile()
  2. 如果是循环获得数据,可以根据isSkip决定是否跳出本次循环继续执行。


//写入结果
func WriteFile(r io.Reader) (isSkip bool) {
  body, err := ioutil.ReadAll(r)
  if err != nil {
    g.Dump("body err:", err.Error())
  }
  re := regexp.MustCompile(`xxxxxxx`)
  ids := re.FindAllSubmatch(body, -1)
  for _, v := range ids {
    if -1 != strings.Index(string(v[2]), `xxxxxxxx`) {
      _, err := fp.Write(v[1])
      if nil != err {
        fmt.Println("写入文件失败:", err)
      }
      _, _ = fp.WriteString("\r\n")
      IDS = append(IDS, string(v[1]))
    }
  }
  //go没有三目运算
  if len(ids) == 0 {
    isSkip = true
  } else {
    isSkip = false
  }
  return
}


相关文章
|
11月前
|
人工智能 安全 Shell
Go并发编程避坑指南:从数据竞争到同步原语的解决方案
在高并发场景下,如钱包转账,数据一致性至关重要。本文通过实例演示了 Go 中如何利用 `sync.Mutex` 和 `sync.RWMutex` 解决数据竞争问题,帮助开发者掌握并发编程中的关键技能。
Go并发编程避坑指南:从数据竞争到同步原语的解决方案
|
11月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
544 86
|
11月前
|
存储 监控 算法
企业电脑监控系统中基于 Go 语言的跳表结构设备数据索引算法研究
本文介绍基于Go语言的跳表算法在企业电脑监控系统中的应用,通过多层索引结构将数据查询、插入、删除操作优化至O(log n),显著提升海量设备数据管理效率,解决传统链表查询延迟问题,实现高效设备状态定位与异常筛选。
241 3
Go语言同步原语与数据竞争:Mutex 与 RWMutex
在Go语言并发编程中,数据竞争是多个goroutine同时读写共享变量且未加控制导致的问题,可能引发程序崩溃或非确定性错误。为解决此问题,Go提供了`sync.Mutex`和`sync.RWMutex`两种同步机制。`Mutex`用于保护临界区,确保同一时间只有一个goroutine访问;`RWMutex`支持多读单写的细粒度控制,适合读多写少场景。使用时需避免死锁,并借助`-race`工具检测潜在的数据竞争,从而提升程序稳定性和性能。
355 51
|
测试技术 Go API
Go 切片导致 rand.Shuffle 产生重复数据的原因与解决方案
在 Go 语言开发中,使用切片时由于其底层数据共享特性,可能会引发意想不到的 Bug。本文分析了 `rand.Shuffle` 后切片数据重复的问题,指出原因在于切片是引用类型,直接赋值会导致底层数组共享,进而影响原始数据。解决方案是使用 `append` 进行数据拷贝,确保独立副本,避免 `rand.Shuffle` 影响原始数据。总结强调了切片作为引用类型的特性及正确处理方法,确保代码稳定性和正确性。
426 82
|
分布式计算 算法 安全
Go语言泛型-泛型约束与实践
Go语言中的泛型约束用于限制类型参数的范围,提升类型安全性。通过接口定义约束,可实现对数值类型、排序与比较等操作的支持。开发者既可使用标准库提供的预定义约束,如constraints.Ordered和constraints.Comparable,也可自定义约束以满足特定需求。泛型广泛应用于通用数据结构(如栈、队列)、算法实现(如排序、查找)及构建高效可复用的工具库,使代码更简洁灵活。
|
设计模式 人工智能 Go
go 依赖注入实践
依赖注入(DI)是一种软件设计模式,旨在降低代码耦合度,提高代码可测试性和可复用性。其核心思想是将依赖项从外部传入使用对象,而非由其内部创建。通过 DI,模块间关系更清晰,便于维护和扩展。常见实现包括方法注入和接口注入,适用于如 Go 等支持函数式编程和接口抽象的语言。
298 8
|
开发框架 JSON 中间件
Go语言Web开发框架实践:路由、中间件、参数校验
Gin框架以其极简风格、强大路由管理、灵活中间件机制及参数绑定校验系统著称。本文详解其核心功能:1) 路由管理,支持分组与路径参数;2) 中间件机制,实现全局与局部控制;3) 参数绑定,涵盖多种来源;4) 结构体绑定与字段校验,确保数据合法性;5) 自定义校验器扩展功能;6) 统一错误处理提升用户体验。Gin以清晰模块化、流程可控及自动化校验等优势,成为开发者的优选工具。
|
开发框架 安全 前端开发
Go Web开发框架实践:模板渲染与静态资源服务
Gin 是一个功能强大的 Go Web 框架,不仅适用于构建 API 服务,还支持 HTML 模板渲染和静态资源托管。它可以帮助开发者快速搭建中小型网站,并提供灵活的模板语法、自定义函数、静态文件映射等功能,同时兼容 Go 的 html/template 引擎,具备高效且安全的页面渲染能力。
|
开发框架 JSON 中间件
Go语言Web开发框架实践:使用 Gin 快速构建 Web 服务
Gin 是一个高效、轻量级的 Go 语言 Web 框架,支持中间件机制,非常适合开发 RESTful API。本文从安装到进阶技巧全面解析 Gin 的使用:快速入门示例(Hello Gin)、定义 RESTful 用户服务(增删改查接口实现),以及推荐实践如参数校验、中间件和路由分组等。通过对比标准库 `net/http`,Gin 提供更简洁灵活的开发体验。此外,还推荐了 GORM、Viper、Zap 等配合使用的工具库,助力高效开发。

热门文章

最新文章