吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5333|回复: 56
收起左侧

[Web逆向] css字体反扒的尝试记录小说下载

  [复制链接]
743567274 发表于 2024-4-10 14:58
首先,不知道发表在哪个区,如果有错,请版主移动一下,谢谢版主~~



前言



本片帖子的基础是前几天在论坛上看到了一篇关于这个的帖子:https://www.52pojie.cn/thread-1910333-1-1.html
这里我也at一下帖子作者:@jing99

网站:aHR0cHM6Ly93d3cuemhpaHUuY29tL21hcmtldC9wYWlkX2NvbHVtbi8xNzMwNjA3ODEwMjI2Njg4MDAwL3NlY3Rpb24vMTczMDE4MTE0ODk2ODMyNTEyMA==


我对于英文特别不敏感,代码很多不会写。所以以下的代码中,如果有问题,请见谅~
我可能比大家想象中的还要小白,所以此篇帖子我会站在比我更小白的角度,尽量让大家看得明白

并且我很喜欢互相交流学习,如果需要交流学习,请私信~~~

本篇分析以学习为目的,禁止触犯法律法规。
浏览器:edge

感谢当前的AI浪潮吧,让编程基础又降低了很多门槛。此分析帖子的很多代码都是全靠AI,我自己只是稍微修改了一下~

分析


打开网址,F12。发现网页直接跳转了~
那么,就新建一个标签页,先F12,设置,禁用JS
再次粘贴网址,跳转。对比一下网页的文字和html源代码中的文字。

下一步确认一下文字动态还是静态
按照上面的方法,再次新建一个标签页,重新打开网址,发现是两次源代码的文字是不一样的。确认是动态加载!


并且字体为base64编码,提取出来,保存为TTF文件!
这里推荐一个查字体的网站:https://www.bejson.com/ui/font/
打开网站后,上传字体文件。

分析到这里,大致的思路已经出来了~
读取网页源代码->提取base64编码的字体文件->取出来字体文件中的字符图片以及真实的Unicode字符->识别图片中的文字->按照规则进行文章的文字替换


读取网页源代码



提取base64编码的字体文件


代码来自于AI

取出来字体文件中的字符图片以及真实的Unicode字符



现在的目的达到了,接下来就是识别这个字符的字,然后把文章的"要"替换为"发"
现在软件可以通过uni8981查询到这个字是"要",但是软件是不知道这个字代表的是"发"
所以,我的思路就是使用图像识别


识别图片中的文字

以下非黑色字体为在实现过程中的曲折,没有很细致,可以跳过
既然python是pip调包侠
那么,用哪个包呢?因为之前没有使用过类似的包,只要靠搜索引擎了


初识:Tesseract
这是一个由谷歌开发的文字识别库,好像挺强大的,但是win系统需要安装一个exe程序。安装好之后可能是设置问题,一直没有识别出来。是TM的一个都没有识别出来~
查文档,正如开篇所讲,确实英文不好。所以放弃~~~


初识:easyocr
这个就不介绍了,PIP过程中,下载的包很大。当然,识别库都需要下载,理解;
遇到的问题就是某些可以识别,某些不能识别。当然,对于一个github上一万多个star的库来说,肯定是我的问题。文档还是一如既往的英语~放弃


后来遇到了CnOCR。使用这个的主要原因是因为有中文文档,最主要的是他有一个在线测试的网站,可以把你的图片上传上去测试~就试了一下。

没问题就决定使用这个库了。接下来一顿pip

果然,厉害了我的GUO(其实后面他给的网址没了,其实不是因为V*N的原因)。。。我就知道过程没有那么顺利~
仔细查看官方文档后,发现需要下载检测模型和识别模型。因为网络原因不能下载,那么就需要手动下载后放入本地指定目录。干吧

已经提取出来了。然后就是把识别到的文字和真实的文字放一起,就可以下一步了~


按照规则进行文章的文字替换


然后进行原文章对比校验
不出意外的话,还是出意外了~

这里其实找了以下原因,是因为字符重复替换的问题

这里解释一下
如果原文本是:一二一二一二一二一二一二一二一二
你识别出来的字符是数组是[("一","二"),("二","一")]
那么替换两次,
第一次把一替换为二,这个时候源文本就全是二了
第二次把二替换为一,文本就全是一了。原理差不多就是这么个原理


从断点中也可以看的出来,数组是从00开始替换;
到02的时候把文章的"不"替换为了"用"
到31的时候又把文章的"用"替换了"可"


到04的时候把个替换为了生
到30的时候又把"生"替换为了"和"


为了解决这个问题,我想出来了一个方案。
新建一个空数组,记录已经替换的位置。下次替换的时候如果此位置已经替换,则跳过不替换~(此代码依旧来自于AI)
[Python] 纯文本查看 复制代码
replaced_positions = set()
    for old, new in result:
        for i, char in enumerate(conent):
            if i not in replaced_positions and char == old:
                conent = conent[:i] + new + conent[i+1:]
                replaced_positions.add(i)
            elif i in replaced_positions:
                continue
    with open('正文.txt','w',encoding='utf-8') as f:
        f.write(conent)



