逆向09-字体加密
1 什么是字体加密
字体加密是页面和前端字体文件相配合完成的一个反爬策略。通过css对其中一些重要数据进行加密,使我们在代码获取的和在页面上看到的数据是不同的。
2 猫眼案例
前端人员通过使用font-face来达到这个目的,font-face是CSS3中的一个模块,他主要是把自己定义的Web字体嵌入到你的网页中。而font-face的格式为:
@font-face{
font-family: "mtsi-font"; // 定义该字体的名称
src: xxxxx; // 定义该字体下载的网址
}
发送新的请求

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

我们要打开woff,ttf,eof这类格式的文件,可以使用下面的网址
https://font.qqe2.com/
https://www.bejson.com/ui/font/
下载文件之后所得

但是我们每次都是变化的---所以我们的思路是,每次请求之后获取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)

3 脚本-字体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)


浙公网安备 33010602011771号