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语言在并发编程中的独特优势,揭秘其高效实现的底层机制。本文通过实例和分析,引导读者从基础到进阶,掌握Goroutines、Channels等核心概念,提升并发处理能力。 ###
|
2月前
|
安全 Serverless Go
Go语言中的并发编程:深入理解与实践####
本文旨在为读者提供一个关于Go语言并发编程的全面指南。我们将从并发的基本概念讲起,逐步深入到Go语言特有的goroutine和channel机制,探讨它们如何简化多线程编程的复杂性。通过实例演示和代码分析,本文将揭示Go语言在处理并发任务时的优势,以及如何在实际项目中高效利用这些特性来提升性能和响应速度。无论你是Go语言的初学者还是有一定经验的开发者,本文都将为你提供有价值的见解和实用的技巧。 ####
|
2月前
|
算法 安全 程序员
Go语言的并发编程:深入理解与实践####
本文旨在探讨Go语言在并发编程方面的独特优势及其实现机制,通过实例解析关键概念如goroutine和channel,帮助开发者更高效地利用Go进行高性能软件开发。不同于传统的摘要概述,本文将以一个简短的故事开头,引出并发编程的重要性,随后详细阐述Go语言如何简化复杂并发任务的处理,最后通过实际案例展示其强大功能。 --- ###
|
2月前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
3月前
|
Cloud Native Go API
Go语言在微服务架构中的创新应用与实践
本文深入探讨了Go语言在构建高效、可扩展的微服务架构中的应用。Go语言以其轻量级协程(goroutine)和强大的并发处理能力,成为微服务开发的首选语言之一。通过实际案例分析,本文展示了如何利用Go语言的特性优化微服务的设计与实现,提高系统的响应速度和稳定性。文章还讨论了Go语言在微服务生态中的角色,以及面临的挑战和未来发展趋势。
|
3月前
|
SQL 关系型数据库 MySQL
Go语言项目高效对接SQL数据库:实践技巧与方法
在Go语言项目中,与SQL数据库进行对接是一项基础且重要的任务
107 11
|
3月前
|
Go 开发者
Go语言中的并发编程:从基础到实践
在当今的软件开发中,并发编程已经成为了一项不可或缺的技能。Go语言以其简洁的语法和强大的并发支持,成为了开发者们的首选。本文将带你深入了解Go语言中的并发编程,从基础概念到实际应用,帮助你掌握这一重要的编程技能。
|
2月前
|
NoSQL 测试技术 Go
自动化测试在 Go 开源库中的应用与实践
本文介绍了 Go 语言的自动化测试及其在 `go mongox` 库中的实践。Go 语言通过 `testing` 库和 `go test` 命令提供了简洁高效的测试框架,支持单元测试、集成测试和基准测试。`go mongox` 库通过单元测试和集成测试确保与 MongoDB 交互的正确性和稳定性,使用 Docker Compose 快速搭建测试环境。文章还探讨了表驱动测试、覆盖率检查和 Mock 工具的使用,强调了自动化测试在开源库中的重要性。
|
2月前
|
Go
Go语言中的并发编程:深入探索与实践###
探索Go语言的并发编程,就像解锁了一把高效处理复杂任务的钥匙。本文旨在通过简明扼要的方式,阐述Goroutines和Channels如何协同工作,以实现高效的并发处理。不同于传统的技术文档,这里我们将用一个生动的故事来串联起这些概念,让你在轻松阅读中领悟到并发编程的精髓。 ###
|
3月前
|
消息中间件 监控 Go
Go语言在微服务架构中的优势与实践
【10月更文挑战第10天】Go语言在微服务架构中的优势与实践