0%

reader3书柜,word转txt,正则清理乱码

搭建私人在线小说藏馆

偶遇doc/docx保存的带乱码小说强如怪物,无需拼尽全力即可战胜

事情是这样的,自从去年11月我鼓捣了自己的NAS,所有的照片、电影、文档都开始远端存储,多端共享本地无负担的日子别提多惬意了。拥有一个大容量NAS对于有仓鼠症的网民真的很重要

但是近来我突然发现,我的一些小说收藏还没有上NAS,也没有被很好的管理,于是一拍脑袋一锤腿——搞个在线书柜!

什么书柜好呢

Calibre-Web

因为以前我用过kindle看书,所以接触过Calibre。

kindle这玩意看似好,但是只要不是在amazon买电子书,用起来还是很麻烦的。当时我为了上传有封面有章节划分的小说,弄了好几个软件协同,爬来爬去,转来转去的,最后用Calibre做的最终的管理和上传(顺便一提这玩意的格式转换是真的慢)。

Calibre给我的印象就是:功能强大但是非常非常的Heavy。不过考虑到Calibre是用来管理出版物的,支持那么多格式和功能,有些负担在所难免;更何况复杂几乎只存在于导入的时候,后续使用起来还是很不错的。

于是在搜索到还有Calibre-Web这种好东西的时候,我没做太多考究就选定了这个方案。

加之当时云计算课程临近结课,迫切需要一个课程设计(好吧我承认搭建个这玩意当课程设计确实是有点糊弄了所以我额外鼓捣了FRP+反代+高可用什么的)我几乎立刻去鼓捣了这玩意。

如果你有注意看题目的话,结果应该显而易见。结论就是这玩意并不是为我的需求提供的,网页端的上传非常难用,几乎只能借助客户端来管理书库。并且也不支持在线阅读,可以说就是给Kindle或者其他阅读器提供的一个下载站。

搭建过程这里不赘述了,可以拉docker镜像也可以直接安装第三方套件(对于群晖用户)

pVu0BWR.png

Reader3

本期主角

吸取上次搭建踩坑的经验,这次多做了一些对比,选择了一个比较适合我的项目。事实上,这个项目有点类似于只有书柜的私人笔趣阁,提供导入书源的途径,也提供一个类似起点的在线阅读器。(还有其他的更像笔趣阁的项目,那就不太符合我的需求了)

搭建

我拉取的是 hectorqin/reader:latest 这个镜像

不出意外的话这是项目地址 hectorqin/reader: 阅读3服务器版,桌面端,iOS可用。后端 Kotlin + Spring Boot + Vert.x + Coroutine ;前端 Vue.js + Element。麻烦点点star,关注一下公众号【假装大佬】❗️

搭建起来也不难,如果有需要可以参考一下这个大佬的教程:快速搭建个人小说站:免费看海量小说 - 知乎

小技巧

这里有一点小技巧,这个项目实际上会把数据存到/storage,但是会从/storage/localStore拉取本地书籍。如果你和我一样有个其他的文件夹存储着书籍数据,可以像我这样额外挂载一下这个目录,这样就可以读取到你的收藏。

pVu0rS1.png

优势

首先就是这玩意可以很方便的批量导入本地书,能挂载一个目录作为“书仓”。而且不会对原始的文件造成影响,也不会把原始文件复制的到处都是(说你呢微信),非常的nice。

其次就是可以在线阅读,而且支持多种终端。因为不同端同账户本质上访问的都是同一套配置所以也可以多端同步阅读进度,很不错。

因为主要服务的还是小说所以对于小说分章有些优化,比较常见但是很实用的功能。

不足

系列短文不友好。我保存了一些从p站down的小短文,这些短文是根据文件夹分的系列;但是在reader3的话只能每一章(一个文档)作为一篇进行导入,这样就会导致比较混乱。感觉这个平台应该需要一个短文分区,可以把一套文作为一个系列导入。

移动端阅读体验有点迷。不过这个问题也不算太大。根据这个项目社群里交流的方法,实际可以通过Edge或者Chrome等应用把网页安装为英语,这样就能比较方便的全屏使用。不过由于我没有配置https所以暂时没有体验到。

信息展示不全,无效信息多。有一些小说(尤其是轻小说或者P站短文)题目比较的长,而这个平台即使的展示信息最多的列表模式也不能很好的展示全部题目。同时因为大多内容我都是通过txt导入的,并没有封面,而这里又强制显示一个图片封面,会导致大量空间被无效的信息占据。感觉缺乏的是一个真正的列表模式和一个根据标题生成图片封面的功能(这一块有个叫VBOOK的软件做的还挺好的)。

