05 April 2015
用Golang实现的搜索引擎爬虫maodou。

去年开始学习搜索引擎,目前主要研究的是爬虫这块。主要是因为爬虫相对简单一些,索引这块不是很了解,只能借助于Solr实现。

之前文档《如何开发搜索引擎——爬虫(一)》介绍了一个比较传统的抓取方式。就是把互联网当作是一张有向图,通过遍历这张图来抓取网站。后来也实现了抓取的爬虫,但一直没有啥实际用途,因为这么抓只能做成Google那种传统搜索引擎。

后来发现了大神binux的垂直搜索爬虫pyspider,用Python写的一个垂直抓取框架。试用了一翻,确实很好用,还有他的demo。这是他抓取豆瓣害羞组的效果页。看到这个才发现,做这个挺有意思的。

Python基本不会,索性就用Golang写了,看了看他的抓取效果,想着自己基于也有的知识也是能够实现的,就用Golang写了一个抓取框架maodou(maodou是媳妇的名字)。抓取害羞组的代码如下,我也是将结果存在了多说

package main

import (
	"github.com/PuerkitoBio/goquery"
	"github.com/mnhkahn/maodou"
	"github.com/mnhkahn/maodou/cygo"
	"github.com/mnhkahn/maodou/dao"
	"github.com/mnhkahn/maodou/models"
	"strings"
)

type Haixiu struct {
	maodou.MaoDou
}

func (this *Haixiu) Start() {
	this.Index(this.Cawl("http://www.douban.com/group/haixiuzu/discussion"))
}

func (this *Haixiu) Index(resp *maodou.Response) {
	resp.Doc(`#content > div > div.article > div:nth-child(2) > table > tbody > tr > td.title > a`).Each(func(i int, s *goquery.Selection) {
		href, has := s.Attr("href")
		if has {
			this.Detail(this.Cawl(href))
		}
	})
}

func (this *Haixiu) Detail(resp *maodou.Response) {
	res := new(models.Result)
	res.Id = strings.Split(resp.Url, "/")[5]
	res.Title = resp.Doc("#content > h1").Text()
	res.Author = resp.Doc("#content > div > div.article > div.topic-content.clearfix > div.topic-doc > h3 > span.from > a").Text()
	res.Figure, _ = resp.Doc("#link-report > div.topic-content > div.topic-figure.cc > img").Attr("src")
	res.Link = resp.Url
	res.Source = "www.douban.com/group/haixiuzu/discussion"
	res.ParseDate = cygo.Now()
	res.Description = "haixiuzu"
	this.Result(res)
}

func (this *Haixiu) Result(result *models.Result) {
	if result.Figure != "" {
		Dao, err := dao.NewDao("duoshuo", `{"short_name":"cyeam","secret":"df66f048bd56cba5bf219b51766dec0d"}`)
		if err != nil {
			panic(err)
		}
		Dao.AddResult(result)
	}
}

func main() {
	maodou.Register(new(Haixiu), 30)
}

介绍一下抓取害羞租时候遇到的问题。抓取的难度就在于模拟浏览。因为小组里的东西肯定是不让随便抓走的,所以就要防止机器抓取。

  1. 第一个是请求头里面的UserAgent,本来想写自己的Cyeambot,发现不行,人家会拦掉不认识的设备;
  2. 第二个是请求头里面的Referer。这个代表了当前请求来源页面。如果是空,就认为是直接在浏览器打开的,或者是从书签打开的。。如果请求的来源一直是空,就有可能是爬虫。但是具体问题具体分析,比如害羞组的首页http://www.douban.com/group/haixiuzu/discussion,常用用户肯定会把这个地址保存在书签里面,所以前面的防爬虫机制尽量不起作用,而里面的帖子,http://www.douban.com/group/topic/73646091/,帖子地址是不可预估的,理论上只能通过前面讨论组的地址进入,那么如果打开具体帖子的Referer也还是空,这样就可以认为是爬虫。我抓取的时候就有遇到这样的问题,有时候会有302的情况出现,就是访问帖子,有一定的概率会跳转到登陆页,让你登陆来防范爬虫。这样应对也不难,就是抓取帖子的时候把Referer加上害羞组的地址即可;
  3. 关于访问次数限制,害怕被封杀IP,我都是没30分钟抓取一次,每访问一次就等待5秒钟,尽量模拟得像人一些。这样没半个小时会访问21次,量不算大。程序运行了快一个月了,IP没有被拦截的迹象。之前抓取CSDN,没有休息5秒钟这一步,爬了一周就不能爬了。。。

最后再说一些没用到的但是很牛逼的技术:

  1. IP代理抓取。如果你有21台代理机器,那么上面的抓取可以同时进行。这样能大大降低抓取时间。这一般是企业级爬虫要用到的,对抓取速度有要求,像我这种半小时抓21页的就不需要用这个。如果有代理服务器,抓取豆瓣所有帖子也可以很快。当然,这样也会有问题,就是你抓取这么多东西就会有被封杀IP的风险了,所以大规模抓取还需要定时更换代理IP。其实这招可以被认为是黑科技,就是去搞中了病毒的电脑(俗称肉鸡),用这个进行代理,一般网上都有卖的;
  2. 关于封杀IP。你只要别太恶意去抓,一般也是不会去封你IP的。现在都还是IPv4,IP地址是有限的,而设备越来越多,封杀一个IP有可能导致整个小区都没办法访问该网站了,所以也不用特别担心;
  3. 如何更像人去抓取。有一个模拟器,叫做selenium。我发现Golang也有相应的客户端tebeka - selenium。用这个,你模拟登陆,自动记录你的Cookie并附带到抓取请求头里面,这样有了登陆信息,就更难进行封杀了。

最后就是展示部分,点这里访问。关于前端图片瀑布流的东西我一窍不通,完全抄袭了binux大神写的前端代码,请大神原谅。。。此爬虫并没有去抓取豆瓣小组里面的图片,只是记录了地址,所以在展示的时候还是需要访问豆瓣的图片地址,这就属于盗链图片。豆瓣肯定会对此进行处理。

你把豆瓣的图片放在你自己的网页上面展示,这就属于盗链了。浏览器在加载完HTML之后会去加载图片,这时图片请求会自动追加Referer,也就是你自己网页的地址,豆瓣的图片服务器发现请求来源是其他站点的,就会取消响应。

  1. 盗链图片有两种方式,第一种是在网页里面加iframe,这个标签能新建一个嵌套的HTML,这样浏览器在请求图片的时候不会自动追加Referer,图片就能打开了;

  2. 还有一种方式,就是用HTTPS发送请求。你把自己站点做成HTTPS的请求,这样在请求图片的时候,浏览器会把Referer去掉,保护用户隐私。搭建HTTPS服务器需要SSL证书,一般是一年$10。我去申请一个免费的,人家就是不同意。无奈之下,只能自己给自己颁发证书。坏处就是,用户第一次登陆我的站点的时候,浏览器会提示你不安全。铁道部不是也这样么。。。盗链图片的详细说明请参考最下面的两篇引用文章。


最近GitHub被DDos了,我的博客访问量也有点降低了。本来量就不多。。。是不是需要把博客牵走了。


最近有个同事离职了,跟我一起入职的,有点惋惜。可能互联网就是这样。


参考文献
  1. 《HTTP Referer安全与反反盗链》
  2. 《Nginx下绑定自己签发的免费SSL数字安全证书》 - 朱俊

原文链接:垂直搜索爬虫——maodou,转载请注明来源!

EOF