字体反爬之奇闻007网简单又特殊的字体反爬

一个偶然的机会,一个朋友让我帮他看看某个网站的反爬机制,他弄了大半天都没有解决,这个网站就是奇闻007,一开始我也没搞明白它的反爬机制,后来经过仔细分析调试发现它的反爬机制是字体反爬,一个简单而又特殊的字体反爬,为什么说它简单而又特殊呢?因为它是字体反爬但是它的字体文件的编码不会随着页面的刷新而刷新,但是它的字有点多278个字,特殊在于它不像我们常见的反爬网页源代码显示的是编码而是中文汉字,这样就起到了混淆的作用。

目标分析

我们先打开奇闻007的一个页面,检查一下元素看看它的反爬现象:

可以发现浏览器Elements里的(渲染后)段落数据不一样,简直词不达意,但是段落里的有些字还是在页面渲染是正确的,比如字,页面看到的和渲染的是一样。我们再看看网页原代码:
发现和浏览器Elements里的(渲染后)段落数据一样和页面看到的不一样,那么它是怎么实现的呢?我们在Chrome控制台搜索article发现:
article相关的请求虽然有几个,但是仔细分析都不是我们想要的(不是解密相关)。我们再看看请求中的js请求,看看能否找到加密解密的相关js:
发现好多js都请求失败,其他js也不是加密解密的文件,我们再使用Chrome浏览器的一个插件Toggle JavaScript,通过它我们可以拦截页面所有的js请求,我们测试一下是不是不需要js这个页面也是能够正常显示:
这就说明这个加密解密跟js没有关系,那它是如何做到代码上混淆页面正常显示的呢?那么我们再看看除了js还有那些可疑请求:
除了js和本页面的html也没有什么可疑请求,唯一一个可疑的可能就是字体文件hansansjm.ttf,难道是字体反爬?不像啊,以前做过字体反爬,字体反爬的网页原代码显示都是的方式,而浏览器Elements里显示,难道是特殊的字体反爬?抱着尝试的态度下载这个字体文件然后用百度FontEditor打开查看:
发现这个字体文件的文字还挺多的居然有3页,因为在前面我们看到Elements中的字对应页面显示的字,我们看看这几页有没有字:
还真有,我们在第3页找到了字,它的编码是$76F4,我们再看看其他的一些代码中和显示结果不一样的字是否能在字体文件中找到,结果发现都能找到,那就说明这个反爬还真是字体反爬。

按照之前做过的字体反爬经验,我们每刷新一下字体文件里文字对应的编码都是会变的,我们也刷新一下页面然后再看看字体文件的字以及它们对应的编码会不会改变,经过多次尝试发现它的字体文件里面的字以及字对应的编码是不会变的。

这就简单了,我们直接把每个字的映射关系弄出来,然后在页面替换就能获取正常的的数据了,可是有这么多字,现在我们只知道部分字的对应关系,比如对应,其他的不知道,这样就很难做映射,而且有个疑问为什么是对应而不是其他什么字?它们之间有什么联系?

我们可以使用Python的fonttools库把字体和编码的映射弄出来,比如字我们可以找出它的编码0x76f40x76f4有什么关系,凭借着以前的经验,我推断它可能是Unicode编码转为字符,我们使用js的String.fromCharCode()方法或者Python的chr()方法试一下:

真的如我预测的一样,这样我们就可以解决这个反爬了

解密

按照上面的分析我们只需要下载字体文件,使用fonttools库把字体和编码的映射弄出来,我们再手动把这278个字手动敲出来,再用chr()方法把真正的映射关系弄出来,最后替换文字,就可以提取数据了。

from fontTools.ttLib import TTFont
import requests
from lxml import etree

