T9 字体雪碧图
一:抓包分析
同样的下一页抓包


可以看到接口返回值有几个很重要的部分
css_code:偏移信息
page_data:替换后的值
sprite:拼接png的base64

能看到偏移从-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)
|
执行结果:

与预期一致
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), '')
|

生成如上图结果
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
|

获得最终需要的映射值
1
| {'2506': '7', '3463': '3', '366': '1', '9979': '8', '3212': '5', '5865': '4', '571': '0', '6315': '2', '779': '9'}
|
4、数值替换得到实际值、编码
编码时候发现结果总是错的,分析其中的原因,发现当偏移值不是从-8px开始后,结果明显不对

修改映射代码强制加-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
|

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