内容简介:本文首发于前排提示:本文是一个入门级教程,讲述基本的爬虫与服务器关系。诸如无头浏览器、js挖取等技术暂不讨论。面对大大小小的爬虫应用,反爬是一个经久不衰的问题。网站会进行一些限制措施,以阻止简单的程序无脑的获取大量页面,这会对网站造成极大的请求压力。
本文首发于 https://imagician.net/archives/93/ 。欢迎到我的博客 https://imagician.net/ 了解更多。
前排提示:本文是一个入门级教程,讲述基本的爬虫与服务器关系。诸如无头浏览器、js挖取等技术暂不讨论。
面对大大小小的爬虫应用,反爬是一个经久不衰的问题。网站会进行一些限制措施,以阻止简单的程序无脑的获取大量页面,这会对网站造成极大的请求压力。
要注意的是,本文在这里说的是,爬取公开的信息。比如,文章的标题,作者,发布时间。既不是隐私,也不是付费的数字产品。网站有时会对有价值的数字产品进行保护,使用更复杂的方式也避免被爬虫“窃取”。这类信息不仅难以爬取,而且不应该被爬取。
网站对公开内容设置反爬是因为网站把访问者当做 “人类” ,人类会很友善的访问一个又一个页面,在页面间跳转,同时还有登录、输入、刷新等操作。机器像是 “见了鬼” 一股脑的“Duang Duang Duang Duang”不停请求某一个Ajax接口,不带登录,没有上下文,加大服务器压力和各种流量、带宽、存储开销。
比如B站的反爬
package main import ( "github.com/zhshch2002/goribot" "os" "strings" ) func main() { s := goribot.NewSpider(goribot.SpiderLogError(os.Stdout)) var h goribot.CtxHandlerFun h= func(ctx *goribot.Context) { if !strings.Contains(ctx.Resp.Text,"按时间排序"){ ctx.AddItem(goribot.ErrorItem{ Ctx: ctx, Msg: "", }) ctx.AddTask(goribot.GetReq("https://www.bilibili.com/video/BV1tJ411V7eg"),h) ctx.AddTask(goribot.GetReq("https://www.bilibili.com/video/BV1tJ411V7eg"),h) ctx.AddTask(goribot.GetReq("https://www.bilibili.com/video/BV1tJ411V7eg"),h) ctx.AddTask(goribot.GetReq("https://www.bilibili.com/video/BV1tJ411V7eg"),h) } } s.AddTask(goribot.GetReq("https://www.bilibili.com/video/BV1tJ411V7eg"),h) s.Run() }
运行上述代码会不停的访问 https://www.bilibili.com/vide... 这个地址。利用 Goribot 自带的错误记录工具,很快B站就封禁了我……可以看到下面图片里B站返回的 HTTP 403 Access Forbidden
。
对不起,又迫害小破站了,我回去就冲大会员去。别打我;-D。
侵入式的反爬手段
很多网站上展示的内容,本身就是其产品,包含价值。这类网站会设置一些参数(比如Token)来更精确的鉴别机器。
图为例,某站的一个Ajax请求就带有令牌Token、签名Signature、以及Cookie里设置了浏览器标识。
此类技术反爬相当于声明了此信息禁止爬取,这类技术不再本文讨论范围内。
遵守“礼仪”
后文中出现的举例以 net/http
和 Goribot 为主,
。
Goribot提供了许多工具,是一个轻量的爬虫框架,具体了解 请见文档 。
go get -u github.com/zhshch2002/goribot
遵守robots.txt
robots.txt是一种存放于网站根目录下(也就是 /robots.txt
)的一个文本文件,也就是txt。这个文件描述了蜘蛛可以爬取哪些页面,不可以爬取哪些。注意这里说的是允许,robots.txt只是一个约定,没有别的用处。
但是,一个不遵守robots.txt的爬虫瞎访问那些不允许的页面,很显然是不正常的(前提是那些被不允许的页面不是爬取的目标,只是无意访问到)。这些被robots.txt限制的页面通常更敏感,因为那些可能是网站的重要页面。
我们限制自己的爬虫不访问那些页面,可以有效地避免某些规则的触发。
Goribot 中对robots.txt的支持使用了 github.com/slyrz/robots 。
s := goribot.NewSpider( goribot.RobotsTxt("https://github.com", "Goribot"), )
这里创建了一个爬虫,并加载了一个robots.txt插件。其中 "Goribot"
是爬虫名字,在robots.txt文件里对不同名字的爬虫可以设置不同的规则,此参数与之相对。 "https://github.com"
是获取robots.txt的地址,因为前文说过robots.txt只能设置在网站根目录,且作用域只有同host下的页面,这里只需设置根目录的URL即可。
控制并发、速率
想像一下,你写了一个爬虫,只会访问一个页面,然后解析HTML。这个程序放在一个死循环里,循环中不停创建新线程。嗯,听起来不错。
对于网站服务器来看,有一个IP,开始很高频请求,而且流量带宽越来越大,一回神3Gbps!!!?你这是访问是来DDos的?果断ban IP。
之后,你就得到了爬虫收集到的一堆 HTTP 403 Access Forbidden
。
当然上述只是夸张的例子,没有人家有那么大的带宽……啊,好像加拿大白嫖王家里就有。而且也没人那么写程序。
控制请求的并发并加上延时,可以很大程度减少对服务器压力,虽然请求速度变慢了。但我们是来收集数据的,不是来把网站打垮的。
在 Goribot 中可以这样设置:
s := goribot.NewSpider( goribot.Limiter(false, &goribot.LimitRule{ Glob: "httpbin.org", Rate: 2, // 请求速率限制(同host下每秒2个请求,过多请求将阻塞等待) }), )
Limiter在Goribot中是一个较为复杂的扩展,能够控制速率、并发、白名单以及随机延时。更多内容请参考)。
技术手段
网站把所有请求者当做人处理,把不像人的行为的特征作为检测的手段。于是我们可以使程序模拟人(以及浏览器)的行为,来避免反爬机制。
UA
作为一个爬虫相关的开发者,UA肯定不陌生,或者叫User-Agent用户代理。比如你用Chrome访问量GitHub的网站,HTTP请求中的UA就是由Chrome浏览器填写,并发送到网站服务器的。UA的字面意思,用户代理,也就是说用户通过什么 工具 来访问网站。(毕竟用户不能自己直接去写HTTP报文吧,开发者除外;-D)
网站可以通过鉴别UA来简单排除一些机器发出的请求。比如Golang原生的 net/http
包中会自动设置一个UA,标明请求由Golang程序发出,很多网站就会过滤这样的请求。
在Golang原生的 net/http
包中,可以这样设置UA:(其中 "User-Agent"
大小写不敏感)
r, _ := http.NewRequest("GET", "https://github.com", nil) r.Header.Set("User-Agent", "Goribot")
在Goribot中可以通过链式操作设置请求时的UA:
goribot.GetReq("https://github.com").SetHeader("User-Agent", "Goribot")
总是手动设置UA很烦人,而且每次都要编一个UA来假装自己是浏览器。于是我们有自动随机UA设置插件:
s := goribot.NewSpider( goribot.RandomUserAgent(), )
Referer
Referer是包含在请求头里的,表示“我是从哪个URL跳转到这个请求的?”简称“我从哪里来?”。如果你的程序一直发出不包含Referer或者其为空的请求,服务器就会发现“诶,小老弟,你从哪来的?神秘花园吗?gun!”然后你就有了 HTTP 403 Access Forbidden
。
在Golang原生的 net/http
包中,可以这样设置Referer:
r, _ := http.NewRequest("GET", "https://github.com", nil) r.Header.Set("Referer", "https://www.google.com")
在Goribot中可装配Referer自动填充插件来为新发起的请求填上上一个请求的地址:
s := goribot.NewSpider( goribot.RefererFiller(), )
Cookie
Cookie应该很常见,各种网站都用Cookie来存储账号等登录信息。Cookie本质上是网站服务器保存在客户端浏览器上的键值对数据,关于Cookie的具体知识可以百度或者谷歌。
创建Goribot爬虫时会顺带一个Cookie Jar,自动管理爬虫运行时的Cookie信息。我们可以为请求设置Cookie来模拟人在浏览器登录时的效果。
使用Golang原生的 net/http
,并启用Cookie Jar,用Cookie设置登录:
package main // 代码来自 https://studygolang.com/articles/10842 ,非常感谢 import ( "fmt" "io/ioutil" "net/http" "net/http/cookiejar" // "os" "net/url" "time" ) func main() { //Init jar j, _ := cookiejar.New(nil) // Create client client := &http.Client{Jar: j} //开始修改缓存jar里面的值 var clist []*http.Cookie clist = append(clist, &http.Cookie{ Name: "BDUSS", Domain: ".baidu.com", Path: "/", Value: "cookie 值xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", Expires: time.Now().AddDate(1, 0, 0), }) urlX, _ := url.Parse("http://zhanzhang.baidu.com") j.SetCookies(urlX, clist) fmt.Printf("Jar cookie : %v", j.Cookies(urlX)) // Fetch Request resp, err = client.Do(req) if err != nil { fmt.Println("Failure : ", err) } respBody, _ := ioutil.ReadAll(resp.Body) // Display Results fmt.Println("response Status : ", resp.Status) fmt.Println("response Body : ", string(respBody)) fmt.Printf("response Cookies :%v", resp.Cookies()) }
在Goribot中可以这样:
s.AddTask(goribot.GetReq("https://www.bilibili.com/video/BV1tJ411V7eg").AddCookie(&http.Cookie{ Name: "BDUSS", Value: "cookie 值xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", Expires: time.Now().AddDate(1, 0, 0), }),handlerFunc)
如此在稍后的 s.Run()
中,这一请求将会被设置Cookie且后续Cookie由Cookie Jar维护。
欢迎关注我们的微信公众号,每天学习 Go 知识
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 利用闲置 VPS 搜寻外星文明(SETI)
- DirtyWords for Xcode - 做个文明的程序员
- 知乎参展网络安全周:用AI算法治理不文明网络语言
- 开发者公布Excel重制《文明1》下载链接,你的CPU顶得住吗?
- 低至 2 折的 Fantastical 2、TextExpander、文明 6……这个 Bundle 能帮你省下不少钱
- 爬虫需谨慎,那些你不知道的爬虫与反爬虫套路!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。