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
}


相关文章
|
3天前
|
Go 开发者
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
|
3天前
|
负载均衡 算法 数据库连接
Go语言性能优化实践:案例分析与解决方案
【2月更文挑战第18天】本文将通过具体的案例分析,探讨Go语言性能优化的实践方法和解决方案。我们将分析几个典型的性能瓶颈问题,并详细介绍如何通过优化代码、调整并发模型、改进内存管理等方式来提升程序的性能。通过本文的学习,读者将能够掌握一些实用的Go语言性能优化技巧,为实际项目开发中的性能优化工作提供指导。
|
3天前
|
运维 网络协议 安全
长连接网关技术专题(十):百度基于Go的千万级统一长连接服务架构实践
本文将介绍百度基于golang实现的统一长连接服务,从统一长连接功能实现和性能优化等角度,描述了其在设计、开发和维护过程中面临的问题和挑战,并重点介绍了解决相关问题和挑战的方案和实践经验。
117 1
|
3天前
|
Java Go C++
Go语言中的面向对象编程实践
【2月更文挑战第10天】本文将深入探讨在Go语言中如何进行面向对象编程实践。我们将了解如何在Go中实现封装、继承和多态,以及如何利用结构体、接口和方法来构建健壮和可维护的对象导向程序。通过实际代码示例,我们将更好地理解Go的OOP特性,并学习如何有效地运用它们。
|
3天前
|
Go 开发者
Go语言中的错误处理与异常机制:实践与最佳策略
【2月更文挑战第7天】Go语言以其独特的错误处理机制而闻名,它鼓励显式错误检查而不是依赖于异常。本文将探讨错误处理与异常机制在Go语言中的实际应用,并分享一些最佳实践,帮助开发者编写更加健壮和易于维护的Go代码。
|
3天前
|
消息中间件 Go API
基于Go语言的微服务架构实践
随着云计算和容器化技术的兴起,微服务架构成为了现代软件开发的主流趋势。Go语言,以其高效的性能、简洁的语法和强大的并发处理能力,成为了构建微服务应用的理想选择。本文将探讨基于Go语言的微服务架构实践,包括微服务的设计原则、服务间的通信机制、以及Go语言在微服务架构中的优势和应用案例。
|
3天前
|
存储 编译器 Go
Go语言学习12-数据的使用
【5月更文挑战第5天】本篇 Huazie 向大家介绍 Go 语言数据的使用,包含赋值语句、常量与变量、可比性与有序性
50 6
Go语言学习12-数据的使用
|
3天前
|
Java Go
Go语言学习11-数据初始化
【5月更文挑战第3天】本篇带大家通过内建函数 new 和 make 了解Go语言的数据初始化过程
29 1
Go语言学习11-数据初始化
|
3天前
|
消息中间件 Go API
Golang深入浅出之-Go语言中的微服务架构设计与实践
【5月更文挑战第4天】本文探讨了Go语言在微服务架构中的应用,强调了单一职责、标准化API、服务自治和容错设计等原则。同时,指出了过度拆分、服务通信复杂性、数据一致性和部署复杂性等常见问题,并提出了DDD拆分、使用成熟框架、事件驱动和配置管理与CI/CD的解决方案。文中还提供了使用Gin构建HTTP服务和gRPC进行服务间通信的示例。
29 0
|
3天前
|
存储 缓存 监控
【Go语言专栏】Go语言应用的性能调优实践
【4月更文挑战第30天】本文介绍了Go语言应用的性能调优技巧,包括使用`pprof`进行性能分析、选择正确算法与数据结构、减少内存分配、优化并发及避免阻塞操作、选用合适锁机制。此外,文章还提到了编译选项如`-trimpath`和`-ldflags`,以及系统资源和环境调优。通过实例展示了代码优化、并发处理和锁的使用。最后,推荐了进一步学习资源,鼓励读者深入探索Go语言的性能优化。