吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5874|回复: 55
收起左侧

[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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
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] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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, 2025-5-25 00:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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