0x00
gocolly是使用golang实现的一个爬虫库。之前在爬某些网页的时候做过简单应用,最近在爬某电商网站的时候,发现关于这个还挺有意思,所以趁年前有时间,看了下源码实现。
gocolly内部依赖的是http.Client与goquery。其中goquery的语法和jquery相同,整个流程比较简单。
0x01
gocolly的使用很简单,具体可以看给出的一些例子。
套路一般就是设置相关的回调函数,然后访问某个URL,引擎会根据访问结果来调用相应的回调函数。
回调函数接口包括:
// RequestCallback is a type alias for OnRequest callback functions
type RequestCallback func(*Request) //发起访问前调用回调
// ResponseCallback is a type alias for OnResponse callback functions
type ResponseCallback func(*Response) //访问结束后调用回调
// HTMLCallback is a type alias for OnHTML callback functions
type HTMLCallback func(*HTMLElement) //访问结束后根据不同的querySelector 调用回调
// XMLCallback is a type alias for OnXML callback functions
type XMLCallback func(*XMLElement) //访问结束后根据不同的querySelector 调用回调
// ErrorCallback is a type alias for OnError callback functions
type ErrorCallback func(*Response, error) //发生错误时调用回调
// ScrapedCallback is a type alias for OnScraped callback functions
type ScrapedCallback func(*Response) //针对当前response的其他回调都调用过以后会调用这个回调
访问的接口可以简单调用Visit/Post。
下面就是一个最简单的例子:
func main() {
c := colly.NewCollector(
colly.AllowedDomains("hackerspaces.org", "wiki.hackerspaces.org"),
)
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Printf("Link found: %q -> %s\n", e.Text, link)
c.Visit(e.Request.AbsoluteURL(link))
})
c.OnRequest(func(r *colly.Request) {
fmt.Println("Visiting", r.URL.String())
})
c.Visit("https://hackerspaces.org/")
}
0x02
gocolly的核心在于 Collector 结构体:
type Collector struct {
UserAgent string
MaxDepth int
AllowedDomains []string
DisallowedDomains []string
DisallowedURLFilters []*regexp.Regexp
URLFilters []*regexp.Regexp
AllowURLRevisit bool
MaxBodySize int
CacheDir string
IgnoreRobotsTxt bool
Async bool
ParseHTTPErrorResponse bool
ID uint32
DetectCharset bool
RedirectHandler func(req *http.Request, via []*http.Request) error
store storage.Storage
debugger debug.Debugger
robotsMap map[string]*robotstxt.RobotsData
htmlCallbacks []*htmlCallbackContainer
xmlCallbacks []*xmlCallbackContainer
requestCallbacks []RequestCallback
responseCallbacks []ResponseCallback
errorCallbacks []ErrorCallback
scrapedCallbacks []ScrapedCallback
requestCount uint32
responseCount uint32
backend *httpBackend
wg *sync.WaitGroup
lock *sync.RWMutex
}
关于这个结构体我只介绍其中几个比较关键的字段:
- UserAgent:ua的设置还是比较关键的,一般网站在反爬虫的时候都会检查ua字段。colly默认的ua简直送上门给人毙的。
对ua的设置可以看gocolly.extensions 包的 RandomUserAgent 函数,这个函数会随机生成某个版本的FF和chrome的ua。 - MaxDepth:用于控制某个请求的深度。我们知道在爬虫访问中有可能会在某个Request的基础上继续访问。这里每多访问一层就会将depth+1,如果超过MaxDepth,那么访问终止。默认值为0,即不检查深度。
- AllowedDomains:爬虫允许访问的domain,这里只能做精确匹配。
- DisallowedDomains:你懂的。当然我们知道,在大部分ACL实现中,deny的项都位于allow之前,这里也不例外。所以对url的检查会先检查 disallow再检查allow。
- DisallowedURLFilters:正则表达式,用于检查具体的url,等于空就是没有拒绝项。
- URLFilters:正则表达式,用于检查具体url,等于空就是全部允许。
- AllowURLRevisit:是否允许相同的url被二次访问。
- MaxBodySize:对response包体大小的限制。
- CacheDir:对特定url访问的cache。比较奇怪的时没有看到过期时间的限制。所以当成功get完某个url(http status < 500)并且开启了cache功能,那么之后对这个url的访问都会直接访问cache。
- IgnoreRobotsTxt:事实上应该很少有人会遵守robotstxt了吧。
- Async:异步访问。但是这个标志是针对所有请求的,也就是说当这个标志被设置了,没法知道某个具体的请求是否完成。Collector.Wait() 函数之后在所有请求完成时才会返回。当然对单个request的异步完成事件也是很容易实现的。既可以使用原有的回调函数;也可以使用context来通知。
- backend:真正的http访问引擎。
0x03
当 Collector 初始化完成之后,colly支持 Get/Post/Post Multi-part发起请求,同时可以自定义请求头部。在colly.Request中,我们可以设置一个Context字段,这个字段可以保存与访问有关的数据结构。
发起请求实际上是通过调用 http.Client 接口实现的。http.Client包中对cookie的实现挺不错,实际上我们已经不用额外增加对cookie的处理。
对3xx的情况,http.Client也做了比较好的处理。不过对内的跳转做了10次的限制。每次跳转都会设置相应的 Referer 值。
另外为了对抗某些简单的防御,colly的httpBackend还支持设置一些 LimitRule。LimitRule可以设置对访问某个域名的间隔(固定delay+随机delay)。另外LimitRule可以限制对某个域名的并发访问数量。LimitRule通过正则表达式来匹配 HOST。
0x04
接下来介绍下colly的一些杂项。
- colly实现了InMemoryStorage用语记录访问过的url。如果 Collector 的AllowURLRevisit没有被设置,那么无法访问同一url。
在爬某些网站时,我们还是需要把 AllowURLRevisit 标志置上。否则某些由于访问失败重新回到url池的url就无法再次访问了。 - colly实现了一个queue,相当于一个url池。
- colly提供了proxy功能,具体的proxy还是会通过http.Client实现。在调试中可以设置 burpsuite 的代理地址,抓取https包。
- extension包实现了 Referrer 功能。
func Referrer(c *colly.Collector) {
c.OnResponse(func(r *colly.Response) {
r.Ctx.Put("_referrer", r.Request.URL.String())
})
c.OnRequest(func(r *colly.Request) {
if ref := r.Ctx.Get("_referrer"); ref != "" {
r.Headers.Set("Referrer", ref)
}
})
}
可以看出,这里Referrer是通过Request的Ctx字段进行传递的。也就是说我们需要复用一个Request持续访问页面内链接。另外Referrer其实是增加了两个回调函数。因此只需要调用一次,后续都会自动实现referer功能。
- debug包实现了调试功能。日志可以打印到io.Writer接口或者web中。
0x05
colly在日常使用中,应该满足了大部分的需求。对于对抗这块,也做了一些比较基础的功能。更深入的功能,就需要自己来慢慢研究了。