当我们使用golang进行网络编程时,通常使用自带库 net/http 足够应付日常各类需求。以及在应对简单的登录请求脚本编写,直接使用 http 即可。本次我们主题不是如何使用 http 网络编程,而是如何实现某某网站的自动登录脚本,并进行日常调用,无需进行人工操作。要完成这项需求,我们就离不开爬虫框架的帮助。相比于Python丰富的爬虫框架,go中爬虫框架寥寥无几,其中colly是go实现的轻量化爬虫框架,但是在查阅相应的资料文献却也是少的可怜,这时候我们只有依赖官网文档来学习这个框架。
1、理论篇
通过访问github.com/gocolly/col… 链接,直接上官网用例
import {
"fmt"
"github.com/gocolly/colly"
}
func main() {
// 创建collector
c := colly.NewCollector()
// 监听事件,找到所有页面链接并访问
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
e.Request.Visit(e.Attr("href"))
})
// 请求配置
c.OnRequest(func(r *colly.Request) {
fmt.Println("Visiting", r.URL)
})
c.Visit("http://go-colly.org/")
}
github文档写的很简单,在网上搜罗了一大圈,都是一如既地简洁,但这一点也不妨碍我们使用体验,无非还是从官方用例和源码解析来一步一步学习,我们下面以实战项目来逐步了解colly框架。
step1. 创建项目文件目录
此处以mac os系统为系统环境进行操作:
mkdir /Desktop/scrapshell
该项目基于go1.19版本进行创建,故我们采用Go module的方式进行项目的初始化过程。因此我们需要在本地执行go mod init <module 名>命令
go mod init io.adana/sakura/scrapshell
step2. 安装colly库
安装命令如下:
go get -u github.com/gocolly/colly
最新的go版本可以使用命令如下:
go install github.com/gocolly/colly
step3. 编写代码
首先,我们应该知道colly框架的“三板斧”基本套路:
a. 创建collector
colly.NewCollector()
在这里我们阅读下源码:
// NewCollector 创建一个新的带有默认配置的collector
func NewCollector(options ...func(*Collector)) *Collector {
c := &Collector{}
c.Init()
for _, f := range options {
f(c)
}
c.parseSettingsFromEnv()
return c
}
这里可以很轻易看到带有一个定义Collector类型的可变参数options,不传即为默认配置。那我们看看Collector这个结构体到底有哪些可自定义的配置属性。
// Collector 提供爬虫作业的一个爬取实例
type Collector struct {
// UserAgent 是 User-Agent字符串用于HTTP请求
UserAgent string
// MaxDepth 限制访问URLs的递归深度.
// 对于无限递归默认给定为0.
MaxDepth int
// AllowedDomains 是域名白名单
AllowedDomains []string
// DisallowedDomains 是域名黑名单
DisallowedDomains []string
// DisallowedURLFilters 是限定范围链接的一个规则列表,当任意一条规则能匹配到该链接,则请求将立即停止访问
// DisallowedURLFilters 会优先于URLFilters规则匹配
DisallowedURLFilters []*regexp.Regexp
// URLFilters 与 DisallowedURLFilters 相反
URLFilters []*regexp.Regexp
// AllowURLRevisit 允许同一个链接多次下载
AllowURLRevisit bool
// MaxBodySize 是限制检索到的响应实体字节数
// 0 代表无限制.
// MaxBodySize 默认值是 10MB (10 * 1024 * 1024 bytes).
MaxBodySize int
// CacheDir 指定Get请求缓存为文件的位置,当没给定值,则该配置无效
CacheDir string
// IgnoreRobotsTxt 允许该Collector忽略任何目标宿主robots.txt文件设置性限制.访问http://www.robotstxt.org/查看更多详情
IgnoreRobotsTxt bool
// Async 开启异步网络链接,使用Collector.Wait()可以确保完成所有的请求
Async bool
// ParseHTTPErrorResponse 允许解析不是2xx状态码的http响应.colly默认只会解析成功状态的http响应。
// 设置 ParseHTTPErrorResponse 为 true可以使它生效.
ParseHTTPErrorResponse bool
// ID 是特定collector的唯一标识
ID uint32
// DetectCharset 能在没有显式字符集声明下,针对非utf-8编码的响应体开启编码检测,该特点使用参见 https://github.com/saintfish/chardet
DetectCharset bool
// RedirectHandler允许控制如何管理一个重定向
RedirectHandler func(req *http.Request, via []*http.Request) error
// CheckHead会在每次Get请求预检响应后执行一次head请求
CheckHead bool
...
}
b. 事件监听配置
// 请求相关属性
c.OnRequest(func(r *colly.Resquest) {
r.Header.Set("key", "value")
})
// 响应相关属性
c.OnResponse(func(r *colly.Response) {
r.Header.Set("key", "value")
})
// html页面信息获取;goSelector 选择器可参见 https://github.com/PuerkitoBio/goquery
c.OnHtml("#currencies-all tbody tr", func(e *colly.HTMLElement) {
})
// xml监听,并执行xml标签所在的xml内容 https://github.com/antchfx/xmlquery
c.OnXml("xxxxx", func(x *colly.XMLElement) {
})
// 取消对指定选择器的html监听
c.OnHTMLDetach("#currencies-all tbody tr")
// 取消对指定xml标签的xml监听
c.OnXmlDetach("xxxxxx")
// http请求错误后回调
c.OnError(func (r *colly.Response, e Error) {
})
// 在抓取工作完成后执行,该函数将在OnHtml后执行
c.OnScraped(func(r *colly.Response) {
})
// 自定义http配置
c.WithTransport(&http.Transport {
Proxy: http.ProxyFromEnvironment,
DisableKeepAlives: true, // keep-alive关闭
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},// 开启TLS配置项
DialContext: (&net.Dialer{ // 拨号内容(针对无加密TCP连接)
Timeout: 30 * time.Second, // 超时时间
KeepAlive: 30 * time.Second, // keepAlive 超时时间
DualStack: true, // 开启支持RFC 6555快速回退,即ipv6出现故障,快速切回ipv4
}).DialContext,
})
c. 开启网址访问
c.Visit("https://xxx.xxx.com")
2、试炼篇
通过前面官方文档go-colly.org/docs/ 入门手册的学习,我们看的出官方给出的使用方法很简单,但不难发现保留了强大的自定义配置功能。在进入实战篇前,有些避免网站反爬虫措施是必要的。下面把常见的措施列举出来。
调试 debugger
package main
import (
"fmt"
"github.com/gocolly/colly"
"github.com/gocolly/colly/debug"
)
func main() {
// 创建collector
c := colly.NewCollector(
// 开启debugger模式
colly.Debugger(&debug.LogDebugger{})
)
}
防止IP禁用
package main
import (
"github.com/gocolly/colly"
"github.com/gocolly/colly/proxy"
)
func main() {
c := colly.NewCollector()
// 轮询切换代理
switcher, err := proxy.RoundRobinProxySwitcher(
"https://127.0.0.1:9998",
"http://127.0.0.1:9999"
)
if err != nil {
return
} else {
c.SetProxyFunc(switcher)
}
}
随机UserAgent、重复访问
重复访问
c := colly.NewCollector(
colly.AllowURLRevisit(),
)
随机UserAgent
直接粘贴官方案例
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandomString() string {
b := make([]byte, rand.Intn(10)+10)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
c := colly.NewCollector()
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", RandomString())
})
还有一种方式:
import (
"log"
"github.com/gocolly/colly"
"github.com/gocolly/colly/extensions"
)
func main() {
c := colly.NewCollector()
// 扩展提供随机UserAgent
extensions.RandomUserAgent(c)
}
3.实战篇
下面就是基于某网站实现抓取验证码并识别文本内容
package main
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/gocolly/colly"
"github.com/gocolly/colly/debug"
"github.com/gocolly/colly/extensions"
"github.com/otiai10/gosseract/v2"
"log"
"net/http"
"net/http/cookiejar"
"os"
)
func main() {
var captchaId, img string
var ip = "**.***.**.**"
c := colly.NewCollector(
colly.Debugger(&debug.LogDebugger{}),
colly.AllowedDomains(ip),
)
handleCommonBiz(c)
c.OnResponse(func(r *colly.Response) {
assembleResponseHeaders(r)
fmt.Println("response status:", r.StatusCode)
body := r.Body
captcha := &Captcha{}
convertBodyToJson(body, captcha)
// write to temp file.
writeImgToFile(captcha, img)
// get context from ocr
client := gosseract.NewClient()
defer client.Close()
client.SetImage("test.png")
text, _ := client.Text()
fmt.Println("context:", text)
})
// get captcha
err := c.Visit("https://" + ip + "/usmapi/v1/misc/captcha?r=0.2388254867807843&type=login_image")
if err != nil {
log.Fatal(err)
return
}
}
func handleCommonBiz(c *colly.Collector) {
extensions.RandomUserAgent(c)
cookie, _ := cookiejar.New(nil)
c.SetCookieJar(cookie)
// 解决无证书的https请求无内容的问题
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
c.WithTransport(tr)
c.OnRequest(func(r *colly.Request) {
assembleRequestHeader(r)
})
}
func assembleRequestHeader(r *colly.Request) {
r.Headers.Set("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")
r.Headers.Set("Accept-Encoding", "gzip, deflate, br")
r.Headers.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
r.Headers.Set("Cache-Control", "no-cache")
r.Headers.Set("Connection", "keep-alive")
r.Headers.Set("sec-ch-ua", "Not?A_Brand\";v=\"8\", \"Chromium\";v=\"108\", \"Microsoft Edge\";v=\"108")
r.Headers.Set("sec-ch-ua-mobile", "?0")
r.Headers.Set("sec-ch-ua-platform", "Windows")
r.Headers.Set("Sec-Fetch-Dest", "document")
r.Headers.Set("Sec-Fetch-Mode", "navigate")
r.Headers.Set("Sec-Fetch-Site", "same-origin")
r.Headers.Set("Sec-Fetch-User", "?1")
}
func assembleResponseHeaders(r *colly.Response) {
r.Headers.Set("Server", "nginx")
r.Headers.Set("Content-Type", "text/json")
r.Headers.Set("Transfer-Encoding", "chunked")
r.Headers.Set("Connection", "keep-alive")
r.Headers.Set("ETag", "W/\"631adc36-26f\"")
r.Headers.Set("X-Frame-Options", "sameorigin")
}
func writeImgToFile(captcha *Captcha, img string) {
img = captcha.Data.Image
data, err := base64.StdEncoding.DecodeString(img)
if err != nil {
log.Fatal(err)
}
f, _ := os.OpenFile("test.png", os.O_RDWR|os.O_CREATE, os.ModePerm)
defer func(f *os.File) {
err := f.Close()
if err != nil {
}
}(f)
_, err = f.Write(data)
if err != nil {
return
}
}
func convertBodyToJson(body []byte, captcha *Captcha) {
err := json.Unmarshal(body, captcha)
if err != nil {
log.Fatal("analyse the json has error:", err)
return
}
}
type Data struct {
CaptchaId string `json:"captcha_id"`
Image string `json:"image"`
}
type Captcha struct {
Code string `json:"code"`
Data Data
Msg string `json:"msg"`
}
总结
通过上面一个小需求,我们还是可以一窥colly爬虫框架的使用之道。当然在实战爬虫中这个代码还是一个初步版本的源代码,在后续系列文章中我会持续完善该功能。本章只是简单了解下colly框架的基本使用,其中还有很多特点并未一一展示出来,有需要的童鞋可以直接访问go-colly.org/docs/。