猿人学web-13题

破题

1
2
3
4
5
6
7
8
9
10
11
12
success: function(data) {
var s = '<tr class="odd">';
ttf = data.woff;
$('.font').text('').append('@font-face { font-family:"fonteditor";src: url(data:font/truetype;charset=utf-8;base64,' + ttf + '); }');
datas = data.data;
$.each(datas, function(index, val) {
var html = '<td class="info fonteditor">' + val.value + '</td>';
s += html
});
$('.data').text('').append(s + '</tr>')
}

看一下13.html 中的ajax 的回调,相当于每次页面中的数字每次都是根据响应中的 woff 内容加载出来的

复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: 'PingFangSC-Regular';
src: url(data:application/font-woff;charset=utf-8;base64,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)
}
</style>
</head>
<body>
<div style="font-family: PingFangSC-Regular;">
<td class="info fonteditor"> &#xe519 &#xc974 &#xf296 &#xb291 </td>
</div>
</body>
</html>

td 中的内容要和style 中的内容保持一致。

python 操作ttf

1
2
3
4
5
6
7
response = requests.post(url, headers=headers, data=data)
res_data = response.json()
with open('font.ttf', 'wb') as f:
f.write(base64.b64decode(res_data['woff']))

font = TTFont('font.ttf') # 打开本地的ttf文件
font.saveXML('font.xml') # 转换成xml

将数据保存为ttf 和xml 后 使用在线工具 加载字形,最后发现数据内容是

