python爬虫 js逆向之svg字体反爬破解

前言

同样的,接上一篇 python爬虫 - js逆向之woff字体反爬破解 ,而且也是同一个站的数据,只是是不同的反爬

网址:

aHR0cDovL3{防查找,删除我,包括花括号}d3dy5kaWFuc{防查找,删除我,包括花括号}GluZy5jb20vcmV2aWV3L{防查找,删除我,包括花括号}zEwMDM1NDgxNjI=

 

分析

打开网站:

 

 

这个根据之前的经验,现在直接找css,找映射关系就行了,所以看右边的css,看到有很多东西,其中有两个比较重要:

 

 

有没有想过,为啥给个ji6c6就能映射成【一】呢?为什么不是其他的,看到上面的background-image的url就很可疑了,打开这个url看看:

 

 

 

 

卧槽,这就是一堆字体啊,跟上一篇的woff字体有得一拼了,我猜哈,他就是通过这个的css来控制显示的:

 

那行,我们随便改个数值看看:

 

改之前:

 

 

 改之后,只改了宽度的px,

 

 

 再改个高度的px看看,我估计随便输入的值哈,所以肯定是对不齐的

 

 

 

那么,再看下svg源码的文字,

 

 

 对得上,所以宽度跟高度改来改去,现在能够确定的是,最开始的-336px和-214px,这个负值跟svg的值是相反的,svg是正数,css是负数,这个不重要哈,同时,我们也可以确认下:

 

用截图工具大概取个值,

 

 

而源码里的是这样的:

 

 

感觉有点不一样,不过至少,宽度339px和336px能够约等于上,但是高度对不上,很奇怪了。

 

没事,再看看哈,把高度的值改为0px看看,改完成这样了

 

 

 有老哥要问了,改为0px干嘛,现在不是要找高度的规律吗,对啊,要找规律就要看看第一排的px是多少啊,上面的0px显然不对,那么说明第一排的纵坐标的不是从0px,那稍微调下,调成一个与其他内容规整排列的

 

没过一会儿,就调出一个规整排列的,结果是-13px

 

 

再看第二排是多少:57px

 

 第三排:104px

 

 第四排:141px

 

 

 第五排:175px

第六排:214px

第七排:257px

。。。。

 

结果发现这些值并没有很大的关联,插值并不是一致的

 

 看来这条路不通,换,再回过头看css:

 

 

圈出来的就非常关键了,width为14px,大胆猜测,应该就是一个字体的宽度,高度的话,应该就似乎height加上margin-top的值,极为39px

 

那行,一个字体占比就是宽度为14,高度39,用这个值再次验证一下,再回过头看这个【一】,

-336px    -214px
 
336/14 = 24
214/39 = 5.48717.... 可以这里肯定是要做取整计算的,直接记为6
 
也就是是是再第6排第24个字
 
那看下那边的svg源码是不是:

 

再看,这里数起来,麻烦,直接用python操作了:

 

 

 

果然了,对上了,有点小激动,再次验证下,看后面的【直】

 

 

196/14 = 14
57/39 =  1.4625... ≈ 2
 
看是不是第2排第14个:
 

 

 

 

 

 

 ok,又对上了,再随机找个吧,一次两次,万一还是巧合可以,随机再找一个还能对上,那就一定是了:

 

就找个【是】

 

 

140/14=10

1723/39=44.1794...≈ 45

 

45排有点多,看源码,源码里的y,1723大于上一排的1707,小于这一排的1746,那说明就是这一排了

 

 

然后直接说,也刚好是第10个数,ok了。而且这里还看出一个规律,【是】的高度是1723px,在svg源码里:

 

 

1746>1723>1707,所以闭着眼睛也知道一定在这一行,那么这里还可以有一个方法,用常用的那几个算法就能很快就能定位在哪一排,然后定位是哪一个字就行了,卧槽,感觉很简单啊 

 

 

 

规律找出来了,剩下的就是代码调试了

 

 

调试

 

 为了方便调试,就不每操作一次实际请求一次,因为这个平台的风控挺强的,所以直接查看源码,然后把内容保存到本地吧:

 

 

流程就是,读取源码,然后先把内容用xpath提取出来,把那些class属性找出来,然后请求拿到css,再通过css内容找到设置的宽度高度,同时把css里的svg的url请求一下,保存到本地,接着通过给定的宽度高度,取svg的原内容找就行了

 

所有需要i请求的,我都暂时保存在本地了,svg:

 

 

 

 同时直接复制css里的内容,手动放到content2.html文件里了,给了一个style标签

 

 

 

 

 

python实现

 

 

