趁热打铁来写字体反爬的第二篇,首先是题目

image.png

网页上显示的不是常规的数字,源码里面也是一些汉字

image.png

虽然看上去很乱,但是仔细观察还是能发现一些规律,比如:长 对应 2,思对应 1

所以这里的解题思路,也是先找到这些汉字的映射表

还是像第一题那样,把网页里面的base64保存成本地的ttf文件,然后打开查看:

image.png

这里不再是像第一题的英文单词,而是 uni601D 这样形似 unicode 编码的,解码一下试试

1
2
3
4
5
In [10]: a
Out[10]: '\\u601D'

In [11]: a.encode("latin-1").decode("unicode_escape")
Out[11]: '思'

unicode_escape 是一种编码集,类似 utf-8,这种编码集直接将unicode内存编码存储进文件

参考:https://blog.csdn.net/qq_40728667/article/details/122282693

各个编码单字节的外围:ASCII(0-127), LATIN1(0-255), UTF8(0-253)

参考:https://blog.csdn.net/liuhhaiffeng/article/details/80162033

和源码中的汉字是一致的,那么我们只要拿到前面十个unicode编码,解码出它们对应的汉字,然后就能根据顺序得出每个汉字对应的数字

1
2
3
4
5
6
7
8
9
10
11
12
13
def parse_ttf():
"""
构造汉字到数字的映射
"""
font = TTFont("page-1.ttf")
name_list = font["cmap"].tables[0].ttFont.getGlyphOrder()
unicode_list = ["\\u" + c[-4:] for c in name_list[1:11]]

cha2num = {}
for index, u in enumerate(unicode_list):
c = u.encode("latin-1").decode("unicode_escape")
cha2num[c] = str(index)
return cha2num

同一个汉字不同unicode?

从ttf提取的汉字,可能会出现和网页的汉字对不上的情况

比如网页的是 ,ttf的是 ,两者看起来相似,但是unicode码是不同的

1
2
3
4
5
In [1]: '鼻'.encode('unicode_escape')
Out[1]: b'\\u9f3b'

In [2]: '⿐'.encode('unicode_escape')
Out[2]: b'\\u2fd0'

这里有解释这种现象

产生的具体原因我找了很久也没找到,猜测是网页在自动utf8解码的时候,把汉字转换错了

为了应对这种情况,我之前有专门整理大部分这些生僻字的映射,基本可以解决这道题出现的所有情况

