【爬虫】Python3突破css文件的数据隐藏

原创内容转载请注明出处:https://blog.csdn.net/mingshao104/article/details/89155109

一、css文件进行数据隐藏的原理;

1、 后端将一些确定的数据(如数字、汉字等)写入svg文件中进行数据隐藏,然后通过css文件显示svg文件的相对位置,达到前端的展示;

二、突破css文件数据隐藏;

1、获取被修饰数据的css文件,以及对应的svg文件,我们以某点评为例;
svg文件地址:http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/7ad32c1cc786375d3c49a40e9113682d.svg
css文件地址:http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/0f9a1a0cbe115484299a3d9b29f27f89.css
页面地址:http://www.dianping.com/search/keyword/5/20_电动车/g34204
2、解析处理css和cvg文件;
首先可以使用浏览器访问另存为本地文件的方式获取目标文件然后用open函数读取,也可以使用requests直接获取;

import requests
css_url = 'http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/0f9a1a0cbe115484299a3d9b29f27f89.css'
svg_url = 'http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/7ad32c1cc786375d3c49a40e9113682d.svg'
css_resp = requests.get(css_url)
print(css_resp.text)
svg_resp = requests.get(svg_url)
print(svg_resp.text)

输出分别如下:

.npq6t{background:-144.0px -1658.0px;}.npl7k{background:-60.0px -1257.0px;}.npndh{background:-156.0px -2115.0px;}.fmvgl{background:-156.0px -1577.0px;}.np2kv{background:-504.0px -751.0px;}.fm3y2{background:-132.0px -1996.0px;}
# 此处输出只截取了少部分
<?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="180.0px">
<style>text {font-family:Microsoft YaHei,Hiragino Sans GB;font-size:12px;fill:#999;}
	<text x="12 24 36 48 60 72 84 96 108 120 132 144 156 168 180 192 204 216 228 240 252 264 276 288 300 312 324 336 348 360 372 384 396 408 420 432 444 456 468 480 492 504 516 528 540 552 564 576 588 600 612 624 636 648 660 672 684 696 708 720 732 744 756 768 780 792 804 816 828 840 852 864 876 888 900 912 924 936 948 960 972 984 996 1008 1020 1032 1044 1056 1068 1080 1092 1104 1116 1128 1140 1152 1164 1176 1188 1200 1212 1224 1236 1248 1260 1272 1284 1296 1308 1320 1332 1344 1356 1368 1380 1392 1404 1416 1428 1440 " y="41">64932835581751497040211980298958011706657993419421</text>
	<text x="12 24 36 48 60 72 84 96 108 120 132 144 156 168 180 192 204 216 228 240 252 264 276 288 300 312 324 336 348 360 372 384 396 408 420 432 444 456 468 480 492 504 516 528 540 552 564 576 588 600 612 624 636 648 660 672 684 696 708 720 732 744 756 768 780 792 804 816 828 840 852 864 876 888 900 912 924 936 948 960 972 984 996 1008 1020 1032 1044 1056 1068 1080 1092 1104 1116 1128 1140 1152 1164 1176 1188 1200 1212 1224 1236 1248 1260 1272 1284 1296 1308 1320 1332 1344 1356 1368 1380 1392 1404 1416 1428 1440 " y="89">50187287253831143170268756680338206343457507265964</text>
	<text x="12 24 36 48 60 72 84 96 108 120 132 144 156 168 180 192 204 216 228 240 252 264 276 288 300 312 324 336 348 360 372 384 396 408 420 432 444 456 468 480 492 504 516 528 540 552 564 576 588 600 612 624 636 648 660 672 684 696 708 720 732 744 756 768 780 792 804 816 828 840 852 864 876 888 900 912 924 936 948 960 972 984 996 1008 1020 1032 1044 1056 1068 1080 1092 1104 1116 1128 1140 1152 1164 1176 1188 1200 1212 1224 1236 1248 1260 1272 1284 1296 1308 1320 1332 1344 1356 1368 1380 1392 1404 1416 1428 1440 " y="121">87056740226924694393</text>
</svg>

2.1、对于css文件的处理,由于获取的css文件具有结构化的特点,所以直接使用re库进行正则匹配抽取;
css文件数据特点:类名结构化,均为5个字符或数字组成,位置均为负数浮点数字;

观察页面正文发现,该页面被修饰的数字均为 f j 开头的class类名;
在这里插入图片描述
所以本次svg文件中的数字对应的为fj开头的class类,其他class类修饰另外svg文件,即:不止一个svg文件;

import re
pat = re.compile('\.(\w{5})\{background:-(\d+)\.0px -(\d+)\.0px;\}')
res = pat.findall(css_resp.text)
# print(res)
px = {_cls[0]: {'x': _cls[1], 'y': _cls[2]}
       for _cls in res if str(_cls[0]).startswith('fj')}
print(px)

输出结果如下:

{'fjus8': {'x': '163', 'y': '88'}, 'fj17l': {'x': '295', 'y': '7'}, 'fjqer': {'x': '463', 'y': '55'}, 'fjvty': {'x': '343', 'y': '7'}, 'fj7eh': {'x': '391', 'y': '7'}, 'fjzom': {'x': '235', 'y': '7'}, 'fj1ye': {'x': '571', 'y': '55'}, 'fjit1': {'x': '259', 'y': '55'}, 'fjoeo': {'x': '55', 'y': '88'}, 'fj36l': {'x': '487', 'y': '55'}, 'fjevk': {'x': '103', 'y': '88'}, 'fj78x': {'x': '7', 'y': '88'}, 'fjdti': {'x': '187', 'y': '88'}, 'fjq0w': {'x': '7', 'y': '7'}, 'fjs0e': {'x': '283', 'y': '7'}, 'fj39d': {'x': '342', 'y': '55'}, 'fjxtw': {'x': '535', 'y': '55'}, 'fjv86': {'x': '547', 'y': '55'}, 'fjm3j': {'x': '126', 'y': '88'}, 'fjnww': {'x': '583', 'y': '7'}, 'fj468': {'x': '354', 'y': '55'}, 'fjvy8': {'x': '354', 'y': '7'}, 'fj1ki': {'x': '271', 'y': '55'}, 'fjgwi': {'x': '439', 'y': '55'}, 'fjvfo': {'x': '271', 'y': '7'}, 'fj2wu': {'x': '523', 'y': '55'}, 'fjyj6': {'x': '571', 'y': '7'}, 'fjiul': {'x': '415', 'y': '7'}, 'fju5t': {'x': '235', 'y': '55'}, 'fjik1': {'x': '259', 'y': '7'}, 'fjedc': {'x': '331', 'y': '7'}, 'fjfj8': {'x': '127', 'y': '7'}, 'fjbh0': {'x': '163', 'y': '55'}, 'fjxgd': {'x': '415', 'y': '55'}, 'fjpw7': {'x': '67', 'y': '7'}, 'fjnsb': {'x': '175', 'y': '7'}, 'fjund': {'x': '139', 'y': '7'}, 'fj0rd': {'x': '583', 'y': '55'}, 'fjgil': {'x': '391', 'y': '55'}, 'fjnwg': {'x': '139', 'y': '55'}, 'fj8tf': {'x': '451', 'y': '55'}, 'fj2v4': {'x': '175', 'y': '88'}, 'fjulz': {'x': '175', 'y': '55'}, 'fjj0y': {'x': '319', 'y': '55'}, 'fj87i': {'x': '426', 'y': '55'}, 'fjye7': {'x': '43', 'y': '7'}, 'fjuv1': {'x': '451', 'y': '7'}, 'fj42t': {'x': '114', 'y': '7'}, 'fj3pc': {'x': '151', 'y': '55'}, 'fjt3m': {'x': '487', 'y': '7'}, 'fjjeh': {'x': '199', 'y': '55'}, 'fjvgd': {'x': '307', 'y': '55'}, 'fjwwb': {'x': '523', 'y': '7'}, 'fj0mo': {'x': '79', 'y': '7'}, 'fj2jk': {'x': '511', 'y': '55'}, 'fje3j': {'x': '283', 'y': '55'}, 'fjlm5': {'x': '19', 'y': '55'}, 'fj67z': {'x': '91', 'y': '7'}, 'fj28r': {'x': '31', 'y': '88'}, 'fjz14': {'x': '535', 'y': '7'}, 'fjg3q': {'x': '330', 'y': '55'}, 'fjx3o': {'x': '295', 'y': '55'}, 'fjvpz': {'x': '223', 'y': '55'}, 'fjrzj': {'x': '43', 'y': '88'}, 'fjb7l': {'x': '30', 'y': '55'}, 'fjqsl': {'x': '55', 'y': '7'}, 'fjqqg': {'x': '211', 'y': '55'}, 'fjqto': {'x': '475', 'y': '55'}, 'fj2ny': {'x': '187', 'y': '55'}, 'fj63u': {'x': '319', 'y': '7'}, 'fj8i4': {'x': '547', 'y': '7'}, 'fjfhg': {'x': '595', 'y': '7'}, 'fj9cw': {'x': '367', 'y': '55'}, 'fjniq': {'x': '91', 'y': '55'}, 'fj3tt': {'x': '127', 'y': '55'}, 'fj743': {'x': '403', 'y': '7'}, 'fjqrd': {'x': '151', 'y': '7'}, 'fj4c1': {'x': '67', 'y': '88'}, 'fj5rc': {'x': '367', 'y': '7'}, 'fjo5v': {'x': '559', 'y': '55'}, 'fjldt': {'x': '163', 'y': '7'}, 'fjrlm': {'x': '67', 'y': '55'}, 'fjg95': {'x': '499', 'y': '7'}, 'fjuc3': {'x': '247', 'y': '7'}, 'fjhtd': {'x': '559', 'y': '7'}, 'fja0d': {'x': '235', 'y': '88'}, 'fjbet': {'x': '247', 'y': '55'}, 'fj9mh': {'x': '7', 'y': '55'}, 'fjgsj': {'x': '19', 'y': '7'}, 'fjv8k': {'x': '103', 'y': '55'}, 'fjkqa': {'x': '223', 'y': '88'}, 'fjx4w': {'x': '19', 'y': '88'}, 'fj400': {'x': '439', 'y': '7'}, 'fj3wl': {'x': '151', 'y': '88'}, 'fjupt': {'x': '139', 'y': '88'}, 'fj19t': {'x': '223', 'y': '7'}, 'fjmuq': {'x': '462', 'y': '7'}, 'fjuzt': {'x': '499', 'y': '55'}, 'fjkgf': {'x': '475', 'y': '7'}, 'fjrrv': {'x': '115', 'y': '88'}, 'fjgg2': {'x': '79', 'y': '88'}, 'fjmzs': {'x': '115', 'y': '55'}, 'fjq3f': {'x': '307', 'y': '7'}, 'fjdww': {'x': '43', 'y': '55'}, 'fjhlh': {'x': '511', 'y': '7'}, 'fj9ib': {'x': '187', 'y': '7'}, 'fj27k': {'x': '78', 'y': '55'}, 'fjach': {'x': '199', 'y': '7'}, 'fj4yd': {'x': '402', 'y': '55'}, 'fj5id': {'x': '211', 'y': '7'}, 'fjyuy': {'x': '55', 'y': '55'}, 'fjg69': {'x': '199', 'y': '88'}, 'fj2m5': {'x': '31', 'y': '7'}, 'fjlir': {'x': '103', 'y': '7'}, 'fjpny': {'x': '595', 'y': '55'}, 'fjpju': {'x': '211', 'y': '88'}, 'fjgmk': {'x': '379', 'y': '55'}, 'fjcp8': {'x': '91', 'y': '88'}, 'fjeih': {'x': '379', 'y': '7'}, 'fjniw': {'x': '426', 'y': '7'}}

2.2、SVG 是一种基于 XML 语法的图像格式,全称是可缩放矢量图(Scalable Vector Graphics)。其他图像格式都是基于像素处理的,SVG 则是属于对图像的形状描述,所以它本质上是文本文件;可以使用xml库进行描述数据的读取;

import xml.dom.minidom
dom = xml.dom.minidom.parseString(svg_resp.text)
root = dom.getElementsByTagName('text')

for text in root:
    x = text.getAttribute('x')
    y = text.getAttribute('y')
    data = text.firstChild.data
    print(x)
    print(y)
    print(data, '\n')

输出如下:

12 24 36 48 60 72 84 96 108 120 132 144 156 168 180 192 204 216 228 240 252 264 276 288 300 312 324 336 348 360 372 384 396 408 420 432 444 456 468 480 492 504 516 528 540 552 564 576 588 600 612 624 636 648 660 672 684 696 708 720 732 744 756 768 780 792 804 816 828 840 852 864 876 888 900 912 924 936 948 960 972 984 996 1008 1020 1032 1044 1056 1068 1080 1092 1104 1116 1128 1140 1152 1164 1176 1188 1200 1212 1224 1236 1248 1260 1272 1284 1296 1308 1320 1332 1344 1356 1368 1380 1392 1404 1416 1428 1440 
41
64932835581751497040211980298958011706657993419421

12 24 36 48 60 72 84 96 108 120 132 144 156 168 180 192 204 216 228 240 252 264 276 288 300 312 324 336 348 360 372 384 396 408 420 432 444 456 468 480 492 504 516 528 540 552 564 576 588 600 612 624 636 648 660 672 684 696 708 720 732 744 756 768 780 792 804 816 828 840 852 864 876 888 900 912 924 936 948 960 972 984 996 1008 1020 1032 1044 1056 1068 1080 1092 1104 1116 1128 1140 1152 1164 1176 1188 1200 1212 1224 1236 1248 1260 1272 1284 1296 1308 1320 1332 1344 1356 1368 1380 1392 1404 1416 1428 1440 
89
50187287253831143170268756680338206343457507265964

12 24 36 48 60 72 84 96 108 120 132 144 156 168 180 192 204 216 228 240 252 264 276 288 300 312 324 336 348 360 372 384 396 408 420 432 444 456 468 480 492 504 516 528 540 552 564 576 588 600 612 624 636 648 660 672 684 696 708 720 732 744 756 768 780 792 804 816 828 840 852 864 876 888 900 912 924 936 948 960 972 984 996 1008 1020 1032 1044 1056 1068 1080 1092 1104 1116 1128 1140 1152 1164 1176 1188 1200 1212 1224 1236 1248 1260 1272 1284 1296 1308 1320 1332 1344 1356 1368 1380 1392 1404 1416 1428 1440 
121
87056740226924694393

3、将css的偏移量与svg的图线描述数据对应上;
观察数据可以得到:
 svg中的y值只有3个取值,分别为41、89、121; 而x值为初值12,终值1440的递增数列,增量为12,共120个值;但是隐藏的数据每行却只有最多50个;

代码显示css中的偏移值分布:

css_x_set = {int(item['x']) for item in px.values()}
css_y_set = {int(item['y']) for item in px.values()}
css_x = sorted(list(css_x_set))
css_y = sorted(list(css_y_set))
print(css_x)
print(css_y)

输出结果为:

[7, 19, 30, 31, 43, 55, 67, 78, 79, 91, 103, 114, 115, 126, 127, 139, 151, 163, 175, 187, 199, 211, 223, 235, 247, 259, 271, 283, 295, 307, 319, 330, 331, 342, 343, 354, 367, 379, 391, 402, 403, 415, 426, 439, 451, 462, 463, 475, 487, 499, 511, 523, 535, 547, 559, 571, 583, 595]
[7, 55, 88]

 css提取出的偏移数据px中y的取值为7,55,88; 而x值的取值初始值为7,终值为595,递增的增量为12,却存在部分的数据有偏差,若固定增量为12则刚好为50个取值;

css中类的修饰backgroud展示图片,px负数值越小越往,图片越往左上方移动,即px绝对值越大,能看到的内容(焦点)越往右下方移动;在svg中,x,y的值越大,表明位置越往右下;即两者的数据变化与显示内容是相同的方向;

对于css中存在部分数据偏差的问题,利用代码,进行数据重整;

思路将svg中的y值化为小到大排序列表的下标(从0开始),即为:行数 ,建立映射表 svg_data,y为key,svg中y行对应的真实值为value(后面根据下标取值);
对输入px值进行处理,计算其对应的位置,即多少行多少列,css_y中没有异常值(即值与行数对应),所以直接取值对应的index,css_x中存在偏差值(x值与列数不对应),所以通过 (x - 初始值) / 公差d, 四舍五入计算位置x_index;


x值与列数不对应说明:【爬虫】Python3突破css文件的数据隐藏_第1张图片
如上图中30、31,该px偏移值作用于svg图后,显示出来的值是相同的;


# 计算svg数据中每一行的数据字典,及每一行最多的字符数;
svg_data = {_y.index(text.getAttribute('y')): text.firstChild.data
                for text in root}
print(svg_data)
line_len = max([len(s) for s in svg_data.values()])
print(line_len)

# 计算px中x的公差d
_start = self.css_x[0]
_end = self.css_x[-1]
d = round(_end - _start) / line_len)
print(d)

