26

浅谈字体反爬虫的一些思路

 4 years ago
source link: https://www.tuicool.com/articles/I7jAj27
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0×0前言

反爬虫和爬虫之间的较量已经争斗多年,不管是攻还是守,已经持续N年,这是一个没有硝烟的战场,大家都知道爬虫和反爬之家的道高一尺魔高一丈的关系。但这个方案可以很大程度上可以增加普通爬虫的采集成本,在不使用OCR的前提下,算是比较极致的方案了。当然方案有很多种,层出不穷的各种方法,这里介绍的时候反爬虫的中的一种比较实用的方案,字体反爬也就是自定义字体反爬通过调用自定义的ttf文件来渲染网页中的文字,而网页中的文字不再是文字,而是相应的字体编码,通过复制或者简单的采集是无法采集到编码后的文字内容!必须通过程序去处理才能达到采集成本。

zAR7Rj3.jpg!web

效果展示!

0×1思路

细心的人会问,为什么不把所有的内容都替换成编码呢?这个就涉及到加载和渲染速度的问题。还有如果启动字体反爬虫,基本上已经告别SEO了,请仔细考虑中间的厉害关系,你懂得!

我们知道,单纯汉字就有好几千个,还有各种字符,有的还包含各种外国人的字符串!如果全部放到自定义字体库中的话,这个文件灰常大,几十兆是肯定有的了,那后果啥样就很清楚了,加载肯定很慢,更糟糕的是如此之多的字体需要浏览器去渲染,那效果,卡到爆!!!

为了解决这个问题,我们可以选择只渲染少量的、部分的文字,假设50个字,那么字体库就会小到几十K了,相当于一个小图片而已,加上CDN加速之类的,解决了。具体网络上又N种方法参考方法我会贴在下面!

如此简单?50个字儿呢可不是随便随便选择的,要选择那些爬虫采集不到就会很大改变整个语句的语义的词,直接点吧,也就是量词、否定词之类的。如原文“我有一头朱佩琪,我从来都不骑”,我们把其中的“一”、“不”放到我们的自定义字库中,这样一来,爬虫采集到的就是“我有头朱佩琪,我从来都骑”,嘿嘿,如果加上数字就更叼了,“漏洞版本 .. ???“ 是不是很猥琐。

但是上述方法早已让网络各位大神破解一遍了,这可如何是好呢?办法总是有的!如果让“叼”字的编码随机变化,但字体信息不变,浏览器渲染出来还是“叼”字那不就完美了,于是,每个网页加载的时候,都随机加载了一套字体库,字体库的内容还是50个字,但每个字的顺序编码都是变化的,虽然我们打乱了关键字的编码顺序,但是每个字对应的字体信息是不变的,例如,“是”字一共有9划,每一笔划都有相应的x、y坐标信息,浏览器正是根据这些笔划信息渲染

eeIbeu2.jpg!web

如果吧,unicode编码和x,y坐标都骚做改动。他需要采集我的每一套字体库并且建立关系,这样增加的爬虫的成本,美滋滋。

0×3实现

基于微软雅黑字库信息,抽取其中的关键字的字体信息,生成ttf 后 使用下方代码 开始随机然后随机生成上千套字库,后文章显示时随机从文件或者裤中查询出一套字库,并把文章中的关键字替换成Unicode编码进行渲染!

# encoding: utf-8

# -*-*-

# By:连长 『zh (www.lianzhang.org)』

# -*-*-

import random

from fontTools import ttx

from fontTools.ttLib import TTFont

def random_unicode(lengths):  # 随机生成Unicode字符集

    while True:

        shuma = ((str(random.sample(random_list, int(lengths))).replace('\'', '')).replace(',', '')). \

            replace(' ', '').replace('[', '').replace(']', '')

        if shuma[0].isalpha():

            return shuma

        else:

            continue


def TTFontsXML(filenames):  # 转换成XMl 到临时目录

    filenametemp = "temp/toolstemp.xml"

    font = TTFont(filenames)

    font.saveXML(filenametemp)

    return filenametemp


def TTFonts(filenames):  # 转换XML转换ttf

    try:

        print("开始转换字体!!!" + filenames)

        ttx.main([filenames])

        print ("-----------------------------------")

    except Exception as e:

        print ("Something went wrong converting ttx -> ttf/otf:")

        print (e)

        exit()


