记一次小程序爬虫(反编译-自动化字体映射生成)

Hi I'm Shendi

最近有小程序爬虫需求,于是在这里简略记录下来经过


记一次小程序爬虫(反编译-自动化字体映射生成)


方案

爬取数据的办法通常有几种,一种是自动化脚本。即模拟用户操作,在移动设备上可以通过无障碍来获取元素数据,另一种是通过接口获取数据。

通过监听移动设备数据(中间人)

能直接通过接口爬取数据是最好的,所以最开始使用的就是监听数据的形式

具体流程就是在移动设备设置代理,让数据经过电脑的代理服务器,并在移动设备安装证书,这样就可以监听到数据了

不过这个方法尝试失败了,因为在Android9以后,用户证书不再被信任,而要安装系统证书需要Root...尝试一番太过麻烦就放弃了。

通过无障碍抓取数据

本着可见皆可爬的原则,觉得无障碍爬取一定是没有问题,所以在稍微尝试了一番后,编写了无障碍APP爬取,果然是可以爬的,然后我就开始编写后续所有的代码...

其中有图片爬取,不过没关系,可以自动化操作,点击图片,长按保存让其进入相册,然后在相册中读取最新的图片就可以了。并且小程序保存会自动输出保存的图片路径,也可以通过无障碍获取这个地址进行操作。

等我编写完,我发现,有一个数据是乱的(正常的文字,但是连起来狗屁不通,与界面上显示的不一致),我开始仔细分析哪里的问题...思索无果放弃了。

现在对于要完成这个目标的选择有:

  • 尝试反编译小程序源码
  • 直接通过OCR读取屏幕转文字,配合无障碍完成整个爬取,这是没有任何办法时的最后的办法

反编译小程序代码

但总是要爬的,所以开始反编译,因为微信可以电脑上打开小程序,所以反编译简单了起来,只要使用工具就可以了,反编译成功了,获取到了小程序源码。

反编译过程可以看这个:https://blog.csdn.net/weixin_54261528/article/details/145663372

反编译后,我又想到是否可以直接在电脑上监听接口数据,因为小程序是电脑微信打开的,我使用mitmproxy监听,的确监听到了,这样token就可以拿到了。

不过其中的一些关键数据是加密的,正好有源码,查看源码,一个个寻找...

最终,这个小程序使用RSA加密了数据,私钥都在小程序代码中,所以我很容易解密了。接口包含timestamp,token,random,sign,这四个关键参数,根据源代码显示的,将前三个字符串拼接,通过md5生成的sign就可以通过接口验证了。

现在一切都通了,好像很顺利,但是我解密的数据是HTML实体编码的,我解码后发现,跟无障碍遇到的情况一样,跟显示的文字完全对不上!我仔细查找源代码,以为有什么地方漏看了,少了一个解码步骤...

除了一个加载自定义字体的操作,没有任何东西了,所以,是不是字体的问题?了解后,果然,字体反爬是一种常见的手段,但是如何用字体将实体编码解析成文本?这可就头疼了。

自定义字体的自动化映射获取

最后的问题就是处理掉这个字体的问题,整个爬取就大功告成了

我通过源代码,下载了自定义的字体文件,这个字体可以理解为一个编码对应一个图片...所以,能想到的就是编写一个映射,将编码与正确文字对应...但手动编写也太浪费时间了,并且如果字很多呢?

字体在线编辑可以用这个看字体编码与字体对应:https://kekee000.github.io/fonteditor/

所以我第一时间想到,能不能使用OCR识别来全自动生成映射?只要把字体分割成一个个的,然后一个个识别就可以了...于是编写了以下代码(Python)

python 复制代码
import io
import os
import ddddocr
from PIL import ImageDraw, ImageFont, Image
from fontTools.ttLib import TTFont