将字体字形数据和具体数值做一个映射

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
156
157
158
159
160
161
162
163
164
165
166
data = {

'0': """<contour>
<pt x="300" y="728" on="1"/>
<pt x="171" y="729" on="0"/>
<pt x="107" y="615" on="1"/>
<pt x="50" y="519" on="0"/>
<pt x="50" y="195" on="0"/>
<pt x="107" y="99" on="1"/>
<pt x="171" y="-14" on="0"/>
<pt x="427" y="-14" on="0"/>
<pt x="493" y="99" on="1"/>
<pt x="550" y="195" on="0"/>
<pt x="550" y="519" on="0"/>
<pt x="493" y="615" on="1"/>
<pt x="427" y="729" on="0"/>
</contour>
<contour>
<pt x="300" y="658" on="1"/>
<pt x="396" y="658" on="0"/>
<pt x="438" y="555" on="1"/>
<pt x="469" y="483" on="0"/>
<pt x="469" y="233" on="0"/>
<pt x="438" y="159" on="1"/>
<pt x="396" y="57" on="0"/>
<pt x="204" y="57" on="0"/>
<pt x="162" y="159" on="1"/>
<pt x="132" y="233" on="0"/>
<pt x="132" y="483" on="0"/>
<pt x="162" y="555" on="1"/>
<pt x="204" y="658" on="0"/>
</contour>""",
"1": """<contour>
<pt x="297" y="714" on="1"/>
<pt x="262" y="674" on="0"/>
<pt x="160" y="612" on="0"/>
<pt x="111" y="598" on="1"/>
<pt x="111" y="516" on="1"/>
<pt x="212" y="546" on="0"/>
<pt x="279" y="614" on="1"/>
<pt x="279" y="0" on="1"/>
<pt x="361" y="0" on="1"/>
<pt x="361" y="714" on="1"/>
</contour>""",
"2": """<pt x="467" y="352" on="1"/>
<pt x="537" y="427" on="0"/>
<pt x="537" y="614" on="0"/>
<pt x="409" y="728" on="0"/>""",
"3": """<contour>
<pt x="307" y="728" on="1"/>
<pt x="205" y="728" on="0"/>
<pt x="142" y="670" on="1"/>
<pt x="76" y="612" on="0"/>
<pt x="66" y="508" on="1"/>
<pt x="147" y="508" on="1"/>
<pt x="154" y="580" on="0"/>
<pt x="236" y="658" on="0"/>
<pt x="308" y="657" on="1"/>
<pt x="378" y="658" on="0"/>
<pt x="417" y="622" on="1"/>
<pt x="454" y="589" on="0"/>
<pt x="454" y="469" on="0"/>
<pt x="379" y="404" on="0"/>
<pt x="307" y="404" on="1"/>
<pt x="252" y="404" on="1"/>
<pt x="252" y="340" on="1"/>
<pt x="310" y="340" on="1"/>
<pt x="385" y="341" on="0"/>
<pt x="468" y="269" on="0"/>
<pt x="468" y="142" on="0"/>
<pt x="425" y="100" on="1"/>
<pt x="379" y="56" on="0"/>
<pt x="302" y="56" on="1"/>
<pt x="235" y="57" on="0"/>
<pt x="190" y="93" on="1"/>
<pt x="137" y="137" on="0"/>
<pt x="134" y="221" on="1"/>
<pt x="51" y="221" on="1"/>
<pt x="60" y="101" on="0"/>
<pt x="136" y="38" on="1"/>
<pt x="200" y="-13" on="0"/>
<pt x="302" y="-14" on="1"/>
<pt x="413" y="-14" on="0"/>
<pt x="550" y="111" on="0"/>
<pt x="550" y="208" on="1"/>
<pt x="550" y="271" on="0"/>
<pt x="514" y="313" on="1"/>
<pt x="481" y="355" on="0"/>
<pt x="418" y="374" on="1"/>
<pt x="535" y="414" on="0"/>
<pt x="535" y="534" on="1"/>
<pt x="535" y="624" on="0"/>
<pt x="410" y="728" on="0"/>
</contour>""",
"4": """<contour>
<pt x="384" y="714" on="1"/>
<pt x="24" y="240" on="1"/>
<pt x="24" y="162" on="1"/>
<pt x="383" y="162" on="1"/>
<pt x="383" y="0" on="1"/>
<pt x="461" y="0" on="1"/>
<pt x="461" y="162" on="1"/>
<pt x="577" y="162" on="1"/>
<pt x="577" y="229" on="1"/>
<pt x="461" y="229" on="1"/>
<pt x="461" y="714" on="1"/>
</contour>
<contour>
<pt x="380" y="607" on="1"/>
<pt x="383" y="607" on="1"/>
<pt x="383" y="229" on="1"/>
<pt x="93" y="229" on="1"/>
</contour>""",
"5":"""<contour>
<pt x="105" y="714" on="1"/>
<pt x="67" y="320" on="1"/>
<pt x="145" y="320" on="1"/>
<pt x="167" y="363" on="0"/>
<pt x="209" y="384" on="1"/>
<pt x="249" y="406" on="0"/>
<pt x="300" y="406" on="1"/>
<pt x="378" y="407" on="0"/>
<pt x="468" y="313" on="0"/>
<pt x="468" y="227" on="1"/>
<pt x="468" y="152" on="0"/>
<pt x="370" y="56" on="0"/>
<pt x="295" y="56" on="1"/>
<pt x="229" y="56" on="0"/>
<pt x="186" y="88" on="1"/>
<pt x="138" y="124" on="0"/>
<pt x="132" y="192" on="1"/>
<pt x="51" y="192" on="1"/>
<pt x="58" y="94" on="0"/>
<pt x="133" y="37" on="1"/>
<pt x="199" y="-14" on="0"/>
<pt x="294" y="-14" on="1"/>
<pt x="400" y="-14" on="0"/>
<pt x="472" y="51" on="1"/>
<pt x="549" y="120" on="0"/>
<pt x="549" y="228" on="1"/>
<pt x="549" y="343" on="0"/>
<pt x="422" y="473" on="0"/>
<pt x="320" y="473" on="1"/>
<pt x="271" y="474" on="0"/>
<pt x="229" y="456" on="1"/>
<pt x="182" y="438" on="0"/>
<pt x="152" y="402" on="1"/>
<pt x="148" y="402" on="1"/>
<pt x="172" y="641" on="1"/>
<pt x="520" y="641" on="1"/>
<pt x="520" y="714" on="1"/>
</contour>""",
'6':"""<pt x="307" y="399" on="1"/>""",
'7':"""<contour>
<pt x="66" y="714" on="1"/>
<pt x="66" y="639" on="1"/>
<pt x="451" y="639" on="1"/>
<pt x="185" y="0" on="1"/>
<pt x="272" y="0" on="1"/>
<pt x="535" y="647" on="1"/>
<pt x="535" y="714" on="1"/>
</contour>""",
'8':"""<pt x="213" y="344" on="0"/>""",
'9':"""<pt x="294" y="659" on="1"/>""",
}

