css字体反扒的尝试记录小说下载

查看 111|回复 11
作者:743567274   
首先,不知道发表在哪个区,如果有错,请版主移动一下,谢谢版主~~
前言

本片帖子的基础是前几天在论坛上看到了一篇关于这个的帖子:https://www.52pojie.cn/thread-1910333-1-1.html
这里我也at一下帖子作者:@jing99
网站:aHR0cHM6Ly93d3cuemhpaHUuY29tL21hcmtldC9wYWlkX2NvbHVtbi8xNzMwNjA3ODEwMjI2Njg4MDAwL3NlY3Rpb24vMTczMDE4MTE0ODk2ODMyNTEyMA==
我对于英文特别不敏感,代码很多不会写。所以以下的代码中,如果有问题,请见谅~
我可能比大家想象中的还要小白,所以此篇帖子我会站在比我更小白的角度,尽量让大家看得明白
并且我很喜欢互相交流学习,如果需要交流学习,请私信~~~
本篇分析以学习为目的,禁止触犯法律法规。
浏览器:edge
感谢当前的AI浪潮吧,让编程基础又降低了很多门槛。此分析帖子的很多代码都是全靠AI,我自己只是稍微修改了一下~
分析

打开网址,F12。
[color=]发现网页直接跳转了
~
那么,就新建一个标签页,先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)
至此,运行。查看结果。


全部结束~

微软, 文字

fortytwo   

跟着你的思路复现了一下
easyocr这个库应该是基于已有的字体来训练的,对于知乎这种自定义的字体,支持不是很好。
其他ocr库不太清楚,我平时使用的ocr插件(utools的)正常识别没有问题。
我采用的是将ttf文件的字形画出来,然后给easyocr识别(也是一样的问题,个别字识别不出来),后面思路一致。
[i]
[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))
QAQ~QL   

有幸前段时间做过这个项目,可以参考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")
jing99   

学习了,谢谢大佬指点。作为新手,这次练习确实收获很多!!
感谢大佬花时间指点迷津
Soma77   

好奇是不是只能扒账号可以看到的部分
jenisonbai   

学习了~~
jing99   


Soma77 发表于 2024-4-10 15:07
好奇是不是只能扒账号可以看到的部分

是的,盐选现在恶心在需要下载 app 才能看全。我是修改了请求头可以爬全,你可以去看看
atoms   

厉害 但反正中间也需要ocr,那是不是可以直接截取原页面的图片,然后将图片直接丢进ocr呢?
齐恩   

斗智斗勇啊,牛逼
Soma77   


jing99 发表于 2024-4-10 15:15
是的,盐选现在恶心在需要下载 app 才能看全。我是修改了请求头可以爬全,你可以去看看

感谢,又学习到不少
您需要登录后才可以回帖 登录 | 立即注册