逆向09-字体加密

1 什么是字体加密

字体加密是页面和前端字体文件相配合完成的一个反爬策略。通过css对其中一些重要数据进行加密,使我们在代码获取的和在页面上看到的数据是不同的。

2 猫眼案例

猫眼即将上映页面
image

前端人员通过使用font-face来达到这个目的,font-face是CSS3中的一个模块,他主要是把自己定义的Web字体嵌入到你的网页中。而font-face的格式为:

@font-face{
font-family: "mtsi-font"; // 定义该字体的名称
src: xxxxx; // 定义该字体下载的网址
    }

发送新的请求
image
上面的看似加密的数字,其实就是woff文件的映射,下面的图片地址可以获取woff文件
image

我们要打开woff,ttf,eof这类格式的文件,可以使用下面的网址

https://font.qqe2.com/
https://www.bejson.com/ui/font/

下载文件之后所得
image
但是我们每次都是变化的---所以我们的思路是,每次请求之后获取woff的地址,再发送新的请求

import requests
import re


cookies = {
  'uuid_n_v': 'v1',
  'uuid': '417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444',
  '_csrf': '8c2931b0fbd966320ec3748e5cb37dc7ba2d7b4e9350ca872f25b35c7283e1fb',
  'Hm_lvt_703e94591e87be68cc8da0da7cbd0be2': '1722237296',
  'HMACCOUNT': 'FA0EEA6697020683',
  '_lx_utm': 'utm_source%3Dgoogle%26utm_medium%3Dorganic',
  '_lxsdk_cuid': '190fd579d3fc8-05490f759e96b5-13462c6f-1fa400-190fd579d3fc8',
  '_lxsdk': '417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444',
  'Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2': '1722237311',
  '__mta': '187134487.1722237296552.1722237301010.1722237312072.5',
  '_lxsdk_s': '190fd579d3f-f00-b49-efd%7C%7C10',
}


headers = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
  'Accept-Language': 'zh-CN,zh;q=0.9',
  'Cache-Control': 'max-age=0',
  'Connection': 'keep-alive',
  # 'Cookie': 'uuid_n_v=v1; uuid=417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444; _csrf=8c2931b0fbd966320ec3748e5cb37dc7ba2d7b4e9350ca872f25b35c7283e1fb; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1722237296; HMACCOUNT=FA0EEA6697020683; _lx_utm=utm_source%3Dgoogle%26utm_medium%3Dorganic; _lxsdk_cuid=190fd579d3fc8-05490f759e96b5-13462c6f-1fa400-190fd579d3fc8; _lxsdk=417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1722237311; __mta=187134487.1722237296552.1722237301010.1722237312072.5; _lxsdk_s=190fd579d3f-f00-b49-efd%7C%7C10',
  'Referer': 'https://www.maoyan.com/films',
  'Sec-Fetch-Dest': 'document',
  'Sec-Fetch-Mode': 'navigate',
  'Sec-Fetch-Site': 'same-origin',
  'Sec-Fetch-User': '?1',
  'Upgrade-Insecure-Requests': '1',
  'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
  'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
  'sec-ch-ua-mobile': '?0',
  'sec-ch-ua-platform': '"Linux"',
}

params = {
  'showType': '2',
}

response = requests.get('https://www.maoyan.com/films', params=params, cookies=cookies, headers=headers)
data = response.text # 获取响应内容 ---woff文件
path = re.findall(',url\("(.*?woff)"', data)[0]
woff_path = 'http:' + path
print(woff_path)
response = requests.get(woff_path)
with open('./font.woff', 'wb') as f:
    f.write(response.content)

image

3 脚本-字体woff做映射表

脚本-字体woff做映射表

4 猫眼案例-实践

下面的代码是 第2步和第3步的结合

import requests
import re
from util import extract_text_from_font
from lxml import etree
def get_woff_path():
    cookies = {
    'uuid_n_v': 'v1',
    'uuid': '417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444',
    '_csrf': '8c2931b0fbd966320ec3748e5cb37dc7ba2d7b4e9350ca872f25b35c7283e1fb',
    'Hm_lvt_703e94591e87be68cc8da0da7cbd0be2': '1722237296',
    'HMACCOUNT': 'FA0EEA6697020683',
    '_lx_utm': 'utm_source%3Dgoogle%26utm_medium%3Dorganic',
    '_lxsdk_cuid': '190fd579d3fc8-05490f759e96b5-13462c6f-1fa400-190fd579d3fc8',
    '_lxsdk': '417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444',
    'Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2': '1722237311',
    '__mta': '187134487.1722237296552.1722237301010.1722237312072.5',
    '_lxsdk_s': '190fd579d3f-f00-b49-efd%7C%7C10',
    }

    headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    # 'Cookie': 'uuid_n_v=v1; uuid=417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444; _csrf=8c2931b0fbd966320ec3748e5cb37dc7ba2d7b4e9350ca872f25b35c7283e1fb; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1722237296; HMACCOUNT=FA0EEA6697020683; _lx_utm=utm_source%3Dgoogle%26utm_medium%3Dorganic; _lxsdk_cuid=190fd579d3fc8-05490f759e96b5-13462c6f-1fa400-190fd579d3fc8; _lxsdk=417107104D7A11EF90DF9368836D3621A416621B3E254DE0B3C80991479F2444; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1722237311; __mta=187134487.1722237296552.1722237301010.1722237312072.5; _lxsdk_s=190fd579d3f-f00-b49-efd%7C%7C10',
    'Referer': 'https://www.maoyan.com/films',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
    'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Linux"',
    }

    params = {
    'showType': '2',
    }

    response = requests.get('https://www.maoyan.com/films', params=params, cookies=cookies, headers=headers)
    data = response.text # 获取响应内容 ---woff文件
    path = re.findall(',url\("(.*?woff)"', data)[0]

    woff_path = 'http:' + path
    return woff_path, data