至此,运行。查看结果。



全部结束~


免费评分

参与人数 18威望 +2 吾爱币 +117 热心值 +18 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
NeroArc + 1 + 1 谢谢@Thanks!
YQYuan + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
liyitong + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
XFiend + 1 + 1 用心讨论,共获提升!
iTMZhang + 1 + 1 热心回复!
ielnaf + 1 + 1 热心回复!
tsformat + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
涛之雨 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
a454635280 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
fortytwo + 2 + 1 谢谢@Thanks!
wzvideni + 1 + 1 我很赞同!
FitContent + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
FateBug + 1 我很赞同!
hefan8 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
anning666 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
放羊的狼 + 1 + 1 现在都这么厉害了么?这是不是也算一种混淆方式?
jing99 + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

fortytwo 发表于 2024-4-11 01:56
本帖最后由 fortytwo 于 2024-4-11 01:59 编辑

跟着你的思路复现了一下
easyocr这个库应该是基于已有的字体来训练的,对于知乎这种自定义的字体,支持不是很好。
其他ocr库不太清楚,我平时使用的ocr插件(utools的)正常识别没有问题。

我采用的是将ttf文件的字形画出来,然后给easyocr识别(也是一样的问题,个别字识别不出来),后面思路一致。



[Python] 纯文本查看 复制代码
import base64
import re

import bs4
import easyocr
import requests
from fontTools.ttLib import TTFont
from PIL import Image, ImageDraw, ImageFont
import os


def ocr(folder_dir: str) -> dict:
    reader = easyocr.Reader(['ch_sim'], gpu=False)
    key_words = {}
    directory = folder_dir
    dir_list = os.listdir(directory)
    for file_name in dir_list:
        full_path = os.path.join(directory, file_name)
        # print(full_path)
        error_key_word = f'\\{file_name}'.encode('utf-8').decode('unicode_escape').replace('.png', '')
        try:
            success_key_word = reader.readtext(full_path, detail=False)[0]
        except IndexError:
            success_key_word = '一'
        key_words.update({error_key_word: success_key_word})
    return key_words


def get_html():
    url = "https://www.zhihu.com/market/paid_column/1730607810226688000/section/1730181148968325120"

    payload = {}
    headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
        'accept-language': 'zh-CN,zh-TW;q=0.9,zh;q=0.8',
        'cache-control': 'no-cache',
        'pragma': 'no-cache',
        'sec-ch-ua': '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'document',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'none',
        'sec-fetch-user': '?1',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
    }

    response = requests.request("GET", url, headers=headers, data=payload)

    return response.text


def html_analyze(html_body: str):
    html_obj = bs4.BeautifulSoup(html_body, features="html.parser")
    return '\n'.join([item.get_text() for item in html_obj.findAll('p')])


def clear_directory(path):
    for filename in os.listdir(path):
        filepath = os.path.join(path, filename)
        try:
            if os.path.isfile(filepath):
                os.remove(filepath)
        except Exception as e:
            print(f"Error deleting {filepath}: {e}")


def export_glyph_images(ttf_file_path, output_folder, image_size=(200, 200), font_size=200):
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    font = TTFont(ttf_file_path)
    cmap = font.getBestCmap()
    pil_font = ImageFont.truetype(ttf_file_path, font_size)

    for unicode_char, glyph_name in cmap.items():
        char = chr(unicode_char)
        img = Image.new("RGBA", image_size, color="white")
        draw = ImageDraw.Draw(img)

        # 绘制字符
        draw.text((0, 0), char, font=pil_font, fill="black", font_size=24, align="center")
        # 保存图片
        unicode_hex_str = f'u{unicode_char:04X}' + ".png"
        print(unicode_hex_str)
        image_file_path = os.path.join(output_folder, unicode_hex_str)
        img.save(image_file_path)

    font.close()


if __name__ == '__main__':
    # 字体缓存地址
    output_folder = r"xxxxxxx"

    clear_directory(output_folder)
    html_body = get_html()
    font_face_regex = re.findall(r'@font-face\s*{([^}]*)}', html_body)
    base64_string = re.findall('base64,(.*?)\);', font_face_regex[3])[0]
    binary_data = base64.b64decode(base64_string)
    with open('zhihu.ttf', 'wb') as f:
        f.write(bytes(binary_data))
    # 指定TTF文件的路径
    ttf_file_path = "zhihu.ttf"
    # 调用函数导出字形图片
    export_glyph_images(ttf_file_path, output_folder)

    keywords = ocr(output_folder)
    content = [item for item in html_analyze(html_body)]

    filter_list = []
    for error_key, success_key in keywords.items():
        for index in range(len(content)):
            if content[index] == error_key and index not in filter_list:
                content[index] = success_key
                filter_list.append(index)

    with open('知乎盐选.txt', 'w', encoding='utf-8') as f:
        f.write(''.join(content))


