vivo 互联网安全团队- Xie Peng

互联网的大数据年代的降临,网络爬虫也成了互联网中一个重要职业,它是一种自动获取网页数据信息的爬虫程序,是网站搜索引擎的重要组成部分。经过爬虫,可以获取自己想要的相关数据信息,让爬虫协助自己的作业,然后下降本钱,进步事务成功率和进步事务效率。

本文一方面从爬虫与反反爬的视点来阐明如何高效的对网络上的揭露数据进行爬取,另一方面也会介绍反爬虫的技能手法,为防止外部爬虫大批量的收集数据的进程对服务器形成超负载方面提供些许建议。

爬虫指的是依照必定规则自动抓取万维网信息的程序,本次主要会从爬虫的技能原理与完结,反爬虫与反反爬虫两个方面进行简略的介绍,介绍的事例均只是用于安全研究和学习,并不会进行很多爬虫或许运用于商业。

一、爬虫的技能原理与完结

1.1 爬虫的界说

爬虫分为通用爬虫和聚集爬虫两大类,前者的方针是在坚持必定内容质量的状况下爬取尽可能多的站点,比方百度这样的搜索引擎便是这种类型的爬虫,如图1是通用搜索引擎的根底架构:

  1. 首要在互联网中选出一部分网页,以这些网页的链接地址作为种子URL;

  2. 将这些种子URL放入待抓取的URL队列中,爬虫从待抓取的URL队列顺次读取;

  3. 将URL经过DNS解析,把链接地址转化为网站服务器对应的IP地址;

  4. 网页下载器经过网站服务器对网页进行下载,下载的网页为网页文档方法;

  5. 对网页文档中的URL进行抽取,并过滤掉现已抓取的URL;

  6. 对未进行抓取的URL继续循环抓取,直至待抓取URL队列为空。

爬虫与反爬虫技术简介

图1.通用搜索引擎的根底架构

爬虫一般从一个或多个 URL 开端,在爬取的进程中不断将新的并且符合要求的 URL 放入待爬队列,直到满意程序的中止条件。

而咱们日常见到的爬虫根本为后者,方针是在爬取少数站点的状况下尽可能坚持精准的内容质量。典型的比方图2抢票软件所示,便是运用爬虫来登录售票网络并爬取信息,然后辅佐商业。

爬虫与反爬虫技术简介

图2.抢票软件

了解了爬虫的界说后,那么应该如何编写爬虫程序来爬取咱们想要的数据呢。咱们可以先了解下现在常用的爬虫结构,由于它可以将一些常见爬虫功用的完结代码写好,然后留下一些接口,在做不同的爬虫项目时,咱们只需求依据实际状况,手写少数需求变动的代码部分,并依照需求调用这些接口,即可以完结一个爬虫项目。

1.2 爬虫结构介绍

常用的搜索引擎爬虫结构如图3所示,首要Nutch是专门为搜索引擎规划的爬虫,不适合用于精确爬虫。Pyspider和Scrapy都是python语言编写的爬虫结构,都支持分布式爬虫。另外Pyspider由于其可视化的操作界面,相比Scrapy全命令行的操刁难用户愈加友爱,可是功用不如Scrapy强壮。

爬虫与反爬虫技术简介

图3.爬虫结构对比

1.3 爬虫的简略示例

除了运用爬虫结构来进行爬虫,也可以从头开端来编写爬虫程序,进程如图4所示:

爬虫与反爬虫技术简介

图4.爬虫的根本原理

接下来经过一个简略的比方来实际演示上述的进程,咱们要爬取的是某运用市场的榜单,以这个作为比方,是由于这个网站没有任何的反爬虫手法,咱们经过上面的进程可以轻松爬取到内容。

爬虫与反爬虫技术简介

爬虫与反爬虫技术简介

图5.网页与其对应的源代码

网页与其对应的源代码如图5所示,关于网页上的数据,假定咱们想要爬取排行榜上每个app的称号以及其分类。