文件管理割裂。虽然有文件的导入功能,但是感觉这个导入像是附加进来的,使用起来非常的不方便,即使我用竖屏展示也不能同屏展示足够多的信息(文件数目)。总之就是使用起来非常的奇怪,给人的感觉就是给你全选然后一键导入的,并不考虑给你在文件方面做过多的挑选。还有他提供的一个筛选已导入书籍的功能感觉有点问题,实际已经导入的还是会被选中(不过讲道理感觉应该是按照文件路径进行筛选的,没道理会出问题吧)

本地搜索缺失。这个比较奇怪,他可能比较多的考虑到使用书源进行搜书的用户,搜索会直接在书源里搜,并不会搜索本地导入的书籍。

anyway

不过这玩意毕竟是开源项目,考虑之后fork一个做些修改吧。原项目说是部分开源,我看了下应该是有个kindle专供页面是需要授权收费的,猜测这部分是闭源的,应该不太影响我要的功能。

总体来说还是不错的,目前需求的情况下暂时足够我使用了。

导入收藏

旧有的Calibre书库我还是准备继续用Calibre管理,暂时不打算导入这个平台。不过那个文件夹里的都是配好的mobi和epub一类的,相必导入也没啥难度。

有些网文小说什么的也是顺利的导入了,结果在某个神秘封闭网站资源导入时遇到了大麻烦。

这玩意!居然!是!用!Word!保存!的!

啊?

纯文本一定要用纯文本格式

你知道我面对一万个word文档的时候有多震撼吗?爬这个的简直是神人了,为他的毅力鼓掌!

我感觉只有N年前的小学信息技术课的时候才有将网页保存到word这种神秘的要求吧?!

考虑到论坛网页其实还会有很多其他的信息,它能做到只保留正文也是很坚持了。

不出意外的,reader3并不支持导入word版的资源。

还有啥好说的,转格式呗!

word2txt.py

内容几乎是处纯血的AIGC,放个全文链接LymoneTest/Tools/doc2txt.py at master · LymoneLM/LymoneTest

基本流程就是遍历指定目录,然后找到所有doc/docx文件,提取对应的文本保存在临时文件里,清洗,最后写入原目录同名txt文件

乱码处理

值得一提的是这个清洗环节,最初版的脚本提取完文本是这个风格的:

