专业的编程技术博客社区

网站首页 > 博客文章 正文

聊天机器人训练语料获取之colly爬虫

baijin 2024-10-03 17:26:28 博客文章 5 ℃ 0 评论


概述

笔者近期需要训练连天机器人, 需要准备大量语料。获取语料的方案很多,其中一种是采取爬虫,获取线上问答。调研大部分爬虫,正好colly能满足我的需求。这是一个采用golang编写的爬虫编程框架,思路清晰,操作简单,性能非常不错。但本文重点不在讲述,colly源代码,而在于参数colly爬虫的常用编程场景。


入手


go get -u github.com/gocolly/colly/v2/...


func main() {
	c := colly.NewCollector()

	// Find and visit all links
	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/")
}


核心api说明


初始化

采用colly.NewCollector()初始化,但是有时候我们需要对爬虫参数进行配置,常见参数如下

方法名称说明UserAgent设置ua参数MaxDepth设置循环访问深度,0表示循环访问AllowedDomains字符串,准许抓取的域名DisallowedDomains字符串,不允许抓取的域名DisallowedURLFilters正则表达式,不允许抓取的连接格式URLFilters正则表达式,允许抓取的连接格式AllowURLRevisit是否允许重复抓取,MaxBodySize响应数据大大小限制默认10MB,0表示无限制CacheDir缓存存放目录IgnoreRobotsTxt是否忽略robots规则文件Async是否采用异步网络请求方案 ,需要和Use Collector.Wait()配套使用Debugger日志配置

举个栗子


c := colly.NewCollector(
    colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"),
     colly.MaxDepth(3),
     colly.AllowedDomains("www.techidea8.com","www.baidu.com"),
     colly.URLFilters(
            regexp.MustCompile("http://www.techidea8\\.com/article/\\d+#34;),
			 regexp.MustCompile("http://www.techidea8\\.com/list/\\d+#34;)
     ),
    colly.AllowURLRevisit(),
    colly.CacheDir("../tmp"),
    colly.Debugger(&debug.LogDebugger{}),
	colly.Async(),
)

用户也可以通过c.CacheDir="../tmp"这种方式直接设置


OnHTML(goqueryselect string,callback (e *colly.HTMLElement ))

该函数对于符合goqueryselect选择器规范的dom执行func 函数操作,对于goqueryselect操作规范,可查询goquery文档,这是一个类似jqury的golangdom操作类库https://www.flysnow.org/2018/01/20/golang-goquery-examples-selector.html

一般我们在callback 中调用Visit循环访问。


通过上下文传递数据

colly实例携带Context对象,我们可以通过该对象传输数据。这对一些应用场景非常适用


func main() {
	// Instantiate default collector
	c := colly.NewCollector()

	// Before making a request put the URL with
	// the key of "url" into the context of the request
	c.OnRequest(func(r *colly.Request) {
		r.Ctx.Put("url", r.URL.String())
	})

	// After making a request get "url" from
	// the context of the request
	c.OnResponse(func(r *colly.Response) {
		fmt.Println(r.Ctx.Get("url"))
	})

	// Start scraping on https://en.wikipedia.org
	c.Visit("https://en.wikipedia.org/")
}


限制爬虫访问频次

colly通过控制爬虫客户端数量和网络请求响应时间来限制爬虫频次,这对一些有频次限制的场景非常重要。colly频次限制关键结构体如下


type LimitRule struct {
	// DomainRegexp is a regular expression to match against domains
	DomainRegexp string
	// DomainRegexp is a glob pattern to match against domains
	DomainGlob string
	// Delay is the duration to wait before creating a new request to the matching domains
	Delay time.Duration
	// RandomDelay is the extra randomized duration to wait added to Delay before creating a new request
	RandomDelay time.Duration
	// Parallelism is the number of the maximum allowed concurrent requests of the matching domains
	Parallelism    int
	waitChan       chan bool
	compiledRegexp *regexp.Regexp
	compiledGlob   glob.Glob
}

其中可以通过Parallelism来限制连接到服务器的客户端数量,通过RandomDelay来设置连接时间,也就是下一次连接在什么时候开启。通过DomainRegexp设置该限制对哪个域名有效。当然,该域名限制支持正则表达式。也可以通过DomainGlob来支持匹配*格式化类型的域名如*techidea*demo如下


func main(){
    c := colly.NewCollector(
		colly.AllowedDomains("www.techidea8.com"),
	)
    c.Limit(&colly.LimitRule{
		DomainRegexp: "www.techidea8.com",
		Parallelism:  1,
		RandomDelay:  5 * time.Second,
	})
	c.OnHTML("a[href]", func(e *colly.HTMLElement) {
		href := e.Attr("href")
		if strings.Contains(href, ".html") {
			e.Request.Visit(href)
		}
	})
}


异步抓取一般方法

colly通过colly.Async()来实现爬虫异步抓取,该方法需要和colly.Wait()配套使用。demo如下


func (){


// Instantiate default collector
	c := colly.NewCollector(
		// Attach a debugger to the collector
		colly.Debugger(&debug.LogDebugger{}),
		colly.Async(),
	)

	// Limit the number of threads started by colly to two
	// when visiting links which domains' matches "*httpbin.*" glob
	c.Limit(&colly.LimitRule{
		DomainGlob:  "*httpbin.*",
		Parallelism: 2,
		RandomDelay: 5 * time.Second,
	})

	// Start scraping in four threads on https://httpbin.org/delay/2
	for i := 0; i < 4; i++ {
		c.Visit(fmt.Sprintf("%s?n=%d", url, i))
	}
	// Start scraping on https://httpbin.org/delay/2
	c.Visit(url)
	// Wait until threads are finished
	c.Wait()
}


通过队列来组织爬虫访问行为

如果需要顺序抓取,可以使用队列实现,colly提供了队列的支持。demo如下


func main() {
	url := "https://www.techidea8.com/archives/"

	// Instantiate default collector
	c := colly.NewCollector(colly.AllowURLRevisit())

	// create a request queue with 2 consumer threads
	q, _ := queue.New(
		2, // Number of consumer threads
		&queue.InMemoryQueueStorage{MaxSize: 10000}, // Use default queue storage
	)

	c.OnRequest(func(r *colly.Request) {
		fmt.Println("visiting", r.URL)
		if r.ID < 15 {
			r2, err := r.New("GET", fmt.Sprintf("%s?x=%v", url, r.ID), nil)
			if err == nil {
				q.AddRequest(r2)
			}
		}
	})

	for i := 0; i < 5; i++ {
		// Add URLs to the queue
		q.AddURL(fmt.Sprintf("%s?n=%d", url, i))
	}
	// Consume URLs
	q.Run(c)

}


使用代理

colly支持socket5代理


func main() {
	url := "https://www.techidea8.com/archives/"

	// Instantiate default collector
	c := colly.NewCollector(colly.AllowURLRevisit())

		// Rotate two socks5 proxies
	rp, err := proxy.RoundRobinProxySwitcher("socks5://127.0.0.1:1337", "socks5://127.0.0.1:1338")
	if err != nil {
		log.Fatal(err)
	}
	c.SetProxyFunc(rp)

	// Consume URLs
	q.Run(c)

}


使用代理

colly支持socket5代理


func main() {
	url := "https://www.techidea8.com/archives/"

	// Instantiate default collector
	c := colly.NewCollector(colly.AllowURLRevisit())

		// Rotate two socks5 proxies
	rp, err := proxy.RoundRobinProxySwitcher("socks5://127.0.0.1:1337", "socks5://127.0.0.1:1338")
	if err != nil {
		log.Fatal(err)
	}
	c.SetProxyFunc(rp)
	
	// Consume URLs
	//...

}


上传文件

有时候我们需要把文件作为参数抓取服务器,此时需要采用PostMultipart方法


func generateFormData() map[string][]byte {
	f, _ := os.Open("gocolly.jpg")
	defer f.Close()

	imgData, _ := ioutil.ReadAll(f)

	return map[string][]byte{
		"firstname": []byte("one"),
		"lastname":  []byte("two"),
		"email":     []byte("onetwo@example.com"),
		"file":      imgData,
	}
}

func main() {
	// Start a single route http server to post an image to.
	setupServer()

	c := colly.NewCollector(colly.AllowURLRevisit(), colly.MaxDepth(5))

	//携带文件访问
	c.PostMultipart("http://localhost:8080/", generateFormData())
	
}


登录后抓取

有时候我们需要完成登录后再进行抓取,可以使用Post方法进行登录


func main() {
// create a new collector
	c := colly.NewCollector()

	// 首先进行登录
	err := c.Post("http://example.com/login", map[string]string{"username": "admin", "password": "admin"})
	if err != nil {
		log.Fatal(err)
	}

	// 登录	后再的逻辑操作
	c.OnResponse(func(r *colly.Response) {
		log.Println("response received", r.StatusCode)
	})

	// 开始抓取
	c.Visit("https://example.com/")
}


抓取本地文件

抓取本地文件有俩种思路,一种是将nginx或apache等服务器将本地文件映射成可以访问的网络地址,再进行抓取,另一种思路是利用colly对文件协议的支持,进行抓取,代码如下


func (){
	//注册file协议
	t := &http.Transport{}
	t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))

	c := colly.NewCollector()
	//支持file协议
	c.WithTransport(t)

	//开始抓取
	c.Visit("file://" + dir + "/html/index.html")
	c.Wait()
}


