T9 字体雪碧图

一:抓包分析

同样的下一页抓包

image-20260507234654063

image-20260507234830210

可以看到接口返回值有几个很重要的部分

css_code:偏移信息

page_data:替换后的值

sprite:拼接png的base64

image-20260507234909693

能看到偏移从-8px–458px分别对应图片中的字母,若你看到的偏移信息没有-8px很大可能是你当前页面的映射值没有0

那么本文的主要步骤就是通过ocr识别图片的文字,找到css_code的偏移信息,让他们一一对应上

二:字体解密

1、ocr识别图片字母

本文直接使用ddddocr

前提是需要将图片的黑色背景转换成白色,黑色字体保留

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import ddddocr
import base64
from PIL import Image
import io


def preprocess_transparent_image(image_base64):
"""预处理透明背景图片,将透明背景转换为白色,保留黑色字体。"""
image_bytes = base64.b64decode(image_base64)
# 将二进制数据转为PIL图像
image = Image.open(io.BytesIO(image_bytes)).convert("RGBA")

# 创建一个新的白色背景图片
new_background = Image.new("RGB", image.size, (255, 255, 255))

# 将原图(黑色字体在透明通道上)粘贴到白色背景上
# 仅当原图像素不透明时,才使用原图的颜色(即黑色字体部分)
new_background.paste(image, mask=image.split()[3]) # 使用alpha通道作为mask

# 将处理后的图片转换回二进制数据
byte_arr = io.BytesIO()
new_background.save(byte_arr, format='PNG')
return byte_arr.getvalue()

调用代码:

1
2
3
4
ocr = ddddocr.DdddOcr()
base64_png = ""
image_bytes = preprocess_transparent_image(base64_png)
res = ocr.classification(image_bytes)

执行结果:

image-20260508003115937

与预期一致

2、获得css_code偏移信息

直接给代码,从-8px - -458px实际对应的值是上一步识别出来的值,也就是-8px的位置对应值为7,实际上用366表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import re

css_code = """"""

# 匹配 class 后面的数字 和 background-position 的偏移值
matches = re.findall(
r'\.class(\d+)\s*\{\s*background-position:\s*(-?\d+)px',
css_code
)

# 转成 偏移值 -> class数字
result = {}

for class_num, offset in matches:
result[offset] = class_num

# 生成完整区间(每50递减)
min_offset = min(map(int, result.keys()))
max_offset = max(map(int, result.keys()))

final_dict = {}

for offset in range(max_offset, min_offset - 1, -50):
final_dict[str(offset)] = result.get(str(offset), '')

image-20260508090448289

生成如上图结果

3、获得最后映射关系

将ocr识别的结果,依次替换上图的key值,得到新的dict为反向的映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
res = "7318540296"

final_dict = {
'-8': '779',
'-58': '6315',
'-108': '',
'-158': '571',
'-208': '5865',
'-258': '3212',
'-308': '9979',
'-358': '366',
'-408': '3463',
'-458': '2506'
}

# 按偏移值从大到小排序(-8 -> -458)
offsets = sorted(map(int, final_dict.keys()), reverse=True)

result = {}

index = 0

for offset in offsets:
class_name = final_dict[str(offset)]

# 空值跳过,但不消耗 res 的字符
if not class_name:
continue

if index < len(res):
result[class_name] = res[index]
index += 1

image-20260508093253378

获得最终需要的映射值

1
{'2506': '7', '3463': '3', '366': '1', '9979': '8', '3212': '5', '5865': '4', '571': '0', '6315': '2', '779': '9'}

4、数值替换得到实际值、编码

编码时候发现结果总是错的,分析其中的原因,发现当偏移值不是从-8px开始后,结果明显不对

image-20260508111034366

修改映射代码强制加-8px上去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def get_final_dict(css_code):
# 匹配 class 后面的数字 和 background-position 的偏移值
matches = re.findall(
r'\.class(\d+)\s*\{\s*background-position:\s*(-?\d+)px',
css_code
)

# 转成 偏移值 -> class数字
result = {}

for class_num, offset in matches:
result[offset] = class_num

# 强制保证 -8 存在
offsets = list(map(int, result.keys()))

min_offset = min(offsets)

# 如果没有 -8,则补进去
if -8 not in offsets:
offsets.append(-8)

max_offset = max(offsets)

final_dict = {}

for offset in range(max_offset, min_offset - 1, -50):
final_dict[str(offset)] = result.get(str(offset), '')
return final_dict

image-20260508111215714

结果正确

更多内容也在公众号更新:码字的秃猴

tuhou