# 可以是.ttf类型的字体文件也可以是.woff类型的字体文件
font = TTFont('hansansjm.ttf')
# 手动把字体文件中的字写下来
font_data = ['万', '三', '上', '下', '不', '与', '世', '东', '两', '个', '中', '为', '主', '丽', '么', '之', '乐', '也', '了', '事', '二',
             '五', '些', '亲', '人', '什', '今', '他', '代', '以', '们', '会', '传', '位', '体', '何', '作', '你', '值', '做', '像', '儿',
             '元', '光', '入', '全', '公', '关', '内', '写', '冰', '出', '分', '刘', '到', '前', '剧', '力', '动', '十', '千', '却', '原',
             '去', '友', '发', '变', '古', '只', '可', '吃', '合', '同', '名', '后', '吗', '吴', '员', '和', '四', '回', '因', '国', '图',
             '圈', '在', '地', '场', '型', '外', '多', '大', '天', '太', '夫', '头', '奇', '女', '她', '好', '如', '妈', '妻', '娱', '婚',
             '子', '学', '孩', '宝', '实', '家', '对', '将', '小', '少', '就', '山', '岁', '已', '巴', '帅', '年', '底', '度', '开', '张',
             '当', '影', '很', '得', '心', '性', '怪', '情', '惊', '想', '意', '感', '戏', '成', '我', '房', '手', '打', '拍', '排', '新',
             '方', '无', '日', '时', '明', '星', '是', '曝', '曾', '最', '月', '有', '服', '本', '机', '李', '来', '杨', '林', '果', '样',
             '榜', '次', '死', '母', '比', '民', '气', '水', '没', '法', '活', '海', '清', '游', '演', '火', '点', '热', '然', '照', '爱',
             '片', '物', '特', '狗', '王', '现', '球', '生', '用', '电', '男', '界', '白', '百', '的', '直', '相', '看', '真', '眼', '着',
             '知', '神', '种', '秘', '称', '穿', '竟', '笑', '第', '粉', '红', '经', '结', '给', '网', '美', '老', '而', '能', '脸', '自',
             '色', '艺', '花', '英', '行', '衣', '被', '装', '西', '要', '见', '视', '认', '让', '说', '谁', '走', '赵', '起', '超', '路',
             '身', '车', '过', '还', '这', '造', '道', '遭', '部', '都', '里', '重', '金', '长', '陈', '面', '颖', '颜', '食', '马', '高',
             '鱼', '黄', '黑', '龙', '一']