# 将字体文件生成映射的python代码,通过ocr自动识别。
class UniversalFontRecognition(object):
    def __init__(self, font_path):
        self.font_path = font_path
        self.ocr = ddddocr.DdddOcr(show_ad=False)

    def font_to_xml(self, xml_path=None):
        """将 TTF 字体文件转换为 XML 格式"""
        if not xml_path:
            filename_with_ext = os.path.basename(self.font_path)
            filename_only = os.path.splitext(filename_with_ext)[0]
            xml_path = f'{filename_only}.xml'
        font = TTFont(self.font_path)
        font.saveXML(xml_path)

    def font_to_img(self, char_list, img_size=100, font_ratio=0.7):
        """将字体字符渲染成图片,并使用 OCR 识别"""
        normal_dict = {}

        for char in char_list:
            char_code = chr(char)  # 直接转换 Unicode
            img = Image.new('RGB', (img_size, img_size), (255, 255, 255))  # 修正背景色
            draw = ImageDraw.Draw(img)
            font = ImageFont.truetype(self.font_path, int(img_size * font_ratio))

            # 获取文本边界
            bbox = draw.textbbox((0, 0), char_code, font=font)
            if bbox is None:
                continue  # 跳过无法获取尺寸的字符
            
            x, y = bbox[2] - bbox[0], bbox[3] - bbox[1]  # 计算宽高
            draw.text(((img_size - x) // 2, (img_size - y) // 2), char_code, font=font, fill=(0, 0, 0))

            # 将图片保存为字节流
            img_bytes = io.BytesIO()
            img.save(img_bytes, format='JPEG')
            image_bytes = img_bytes.getvalue()

            # 使用 OCR 识别
            word = self.ocr.classification(image_bytes)
            normal_dict[char] = word[0] if word and len(word) > 0 else ''

        return normal_dict

    def crack(self):
        """解析 TTF 字体文件并生成 Unicode → 真实字符的映射"""
        with open(self.font_path, 'rb') as fr:
            font_bytes = fr.read()
        with TTFont(io.BytesIO(font_bytes)) as font_parse:
            u_d = font_parse.getBestCmap()  # 获取 Unicode → 字体编码的映射
        return self.font_to_img(list(u_d.keys()))


# === 使用示例 ===
ufr = UniversalFontRecognition(r"font/sfont.ttf")

# 1. 可选:导出 XML,查看字体编码信息
ufr.font_to_xml()

# 2. 生成映射字典
font_map = ufr.crack()

# 3. 打印映射结果
print(font_map)

需要先安装以下依赖,然后通过python直接运行。

复制代码
pip install fonttools pillow ddddocr

效果也是非常不错,但我在查看过程中,发现有些字母大小写识别总是小写,所以手动更改一下映射

最终还是遇到了一个问题,是解密后解码的文字莫名其妙有一个字符乱码,我定位到问题是在解密后的数据就带上了乱码...最终是因为,RSA解密使用的字符串拼接方式,导致了乱码,所以改成直接字节方式就可以了

java 复制代码
public static byte[] decryptLongBinary(String encryptedData, PrivateKey privateKey) throws Exception {
    byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);

    int keySize = 128; // 1024bit RSA, 128 bytes per block
    int chunkSize = keySize; // Each decryption chunk is 128 bytes
    int inputLength = encryptedBytes.length;

    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // Split the data and decrypt chunk by chunk
    for (int i = 0; i < inputLength; i += chunkSize) {
        int len = Math.min(inputLength - i, chunkSize);
        byte[] decryptedChunk = cipher.doFinal(encryptedBytes, i, len);
        baos.write(decryptedChunk);
    }

    return baos.toByteArray();
}

就这样,目标完成。

END

相关推荐
AI服务老曹24 分钟前
以实现生产制造、科技研发、人居生活等一种或多种复合功能的智慧油站开源了
人工智能·科技·自动化·音视频·生活·制造
CL_IN2 小时前
零售业务订单处理自动化:吉客云对接金蝶云星空
windows·自动化·零售
mon_star°3 小时前
搭建刷题专业版小程序系统
微信小程序·小程序·微信公众平台
测试渣6 小时前
性能测试自动化:JMeter脚本设计与分布式压测实战指南
分布式·测试工具·jmeter·自动化
船长@6 小时前
微信小程序刷题逻辑实现:技术揭秘与实践分享
微信小程序·小程序·notepad++
訾博ZiBo8 小时前
使用 DrissionPage 实现网页内容自动化采集
爬虫
yangshuo12819 小时前
豆包API的部署和实战|基于 Selenium 的抖音自动化评论系统开发实践
selenium·测试工具·自动化
爱奇艺技术产品团队15 小时前
服务性能防腐体系:基于自动化压测的熔断机制
运维·自动化
电商数据girl15 小时前
在1688平台上如何实现铺货和上传商品的自动化?
运维·自动化