目标网址:http://shaoq.com/font
该页面文章不是固定的,为动态生成,并且字体做了反爬措施。
该页面结果简单,爬取提取数据基本上一行代码就可以解决。但是爬取下来的为字体加密后的字符。
所以我现在要做的就是,怎么去将&#x…;字符转为对应的汉字。
先手动下载页面中的字体文件。然后使用fontTools模块,读取字体文件,并将其保存为xml格式(正常情况下,字体文件是无法打开查看的)
字体文件的xml结构为:
字体编码名称表:
cmap映射表:
每个字体对应的笔画坐标表(这个是不会变的,但是编码名称会变):
fontTools模块中有提取表格中数据的方法,这里先不演示了,向将该字体文件导入百度字体在线识别看一看对应的文字
可以读取出所有编码名称对应的数字及其汉字,总共601个。我们需要将其拿出来与字符编码名称对应起来,生成一个映射表
texts = [
'','','1','2','3','4','5','6','7','8',
'9','0','店','中','美','家','馆','小','车','大',
'市','公','酒','行','国','品','发','电','金','心',
'业','商','司','超','生','装','园','场','食','有',
'新','限','天','面','工','服','海','华','水','房',
'饰','城','乐','汽','香','部','利','子','老','艺',
'花','专','东','肉','菜','学','福','饭','人','百',
'餐','茶','务','通','味','所','山','区','门','药',
'银','农','龙','停','尚','安','广','鑫','一','容',
'动','南','具','源','兴','鲜','记','时','机','烤',
'文','康','信','果','阳','理','锅','宝','达','地',
'儿','衣','特','产','西','批','坊','州','牛','佳',
'化','五','米','修','爱','北','养','卖','建','材',
'三','会','鸡','室','红','站','德','王','光','名',
'丽','油','院','堂','烧','江','社','合','星','货',
'型','村','自','科','快','便','日','民','营','和',
'活','童','明','器','烟','育','宾','精','屋','经',
'居','庄','石','顺','林','尔','县','手','厅','销',
'用','好','客','火','雅','盛','体','旅','之','鞋',
'辣','作','粉','包','楼','校','鱼','平','彩','上',
'吧','保','永','万','物','教','吃','设','医','正',
'造','丰','健','点','汤','网','庆','技','斯','洗',
'料','配','汇','木','缘','加','麻','联','卫','川',
'泰','色','世','方','寓','风','幼','羊','烫','来',
'高','厂','兰','阿','贝','皮','全','女','拉','成',
'云','维','贸','道','术','运','都','口','博','河',
'瑞','宏','京','际','路','祥','青','镇','厨','培',
'力','惠','连','马','鸿','钢','训','影','甲','助',
'窗','布','富','牌','头','四','多','妆','吉','苑',
'沙','恒','隆','春','干','饼','氏','里','二','管',
'诚','制','售','嘉','长','轩','杂','副','清','计',
'黄','讯','太','鸭','号','街','交','与','叉','附',
'近','层','旁','对','巷','栋','环','省','桥','湖',
'段','乡','厦','府','铺','内','侧','元','购','前',
'幢','滨','处','向','座','下','県','凤','港','开',
'关','景','泉','塘','放','昌','线','湾','政','步',
'宁','解','白','田','町','溪','十','八','古','双',
'胜','本','单','同','九','迎','第','台','玉','锦',
'底','后','七','斜','期','武','岭','松','角','纪',
'朝','峰','六','振','珠','局','岗','洲','横','边',
'济','井','办','汉','代','临','弄','团','外','塔',
'杨','铁','浦','字','年','岛','陵','原','梅','进',
'荣','友','虹','央','桂','沿','事','津','凯','莲',
'丁','秀','柳','集','紫','旗','张','谷','的','是',
'不','了','很','还','个','也','这','我','就','在',
'以','可','到','错','没','去','过','感','次','要',
'比','觉','看','得','说','常','真','们','但','最',
'喜','哈','么','别','位','能','较','境','非','为',
'欢','然','他','挺','着','价','那','意','种','想',
'出','员','两','推','做','排','实','分','间','甜',
'度','起','满','给','热','完','格','荐','喝','等',
'其','再','几','只','现','朋','候','样','直','而',
'买','于','般','豆','量','选','奶','打','每','评',
'少','算','又','因','情','找','些','份','置','适',
'什','蛋','师','气','你','姐','棒','试','总','定',
'啊','足','级','整','带','虾','如','态','且','尝',
'主','话','强','当','更','板','知','己','无','酸',
'让','入','啦','式','笑','赞','片','酱','差','像',
'提','队','走','嫩','才','刚','午','接','重','串',
'回','晚','微','周','值','费','性','桌','拍','跟',
'块','调','糕'
]
现在介绍一下fontTools获取字体文件数据的一些方法:
getBestCmap()返回cmap表中对应的映射,只不过code字段返回的为十进制数据,如果想转为十六进制,使用hex()内置方法即可
getGlyphOrder()方法返回所有字符编码名称,按表格顺序提取,类型为列表
字体编码名称和对应的解码数据数量必须一样,由于解码数据是我从百度字体识别中抓取出来的,固定的,而且位置也是固定的;字体编码名称根据每次请求可能不一样,但是对应位置是固定的。所以可以动态提取字体编码名称和解码数据一一对应。
# 读取字体文件,生成字体编码与文字的映射表
def get_font_map():
"""
读取字体文件,生成字体编码与文字的映射表
:return: 返回编码文字映射字典
"""
# 这个字体文件需要先析网页,找到这个url,然后下载下来到本地,然后使用TTFont()加载字体文件
# 字体文件的名字
font = TTFont('123.woff')
# 得到cmap 字体对应代码->字体名字
# font_cmap = font.getBestCmap()
# 得到所有的字体名字 类似:unif2ab
font_names = font.getGlyphOrder()
print('字体编码名称数量:',len(font_names))
# 使用百度在线字体识别工具获取的文字数据
texts = [
'','','1','2','3','4','5','6','7','8',
'9','0','店','中','美','家','馆','小','车','大',
'市','公','酒','行','国','品','发','电','金','心',
'业','商','司','超','生','装','园','场','食','有',
'新','限','天','面','工','服','海','华','水','房',
'饰','城','乐','汽','香','部','利','子','老','艺',
'花','专','东','肉','菜','学','福','饭','人','百',
'餐','茶','务','通','味','所','山','区','门','药',
'银','农','龙','停','尚','安','广','鑫','一','容',
'动','南','具','源','兴','鲜','记','时','机','烤',
'文','康','信','果','阳','理','锅','宝','达','地',
'儿','衣','特','产','西','批','坊','州','牛','佳',
'化','五','米','修','爱','北','养','卖','建','材',
'三','会','鸡','室','红','站','德','王','光','名',
'丽','油','院','堂','烧','江','社','合','星','货',
'型','村','自','科','快','便','日','民','营','和',
'活','童','明','器','烟','育','宾','精','屋','经',
'居','庄','石','顺','林','尔','县','手','厅','销',
'用','好','客','火','雅','盛','体','旅','之','鞋',
'辣','作','粉','包','楼','校','鱼','平','彩','上',
'吧','保','永','万','物','教','吃','设','医','正',
'造','丰','健','点','汤','网','庆','技','斯','洗',
'料','配','汇','木','缘','加','麻','联','卫','川',
'泰','色','世','方','寓','风','幼','羊','烫','来',
'高','厂','兰','阿','贝','皮','全','女','拉','成',
'云','维','贸','道','术','运','都','口','博','河',
'瑞','宏','京','际','路','祥','青','镇','厨','培',
'力','惠','连','马','鸿','钢','训','影','甲','助',
'窗','布','富','牌','头','四','多','妆','吉','苑',
'沙','恒','隆','春','干','饼','氏','里','二','管',
'诚','制','售','嘉','长','轩','杂','副','清','计',
'黄','讯','太','鸭','号','街','交','与','叉','附',
'近','层','旁','对','巷','栋','环','省','桥','湖',
'段','乡','厦','府','铺','内','侧','元','购','前',
'幢','滨','处','向','座','下','県','凤','港','开',
'关','景','泉','塘','放','昌','线','湾','政','步',
'宁','解','白','田','町','溪','十','八','古','双',
'胜','本','单','同','九','迎','第','台','玉','锦',
'底','后','七','斜','期','武','岭','松','角','纪',
'朝','峰','六','振','珠','局','岗','洲','横','边',
'济','井','办','汉','代','临','弄','团','外','塔',
'杨','铁','浦','字','年','岛','陵','原','梅','进',
'荣','友','虹','央','桂','沿','事','津','凯','莲',
'丁','秀','柳','集','紫','旗','张','谷','的','是',
'不','了','很','还','个','也','这','我','就','在',
'以','可','到','错','没','去','过','感','次','要',
'比','觉','看','得','说','常','真','们','但','最',
'喜','哈','么','别','位','能','较','境','非','为',
'欢','然','他','挺','着','价','那','意','种','想',
'出','员','两','推','做','排','实','分','间','甜',
'度','起','满','给','热','完','格','荐','喝','等',
'其','再','几','只','现','朋','候','样','直','而',
'买','于','般','豆','量','选','奶','打','每','评',
'少','算','又','因','情','找','些','份','置','适',
'什','蛋','师','气','你','姐','棒','试','总','定',
'啊','足','级','整','带','虾','如','态','且','尝',
'主','话','强','当','更','板','知','己','无','酸',
'让','入','啦','式','笑','赞','片','酱','差','像',
'提','队','走','嫩','才','刚','午','接','重','串',
'回','晚','微','周','值','费','性','桌','拍','跟',
'块','调','糕'
]
print('字体编码对应解码字符数量:', len(texts))
# 将 字体名字 和 我们查看到的值 组成一个字典 如:'unif2ab': '副'
font_name_map = {}
for index,value in enumerate(texts):
font_name_map[font_names[index]] = value
# 转为页面抓取的字符格式映射表,如:'': '美'
mappings = {}
for k,v in font_name_map.items():
if k.startswith('uni'):
key_ = k.replace('uni','&#x')
mappings[key_] = v
else:
mappings[k] = v
return mappings
根据此处理过的映射表就可以替换网页中的特殊字符,替换为正常的文字。当然这种方法可能存在一定的问题,不可能100%真实还原。
最好的办法还是根据字符笔画的坐标去解决。这里能力有限,就不演示了。
完整字体解密代码:
import requests
import re
from lxml import etree
from pyquery import PyQuery
from fontTools.ttLib import TTFont
# 下载动态字体文件
def save_font_file():
"""
请求目标网站,下载动态字体文件,并返回页面内容
:return:
"""
# 请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
}
# 目标页面url
url = 'http://shaoq.com/font'
# 开始请求
response = requests.get(url,headers=headers).content.decode('utf-8')
# print(response)
# 提取动态字体url链接
sub_font_url = re.findall(r'src:url\("(.*?)"\)',response)[0]
# 拼接完整的url链接
full_font_url = 'http://shaoq.com/' + sub_font_url
# 请求字体链接,保存字体
res = requests.get(full_font_url,headers=headers).content
with open('123.woff','wb')as fp:
fp.write(res)
return response
# 读取字体
# font = TTFont('123.woff')
# font.saveXML('123.xml')
# 读取字体文件,生成字体编码与文字的映射表
def get_font_map():
"""
读取字体文件,生成字体编码与文字的映射表
:return: 返回编码文字映射字典
"""
# 这个字体文件需要先析网页,找到这个url,然后下载下来到本地,然后使用TTFont()加载字体文件
# 字体文件的名字
font = TTFont('123.woff')
# 得到cmap 字体对应代码->字体名字
# font_cmap = font.getBestCmap()
# 得到所有的字体名字 类似:unif2ab
font_names = font.getGlyphOrder()
print('字体编码名称数量:',len(font_names))
# 使用百度在线字体识别工具获取的文字数据
texts = [
'','','1','2','3','4','5','6','7','8',
'9','0','店','中','美','家','馆','小','车','大',
'市','公','酒','行','国','品','发','电','金','心',
'业','商','司','超','生','装','园','场','食','有',
'新','限','天','面','工','服','海','华','水','房',
'饰','城','乐','汽','香','部','利','子','老','艺',
'花','专','东','肉','菜','学','福','饭','人','百',
'餐','茶','务','通','味','所','山','区','门','药',
'银','农','龙','停','尚','安','广','鑫','一','容',
'动','南','具','源','兴','鲜','记','时','机','烤',
'文','康','信','果','阳','理','锅','宝','达','地',
'儿','衣','特','产','西','批','坊','州','牛','佳',
'化','五','米','修','爱','北','养','卖','建','材',
'三','会','鸡','室','红','站','德','王','光','名',
'丽','油','院','堂','烧','江','社','合','星','货',
'型','村','自','科','快','便','日','民','营','和',
'活','童','明','器','烟','育','宾','精','屋','经',
'居','庄','石','顺','林','尔','县','手','厅','销',
'用','好','客','火','雅','盛','体','旅','之','鞋',
'辣','作','粉','包','楼','校','鱼','平','彩','上',
'吧','保','永','万','物','教','吃','设','医','正',
'造','丰','健','点','汤','网','庆','技','斯','洗',
'料','配','汇','木','缘','加','麻','联','卫','川',
'泰','色','世','方','寓','风','幼','羊','烫','来',
'高','厂','兰','阿','贝','皮','全','女','拉','成',
'云','维','贸','道','术','运','都','口','博','河',
'瑞','宏','京','际','路','祥','青','镇','厨','培',
'力','惠','连','马','鸿','钢','训','影','甲','助',
'窗','布','富','牌','头','四','多','妆','吉','苑',
'沙','恒','隆','春','干','饼','氏','里','二','管',
'诚','制','售','嘉','长','轩','杂','副','清','计',
'黄','讯','太','鸭','号','街','交','与','叉','附',
'近','层','旁','对','巷','栋','环','省','桥','湖',
'段','乡','厦','府','铺','内','侧','元','购','前',
'幢','滨','处','向','座','下','県','凤','港','开',
'关','景','泉','塘','放','昌','线','湾','政','步',
'宁','解','白','田','町','溪','十','八','古','双',
'胜','本','单','同','九','迎','第','台','玉','锦',
'底','后','七','斜','期','武','岭','松','角','纪',
'朝','峰','六','振','珠','局','岗','洲','横','边',
'济','井','办','汉','代','临','弄','团','外','塔',
'杨','铁','浦','字','年','岛','陵','原','梅','进',
'荣','友','虹','央','桂','沿','事','津','凯','莲',
'丁','秀','柳','集','紫','旗','张','谷','的','是',
'不','了','很','还','个','也','这','我','就','在',
'以','可','到','错','没','去','过','感','次','要',
'比','觉','看','得','说','常','真','们','但','最',
'喜','哈','么','别','位','能','较','境','非','为',
'欢','然','他','挺','着','价','那','意','种','想',
'出','员','两','推','做','排','实','分','间','甜',
'度','起','满','给','热','完','格','荐','喝','等',
'其','再','几','只','现','朋','候','样','直','而',
'买','于','般','豆','量','选','奶','打','每','评',
'少','算','又','因','情','找','些','份','置','适',
'什','蛋','师','气','你','姐','棒','试','总','定',
'啊','足','级','整','带','虾','如','态','且','尝',
'主','话','强','当','更','板','知','己','无','酸',
'让','入','啦','式','笑','赞','片','酱','差','像',
'提','队','走','嫩','才','刚','午','接','重','串',
'回','晚','微','周','值','费','性','桌','拍','跟',
'块','调','糕'
]
print('字体编码对应解码字符数量:', len(texts))
# 将 字体名字 和 我们查看到的值 组成一个字典 如:'unif2ab': '副'
font_name_map = {}
for index,value in enumerate(texts):
font_name_map[font_names[index]] = value
# 转为页面抓取的字符格式映射表,如:'': '美'
mappings = {}
for k,v in font_name_map.items():
if k.startswith('uni'):
key_ = k.replace('uni','&#x')
mappings[key_] = v
else:
mappings[k] = v
return mappings
# 根据处理后的映射表和网页内容,进行解码并提取文字内容
def html_font_parse(response,mappings):
# 解码后的响应内容变量
response_ = response
# 循环处理后的字体映射表
for k,v in mappings.items():
# 拼接页面中完整的乱码字符
uni_font = k + ';'
# 判断乱码字符是否存在抓取下来的网页内容中
if uni_font in response_:
# 如果存在,将其替换为解码后的汉字
response_ = response_.replace(uni_font,mappings[k])
# print(response_)
# 根据解码后的网页内容提取数据
tree = etree.HTML(response_)
text = tree.xpath('//body//text()')[3:]
text = ''.join(text).replace('\n','')
print(text)
if __name__ == '__main__':
response = save_font_file()
mappings = get_font_map()
html_font_parse(response,mappings)
已解码成功: