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

本文首发于 https://imagician.net/archives/93/ 。欢迎到我的博客 https://imagician.net/ 了解更多。

前排提示:本文是一个入门级教程,讲述基本的爬虫与服务器关系。诸如无头浏览器、js挖取等技术暂不讨论。

面对大大小小的爬虫应用,反爬是一个经久不衰的问题。网站会进行一些限制措施,以阻止简单的程序无脑的获取大量页面,这会对网站造成极大的请求压力。

要注意的是,本文在这里说的是,爬取公开的信息。比如,文章的标题,作者,发布时间。既不是隐私,也不是付费的数字产品。网站有时会对有价值的数字产品进行保护,使用更复杂的方式也避免被爬虫“窃取”。这类信息不仅难以爬取,而且不应该被爬取。

网站对公开内容设置反爬是因为网站把访问者当做“人类”,人类会很友善的访问一个又一个页面,在页面间跳转,同时还有登录、输入、刷新等操作。机器像是“见了鬼”一股脑的“Duang Duang Duang Duang”不停请求某一个Ajax接口,不带登录,没有上下文,加大服务器压力和各种流量、带宽、存储开销。

比如B站的反爬

package main

import (
    "github.com/zhshch2002/gospider"
    "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/video/BV1tJ411V7eg 这个地址。利用Goribot自带的错误记录工具,很快B站就封禁了我……可以看到下面图片里B站返回的HTTP 403 Access Forbidden

HTTP 403 Access Forbidden

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

侵入式的反爬手段

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

侵入式的反爬手段

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

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

遵守“礼仪”

后文中出现的举例以net/httpGoribot为主,因为那个库是我写的

Goribot提供了许多工具,是一个轻量的爬虫框架,具体了解请见文档

go get -u github.com/zhshch2002/gospider

遵守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维护。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 网络爬虫 网络爬虫(web crawler),以前经常称之为网络蜘蛛(spider),是按照一定的规则自动浏览万维...
    她即我命阅读 1,821评论 0 1
  • 33款可用来抓数据的开源爬虫软件工具 要玩大数据,没有数据怎么玩?这里推荐一些33款开源爬虫软件给大家。 爬虫,即...
    visiontry阅读 7,317评论 1 99
  • 通过User-Agent来控制访问 无论是浏览器还是爬虫程序,在向服务器发起网络请求的时候,都会发过去一个头文件:...
    Jacqueslim阅读 23,826评论 0 64
  • HTTP基本原理 URI、URL、URN(Uninform Resource) URI(Identifier):统...
    GHope阅读 2,076评论 2 26
  • 1、多条件区间判断 【例1】按销售量计算计成比率。 2、多条件组合判断 【例2】如果金额小于500并且B列为“未到...
    伊丽莎白丽阅读 396评论 0 1