# 编码和文字之间的映射关系
name_font = {name:fonts for name, fonts in zip(font.getGlyphOrder()[1:],font_data)}
# 页面字和文字的映射
code_font = {chr(code):name_font[name] for code, name in font.getBestCmap().items() if name in name_font }
"""
完整的映射关系(code_font变量),字典的key是网页源代码中的文字,value是页面显示的文字
{'⺟': '比', '⺠': '气', '⻅': '视', '⻋': '过', '⻓': '陈', '⻝': '马', '⻢': '高', '⻥': '黄', '⻩': '黑', '⻰': '一', '⼀': '万', '⼆': '五', '⼈': '什', '⼉': '元', '⼊': '全', '⼒': '动', '⼗': '千', '⼤': '天', '⼥': '她', '⼦': '学', '⼩': '少', '⼭': '岁', '⼼': '性', '⼿': '打', '⽅': '无', '⽆': '日', '⽇': '时', '⽉': '有', '⽐': '民', '⽓': '水', '⽔': '没', '⽕': '点', '⽚': '物', '⽣': '用', '⽤': '电', '⽩': '百', '⽹': '美', '⽼': '而', '⽽': '能', '⾃': '色', '⾊': '艺', '⾏': '衣', '⾐': '被', '⾛': '赵', '⾝': '车', '⾥': '重', '⾦': '长', '⾯': '颖', '⾷': '马', '⾼': '鱼', '⿊': '龙', '一': '万', '万': '三', '三': '上', '上': '下', '下': '不', '不': '与', '与': '世', '世': '东', '东': '两', '两': '个', '个': '中', '中': '为', '为': '主', '主': '丽', '丽': '么', '么': '之', '之': '乐', '乐': '也', '也': '了', '了': '事', '事': '二', '二': '五', '五': '些', '些': '亲', '亲': '人', '人': '什', '什': '今', '今': '他', '他': '代', '代': '以', '以': '们', '们': '会', '会': '传', '传': '位', '位': '体', '体': '何', '何': '作', '作': '你', '你': '值', '值': '做', '做': '像', '像': '儿', '儿': '元', '元': '光', '光': '入', '入': '全', '全': '公', '公': '关', '关': '内', '内': '写', '写': '冰', '冰': '出', '出': '分', '分': '刘', '刘': '到', '到': '前', '前': '剧', '剧': '力', '力': '动', '动': '十', '十': '千', '千': '却', '却': '原', '原': '去', '去': '友', '友': '发', '发': '变', '变': '古', '古': '只', '只': '可', '可': '吃', '吃': '合', '合': '同', '同': '名', '名': '后', '后': '吗', '吗': '吴', '吴': '员', '员': '和', '和': '四', '四': '回', '回': '因', '因': '国', '国': '图', '图': '圈', '圈': '在', '在': '地', '地': '场', '场': '型', '型': '外', '外': '多', '多': '大', '大': '天', '天': '太', '太': '夫', '夫': '头', '头': '奇', '奇': '女', '女': '她', '她': '好', '好': '如', '如': '妈', '妈': '妻', '妻': '娱', '娱': '婚', '婚': '子', '子': '学', '学': '孩', '孩': '宝', '宝': '实', '实': '家', '家': '对', '对': '将', '将': '小', '小': '少', '少': '就', '就': '山', '山': '岁', '岁': '已', '已': '巴', '巴': '帅', '帅': '年', '年': '底', '底': '度', '度': '开', '开': '张', '张': '当', '当': '影', '影': '很', '很': '得', '得': '心', '心': '性', '性': '怪', '怪': '情', '情': '惊', '惊': '想', '想': '意', '意': '感', '感': '戏', '戏': '成', '成': '我', '我': '房', '房': '手', '手': '打', '打': '拍', '拍': '排', '排': '新', '新': '方', '方': '无', '无': '日', '日': '时', '时': '明', '明': '星', '星': '是', '是': '曝', '曝': '曾', '曾': '最', '最': '月', '月': '有', '有': '服', '服': '本', '本': '机', '机': '李', '李': '来', '来': '杨', '杨': '林', '林': '果', '果': '样', '样': '榜', '榜': '次', '次': '死', '死': '母', '母': '比', '比': '民', '民': '气', '气': '水', '水': '没', '没': '法', '法': '活', '活': '海', '海': '清', '清': '游', '游': '演', '演': '火', '火': '点', '点': '热', '热': '然', '然': '照', '照': '爱', '爱': '片', '片': '物', '物': '特', '特': '狗', '狗': '王', '王': '现', '现': '球', '球': '生', '生': '用', '用': '电', '电': '男', '男': '界', '界': '白', '白': '百', '百': '的', '的': '直', '直': '相', '相': '看', '看': '真', '真': '眼', '眼': '着', '着': '知', '知': '神', '神': '种', '种': '秘', '秘': '称', '称': '穿', '穿': '竟', '竟': '笑', '笑': '第', '第': '粉', '粉': '红', '红': '经', '经': '结', '结': '给', '给': '网', '网': '美', '美': '老', '老': '而', '而': '能', '能': '脸', '脸': '自', '自': '色', '色': '艺', '艺': '花', '花': '英', '英': '行', '行': '衣', '衣': '被', '被': '装', '装': '西', '西': '要', '要': '见', '见': '视', '视': '认', '认': '让', '让': '说', '说': '谁', '谁': '走', '走': '赵', '赵': '起', '起': '超', '超': '路', '路': '身', '身': '车', '车': '过', '过': '还', '还': '这', '这': '造', '造': '道', '道': '遭', '遭': '部', '部': '都', '都': '里', '里': '重', '重': '金', '金': '长', '长': '陈', '陈': '面', '面': '颖', '颖': '颜', '颜': '食', '食': '马', '马': '高', '高': '鱼', '鱼': '黄', '黄': '黑', '黑': '龙', '龙': '一', '老': '而', '路': '身', '不': '与', '力': '动', '年': '底', '里': '重', '林': '果', '什': '今', '行': '衣'}
"""

# 定义一个文字替换的函数
def replace_word(row,replace_dict):
    result = []
    for word in row:
        if word in replace_dict.keys():
            result.append(replace_dict[word])
        else:
            result.append(word)
    return ''.join(result)

# 请求的url地址
url = "http://www.qiwen007.com/zbwz/366283.html"

headers = {
    'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36",
    }
response = requests.get(url, headers=headers)
html = etree.HTML(response.text)
# 源文章标题
row_title_list = html.xpath('//div[@class="article_main_right"]/h1/text()')
# 源文章内容
row_content_list = html.xpath('//div[@class="article"]//p/text()')
if row_title_list:
  row_title = row_title_list[0]
  print(replace_word(row_title,code_font ))
else:
  print('没有提取到数据')
if row_content_list:
  row_content = '\n'.join(row_content_list)
  print(replace_word(row_content ,code_font ))
else:
  print('没有提取到数据')

结果:

你可能感兴趣的:(字体反爬之奇闻007网简单又特殊的字体反爬)