Unicode基本汉字、部首扩展、康熙部首对照字典_银古 | cxs的博客-CSDN博客_unicode 常用汉字

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
{
"⼀": "一",
"⼄": "乙",
"⼆": "二",
"⼈": "人",
"⼉": "儿",
"⼊": "入",
"⼋": "八",
"⼏": "几",
"⼑": "刀",
"⼒": "力",
"⼔": "匕",
"⼗": "十",
"⼘": "卜",
"⼚": "厂",
"⼜": "又",
"⼝": "口",
"⼞": "口",
"⼟": "土",
"⼠": "士",
"⼤": "大",
"⼥": "女",
"⼦": "子",
"⼨": "寸",
"⼩": "小",
"⼫": "尸",
"⼭": "山",
"⼯": "工",
"⼰": "己",
"⼲": "干",
"⼴": "广",
"⼸": "弓",
"⼼": "心",
"⼽": "戈",
"⼿": "手",
"⽀": "支",
"⽂": "文",
"⽃": "斗",
"⽄": "斤",
"⽅": "方",
"⽆": "无",
"⽇": "日",
"⽈": "曰",
"⽉": "月",
"⽊": "木",
"⽋": "欠",
"⽌": "止",
"⽍": "歹",
"⽏": "毋",
"⽐": "比",
"⽑": "毛",
"⽒": "氏",
"⽓": "气",
"⽔": "水",
"⽕": "火",
"⽖": "爪",
"⽗": "父",
"⽚": "片",
"⽛": "牙",
"⽜": "牛",
"⽝": "犬",
"⽞": "玄",
"⽟": "玉",
"⽠": "瓜",
"⽡": "瓦",
"⽢": "甘",
"⽣": "生",
"⽤": "用",
"⽥": "田",
"⽩": "白",
"⽪": "皮",
"⽫": "皿",
"⽬": "目",
"⽭": "矛",
"⽮": "矢",
"⽯": "石",
"⽰": "示",
"⽲": "禾",
"⽳": "穴",
"⽴": "立",
"⽵": "竹",
"⽶": "米",
"⽸": "缶",
"⽹": "网",
"⽺": "羊",
"⽻": "羽",
"⽼": "老",
"⽽": "而",
"⽿": "耳",
"⾁": "肉",
"⾂": "臣",
"⾃": "自",
"⾄": "至",
"⾆": "舌",
"⾈": "舟",
"⾉": "艮",
"⾊": "色",
"⾍": "虫",
"⾎": "血",
"⾏": "行",
"⾐": "衣",
"⾒": "儿",
"⾓": "角",
"⾔": "言",
"⾕": "谷",
"⾖": "豆",
"⾚": "赤",
"⾛": "走",
"⾜": "足",
"⾝": "身",
"⾞": "车",
"⾟": "辛",
"⾠": "辰",
"⾢": "邑",
"⾣": "酉",
"⾤": "采",
"⾥": "里",
"⾦": "金",
"⾧": "长",
"⾨": "门",
"⾩": "阜",
"⾪": "隶",
"⾬": "雨",
"⾭": "青",
"⾮": "非",
"⾯": "面",
"⾰": "革",
"⾲": "韭",
"⾳": "音",
"⾴": "页",
"⾵": "风",
"⾶": "飞",
"⾷": "食",
"⾸": "首",
"⾹": "香",
"⾺": "马",
"⾻": "骨",
"⾼": "高",
"⿁": "鬼",
"⿂": "鱼",
"⿃": "鸟",
"⿄": "卤",
"⿅": "鹿",
"⿇": "麻",
"⿉": "黍",
"⿊": "黑",
"⿍": "鼎",
"⿎": "鼓",
"⿏": "鼠",
"⿐": "鼻",
"⿒": "齿",
"⿓": "龙",
"⼣": "夕",
"⺁":"厂",
"⺇":"几",
"⺌":"小",
"⺎":"兀",
"⺏":"尣",
"⺐":"尢",
"⺑":"𡯂",
"⺒":"巳",
"⺓":"幺",
"⺛":"旡",
"⺝":"月",
"⺟":"母",
"⺠":"民",
"⺱":"冈",
"⺸":"芈",
"⻁":"虎",
"⻄":"西",
"⻅":"见",
"⻆":"角",
"⻇":"𧢲",
"⻉":"贝",
"⻋":"车",
"⻒":"镸",
"⻓":"长",
"⻔":"门",
"⻗":"雨",
"⻘":"青",
"⻙":"韦",
"⻚":"页",
"⻛":"风",
"⻜":"飞",
"⻝":"食",
"⻡":"𩠐",
"⻢":"马",
"⻣":"骨",
"⻤":"鬼",
"⻥":"鱼",
"⻦":"鸟",
"⻧":"卤",
"⻨":"麦",
"⻩":"黄",
"⻬":"齐",
"⻮":"齿",
"⻯":"竜",
"⻰":"龙",
"⻳":"龟",
"⾅":"臼",
"⼝":"口",
"⼾":"户",
"⼉":"儿",
"⼱":"巾"
}

代码整理

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
# -*- coding:utf-8 -*-

from io import BytesIO
from hanzi_mappings import hz_maps

import requests
import base64
from parsel import Selector
from fontTools.ttLib import TTFont


def parse_ttf(b64_str):
"""
构造汉字到数字的映射
"""
content = base64.b64decode(b64_str)
# with open("page.ttf", "wb") as f:
# f.write(content)

font = TTFont(BytesIO(content))
name_list = font["cmap"].tables[0].ttFont.getGlyphOrder()
unicode_list = ["\\u" + c[-4:] for c in name_list[1:11]]

cha2num = {}
for index, u in enumerate(unicode_list):
c = u.encode("latin-1").decode("unicode_escape")
cha2num[c] = str(index)
return cha2num


def parse_html(html_text):
dom = Selector(text=html_text)
b64_str = dom.css("style::text").re_first(r"base64,(.+?)\)")
mappings = parse_ttf(b64_str)

fake_strings = dom.css(".col-md-1::text").re(r"\s+(.+)\s+")
for string in fake_strings:
real_num = ""
for c in string:
try:
n = mappings[c]
except KeyError as e:
n = mappings[hz_maps[c]]
real_num += n
yield real_num


def glidedsky_login():
"""
网站登录,才能看到题目
注意题目域名也必须是 www.glidedsky.com
"""
EMAIL = ""
PASSWORD = ""
LOGIN_URL = "http://www.glidedsky.com/login"

session = requests.session()
resp = session.get(LOGIN_URL)
dom = Selector(resp.text)
_token = dom.css("meta[name='csrf-token']::attr(content)").get()
form_data = {
"_token": _token,
"email": EMAIL,
"password": PASSWORD,
}
session.post(LOGIN_URL, data=form_data)
return session


def main():
session = glidedsky_login()

for page in range(1, 11):
url = "http://www.glidedsky.com/level/web/crawler-font-puzzle-2?page=" + str(
page
)
resp = session.get(url)
print(
f"page {page}: ",
[num for num in parse_html(resp.content.decode())],
)


if __name__ == "__main__":
main()

  • 直接从源码截取的base64,可能会长度不够,报错:Incorrect padding需要补齐成4的整倍数

  • hz_maps 即是上面整理的那个汉字映射

运行结果

image.png