SVG反爬虫不同于字体反爬虫,它巧妙的利用css 与 svg的关系,将字符映射到网页中,看起来虽然正常,但是却抓取不到有效内容。本文带你深入浅出,破了SVG反爬虫的套路,学会之后,可应用于某点评网。
一、初识SVG反爬:
为了防止面对监狱编程,我在本地自己做了一个网页用于爬虫测试。
任务是爬取票据中的产品价格信息,按照往常一样审查元素定位目标节点,但是发现事情并不简单。
看图:
¥符号后面并没有我们想要的价格信息,而是四个d标签取而代之。随便选中一个7,发现对应的是属性class=lhtqsc的d标签。
看css样式:
有两段描述这个标签的语句,第一段比较常规,但是其中的
background-image: url(../css/zhiliao.svg);
就有点奇怪,而第二段,描述的是background,它的值是两个 大小值,单位px。
打开svg文件查看:
貌似是没什么规律的数字
查看该页面网页源代码,发现是张这样的:
二、什么是SVG?
想要搞定SVG反爬虫就得先搞清楚SVG:
SVG是一种基于xml用于描述矢量图的图形格式,由于矢量图放大或缩写都不会影响图形的质量,所以被较多的应用在web站点与APP中,常见的存在形式是图标。
编写一个SVG文件:
为了方便,我们可以把要写的svg,写到HTML文件中,新建一个svg.html键入如下内容:
<!DOCTYPE html>
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<text x="0" y="15" fill="red">I love zhiliao</text>
</svg>
</body>
</html>
<text x="0" y="15" fill="red">I love zhiliao</text>
很关键,这一行用text标签定义了一段文本,fill是文本颜色属性,为红色,并且指定了文本的坐标。
该坐标的规则是:
1.以左上角为坐标系的原点(0,0)
2.X 轴的正方向向右,从 0,0 点开始向右, x 逐渐增大。Y 轴的正方向向下,从 0,0 点开始向下, y 逐渐增大。
3.坐标以像素为单位
4.n 个字符可以有 n 个位置参数。
增加一行:
<text x="10 20 30 40 50 60 70 80 90" y="100" fill="black">I love zhiliao</text>
这行代码指定了前九个字符的位置,且指定了文本的颜色,y的值也增大到100,浏览器打开如下:
再次增加一行:
<text x="40 20 30 10 50 60 70 80 90" y="120" fill="red">I love zhiliao</text>
上图可以看到,在不改变文本顺序的情况下通过改变x中的值,让第一个字符I和第四个字符o交换了位置。
三、SVG与CSS的联系:
现在我们尝试把css和svg联系起来,更加深入的理解,方便我们做下一步抓取工作:
在svg中,X轴正方向为从左到右,y轴的正方向是从上到下;
在css中,X轴负数向右,Y轴是负数向下。
新建一个zhiliao.svg并键入:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="650px" height="230.0px">
<style>text {font-family:PingFangSC-Regular,Microsoft YaHei,'Hiragino Sans GB',Helvetica;font-size:14px;fill:#666;}
<text x='10 20 30 10 50 60 70 80 90 100 110 120 130 140 150 160' y='30'>hello,zhiliao</text>
</svg>
我们在style中 定义了字符的风格,大小(14px),以及颜色(#666)。定义了每个字符所在的位置。
现在我们对第一个字符h进行定位,
X轴计算法则:字符大小 / 2 + 字符的x轴起点位置参数
Y轴计算法则:(y 轴高度 - 字符 y 轴起点 - 字符大小)/ 2 + 字符 y 轴起点位置参数 + 字符大小数值的一半
也就是:
X = 14/2 + 0 = 7
Y = (30 - 0 - 14) / 2 + 0 + 14 / 2 = 15
svg中对应的定位就是 x = 7 ,y = 15
css与svg相反,所以是 -7,-15
现在新建一个zhiliao.css文件,键入如下内容:
d[class^="lht"] {
width: 14px;
height: 30px;
background-image: url(zhiliao.svg);
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle;
}
.lhttest {
background: -7px -15px;
}
新建一个demo.html,键入:
<html>
<head>
<link type="text/css" rel="stylesheet" href="zhiliao.css">
</head>
<body>
<d class="lhttest"></d>
</body>
</html>
html中会引入css文件,css又会引入svg文件。通过d标签的class属性值读取css对应的svg标签,完成对svg的映射。
然后把demo.html拖到浏览器,可以看到:
字符h完完整整的被我们成功的映射出来了。
搞懂了这些,反爬就不是难事了,我们要做的是让程序搞定这些。
四、Python抓取数据:
回到刚才的目标网站,开始实战抓取。
1.获取css样式文件与svg文件内容:
import re
import requests
from lxml import etree
svg_link = "http://localhost/zhiliao_svg/css/zhiliao.svg"
css_link = "http://localhost/zhiliao_svg/css/style.css"
#获取svg与css文件内容
svg_text = requests.get(svg_link).text
css_text = requests.get(css_link).text
2.获取匹配出class属性值对应css参数,与字符大小:
css_text = css_text.replace('\n', '').replace(' ', '') #将换行符与空格清除,方便下一步匹配
#定义一个函数用于匹配class属性为xx对应的css参数
def find_xy(in_text, class_value):
re_sentence = '.%s{background:-(\d+)px-(\d+)px;}' % class_value
pattern = re.compile(re_sentence)
res = pattern.findall(in_text)
return res[0]
#寻找出svg文件中定义的字符大小
def find_font_size(in_text):
fs = re.compile(r'font-size:(\d+)px')
res = fs.findall(in_text)
return int(res[0])
print(find_xy(css_text, "lhtqsc"), find_font_size(svg_text))
测试寻找class=lhtqsc的css对应参数和字符大小,输出如下:
('288', '141') 14
3.观察svg文件:
这里有四个text标签,对应四个文本,每个文本X轴都是从0开始,并且以14递增
Y轴不相同的,分别是 38, 83 , 120 ,164
文本顺序也有所不同。
现在我们get这些数据:
soup = etree.HTML(svg_text).xpath("//text")
list_y = [i.get('y') for i in soup]
list_text = [i.text for i in soup]
print(list_y)
print(list_text)
输出:
['38', '83', '120', '164']
['154669136497975167479825383996313925720573', '560862462805204755437571121437458524985017', '671260781104096663000892328440489239185923', '684431081139502796807382']
到这一步,我们成功的分别取出了y的和text标签的四个值,并组成了列表。
但是,这些元素 几乎没有和 [‘38’, ‘83’, ‘120’, ‘164’] 中有相等的,
这对于我们寻找text标签包含的文本值并不影响,我们找到最近接近的即可。
我们仍然以 class=lhtqsc为例子,寻找它最近的y值,从而找出是哪个text标签包含的值。
完善我们的代码:
soup = etree.HTML(svg_text).xpath("//text")
list_y = [i.get('y') for i in soup] #找出所有的y值
list_text = [i.text for i in soup] #找出所有的text值
svg_font_size = find_font_size(svg_text) #找出font-size值
x, y = find_xy(css_text, "lhtqsc") #找出class=lhtqsc的xy坐标
real_y = [i for i in list_y if y <= i][-1] #找出是哪一个 y
real_text = list_text[list_y.index(real_y)] #找出是那个一 text
print(real_text)
输出:
684431081139502796807382
然后就可以利用切片的特性寻找到对应的数值啦!
pos = int(x) // svg_font_size
print(real_text[pos])
我们打开网站,看看对应的是不是数字7
到这里,就算成功了,是不是很简单呢?跟我一起做一遍吧。更多干货内容,欢迎关注公众号:知了python