# (1)获取woff文件
woff_path, data = get_woff_path()
# print(woff_path)
response = requests.get(woff_path)
with open('./font.woff', 'wb') as f:
    f.write(response.content)

# (2)解析woff文件,获取字体映射关系
font_map = extract_text_from_font('./font.woff')
# print(font_map)

# (3)字体解密
for key, value in font_map.items():
    data = data.replace(key + ";", value)
# print(data)
# (4)解析新的data,获取电影名称和想看人数

html_Data = etree. HTML(data)
div_list = html_Data.xpath('//dl[@class="movie-list"]//dd') 
print(len(div_list))
for v in div_list:
    try:
        title = v.xpath(f'./div[2]/a/text()')[0]
        stone_font = v.xpath(f'./div[3]/span/text()')[0]
        print("title:", title)
        print("stone_font:", stone_font + "人想看")
        print("*" * 50)
    except IndexError:
        continue

5 番茄小说案例

import requests
from io import BytesIO
from lxml import etree
from fontTools.ttLib import TTFont
from PIL import Image, ImageDraw, ImageFont
import ddddocr


IMAGE_SIZE = 1024


def convert_cmap_to_image(cmap_code, font_path):
    img_size = 1024
    img = Image.new("1", (img_size, img_size), 255)  # 创建一个黑白图像对象
    draw = ImageDraw.Draw(img)  # 创建图对象
    font = ImageFont.truetype(font_path, int(img_size*0.7)) # 加载字体文件
    character = chr(cmap_code)  # 将 cmap code转换为字符
    bbox = draw.textbbox((0, 0), character, font=font)  # 获取文本在图像中的边界框,bbox获取的是四个值
    width = bbox[2] - bbox[0]  # 文本宽度
    height = bbox[3] - bbox[1]  # 文本高度
    draw.text(((img_size - width) // 2, (img_size - height) // 7), character, font=font)  # 绘制文本,居中显示
    return img


def extract_text_from_font(font_path: str) -> dict:
    """从字体文件中提取字符映射表
    Args:
        font_path: 字体文件路径
    Returns:
        字符映射字典
    """
    try:
        font = TTFont(font_path)
    except Exception as e:
        raise ValueError(f"字体文件加载失败: {e}")

    ocr = ddddocr.DdddOcr(beta=True, show_ad=False)
    font_map = {}

    for cmap_code, glyph in font['cmap'].getBestCmap().items():
        image = convert_cmap_to_image(cmap_code, font_path)
        bytes_io = BytesIO()
        image.save(bytes_io, format='PNG')
        text = ocr.classification(bytes_io.getvalue())
        # 生成对应名字的图片
        # image.save(f'./imgs/{text}.png', format='PNG')
        # font_map[glyph.replace("uni", "&#x").lower()] = text
        font_map[hex(cmap_code).replace("0x", r"")] = text

    return font_map

def change_code(codes, font_map):
    result = ""
    for code in codes:
        if repr(code)[3:-1]:
		# repr(code)原来是一个方块,现在要的是十六进制形式
            code = font_map[repr(code)[3:-1]]
        result += code
    return result



def spider(font_map) -> None:
    """爬取网页内容"""
    url = "https://fanqienovel.com/reader/7514612510180246040"
    
    headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7',
        'cache-control': 'no-cache',
        'pragma': 'no-cache',
        'priority': 'u=0, i',
        'referer': 'https://fanqienovel.com/page/7514612419809774616?enter_from=search',
        'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'document',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'same-origin',
        'sec-fetch-user': '?1',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
        'Cookie': 'x-web-secsdk-uid=168b1e60-92b4-451c-b5df-66a67421a900; Hm_lvt_2667d29c8e792e6fa9182c20a3013175=1764849896; HMACCOUNT=23DCAE7CD8AC0292; s_v_web_id=verify_mire277a_dYF3OgTA_erP0_4hLg_AtMC_bxEp2Ra2N96u; csrf_session_id=87f3ca548dbb03402f5af216156c9a75; novel_web_id=7579972548583654948; Hm_lpvt_2667d29c8e792e6fa9182c20a3013175=1764849946; ttwid=1%7CJfBjmEgyk9CrFZ1hYB90tW9s4rQcBqYCkcMXl69hU5o%7C1764849946%7C7c3dd222b53205e1b4619de840e3824edc2c59217a0ca41814199b549c4d4118'
    }

    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
    except requests.RequestException as e:
        print(f"请求失败: {e}")
        return

    data = response.text

    html_data = etree.HTML(data)
    div_list = html_data.xpath('//*[@id="app"]/div/div/div/div[2]/div[2]/div/p')
    
    for v in div_list:
	# 映射转换
        ret = change_code(v.xpath('string()'), font_map)
        print(ret)

if __name__ == "__main__":

    font_map = extract_text_from_font('./zzz.woff2')

    spider(font_map)


posted @ 2024-07-29 17:24  流年中渲染了微笑  阅读(26)  评论(0)    收藏  举报