有些网站为了反爬,对网页中的一些数据进行了字体加密,用户浏览网页时显示的是正常的,但是爬取网页源代码时,却是乱码。
例如网页显示的是 2020-08-09
但查看源代码时显示的是 龒餼龒餼-餼龤-餼龥
页面在css中使用font-face定义了字符集,并通过unicode去映射展示,浏览器会加载css中的font字体为用户渲染好,所以浏览页面时是正常的,而对于爬虫来说却极其不友好,因为爬取下来的源代码未经过浏览器渲染,都是乱码。
1.最简单的,直接在页面源码中搜索font_face,如果能找到类似如下信息,base64后面的字符串就是加密字体了。
@font-face{font-family:swiper-icons;src:url(“data:application/font-woff;charset=utf-8;base64, d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRAAAfnP/u8EFTQAA”) format(“woff”);font-weight:400;font-style:normal}
其中 d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRAAAfnP/u8EFTQAA 是加密字体。
2.有些网页的加密字体不是直接明文写在代码中的,而是对加密字体进行了一次编码,在加载页面时再动态解码渲染页面,相当于又加了一次密,例如:
解密后的code是一段js,如下:
d.write("")
其中base64,后面的字符串就是加密字体
得到加密字体字符串后,进行base64解密,存储为.ttf文件。
binData = base64.decodebytes(font_str.encode())
with open('data/xxx.ttf', 'wb') as f:
f.write(binData)
f.close()
# 加载字体生成映射关系
font = TTFont(path)
cmap = font.getBestCmap()
#解密
def to_num(self, getText):
try:
retList = []
for ch in getText: # ord()以字符作为参数,返回对应的Unicode数值
if ord(ch) in self.c:
retList.append(int(self.c[ord(ch)][-2:]) - 1)
else:
retList.append(ch)
crackText = ''
for num in retList:
crackText += str(num)
return crackText
except:
return getText
有时,网站的加密字体的固定的只要拿到一次就可以重复使用了,但有时每发送一次请求,加密字体都不一样,这时就需要每次请求后重新解析得到加密字体,下面的代码就是每次请求时加密字体都不一样的示例。
import base64
import json
import re
import js2py # 用于执行js脚本
import requests
import xlwt
from bs4 import BeautifulSoup
from fontTools.ttLib import TTFont
# 解密字体工具类
class FontToNum:
def __init__(self, path, font_str):
binData = base64.decodebytes(font_str.encode())
with open(path, 'wb') as f:
f.write(binData)
f.close()
self.font01 = TTFont(path)
self.c = self.font01.getBestCmap()
def to_num(self, getText):
try:
retList = []
for ch in getText:
# ord()以字符作为参数,返回对应的Unicode数值
if ord(ch) in self.c:
retList.append(int(self.c[ord(ch)][-2:]) - 1)
else:
retList.append(ch)
crackText = ''
for num in retList:
crackText += str(num)
return crackText
except:
return getText
fw_log = open('data/xxx_log.txt', mode='w', encoding='utf-8')
fw = open('data/xxx.txt', mode='w', encoding='utf-8')
book = xlwt.Workbook()
sheet = book.add_sheet('sheet1')
sheet.write(0, 0, '字段1')
sheet.write(0, 1, '加密了的字段2')
sheet.write(0, 2, '字段3')
sheet.write(0, 3, '字段4')
sheet.write(0, 4, '字段5')
sheet.write(0, 5, '加密了的字段6')
sheet.write(0, 6, '字段7')
url = 'https://xxx/xxx/xxx'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0'
}
raw = 1
for i in range(1, 21):
for j in range(5):
try:
res = requests.get(url.format(str(i)), headers=headers, timeout=(60, 66))
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'lxml')
tr_list = soup.select('[class="table-plate3"]')
# 获取加密字体字符串
font_js = re.search("unescape\(\"(.*?)\"\);", res.text).group(0) # 得到生成加密字体的js代码
font_css = js2py.eval_js(font_js) # 执行js代码
font_str = re.search("base64,(.*?)'", font_css).group(1) # 拿到加密字体字符串
# 解码工具类
font_to_num = FontToNum('data/font/' + str(i) + '.ttf', font_str)
result = {}
for tr in tr_list:
name = tr.select_one('[class=tp2_com]').text.strip()
money = tr.select_one('[class=tp-mean]').text.strip()
touzilunci = tr.select('td')[3].text.strip()
touzifang = tr.select('td')[4].text.strip()
hangye = tr.select_one('[class=tp3]').text.strip()
time = tr.select('td')[6].text.strip()
detaio_href = tr.select_one('[class=show-detail]').get('href').strip()
result['字段1'] = name
result['加密了的字段2'] = font_to_num.to_num(money)
result['字段3'] = touzilunci
result['字段4'] = touzifang
result['字段5'] = ','.join([v for v in hangye.split(' ') if v])
result['加密了的字段6'] = font_to_num.to_num(time)
result['字段7'] = detaio_href
sheet.write(raw, 0, name)
sheet.write(raw, 1, font_to_num.to_num(money))
sheet.write(raw, 2, touzilunci)
sheet.write(raw, 3, touzifang)
sheet.write(raw, 4, ','.join([v for v in hangye.split(' ') if v]))
sheet.write(raw, 5, font_to_num.to_num(time))
sheet.write(raw, 6, detaio_href)
fw.write(json.dumps(result) + '\n')
raw += 1
break
except Exception as e:
print(e)
continue
fw_log.close()
fw.close()
book.save('data/xxx.xls')