1
2
3
4
5
6
7
8
第一章: 0 A) l' r( c6 s+ \: d* _ 
作者:作者名# ^* y; q+ _0 i % O' u8 K7 ]1 v$ H) C
我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字6 N" k0 a7 _( a/ p 0 X) e" I. M$ N& b! J# g
我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字。
/ b5 Q: B' E. c& j 3 U) }. }- P% c& Y" N
我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字( [& }& ?' G: V- j% g' { 8 [2 T2 C, o5 \4 d8 H4 H5 e
我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字 B( ]3 E( o$ v; i0 p9 D% q 7 ]0 i) Q3 T3 M& U/ t5 B$ ?4 ~6 Y
我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字我是正文脱敏替代字

可以看到在有些位置会有一些乱码。因为我对word内部究竟是如何存储内容的并不是十分的了解,首先我猜测这些可能是内置的一些控制字符。我将这个猜测提交给DeepSeek参考修改,结果DS给出的新一版本的代码基本就是做了一些ASCII控制字符内容的处理,不过这些内容显然不是控制字符。

再次审阅原本的内容,我发现有些行末的换行符/分段符并不是紧贴着正文之后的,离正文之间还有一整段的空白。于是我试着给中间的这段空白改一下文字颜色,于是顺利的,这段乱码现出了原型。

所以显然这可能是某种网页转存Word的时候出现的字符,或者是某种隐藏的水印。

那接下来思路就很顺利了,用正则匹配一下这些乱码就行了。很明显可以看到这些乱码基本都是在每一段的最末尾,两两为一组,间以空格。不过考虑到部分位置并不总是两两一组所以并没有将这个特征纳入匹配的范围,初始的匹配正则如下:

1
([^\x00-\x7F])([\x20-\x7E]{4,})$

前段先匹配中文字符(包括汉字和中文标点符号)然后如果后面有长段(4个以上)的数字英文和符号的混合体,就匹配上。

不过很显然这样会有几种情况会被错误的匹配到:

  1. 如果末尾没有中文句号,那么类似英文混合的作者名、交流群的群号一类的都会被匹配
  2. 末尾或者独立成行的网站链接也会被匹配

考虑到有用的网站链接并不多这里只能无奈误伤,因为实际如果网站链接出现在末尾,后面也是有可能有乱码的,实际去处理判断乱码从哪里开始并不十分容易(毕竟有些网站的链接采用了一些哈希串也是乱码)。

如果是作者名和群号的话,我简单的设计了一个规则,让原本没有受到这种乱码影响的文章不会被影响:

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 删除行末乱码(至少4个ASCII字符,且包含特殊字符)
for line in lines:
# 匹配行末乱码(前面有非ASCII字符,后面是至少4个可打印ASCII字符)
match = re.search(r'([^\x00-\x7F])([\x20-\x7E]{4,})$', line)
if match:
prefix, junk = match.groups()
# 检查是否只包含英文/数字/空格
if not re.fullmatch(r'[\sA-Za-z0-9]*', junk):
# 保留前缀字符,删除乱码部分
line = line[:match.start(1)] + prefix

cleaned_lines.append(line)

这样的话如果匹配到的是纯英文/数字和空格组合的字串,会被得以保留(根据实际观察,能看到的乱码基本都含有特殊符号,会被这条规则错放过去的乱码应该不会很多)。

总体上看这个匹配写的还是有点激进的,应该会误伤不少正常的内容,不过为了尽可能使得文章可读性更高且处理时尽可能高效还是可以接受的。原始版的内容我也留有备份所以不会有信息上的丢失。

实际使用时发现误伤了一个身份证号,不是谁TM在小说里编个身份证号码啊!

doc与docx

docx应该是某种更先进的word格式。简单查阅得知docx实际上是个压缩包,用一堆xml和其他媒体文件的形式保存着格式信息。

Python(Windows环境下)中对于doc的处理需要引入win32com模块,本质上其实是打开了一个word应用程序:

1
2
3
4
5
6
7
8
9
# 初始化COM库
pythoncom.CoInitialize()

try:
# 启动Word应用程序(仅用于处理.doc文件)
word_app = wc.gencache.EnsureDispatch('Word.Application') if any(
f.lower().endswith('.doc') for f in doc_files) else None
if word_app:
word_app.Visible = False # 不显示Word界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 处理.doc文件
if file_ext == '.doc':
# 创建临时文件保存转换结果
with tempfile.NamedTemporaryFile(suffix='.txt', delete=False) as temp_file:
temp_path = temp_file.name

try:
doc = word_app.Documents.Open(doc_path)
doc.SaveAs(temp_path, FileFormat=7, Encoding=65001) # 7 = txt格式, 65001 = UTF-8编码
doc.Close(SaveChanges=False)

# 读取转换后的内容
with open(temp_path, 'r', encoding='utf-8') as f:
content = f.read()
finally:
# 删除临时文件
if os.path.exists(temp_path):
os.unlink(temp_path)

至于docx的处理就显得更优雅了:

1
2
3
4
# 处理.docx文件
elif file_ext == '.docx':
if HAS_DOCX_SUPPORT:
content = extract_text_using_docx(doc_path)

所以日常使用中如果可以的话还是要优先考虑使用docx存储,方便日后进行处理(当然不是推荐你用docx存小说嗷,就算txt不爽的话,markdown他不香吗?)

媒体丢失

能做出这种神人操作的人,再有什么错误都显得那么的不足为奇。

批量处理那一万个文档的时候,出现了40个左右的文档处理失败,截取一段报错如下:

转换失败: “There is no item named ‘NULL’ in the archive”

in the archive显然是出现在docx的处理中,事实也证明报错的文件都是docx。根据报错应该是缺失了某种内容,但是缺失的内容的名字的NULL?这显然非常奇怪。

根据报错找到一两个文件打开,发现文件有个很显著的共同点:里面有配图,并且配图都报错显示丢失。实际检查docx压缩包,发现甚至没有媒体的那个文件夹。

按理说doc应该也是这种情况,对于出错的docx继续采用doc的处理方法应该就没问题。不过实际涉及文件并不多,于是我选择手动删除涉及到的图片内容。这一部分的处理并没有改动代码。

clear3doc.py

既然转换完成,那么同名的文档也没有存在的必要了。这个程序会遍历目标路径,寻找doc/docx文件,然后如果在相同目录找到了同名的txt文件那就删除这个文档。

主要是和上文程序配套使用,倒是没什么需要特别说明的。

LymoneTest/Tools/clear3doc.py at master · LymoneLM/LymoneTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def delete_doc_with_txt(folder_path):
"""删除存在同名txt文件的doc和docx文件(包括子目录)"""
extensions = ('.doc', '.docx')

for root, dirs, files in os.walk(folder_path):
for filename in files:
if filename.lower().endswith(extensions):
file_path = os.path.join(root, filename)
name = os.path.splitext(filename)[0]
txt_path = os.path.join(root, f"{name}.txt")

if os.path.exists(txt_path):
try:
os.remove(file_path)
print(f"已删除: {file_path} (存在同名txt文件)")
except Exception as e:
print(f"删除失败: {file_path} - {str(e)}")

pdf2txt.py

白菜开会,群英荟萃

里面甚至有PDF格式的文件,甚至只是某篇长文的其中几节,简直了!

话说夸克网盘为什么会有个PDF转txt的功能啊,还加到我右键菜单了,结果登录上才告诉我收费,而且需要上传才能处理,垃圾!

LymoneTest/Tools/pdf2txt.py at master · LymoneLM/LymoneTest

啥也不是,散会!

  1. 不要用Word存TM纯文本啊!
  2. docx确实比doc先进
  3. 睡前别配任何东西
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道