18

Golang 爬虫教程 | 解决反爬问题 | 做一个文明的爬虫

 4 years ago
source link: https://studygolang.com/articles/28065
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文首发于 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

bIjQjaB.png!web

对不起,又迫害小破站了,我回去就冲大会员去。别打我;-D。

侵入式的反爬手段

很多网站上展示的内容,本身就是其产品,包含价值。这类网站会设置一些参数(比如Token)来更精确的鉴别机器。

2a2aQ3F.png!web

图为例,某站的一个Ajax请求就带有令牌Token、签名Signature、以及Cookie里设置了浏览器标识。

此类技术反爬相当于声明了此信息禁止爬取,这类技术不再本文讨论范围内。

遵守“礼仪”

后文中出现的举例以 net/httpGoribot 为主,

因为那个库是我写的

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知识

FveQFjN.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK