go get -u github.com/gocolly/colly/...
import "github.com/gocolly/colly"
Colly 的主要实例是收集器对象,当Colly 收集器任务运行时,收集器负责网络通讯和执行附加的回调任务。你必须初始化收集器
c := colly.NewCollector()
全面的收集器属性列表可以在这儿查看,推荐使用colly.NewCollector(options…)这种方法来初始化收集器
c2 := colly.NewCollector(
colly.UserAgent("xy"),
colly.AllowURLRevisit(),
colly.AllowedDomains("hackerspaces.org", "wiki.hackerspaces.org"),
colly.CacheDir("./coursera_cache"),
)
//或
c2 := colly.NewCollector()
c2.UserAgent = "xy"
c2.AllowURLRevisit = true
//或
c := colly.NewCollector(func(c *colly.Collector) {
extensions.RandomUserAgent(c) // 设置随机头
c.Async=true
})
通过重新设置收集器的属性可以在收集任务运行任何节点改变配置。
一个很好的例子是 User-Agent 切换器,在每个请求上改变User-Agent
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())
})
通过环境变量配置
收集器的默认配置可以通过环境变量来改变,它允许我们在不通过编译的情况下对配置进行微调
环境变量解析是在收集器初始化的最后一步执行,所以每个变量在初始化后的改变都可能被环境变量配置覆盖。
环境变量配置
ALLOWED_DOMAINS (多个域名用逗号分割)
CACHE_DIR (string)
DETECT_CHARSET (y/n)
DISABLE_COOKIES (y/n)
DISALLOWED_DOMAINS (多个域名用逗号分割)
IGNORE_ROBOTSTXT (y/n)
MAX_BODY_SIZE (int)
MAX_DEPTH (int - 0 没有限制)
PARSE_HTTP_ERROR_RESPONSE (y/n)
USER_AGENT (string)
HTTP 配置
Colly 使用golang的 http client 作为网络层,HTTP设置可以通过改变默认的HTTP roundtripper来调整
c := colly.NewCollector()
c.WithTransport(&http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
默认情况下,Colly在请求未完成时会阻塞。所以,在回调函数中递归调用Collector.Visit
会产生不断增长的堆栈,使用Collector.Async = true
可以避免(不要忘记了与异步一起使用c.Wait()
)
Colly使用HTTP keep-alive提高爬取速度,它需要打开文件描述符,所以max-fd
限制可以轻松达到长时间运行任务
使用如下代码可以禁用HTTP keep-alive
c := colly.NewCollector()
c.WithTransport(&http.Transport{
DisableKeepAlives: true,
})
你可以把不同类型的回调函数附加到收集器上来控制收集任务,然后取回信息,你可以在包文档中查看相关章节
c.OnRequest(func(r *colly.Request) {
fmt.Println("Visiting", r.URL)
})
c.OnError(func(_ *colly.Response, err error) {
log.Println("Something went wrong:", err)
})
c.OnResponse(func(r *colly.Response) {
fmt.Println("Visited", r.Request.URL)
})
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
e.Request.Visit(e.Attr("href"))
})
c.OnHTML("tr td:nth-of-type(1)", func(e *colly.HTMLElement) {
fmt.Println("First column of a table row:", e.Text)
})
c.OnXML("//h1", func(e *colly.XMLElement) {
fmt.Println(e.Text)
})
c.OnScraped(func(r *colly.Response) {
fmt.Println("Finished", r.Request.URL)
})
OnRequest:在请求之前调用
OnError:在请求中出现错误时调用
OnResponse:响应接收到之后调用
OnHTML:OnResponse 正确执行后,如果接收到的文本是HTML时执行
OnXML:OnResponse 正确执行后,如果接收到的文本是XML时执行
OnScraped:OnXML 回调后调用
// Before making a request print "Visiting ..."
c.OnRequest(func(r *colly.Request) {
fmt.Println("Visiting", r.URL.String())
})
c.OnError(func(r *colly.Response, err error) {
fmt.Println("Request URL:", r.Request.URL, "failed with response:", r, "\nError:", err)
})
// On every a element which has href attribute call callback
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
// Print link
fmt.Printf("Link found: %q -> %s\n", e.Text, link)
// Visit link found on page
// Only those links are visited which are in AllowedDomains
c.Visit(e.Request.AbsoluteURL(link))
e.Request.Visit(link)
e.ForEach("table.basic-info-table tr", func(_ int, el *colly.HTMLElement) {
switch el.ChildText("td:first-child") {
case "Language":
course.Language = el.ChildText("td:nth-child(2)")
case "Level":
course.Level = el.ChildText("td:nth-child(2)")
case "Commitment":
course.Commitment = el.ChildText("td:nth-child(2)")
case "How To Pass":
course.HowToPass = el.ChildText("td:nth-child(2)")
case "User Ratings":
course.Rating = el.ChildText("td:nth-child(2) div:nth-of-type(2)")
}
})
})
c.OnHTML("#currencies-all tbody tr", func(e *colly.HTMLElement) {
writer.Write([]string{
e.ChildText(".currency-name-container"),
e.ChildText(".col-symbol"),
e.ChildAttr("a.price", "data-usd"),
e.ChildAttr("a.volume", "data-usd"),
e.ChildAttr(".market-cap", "data-usd"),
e.ChildAttr(".percent-change[data-timespan=\"1h\"]", "data-percentusd"),
e.ChildAttr(".percent-change[data-timespan=\"24h\"]", "data-percentusd"),
e.ChildAttr(".percent-change[data-timespan=\"7d\"]", "data-percentusd"),
})
})
// Start scraping on https://hackerspaces.org
c.Visit("https://hackerspaces.org/")
有时候将log.Println()
函数放到回调函数中就足够了,但有时候却不行。Colly有收集器调试的内置功能,调试器接口可以使用不同的调试器实现。
从Colly的repo中添加基本的日志调试器debug
import (
"github.com/gocolly/colly"
"github.com/gocolly/colly/debug"
)
func main() {
c := colly.NewCollector(colly.Debugger(&debug.LogDebugger{}))
// [..]
}
你通过实现debug.Debugger类可以创建任何种类的调试器。一个好的例子是 LogDebugger
使用代理切换器仍然保持集中,而HTTP请求分布在多个代理之间。
Colly通过其SetProxyFunc()
函数来切换代理。任意自定义的函数可以通过SetProxyFunc()
传参,只要这个函数签名为func(*http.Request) (*url.URL, error)
注意:通过使用 -D 可以将ssh 服务用到 sockets5代理上
Colly 有一个内置的代理切换器,可以在每个请求上轮流切换代理列表
package main
import (
"github.com/gocolly/colly"
"github.com/gocolly/colly/proxy"
)
func main() {
c := colly.NewCollector()
if p, err := proxy.RoundRobinProxySwitcher(
"socks5://127.0.0.1:1337",
"socks5://127.0.0.1:1338",
"http://127.0.0.1:8080",
); err == nil {
c.SetProxyFunc(p)
}
// ...
}
实现自定义的代理切换器:
var proxies []*url.URL = []*url.URL{
&url.URL{Host: "127.0.0.1:8080"},
&url.URL{Host: "127.0.0.1:8081"},
}
func randomProxySwitcher(_ *http.Request) (*url.URL, error) {
return proxies[random.Intn(len(proxies))], nil
}
// ...
c.SetProxyFunc(randomProxySwitcher)
Colly可以通过内存存储cookie和访问的url,但它可以被任何实现了colly/storage.Storage的后端存储覆盖
现有的后端存储
Colly 默认的后端存储,可以使用collector.SetStorage() 来覆盖
详细信息查阅redis example
扩展是Colly附带的小型辅助工具,插件列表可以在此处获得
以下示例启用随机User-Agent切换器和Referrer setter扩展,并访问httpbin.org两次
import (
"log"
"github.com/gocolly/colly"
"github.com/gocolly/colly/extensions"
)
func main() {
c := colly.NewCollector()
visited := false
extensions.RandomUserAgent(c)
extensions.Referrer(c)
c.OnResponse(func(r *colly.Response) {
log.Println(string(r.Body))
if !visited {
visited = true
r.Request.Visit("/get?q=2")
}
})
c.Visit("http://httpbin.org/get")
}