在执行的时候发现,当是42,23673px时,会报错:

 

 

 

 

主要是没有那么多的纵向,那看来我们之前用height除以39还是有问题,不能涵盖完整的情况

 

 

后面经过我的测试,发现以下规律:

 

1.高度除以39,结果如果不超过svg总排数的,减去一个通用值(假设为2),即为真实纵向位置

2.高度除以39,结果如果超过svg总排数的,如果值小于【总排数加通用值(假设为2)】,先减去一个通用值(假设为2)之后,如果这个值结果带有小数,再四舍五入,如果结果没有超过总排数,不用四舍五入,直接取整,即为真实纵向位置

3.高度除以39,结果如果超过svg总排数的,如果值大于【总排数加通用值(假设为2)】,直接按最后一排的数作为真实纵向位置

 

也可能这套规律并不通用,至少目前来看没有问题

 

我过了2天,再次打开这个网址,然后看到变了,源码变得如下:

 

 

 

 

然后svg源码也变了:

 

 

 

那这种,就没法用二分法了,所i有,上面想到的思路,并不通用,那咋办呢?这有点难为人了哈

但是,仔细发现,上面发现的规律还是可以通用的

 

只是二分法不通用了,来,继续看【一】,此时是【-126px -2737px】

126/14=9

2737/39=70.179.....

 

先找到70行,然后减2,得68,找第九个:

 

 

 看是不是第9个,没毛病:

 

 

看来,在svg的总排数以前的,都可以这么算,现在找一个纵向相对比较大的:

 

 

56/14=4

3184/39=81.641

 

此时,81.641<80+2,那就直接减2得79,然后小数位四舍五入,得80,找80行第4个:

 

 

 

 

就看是不是真的是这个【粉】字了,拿到刚才得class名【sr4q6】,到网页里覆盖即可:

 

 

果然变成了【粉】字

 

 

 

再来确认上面的规律的第三种情况,找个总值大于80+2的试试,发现,此时的css里没有,那就姑且这么认定了,直接写代码吧

 

同时更新下css和svg,以及html源码

 

此时,纵向的发现没毛病了,但是发现横向的又出现问题了,比如   -462.0px -1470.0px

462/14= 33

1470/39 = 37.69  

按上面的逻辑,应该是33,35,但是一找发现报错:

 

 

 

看来横向也不能直接一昧的除14啊,看看这个本来应该是啥字:

 

 

把【sr7u9】换上去看看,变成了【谁】字

 

 

看看svg里【谁】:

 

 

 

 

卧槽,越来越迷糊了,这到底咋回事,接着再看,看横向坐标都大于等于400的看看:

找到这么多:

 

 

一个一个看,不信找不到规律:

看【srjbq】,-504.0px -424.0px

 

本来是

36  10.89

但这里取得是36  11

 

 

sr4qu  -504.0px -2841.0px

算出是36,72.84 ==> 36,70

 

实际是36,71

 

