Python爬虫之解决字体加密问题!字体加密确实难!

有些网站为了反爬,对网页中的一些数据进行了字体加密,用户浏览网页时显示的是正常的,但是爬取网页源代码时,却是乱码。

原因

页面在css中使用font-face定义了字符集,并通过unicode去映射展示,浏览器会加载css中的font字体为用户渲染好,所以浏览页面时是正常的,而对于爬虫来说却极其不友好,因为爬取下来的源代码未经过浏览器渲染,都是乱码。

解决办法

1. 查找到页面中的加密字体 font_face,常见的有两种情况

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.有些网页的加密字体不是直接明文写在代码中的,而是对加密字体进行了一次编码,在加载页面时再动态解码渲染页面,相当于又加了一次密,例如:

<script>
function font(d) {
let code = unescape("%64%2E%77%72%69%74%65%28%22%3C%73%74%79%6C%65%3E%40%66%6F%6E%74%2D%66%61%63%65%7B%66%6F%6E%74%");
eval(code);//执行code
}(document);
</script>

解密后的code是一段js,如下:

d.write("<style>@font-face{font-family:'cyzone-secret';src:url('data:application/font-ttf;charset=utf-8;base64,AAEAAA

其中base64,后面的字符串就是加密字体

2.base64解密保存字体文件

得到加密字体字符串后,进行base64解密,存储为.ttf文件。

binData = base64.decodebytes(font_str.encode())
with open('data/xxx.ttf', 'wb') as f:
f.write(binData)
f.close()

3.加载字体文件得到映射关系,进行解密

# 加载字体生成映射关系
    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')

学会了吗

 

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值