这个字形和数字的匹配结果可能不唯一。
1
2
3
4
5
6
reversed_dict = {v: k for k, v in fontMap.items()}
d = dict()
for k, v in reversed_dict.items():
for w in tree.xpath('//TTGlyph'):
if k.strip() in str(etree.tostring(w, pretty_print=True), 'utf-8'):
d[v] = w.xpath('./@name')[0].replace('uni', '&#x')

这里找到动态返回内容的临时数字对应关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def realNumer(value, tem):
result = ''
rev_maps = {v: k for k, v in tem.items()}

temp = value.split()
num1 = temp[0]
num2 = temp[1]

if len(temp) == 2:
result = int(rev_maps[num1]) * 10 + int(rev_maps[num2]) * 1

if len(temp) == 3:
num3 = temp[2]
result = int(rev_maps[num1]) * 100 + int(rev_maps[num2]) * 10 + int(rev_maps[num3])

if len(temp) == 4:
num3 = temp[2]
num4 = temp[3]
result = int(rev_maps[num1]) * 1000 + int(rev_maps[num2]) * 100 + int(rev_maps[num3]) * 10 + int(rev_maps[num4])

return result

这里将每一个data 中的value 转化为数字

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
import base64

import requests
from lxml import etree
from fontTools.ttLib import TTFont
from data import data as fontMap
from redis import Redis
from json import loads

parser = etree.XMLParser()

client = Redis()

headers = {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"origin": "https://www.python-spider.com",
"referer": "https://www.python-spider.com/challenge/13",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
"x-requested-with": "XMLHttpRequest"
}
url = "https://www.python-spider.com/api/challenge13"


def realNumer(value, tem):
result = ''
rev_maps = {v: k for k, v in tem.items()}

temp = value.split()
num1 = temp[0]
num2 = temp[1]

if len(temp) == 2:
result = int(rev_maps[num1]) * 10 + int(rev_maps[num2]) * 1

if len(temp) == 3:
num3 = temp[2]
result = int(rev_maps[num1]) * 100 + int(rev_maps[num2]) * 10 + int(rev_maps[num3])

if len(temp) == 4:
num3 = temp[2]
num4 = temp[3]
result = int(rev_maps[num1]) * 1000 + int(rev_maps[num2]) * 100 + int(rev_maps[num3]) * 10 + int(rev_maps[num4])

return result


def main():
sumList = []

for p in range(1, 101):

print(p)
data = {
"page": p
}
response = requests.post(url, headers=headers, data=data)
res_data = loads(response.text)
with open('font.ttf', 'wb') as f:
f.write(base64.b64decode(res_data['woff']))

font = TTFont('font.ttf') # 打开本地的ttf文件
font.saveXML('font.xml') # 转换成xml

xml_data = open('font.xml', 'rb').read()
tree = etree.fromstring(xml_data, parser=parser)
reversed_dict = {v: k for k, v in fontMap.items()}
d = dict()
for k, v in reversed_dict.items():
for w in tree.xpath('//TTGlyph'):
if k.strip() in str(etree.tostring(w, pretty_print=True), 'utf-8'):
d[v] = w.xpath('./@name')[0].replace('uni', '&#x')
print(d)
for j in res_data['data']:
if j['value']:
real = realNumer(j['value'], d)
sumList.append(real)



print(sum(sumList))


if __name__ == '__main__':
main()


猿人学web-13题
https://kingjem.github.io/2025/02/24/猿人学/猿人学Web13题实战/
作者
Ruhai
发布于
2025年2月24日
许可协议