vivo 互联网安全团队 - Xie Peng
互联网的大数据时代的降临,网络爬虫也成了互联网中一个重要行业,它是一种主动获取网页数据信息的爬虫程序,是网站搜索引擎的重要组成部分。通过爬虫,能够获取本人想要的相干数据信息,让爬虫帮助本人的工作,进而降低成本,进步业务成功率和进步业务效率。
本文一方面从爬虫与反反爬的角度来阐明如何高效的对网络上的公开数据进行爬取,另一方面也会介绍反爬虫的技术手段,为避免内部爬虫大批量的采集数据的过程对服务器造成超负载方面提供些许倡议。
爬虫指的是依照肯定规定主动抓取万维网信息的程序,本次次要会从爬虫的技术原理与实现,反爬虫与反反爬虫两个方面进行简略的介绍,介绍的案例均只是用于平安钻研和学习,并不会进行大量爬虫或者利用于商业。
一、爬虫的技术原理与实现
1.1 爬虫的定义
爬虫分为通用爬虫和聚焦爬虫两大类,前者的指标是在放弃肯定内容品质的状况下爬取尽可能多的站点,比方百度这样的搜索引擎就是这种类型的爬虫,如图 1 是通用搜索引擎的基础架构:
- 首先在互联网中选出一部分网页,以这些网页的链接地址作为种子 URL;
- 将这些种子 URL 放入待抓取的 URL 队列中,爬虫从待抓取的 URL 队列顺次读取;
- 将 URL 通过 DNS 解析,把链接地址转换为网站服务器对应的 IP 地址;
- 网页下载器通过网站服务器对网页进行下载,下载的网页为网页文档模式;
- 对网页文档中的 URL 进行抽取,并过滤掉曾经抓取的 URL;
- 对未进行抓取的 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 类别等都是在一个 <li> 标签里,所以咱们只须要申请网页地址,拿到返回的网页源代码,而后对网页源代码进行正则匹配,提取出想要的数据,保留下来即可,如图 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>.*?data-src="(.*?)".*?<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 验证码反爬虫
简直所有的应用程序在波及到用户信息安全的操作时,都会弹出验证码让用户进行辨认,以确保该操作为人类行为,而不是大规模运行的机器。那为什么会呈现验证码呢?在大多数情景下是因为网站的拜访频率过高或者行为异样,或者是为了间接限度某些自动化行为。归类如下:
- 很多状况下,比方登录和注册,这些验证码简直是必现的,它的目标就是为了限度歹意注册、歹意爆破等行为,这也算反爬的一种伎俩。
- 一些网站遇到拜访频率过高的行为的时候,可能会间接弹出一个登录窗口,要求咱们登录能力持续拜访,此时的验证码就间接和登录表单绑定在一起了,这就算检测到异样之后利用强制登录的形式进行反爬。
- 一些较为惯例的网站如果遇到拜访频率稍高的情景的时候,会被动弹出一个验证码让用户辨认并提交,验证以后拜访网站的是不是实在的人,用来限度一些机器的行为,实现反爬虫。
常见的验证码模式包含图形验证码、行为验证码、短信、扫码验证码等,如图 12 所示。对于是否胜利通过验证码,除了可能精确的依据验证码的要求实现相应的点击、抉择、输出等,通过验证码风控也至关重要;比方对于滑块验证码,验证码风控可能会针对滑动轨迹进行检测,如果检测出轨迹非人为,就会断定为高风险,导致无奈胜利通过。
图 12. 验证码反爬虫伎俩
2.6 申请签名校验反爬虫
签名验证是避免服务器被歹意链接和篡改数据的无效形式之一,也是目前后端 API 最罕用的防护形式之一。签名是一个依据数据源进行计算或者加密的过程,用户通过签名后会一个具备一致性和唯一性的字符串,它就是你拜访服务器的身份象征。由它的一致性和唯一性这两种个性,从而能够无效的防止服务器端,将伪造的数据或被篡改的数据当初失常数据处理。
后面在 2.4 节提到的网站是通过客户端渲染网页,数据则是通过 ajax 申请拿到的,这种在肯定水平上晋升了爬虫的难度。接下来剖析 ajax 申请,如图 13 所示,会发现其 ajax 申请是带有申请签名的,analysis 就是加密后的参数,而如果想要破解申请接口,就须要破解该参数的加密办法,这无疑进一步晋升了难度。
图 13. 申请榜单数据的 ajax 申请
2.7 蜜罐反爬虫
蜜罐反爬虫,是一种在网页中暗藏用于检测爬虫程序的链接的伎俩,被暗藏的链接不会显示在页面中,失常用户无法访问,但爬虫程序有可能将该链接放入待爬队列,并向该链接发动申请,开发者能够利用这个特点辨别失常用户和爬虫程序。如图 14 所示,查看网页源码,页面只有 6 个商品,col-md- 3 的 <div> 标签却有 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 文件),并将映射关系蕴含到爬虫代码中,就能够获取到无效数据。解决的步骤如下:
发现问题:查看网页源代码,发现要害字符被编码代替,如
剖析:查看网页,发现利用了 css 自定义字符集暗藏
查找:查找 css 文件 url,获取字符集对应的 url,如 PingFangSC-Regular-num
查找:查找和下载字符集 url
比对:比对字符集中的字符与网页源代码中的编码,发现编码的后四位与字符对应,也即网页源代码对应的口味是 8.9 分
3.3 页面动静渲染反反爬
客户端渲染的反爬虫,页面代码在浏览器源代码中看不到,须要执行渲染并进一步获取渲染后后果。针对这种反爬虫,有以下几种形式破解:
- 在浏览器中,通过开发者工具间接查看 ajax 具体的申请形式、参数等内容;
- 通过 selenium 模仿真人操作浏览器,获取渲染后的后果,之后的操作步骤和服务端渲染的流程一样;
- 如果渲染的数据暗藏在 html 后果的 JS 变量中,能够间接正则提取;
- 如果有通过 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 实现。
接下来的应用步骤如下:
- 数据格式转换: 将人工标注的图片和标签文件转换为 yolov5 接管的数据格式,失去 1100 张图片和 1100 个 yolov5 格局的标签文件;
- 新建数据集: 新建 custom.yaml 文件来创立本人的数据集,包含训练集和验证集的目录、类别数目、类别名;
- 训练调优: 批改模型配置文件和训练文件后,进行训练,并依据训练后果调优超参数。
转换 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 协定,本着不影响网站失常运行以及恪守法律的状况下进行数据爬取;对于反爬虫,因为只有人类可能失常拜访的网页,爬虫在具备等同资源的状况下就肯定能够抓取到。所以反爬虫的目标还是在于可能避免爬虫在大批量的采集网站信息的过程对服务器造成超负载,从而杜绝爬虫行为障碍到用户的体验,来进步用户应用网站服务的满意度。