免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
涛之雨 + 7 + 1 用心讨论,共获提升!

查看全部评分

QAQ~QL 发表于 2024-4-11 19:06
有幸前段时间做过这个项目,可以参考github的反扒项目 https://github.com/stars-rivers/font_transfer
其中字体的思路很好,先解析font,遍历所有字体,组成长文本,塞进CnOcr,切割识别数组并做好字符映射,再替换
殊途同归

贴上rpc代码吧
[Python] 纯文本查看 复制代码
import copy
import json
import math

import cnocr
import flask
import numpy
from PIL import Image, ImageDraw, ImageFont
from flask import request
from fontTools.ttLib import TTFont

# pip install cx_Freeze
class FontTransfer(object):

    def __init__(self):
        self.font_size = 20  # 字体文字的尺寸
        self.ocr = cnocr.CnOcr()

    def get_chars_from_font(self, font_path):
        ttf = TTFont(font_path)
        return {k: v for k, v in ttf['cmap'].getBestCmap().items() if ttf['glyf'][v].xMax}

    def draw_font_word(self, char_unicode, origin, board, font):
        draw = ImageDraw.ImageDraw(board)
        draw.text(tuple(origin), char_unicode, font=font, fill=0)

    def font_to_image(self, font_path):
        char_dict = self.get_chars_from_font(font_path)
        # 字体能被分成多少行多少列的正方形图片
        num = math.ceil(math.sqrt(len(char_dict)))
        # 自适应图片的大小
        image_size = num * (self.font_size + 4)

        font = ImageFont.truetype(font_path, self.font_size)

        # unicode_list = list(char_dict.values())
        unicode_list = list(char_dict.keys())
        board = Image.new('RGB', (image_size, image_size), (255, 255, 255))

        # 这个算法用于确定每个字型的坐标
        origin = [0, 0]
        i = 1
        j = 1
        for k in char_dict.keys():
            origin[0] = (self.font_size + 4) * (j - 1) + 2
            origin[1] = (self.font_size + 4) * (i - 1) + 2
            if j % num == 0:
                i += 1
                j = 0
            char_unicode = chr(k)
            self.draw_font_word(char_unicode, copy.copy(origin), board, font)
            j += 1
        return numpy.asarray(board), unicode_list

    def get_font_transfer_dict(self, font_path):
        img_array, unicode_list = self.font_to_image(font_path)
        string_list = []
        rs = self.ocr.ocr(img_array)
        # print(font_path, rs)
        for res in rs:
            string_list += res['text']
        return dict(zip(unicode_list, string_list))


ft = FontTransfer()

app = flask.Flask(__name__)
app.config['JSON_AS_ASCII'] = False


@app.route('/check', methods=['get'])
def check():
    path = request.values.get('path')
    if path is None:
        return "Err"
    try:
        res_dict = ft.get_font_transfer_dict(path)
        # 双引号json格式
        return json.dumps(res_dict, ensure_ascii=False)
    except Exception as e:
        return "Err " + str(e)


if __name__ == '__main__':
    app.run(debug=True, port=7898, host="0.0.0.0")

免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
涛之雨 + 7 + 1 用心讨论,共获提升!

查看全部评分

jing99 发表于 2024-4-10 15:05
学习了,谢谢大佬指点。作为新手,这次练习确实收获很多!!
感谢大佬花时间指点迷津
Soma77 发表于 2024-4-10 15:07
好奇是不是只能扒账号可以看到的部分
jenisonbai 发表于 2024-4-10 15:11
学习了~~
jing99 发表于 2024-4-10 15:15
Soma77 发表于 2024-4-10 15:07
好奇是不是只能扒账号可以看到的部分

是的,盐选现在恶心在需要下载 app 才能看全。我是修改了请求头可以爬全,你可以去看看
atoms 发表于 2024-4-10 15:58
厉害 但反正中间也需要ocr,那是不是可以直接截取原页面的图片,然后将图片直接丢进ocr呢?
齐恩 发表于 2024-4-10 16:07
斗智斗勇啊,牛逼
Soma77 发表于 2024-4-10 16:17
jing99 发表于 2024-4-10 15:15
是的,盐选现在恶心在需要下载 app 才能看全。我是修改了请求头可以爬全,你可以去看看

感谢,又学习到不少
jing99 发表于 2024-4-10 16:25
atoms 发表于 2024-4-10 15:58
厉害 但反正中间也需要ocr,那是不是可以直接截取原页面的图片,然后将图片直接丢进ocr呢?

这样 ocr 的工作量是不是很大呀,我自己的想法,只是疑问一下
42328669425 发表于 2024-4-10 16:31
代码太多看不懂了  坐等大佬们的成品
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-22 15:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表