def Editfile(fontsjson, files):

    random_list = ["a", "v", "x", "s", "q", "w", "e", "r", "t", "y", "u", "i", "o", "z", "x", "c", "v", "b", "n", "m",

                   "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ]

    shuma = ((str(random.sample(random_list, int(25))).replace('\'', '')).replace(',', '')). \

        replace(' ', '').replace('[', '').replace(']', '')

    filenametemp = "temp/" + shuma + ".xml"


    try:

        with open(files, 'r+') as fileOpen:

            data = fileOpen.read()

            fileOpen.close()


        for key in fontsjson.keys():

            data = data.replace(str(relationdic[key]), str(fontsjson[key]))

            data = data.replace(str(relationdic[key]).upper(), str(fontsjson[key]).upper())

        with open(filenametemp, 'w') as f:

            f.write(data)

            f.close()

        return shuma + ".ttf", filenametemp

    except Exception, ex:

        print ex

        filenametemp = "error"

        filenames = ""

        return filenametemp, filenames


if __name__ == '__main__':

    random_list = ['e', 'a', 'd', 'f', 'c', 'b', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

    ttf_patn = "FontTest.ttf"  # 请输入ttf文件绝对路径:

    unicodelengths = 4  # 输入 UniCode 长度:

    ttfnumber = 10  # 输入生成多少个文件:

    relationdic = {"0": "e2f1", "1": "efab", "2": "eba3", "3": "eba5", "4": "edfd", "5": "c57f", "6": "e261",

                   "7": "f4d2", "8": "bad5", "9": "d4c2",

                   "A": "bfec5", "B": "fc736", "C": "e6d21", "D": "be4a9", "E": "c0e8f", "F": "d3c26", "G": "b18a0",

                   "H": "acb06", "I": "fd33e", "J": "fd36e", "K": "d417c", "L": "ad31e", "M": "ec95a", "N": "b39ce",

                   "O": "d508a", "P": "a961d", "Q": "a76b0", "R": "b7f12", "S": "b0426", "T": "d5941", "U": "ede47",

                   "V": "fc5a6", "W": "ed947", "X": "fd781", "Y": "b761a",

                   "Z": "af370", "a": "fde89", "b": "ecb21", "c": "c123c", "d": "b4c2c", "e": "cbbc7", "f": "c10cb",

                   "g": "cb78b", "h": "fdac7", "i": "076fe", "j": "d0def", "k": "ed6de", "l": "eaa1a", "m": "de1e9",

                   "n": "9eaa5", "o": "123e5", "p": "e12e2", "q": "e5efd", "r": "e6ea9", "s": "e1e8a", "t": "b8eac",

                   "u": "23e1c", "v": "ea6ac",

                   "w": "b87de", "x": "e5dac", "y": "2ccea",

                   "z": "3ada9"}  # 必须当前混淆ttf填入关系 否则不知道谁是谁了。。。生成的json 文件也是错误的。。

    try:

        macs = len(relationdic) + 50 * ttfnumber  # 可能会有重复 多加点

        tem_list = []

        for x in range(0, int(macs)):

            tem_list.append(random_unicode(unicodelengths))

        tem_list = list(set(tem_list))  # 去重

        tempfontsxmlpa = TTFontsXML(ttf_patn)  # 转换到临时XML地址。。

        okjson = []

        for f in range(0, ttfnumber):

            relationdictemp = relationdic.copy()

            for key in relationdictemp.keys():

                b = random.sample(tem_list, 1)

                tem_list.remove(b[0])

                relationdictemp[key] = b[0]

            filenames, filenametemp = Editfile(relationdictemp, tempfontsxmlpa)

            if filenametemp != "error":  # 如果返回修改成功 启动转换ttf的程序!

                TTFonts(filenametemp)

                jsonSeve = {"url": filenames, "data": relationdictemp}

                okjson.append(jsonSeve)

    except Exception, ex:

        print ex

Qv2mqyj.jpg!web

Git:https://github.com/hackxx/AntiCrawlerFontGeneration 

上述代码中 不带x,y混淆,请自己修改代码谢谢,如有需要请留言!

0×4参考

https://www.jianshu.com/p/4d28dd440cdd

https://github.com/FantasticLBP/Anti-WebSpider

*本文作者:安百科技,转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK