来写一下这道,地址:https://match.yuanrenxue.com/match/7

目标就是要拿到所有的胜点数据,然后进行比对筛选

看一下网络抓包

看到没有直接返回数据,而是 &#xb483 &#xe924 &#xe892 &#xb483 等数据,底下也有一个 woff的base64文件

先把字体文件解析下来,然后打开看看:

这里就很明显了, unib483&#xb483 一样是对应数字2的,所以关键是如何拿到小标题和数字的映射关系

这道题不像 字体反爬(一),数字不是是按顺序,而是乱序的,测试的时候发现接口每次返回的数字顺序都不一样

这就需要我们深入到文件的源码里面去探索,首先把woff文件转换成 xml,用的是 TTFont.saveXML 方法

1
2
3
In [7]: from fontTools.ttLib import TTFont
In [9]: tt = TTFont("test.woff")
In [10]: tt.saveXML("test.xml")

打开xml文件,查看每个 TTGlyph 节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<TTGlyph name="unie892" xMin="43" yMin="0" xMax="573" yMax="706">
<contour>
<pt x="382" y="706" on="1"/>
<pt x="43" y="243" on="1"/>
<pt x="43" y="160" on="1"/>
<pt x="382" y="160" on="1"/>
<pt x="382" y="0" on="1"/>
<pt x="449" y="0" on="1"/>
<pt x="449" y="160" on="1"/>
<pt x="573" y="160" on="1"/>
<pt x="573" y="217" on="1"/>
<pt x="449" y="217" on="1"/>
<pt x="449" y="706" on="1"/>
</contour>
<contour>
<pt x="390" y="637" on="1"/>
<pt x="382" y="637" on="1"/>
<pt x="382" y="217" on="1"/>
<pt x="93" y="217" on="1"/>
</contour>
<instructions/>
</TTGlyph>

这里面每个 pt节点就和像素点一样,全部连起来就描绘出了整个数字,后面的 on属性 代表这个像素点显示与否

每个数字的 contour 和 pt 节点数量都是独一无二的,因此可以根据这个特殊性来标识出每个数字,解析xml文件获取节点数量的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def parse_xml(xml_name):

with open(xml_name, "r") as x:
xml_text = x.read()
dom = Selector(xml_text)
glyphs = dom.xpath("//glyf/ttglyph")[1:]
for ttg in glyphs:
uni = ttg.xpath("./@name").re_first("uni(.+)")

contours = ttg.xpath("./contour")
cont_pts = []
for cont in contours:
pts = cont.xpath("./pt").getall()
cont_pts.append(len(pts))
print(uni, tuple(cont_pts))

经过测试整理出下面的字典:

1
2
3
4
5
6
7
8
9
10
11
12
13
# (pt数量, ): 真实数字
pts2num = {
(44,): "3",
(37,): "5",
(32, 13, 12): "8",
(10,): "1",
(11, 4): "4",
(30,): "2",
(29, 12): "9",
(13, 13): "0",
(28, 13): "6",
(7,): "7",
}

接下来就是每次请求api的时候,拿到字体文件解析,然后根据pt数量识别出真实的数字

代码整理

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# encoding: utf8

import requests
import base64
import re
import json

from pprint import pprint
from typing import List
from parsel.selector import Selector
from fontTools.ttLib import TTFont


# 根据每个 TTGlyph的pt个数,先获取所有contour,再分别获取对应的pt数目
pts2num = {
(44,): "3",
(37,): "5",
(32, 13, 12): "8",
(10,): "1",
(11, 4): "4",
(30,): "2",
(29, 12): "9",
(13, 13): "0",
(28, 13): "6",
(7,): "7",
}

hero_names = [
"极镀ギ紬荕",
"爷灬霸气傀儡",
"梦战苍穹",
"傲世哥",
"мaη肆風聲",
"一刀メ隔世",
"横刀メ绝杀",
"Q不死你R死你",
"魔帝殤邪",
"封刀不再战",
"倾城孤狼",
"戎马江湖",
"狂得像风",
"影之哀伤",
"謸氕づ独尊",
"傲视狂杀",
"追风之梦",
"枭雄在世",
"傲视之巅",
"黑夜刺客",
"占你心为王",
"爷来取你狗命",
"御风踏血",
"凫矢暮城",
"孤影メ残刀",
"野区霸王",
"噬血啸月",
"风逝无迹",
"帅的睡不着",
"血色杀戮者",
"冷视天下",
"帅出新高度",
"風狆瑬蒗",
"灵魂禁锢",
"ヤ地狱篮枫ゞ",
"溅血メ破天",
"剑尊メ杀戮",
"塞外う飛龍",
"哥‘K纯帅",
"逆風祈雨",
"恣意踏江山",
"望断、天涯路",
"地獄惡灵",
"疯狂メ孽杀",
"寂月灭影",
"骚年霸称帝王",
"狂杀メ无赦",
"死灵的哀伤",
"撩妹界扛把子",
"霸刀☆藐视天下",
"潇洒又能打",
"狂卩龙灬巅丷峰",
"羁旅天涯.",
"南宫沐风",
"风恋绝尘",
"剑下孤魂",
"一蓑烟雨",
"领域★倾战",
"威龙丶断魂神狙",
"辉煌战绩",
"屎来运赚",
"伱、Bu够档次",
"九音引魂箫",
"骨子里的傲气",
"霸海断长空",
"没枪也很狂",
"死魂★之灵",
]