"""
输出:
{1: '64932835581751497040211980298958011706657993419421', 2: '50187287253831143170268756680338206343457507265964', 0: '87056740226924694393'}
50
12
"""
def px2svg(x: str, y: str) -> int:
    """
    将css类中的px迁移值转化为svg图片显示的真实值
    :param x: css类中的px迁移x值
    :param y: css类中的px迁移y值
    :return: 图片显示的真实值
    """
    x_index = round((int(x) - _start) / d) 
    y_index = css_y.index(int(y))
    return svg_data[y_index][x_index]

三、完整代码如下:

import requests
import xml.dom.minidom
import re


class CssDecode(object):
    def __init__(self, css_url, svg_url, pre=''):
        """
        :param css_url:
        :param svg_url:
        """
        css_resp = requests.get(css_url)
        svg_resp = requests.get(svg_url)

        pat = re.compile('\.(\w{5})\{background:-(\d+)\.0px -(\d+)\.0px;\}')
        res = pat.findall(css_resp.text)

        self.cls = {_c[0]: {'x': _c[1], 'y': _c[2]}
                    for _c in res if str(_c[0]).startswith(pre)}
        css_x_set = {int(item['x']) for item in self.cls.values()}
        css_y_set = {int(item['y']) for item in self.cls.values()}
        self.css_x = sorted(list(css_x_set))
        self.css_y = sorted(list(css_y_set))
        self._start = self.css_x[0]
        self._end = self.css_x[-1]

        dom = xml.dom.minidom.parseString(svg_resp.text)
        root = dom.getElementsByTagName('text')
        y_set = {text.getAttribute('y') for text in root}
        _y = sorted(list(y_set))

        self.svg_data = {_y.index(text.getAttribute('y')): text.firstChild.data
                         for text in root}
        self.line_len = max([len(s) for s in self.svg_data.values()])

        self.d = round((self._end - self._start) / self.line_len)

    def clsname2value(self, class_name: str) -> int:
        """
        获取页面中的类名转为真实显示的值
        :param class_name: 页面中css修饰的类名
        :return: 图片显示的真实值
        """
        try:
            px = self.cls[class_name]
        except KeyError:
            assert 'class_name 不存在'
        else:
            value = self.__px2svg(px['x'], px['y'])
            return value

    def __px2svg(self, x: str, y: str) -> int:
        """
        将css类中的px迁移值转化为svg图片显示的真实值
        :param x: css类中的px迁移x值
        :param y: css类中的px迁移y值
        :return: 图片显示的真实值
        """
        x_index = round((int(x) - self._start) / self.d)
        y_index = self.css_y.index(int(y))
        return self.svg_data[y_index][x_index]


if __name__ == '__main__':
    css_url = 'http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/0f9a1a0cbe115484299a3d9b29f27f89.css'
    svg_url = 'http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/7ad32c1cc786375d3c49a40e9113682d.svg'
    cssdc = CssDecode(css_url, svg_url, pre='fj')
    # 网页提取出来的css类名,如:fjevk 对应真实值为 2
    class_name = 'fjevk'
    value = cssdc.clsname2value(class_name=class_name)
    print(value)

你可能感兴趣的:(爬虫)