抓取本地文件

抓取本地文件有俩种思路,一种是将nginx或apache等服务器将本地文件映射成可以访问的网络地址,再进行抓取,另一种思路是利用colly对文件协议的支持,进行抓取,代码如下


func (){
	//注册file协议
	t := &http.Transport{}
	t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))

	c := colly.NewCollector()
	//支持file协议
	c.WithTransport(t)

	//开始抓取
	c.Visit("file://" + dir + "/html/index.html")
	c.Wait()
}


个性化header

可以在OnRequest方法中使用Headers.Set方法即可


c.OnRequest(func(r *colly.Request) {
		r.Headers.Set("X-Requested-With", "XMLHttpRequest")
		r.Headers.Set("Referer", "https://www.instagram.com/"+instagramAccount)
		if r.Ctx.Get("gis") != "" {
			gis := fmt.Sprintf("%s:%s", r.Ctx.Get("gis"), r.Ctx.Get("variables"))
			h := md5.New()
			h.Write([]byte(gis))
			gisHash := fmt.Sprintf("%x", h.Sum(nil))
			r.Headers.Set("X-Instagram-GIS", gisHash)
		}
	})


个性化header

可以在OnRequest方法中使用Headers.Set方法即可


c.OnRequest(func(r *colly.Request) {
		r.Headers.Set("X-Requested-With", "XMLHttpRequest")
		r.Headers.Set("Referer", "https://www.instagram.com/"+instagramAccount)
		if r.Ctx.Get("gis") != "" {
			gis := fmt.Sprintf("%s:%s", r.Ctx.Get("gis"), r.Ctx.Get("variables"))
			h := md5.New()
			h.Write([]byte(gis))
			gisHash := fmt.Sprintf("%x", h.Sum(nil))
			r.Headers.Set("X-Instagram-GIS", gisHash)
		}
	})


写在最后

colly代码结构简单,可定制性强,接口友好,是一个不可多得的好作品。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表