yuanrenxue_headers = {
"User-Agent": "yuanrenxue.project",
"sessionid": "e5pcc3skngi5s3kes0mwg4tpctykb8sb",
}


def main():
hero_lp = {}
for page in range(1, 6):
# 获取字体数据
ttf_url = f"http://match.yuanrenxue.com/api/match/7?page={page}"
res = requests.get(ttf_url, headers=yuanrenxue_headers)
json_data = json.loads(res.text)
zt_data: List = json_data["data"]
ttf_data: str = json_data["woff"]

b = base64.b64decode(ttf_data)
woff_name = "第七关.woff"
xml_name = "第七关.xml"
with open(woff_name, "wb") as f:
f.write(b)

fonts = TTFont(woff_name)
fonts.saveXML(xml_name)

with open(xml_name, "r") as f:
xml_text = f.read()

dom = Selector(text=xml_text)
uni2num = {}
for ttg in dom.xpath("//glyf/ttglyph")[1:]:
uni = ttg.xpath("./@name").re_first("uni(.+)")

contours = ttg.xpath("./contour")
cont_pts = []
for cont in contours:
pts = cont.xpath("./pt").getall()
cont_pts.append(len(pts))
real_num = pts2num.get(tuple(cont_pts))
uni2num[uni] = real_num

n_i = 1
for data in zt_data:
s = data["value"]
l = re.sub(r"&#x", "", s)
t = [uni2num[n] for n in l.strip().split(" ")]
count = int("".join(t))
hero_lp[hero_names[(page - 1) * 10 + n_i]] = count
n_i += 1

pprint(hero_lp)
d = sorted(hero_lp.items(), key=lambda x: x[1])
print(f"胜点数最多的召唤师为:{d[-1][0]}")


if __name__ == "__main__":
main()

运行结果

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{'Q不死你R死你': 2190,
'мaη肆風聲': 8550,
'ヤ地狱篮枫ゞ': 2333,
'一刀メ隔世': 7037,
'倾城孤狼': 7478,
'傲世哥': 3958,
'傲视之巅': 5468,
'傲视狂杀': 369,
'冷视天下': 9711,
'凫矢暮城': 5688,
'剑尊メ杀戮': 7142,
'占你心为王': 2132,
'哥‘K纯帅': 9291,
'噬血啸月': 35,
'地獄惡灵': 9015,
'塞外う飛龍': 8826,
'孤影メ残刀': 6179,
'寂月灭影': 4337,
'封刀不再战': 4500,
'帅出新高度': 6995,
'帅的睡不着': 9221,
'影之哀伤': 5826,
'御风踏血': 687,
'恣意踏江山': 2920,
'戎马江湖': 2342,
'撩妹界扛把子': 4928,
'望断、天涯路': 5983,
'枭雄在世': 2934,
'梦战苍穹': 5041,
'横刀メ绝杀': 8898,
'死灵的哀伤': 4229,
'溅血メ破天': 5660,
'潇洒又能打': 1206,
'灵魂禁锢': 5413,
'爷来取你狗命': 5553,
'爷灬霸气傀儡': 3236,
'狂得像风': 1926,
'狂杀メ无赦': 4349,
'疯狂メ孽杀': 1533,
'血色杀戮者': 6534,
'謸氕づ独尊': 2827,
'追风之梦': 4384,
'逆風祈雨': 5778,
'野区霸王': 7722,
'霸刀☆藐视天下': 2830,
'風狆瑬蒗': 3705,
'风逝无迹': 6301,
'骚年霸称帝王': 746,
'魔帝殤邪': 8400,
'黑夜刺客': 9107}

胜点数最多的召唤师为:冷视天下

2023.05.23 更新

截取单个字符生成图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from PIL import Image, ImageDraw, ImageFont
from fontTools.ttLib import TTFont

# 读取woff文件,转成TTF格式的字体文件
# font_woff = TTFont('font.woff')
# font_woff.save('font.ttf')

font_file = "font.ttf"
font = TTFont(font_file)
get_cmp = font.getBestCmap()

for i, uni in get_cmp.items():
img = Image.new("1", (50, 50), 255)
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(font_file, int(50 * 0.8))
txt = chr(i)
x, y = draw.textsize(txt, font=font)
draw.text(((50 - x) // 2, (50 - y) // 2 - 5), txt, font=font, fill=0)
img.save(f"{uni}.png")