sr5lf {
background: -518.0px -1324.0px

 

算出37  33.98 ==>33  31

 

实际37,33

 

.srwcf {
background: -532.0px -662.0px

 

算出 38  16.97  ==>  38 14

 

实际 38  17

 

.srqrx {
background: -546.0px -2841.0px

算出  39  72.84 

 

实际 39  71

 


.srauc {
background: -546.0px -14.0px

 

算出  39  0.35

实际  39  1

 

 

.srkfy {
background: -532.0px -741.0px

 

算出 38  19

 

实际 38 19

 

.sr7k0 {
background: -504.0px -772.0px

 

算出 36   19.79

实际  36  20 

 

当我在找规律的时候,我无意间又打开一个标签:

 

 

 

 

哎~,这他妈,看属性d的中间个数,嘿嘿,看来还是可以用二分法查找,ok,不再傻逼的找规律了,直接用算法,二分查找,或者冒泡的都可以,我这里就不耽误时间了,已经花了好几天时间分析了,不能再拖了,我就写个了最容易想的冒泡排序,也不考虑运行时间了,搞爬虫的,不用太纠结这些东西。

 

 

 

ok,代码:

 

 

 

至少现在没报错了,而且对了下都对上了,

还是这个靠谱点,上面总结的规律还是不能完美覆盖问题,就以上这样才是最好的,

行,现在就差最后一个问题,就是文字的顺序,看如下哈,没有做svg的,文字就在外层,有做文字加密的未加密的并列,那这个顺寻就有点不好操作了

 

 

 

再看当我直接用xpath的text时,是如下,所以,这个顺序还真不好处理

 

 

 

咋办,直接字符串替换吧

 

执行:

 

 

看第一条:

一直对乔一乔的印象都还不错 这次到的他家机场店 环境还可以 阿姨服务态度也不错 菜品口感也没问题 就是出了一个小插曲 红糖糍粑里吃出了一块铁 着实吓了一跳 还以为把牙吃掉了店长处理态度也还行 给菜品打了个8折 说事后牙有什么问题 随时联系他 我们也不是讹人的人 小问题也不去麻烦商家了 温馨提示一下估计这个铁的问题应该还存在 进货的时候仔细一点 这样挺影响体验感的

原文内容,完美对上,emoji图的问题暂不考虑,就是一个静态资源,想拼凑一下就是很简单的问题

 

部分python代码

content2.html和css.txt,svg.html,自行下载

def get_real_height(text, raw_height):
    # 这个函数已经弃用,得到的结果不精准
    length = len(text)
    temp_h = raw_height / 39
    if temp_h > length + 2:  # 大于总排数+2
        height = length
    elif temp_h > length:  # 大于总排数,小于总排数+2
        height = length - 2
        height = int(round(height, 0))  # 四舍五入
    else:  # 小于总排数
        height = raw_height // 39
    return height


def get_real_font(text, text_index, tuples=None):
    if not tuples:
        tuples = ('336.0', '214.0')
    width, height = tuples
    width = int(float(width))
    height = int(float(height))
    real_w = int(width / 14)
    d, real_h = get_real_height_v2(text_index, height)
    # print(12312312321, d, real_h)
    # print(555555, real_h, real_w, tuples)
    real_font = text[real_h][real_w]
    return real_font


def get_css():
    f = open('css.txt', encoding='utf-8')
    source_data = f.read()
    f.close()
    cont = re.findall(r'\.(\w+) \{.*?background: -(.*?)px -(.*?)px;', source_data, re.S | re.M)
    css_dict = {}
    for c in cont:
        css_dict[c[0]] = c[1:]
    # print(css_dict)
    return css_dict


def get_data_html():
    css_dict = get_css()
    f = open('svg.html', encoding='utf-8')
    svg = f.read()
    f.close()
    html_source = etree.HTML(svg)
    text = html_source.xpath('//text[position()>1]/text()')
    if not text:
        text = html_source.xpath('//textpath/text()')
    text_index = html_source.xpath('//defs/path/@d')
    text_index = [int(te.split(' ')[1]) for te in text_index]
    f = open('content2.html', encoding='utf-8')
    source_data = f.read()
    source_data = source_data.replace('<svgmtsi class=', '').replace('></svgmtsi>', '')
    f.close()
    html_source2 = etree.HTML(source_data)
    data = html_source2.xpath('//div[contains(@class,"review-words")]')
    for item in data:
        temp_dict = dict()
        raw_cont = item.xpath('.//text()')
        raw_cont = ''.join(raw_cont).strip() if raw_cont else ''
        if not raw_cont:
            continue
        raw_cont = raw_cont.split('"')
        raw_cont = [t for t in raw_cont if t]
        real_cont = raw_cont[:]
        # print(real_cont)
        # print(raw_cont)
        for ind, ra in enumerate(raw_cont):
            if ra.startswith('sr'):
                real_c = parser_svg_font(ra, css_dict, text, text_index)
                real_cont[ind] = real_c
        print('raw', real_cont)
        temp_dict['raw_cont'] = ''.join(real_cont)
        print('real', temp_dict['raw_cont'])


def parser_svg_font(s, css_dict, text, text_index):
    cont = []
    if isinstance(s, list):
        cont = []
        for i in s:
            temp = css_dict.get(i)
            real_font = get_real_font(text, text_index, temp)
            # print(real_font)
            cont.append(real_font)
    elif isinstance(s, str):
        temp = css_dict.get(s)
        cont = get_real_font(text, text_index, temp)
    # print(123123, cont)
    return cont


def get_real_height_v2(data_list, target):
    """
    :param data_list: 传入的有序列表
    :param target:  传入要查找的目标值
    """
    # data_list默认已排好序
    if target in data_list:
        return data_list.index(target)
    for index, d in enumerate(data_list):
        if d > target:
            if index == 0:
                return d, index
            else:
                if data_list[index - 1] < target:
                    return d, index


get_data_html()

然后有没有可优化的地步,那肯定有的,那个处理svg部分的映射关系,和横向坐标除以14,然后判断svg的classa是否是sr开头的,计算映射真实文字的函数,等等,很多都可以优化的,具体细节就不去抠了,核心的逻辑能实现,剩下的就是完善优化了。

原文地址:https://www.cnblogs.com/Eeyhan/p/15578344.html