咱们首要剖析网页源代码,发现可以直接在网页源代码中搜索到“抖音”等app的称号,接着看到app称号、app类别等都是在一个

  • 标签里,所以咱们只需求恳求网页地址,拿到回来的网页源代码,然后对网页源代码进行正则匹配,提取出想要的数据,保存下来即可,如图6所示。
    #获取网页源码
    def get_one_page(url):
        try:
            response = requests.get(url)
            if response.status_code == 200:
               return response.text
            return None
        except RequestException:
          return None
    #正则匹配提取方针信息并形成字典
    def parse_one_page(html):
        pattern = re.compile('<li>.*?src="https://juejin.im/post/7145268210126815239/(.*?)".*?<h5>.*?det.*?>(.*?)</a>.*?p.*?<a.*?>(.*?)</a>.*?</li>',re.S)
        items = re.findall(pattern, html)
        j = 1
        for item in items[:-1]:
            yield {'index': str(j),
                'name': item[1],
                'class':item[2]
            }
            j = j+1
    #成果写入txt
    def write_to_file(content):
        with open(r'test.txt', 'a', encoding='utf-8') as f:
            f.write(json.dumps(content, ensure_ascii=False)+'\n')
    

    爬虫与反爬虫技术简介

    图6.爬虫的代码以及成果

    二、反爬虫相关技能

    在了解详细的反爬虫办法之前,咱们先介绍下反爬虫的界说和意义,约束爬虫程序拜访服务器资源和获取数据的行为称为反爬虫。爬虫程序的拜访速率和意图与正常用户的拜访速率和意图是不同的,大部分爬虫会无节制地对方针运用进行爬取,这给方针运用的服务器带来巨大的压力。爬虫程序宣布的网络恳求被运营者称为“废物流量”。开发者为了保证服务器的正常工作或下降服务器的压力与运营本钱,不得不使出各种各样的技能手法来约束爬虫对服务器资源的拜访。

    所以为什么要做反爬虫,答案是显然的,爬虫流量会进步服务器的负载,过大的爬虫流量会影响到服务的正常工作,然后形成收入损失,另一方面,一些中心数据的外泄,会使数据拥有者失去竞争力。

    常见的反爬虫手法,如图7所示。主要包括文本混杂、页面动态烘托、验证码校验、恳求签名校验、大数据风控、js混杂和蜜罐等,其间文本混杂包括css偏移、图片假装文本、自界说字体等,而风控策略的制定则往往是从参数校验、行为频率和方法反常等方面动身的。

    爬虫与反爬虫技术简介

    图7.常见的反爬虫手法

    2.1 CSS偏移反爬虫

    在搭建网页的时分,需求用CSS来控制各类字符的方位,也正是如此,可以运用CSS来将浏览器中显现的文字,在HTML中以乱序的办法存储,然后来约束爬虫。CSS偏移反爬虫,便是一种运用CSS款式将乱序的文字排版成人类正常阅览顺序的反爬虫手法。这个概念不是很好了解,咱们可以经过对比两段文字来加深对这个概念的了解:

    • **HTML 文本中的文字:**我的学号是 1308205,我在北京大学读书。

    • 浏览器显现的文字:我的学号是 1380205,我在北京大学读书。

    以上两段文字中浏览器显现的应该是正确的信息,假如咱们按之前说到的爬虫进程,剖析网页后正则提取信息,会发现学号是错的。

    接着看图8所示的比方,假如咱们想爬取该网页上的机票信息,首要需求剖析网页。红框所示的价格467对应的是中国民航的从石家庄到上海的机票,可是剖析网页源代码发现代码中有 3 对 b 标签,第 1 对 b 标签中包括 3 对 i 标签,i 标签中的数字都是 7,也便是说第 1 对 b 标签的显现成果应该是 777。而第 2 对 b 标签中的数字是 6,第 3 对 b 标签中的数字是 4,这样的话咱们会无法直接经过正则匹配得到正确的机票价格。

    爬虫与反爬虫技术简介

    图8.CSS 偏移反爬虫比方

    2.2 图片假装反爬虫

    图片假装反爬虫,它的本质便是用图片替换了本来的内容,然后让爬虫程序无法正常获取,如图9所示。这种反爬虫的原理非常简略,便是将本应是普通文本内容的部分在前端页面中用图片来进行替换,遇到这种事例可以直接用ocr辨认图片中的文字就可以绕过。并且由于是用图片替换文本显现,所以图片本身会相对比较清晰,没有很多噪声搅扰,ocr辨认的成果会很精确。

    爬虫与反爬虫技术简介

    图9. 图片假装反爬虫比方

    2.3 自界说字体反爬虫

    在 CSS3 年代,开发者可以运用@font-face为网页指定字体。开发者可将心仪的字体文件放在 Web 服务器上,并在 CSS 款式中运用它。用户运用浏览器拜访 Web 运用时,对应的字体会被浏览器下载到用户的计算机上,可是咱们在运用爬虫程序时,由于没有相应的字体映射联系,直接爬取就会无法得到有用数据。

    如图10所示,该网页中每个店铺的评价数、人均、口味、环境等信息均是乱码字符,爬虫无法直接读取到内容。

    爬虫与反爬虫技术简介

    图10. 自界说字体反爬虫比方

    2.4 页面动态烘托反爬虫

    网页按烘托办法的不同,大体可以分为客户端和服务端烘托。

    • 服务端烘托,页面的成果是由服务器烘托后回来的,有用信息包括在恳求的 HTML 页面里面,经过检查网页源代码可以直接检查到数据等信息;

    • 客户端烘托,页面的主要内容由 JavaScript 烘托而成,真实的数据是经过 Ajax 接口等方法获取的,经过检查网页源代码,无有用数据信息。

    客户端烘托和服务器端烘托的最重要的区别便是究竟是谁来完结html文件的完整拼接,假如是在服务器端完结的,然后回来给客户端,便是服务器端烘托,而假如是前端做了更多的作业完结了html的拼接,则便是客户端烘托。

    爬虫与反爬虫技术简介

    爬虫与反爬虫技术简介

    图11.客户端烘托比方

    2.5 验证码反爬虫

    简直所有的运用程序在涉及到用户信息安全的操作时,都会弹出验证码让用户进行辨认,以确保该操作为人类行为,而不是大规模运转的机器。那为什么会呈现验证码呢?在大多数景象下是由于网站的拜访频率过高或许行为反常,或许是为了直接约束某些自动化行为。归类如下:

    1. 很多状况下,比方登录和注册,这些验证码简直是必现的,它的意图便是为了约束歹意注册、歹意爆炸等行为,这也算反爬的一种手法。

    2. 一些网站遇到拜访频率过高的行为的时分,可能会直接弹出一个登录窗口,要求咱们登录才能继续拜访,此刻的验证码就直接和登录表单绑定在一起了,这就算检测到反常之后运用强制登录的办法进行反爬。

    3. 一些较为常规的网站假如遇到拜访频率稍高的景象的时分,会自动弹出一个验证码让用户辨认并提交,验证当时拜访网站的是不是真实的人,用来约束一些机器的行为,完结反爬虫。

    常见的验证码方法包括图形验证码、行为验证码、短信、扫码验证码等,如图12所示。关于能否成功经过验证码,除了可以精确的依据验证码的要求完结相应的点击、选择、输入等,经过验证码风控也至关重要;比方关于滑块验证码,验证码风控可能会针对滑动轨道进行检测,假如检测出轨道非人为,就会判定为高风险,导致无法成功经过。

    爬虫与反爬虫技术简介

    图12.验证码反爬虫手法

    2.6 恳求签名校验反爬虫

    签名验证是防止服务器被歹意链接和篡改数据的有用办法之一,也是现在后端API最常用的防护办法之一。签名是一个依据数据源进行计算或许加密的进程,用户经过签名后会一个具有一致性和唯一性的字符串,它便是你拜访服务器的身份标志。由它的一致性和唯一性这两种特性,然后可以有用的防止服务器端,将假造的数据或被篡改的数据最初正常数据处理。

    前面在2.4节说到的网站是经过客户端烘托网页,数据则是经过ajax恳求拿到的,这种在必定程度上进步了爬虫的难度。接下来剖析ajax恳求,如图13所示,会发现其ajax恳求是带有恳求签名的,analysis便是加密后的参数,而假如想要破解恳求接口,就需求破解该参数的加密办法,这无疑进一步进步了难度。

    爬虫与反爬虫技术简介

    爬虫与反爬虫技术简介

    图13. 恳求榜单数据的ajax恳求

    2.7 蜜罐反爬虫

    蜜罐反爬虫,是一种在网页中躲藏用于检测爬虫程序的链接的手法,被躲藏的链接不会显现在页面中,正常用户无法拜访,但爬虫程序有可能将该链接放入待爬队列,并向该链接发起恳求,开发者可以运用这个特色区别正常用户和爬虫程序。如图14所示,检查网页源码,页面只要6个商品,col-md-3的

    标签却有 8 对。该 CSS 款式的作用是躲藏标签,所以咱们在页面只看到 6 件商品,爬虫程序会提取到 8 件商品的 URL。

    爬虫与反爬虫技术简介

    爬虫与反爬虫技术简介

    图14.蜜罐反爬虫比方

    三、反反爬相关技能

    针对上一节说到的反爬虫相关技能,有以下几类反反爬技能手法:css偏移反反爬、自界说字体反反爬、页面动态烘托反反爬、验证码破解等,下面对这几类办法进行详细的介绍。

    3.1 CSS偏移反反爬

    3.1.1 CSS偏移逻辑介绍

    那么关于以上2.1css偏移反爬虫的比方,怎么才能得到正确的机票价格呢。仔细观察css款式,可以发现每个带有数字的标签都设定了款式,第 1 对 b 标签内的i 标签对的款式是相同的,都是width: 16px;另外,还注意到最外层的 span 标签对的款式为width:48px。

    假如依照 css款式这条线索来剖析的话,第 1 对 b 标签中的 3 对 i 标签刚好占满 span 标签对的方位,其方位如图15所示。此刻网页中显现的价格应该是 777,可是由于第 2 和第 3 对 b 标签中有值,所以咱们还需求计算它们的方位。由于第 2 对 b 标签的方位款式是 left:-32px,所以第 2 对 b 标签中的值 6 就会掩盖本来第 1 对 b 标签中的中的第 2 个数字 7,此刻页面应该显现的数字是 767。

    按此规律计算,第 3 对 b 标签的方位款式是 left:-48px,这个标签的值会掩盖第 1 对 b 标签中的第 1 个数字 7,最终显现的票价便是 467。

    爬虫与反爬虫技术简介

    图15.偏移逻辑

    3.1.2 CSS偏移反反爬代码完结

    因而接下来咱们按以上css款式的规律来编写代码对该网页爬取获取正确的机票价格,代码和成果如图16所示。

    if __name__ == '__main__':
        url = 'http://www.porters.vip/confusion/flight.html'
        resp = requests.get(url)
        sel = Selector(resp.text)
        em = sel.css('em.rel').extract()
        for element in range(0,1):
            element = Selector(em[element])
            element_b = element.css('b').extract()
            b1 = Selector(element_b.pop(0))
            base_price = b1.css('i::text').extract()
            print('css偏移前的价格:',base_price)
            alternate_price = []
            for eb in element_b:
                eb = Selector(eb)
                style = eb.css('b::attr("style")').get()
                position = ''.join(re.findall('left:(.*)px', style))
                value = eb.css('b::text').get()
                alternate_price.append({'position': position, 'value': value})
            print('css偏移值:',alternate_price)
            for al in alternate_price:
                position = int(al.get('position'))
                value = al.get('value')
                plus = True if position >= 0 else False
                index = int(position / 16)
                base_price[index] = value
            print('css偏移后的价格:',base_price)
    

    爬虫与反爬虫技术简介

    图16. CSS 偏移反反爬代码与成果

    3.2 自界说字体反反爬

    针关于以上2.3自界说字体反爬虫的状况,解决思路便是提取出网页中自界说字体文件(一般为WOFF文件),并将映射联系包括到爬虫代码中,就可以获取到有用数据。解决的进程如下:

    发现问题:检查网页源代码,发现关键字符被编码代替,如&#xefbe

    爬虫与反爬虫技术简介

    剖析:检查网页,发现运用了css自界说字符集躲藏

    爬虫与反爬虫技术简介

    爬虫与反爬虫技术简介

    查找:查找css文件url,获取字符集对应的url,如PingFangSC-Regular-num

    查找:查找和下载字符集url

    爬虫与反爬虫技术简介

    比对:比对字符集中的字符与网页源代码中的编码,发现编码的后四位与字符对应,也即网页源代码对应的口味是8.9分

    爬虫与反爬虫技术简介

    3.3 页面动态烘托反反爬

    客户端烘托的反爬虫,页面代码在浏览器源代码中看不到,需求履行烘托并进一步获取烘托后成果。针对这种反爬虫,有以下几种办法破解:

    1. 在浏览器中,经过开发者东西直接检查ajax详细的恳求办法、参数等内容;

    2. 经过selenium模拟真人操作浏览器,获取烘托后的成果,之后的操作进程和服务端烘托的流程相同;

    3. 假如烘托的数据躲藏在html成果的JS变量中,可以直接正则提取;

    4. 假如有经过JS生成的加密参数,可以找出加密部分的代码,然后运用pyexecJS来模拟履行JS,回来履行成果。

    3.4 验证码破解

    下面举例一个辨认滑块验证码的比方,如图17所示,是运用方针检测模型来辨认某滑块验证码缺口方位的成果示例,这种破解滑块验证码的办法对应的是模拟真人的办法。不采用接口破解的原因一方面是破解加密算法有难度,另一方面也是加密算法可能每天都会变,这样破解的时间本钱也比较大。

    爬虫与反爬虫技术简介

    图17. 经过方针检测模型辨认滑块验证码的缺口

    3.4.1 爬取滑块验证码图片

    由于运用的方针检测模型yolov5是有监督学习,所以需求爬取滑块验证码的图片并进行打标,然后输入到模型中练习。经过模拟真人的办法在某场景爬取部分验证码。

    爬虫与反爬虫技术简介

    图18. 爬取的滑块验证码图片

    3.4.2 人工打标

    本次运用的是labelImg来对图片人工打标签的,人工打标耗时较长,100张图片一般耗时40分钟左右。自动打标代码写起来比较复杂,主要是需求别离提取出验证码的所有背景图片和缺口图片,然后随机生成缺口方位,作为标签,同时将缺口放到对应的缺口方位,生成图片,作为输入。

    爬虫与反爬虫技术简介

    图19. 对验证码图片打标签以及打标签后生成的xml文件

    3.4.3 方针检测模型yolov5

    直接从github下clone yolov5的官方代码,它是依据pytorch完结。

    接下来的运用进程如下:

    1. **数据格局转化:**将人工标示的图片和标签文件转化为yolov5接纳的数据格局,得到1100张图片和1100个yolov5格局的标签文件;

    2. **新建数据集:**新建custom.yaml文件来创立自己的数据集,包括练习集和验证集的目录、类别数目、类别名;

    3. **练习调优:**修正模型配置文件和练习文件后,进行练习,并依据练习成果调优超参数。

    转化xml文件为yolov5格局的部分脚本:

    for member in root.findall('object'):
               class_id = class_text.index(member[0].text)
               xmin = int(member[4][0].text)
               ymin = int(member[4][1].text)
               xmax = int(member[4][2].text)
               ymax = int(member[4][3].text)
               # round(x, 6) 这儿我设置了6位有用数字,可依据实际状况更改
               center_x = round(((xmin + xmax) / 2.0) * scale / float(image.shape[1]), 6)
               center_y = round(((ymin + ymax) / 2.0) * scale / float(image.shape[0]), 6)
               box_w = round(float(xmax - xmin) * scale / float(image.shape[1]), 6)
               box_h = round(float(ymax - ymin) * scale / float(image.shape[0]), 6)
               file_txt.write(str(class_id))
               file_txt.write(' ')
               file_txt.write(str(center_x))
               file_txt.write(' ')
               file_txt.write(str(center_y))
               file_txt.write(' ')
               file_txt.write(str(box_w))
               file_txt.write(' ')
               file_txt.write(str(box_h))
               file_txt.write('\n')
           file_txt.close()
    

    练习参数设置:

    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', type=str, default='yolov5s.pt', help='initial weights path')
    parser.add_argument('--cfg', type=str, default='./models/yolov5s.yaml', help='model.yaml path')
    parser.add_argument('--data', type=str, default='data/custom.yaml', help='data.yaml path')
    parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path')
    # parser.add_argument('--epochs', type=int, default=300)
    parser.add_argument('--epochs', type=int, default=50)
    # parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs')
    parser.add_argument('--batch-size', type=int, default=8, help='total batch size for all GPUs')
    parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='[train, test] image sizes')
    parser.add_argument('--rect', action='store_true', help='rectangular training')
    parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
    parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
    parser.add_argument('--notest', action='store_true', help='only test final epoch')
    parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')
    parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters')
    parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
    parser.add_argument('--cache-images', action='store_true', help='cache images for faster training')
    parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
    parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
    parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
    parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer')
    parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
    parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify')
    parser.add_argument('--workers', type=int, default=8, help='maximum number of dataloader workers')
    parser.add_argument('--project', default='runs/train', help='save to project/name')
    parser.add_argument('--entity', default=None, help='W&B entity')
    parser.add_argument('--name', default='exp', help='save to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    parser.add_argument('--quad', action='store_true', help='quad dataloader')
    parser.add_argument('--linear-lr', action='store_true', help='linear LR')
    parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
    parser.add_argument('--upload_dataset', action='store_true', help='Upload dataset as W&B artifact table')
    parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval for W&B')
    parser.add_argument('--save_period', type=int, default=-1, help='Log model after every "save_period" epoch')
    parser.add_argument('--artifact_alias', type=str, default="latest", help='version of dataset artifact to be used')
    opt = parser.parse_args()
    

    3.4.4 方针检测模型的练习成果

    模型根本在50次迭代的时分在precision和recall以及mAP上现已达到了瓶颈。猜测成果也有如下问题:大部分可以是可以精确框出缺口,但也呈现少数框错、框出两个缺口、框不出缺口的状况。

    爬虫与反爬虫技术简介

    爬虫与反爬虫技术简介

    图20. 上:模型的练习成果走势图;

    下:模型对部分验证集的猜测成果

    四、总结

    本次简略对爬虫以及反爬虫的技能手法进行了介绍,介绍的技能和事例均只是用于安全研究和学习,并不会进行很多爬虫或许运用于商业。

    关于爬虫,本着爬取网络上揭露数据用于数据剖析等的意图,咱们应该恪守网站robots协议,本着不影响网站正常运转以及恪守法律的状况下进行数据爬取;关于反爬虫,由于只要人类可以正常拜访的网页,爬虫在具备同等资源的状况下就必定可以抓取到。所以反爬虫的意图仍是在于可以防止爬虫在大批量的收集网站信息的进程对服务器形成超负载,然后根绝爬虫行为阻碍到用户的体会,来进步用户运用网站服务的满意度。