古人云书中自由黄金物,殊不知读书也是销金窟。手头研究搞了一段时间发现一时结束不了之后,心里松懈下来开始看各种怪东西,偶有和此前研究或者别的想做的题目相关的东西,偏偏也没有流式排版的文件可以直接划线阅读,只能识别 PDF 之后再导出划线和批注,稍作处理后又做成 CSV 导入给 Readwise 存档。整个流程麻烦与否且不论,最大的问题就是光学识别过后的 PDF 文件划出来的线并不干净。

什么叫不干净呢?会存在多余的空格、全半角标点不对、字词识别错误、翻页被截断、页边码乱入正文等等情况。快速浏览还行,但要是拿来存档肯定不行,引用的时候也要手动处理。当然也可以用程序处理,例如将文段中的空格批量移除、将标点全部替换成全角、将引号都换成直角——问题在于各种边缘情况很难处理,例如批量替换空格,那么中英文之间是否要再加空格,英文两个词之间的空格也可能被不小心消除,真要一个个把边缘情况处理掉,那真是写到精神崩溃。

前几天偶然想到直接让 GPT 处理,考虑上下文限制,加个分段应该就可以。再进一步,连 Python 也懒得写了,直接让 GPT-4 按要求写了一个,再手动指挥它 debug 两轮,出来的脚本虽说有点慢,但也就能用了。佛祖云:能用是福。测试一个简单文件能用后,我也就不再折腾它,直接开始给它上强度,让它帮我处理一本从繁体竖排 OCR 来的书:《推理小说这样读》。最终结果是它吞掉了一些 Markdown 标记,偶尔会搞不懂需求开始阐释文本或是胡编一些内容,但大体上没问题,文本出来非常干净,人肉稍微再处理一下就算是够用的程度。

随后我又想到应该再做个程序把校对后的文本直接处理成 CSV,此处用 GPT3.5 调试了几轮,均不得善终;最后换 GPT4 一次性成功,再提示它修改几个细节后就完全可用了。最后就是将俩脚本拼接一下,剪刀浆糊一糊弄,全自动小书僮就做好了,除了有点花钱没有别的什么缺点。配合 Readwise 每天回顾的时候还可以顺便再人肉校对一下,同时用官方插件也可以将整个记录同步到 Obsidian 存档方便整体检索,基本上算是没有添加额外负担的整理方法。且多端(Readwise、Obsidian 和 PDF)都保留了记录,满足数字仓鼠患者的需求。

导入 Readwise 后效果图
导入 Readwise 后效果图
同步到 Obsidian 的效果
同步到 Obsidian 的效果

也许会有人说我要求太低,毕竟这东西错误率还是有一些,但真实情况却是 GPT-4 能把校对这类任务做得好很多(至少乱来频率大大降低),只是我的钱包不足以支撑资本主义的无情压迫(哪儿打得起那么多API),因此也就放弃了把要用的书都批量转成流式排版的疯狂想法。类似的想法包括但不限于把所有的托克维尔或布迪厄二手研究 OCR 处理干净之后送去做预训练模型,获得伟大的社会理论机器人;以及把一串文献一次性喂给 GPT-4 32K 版本,用真金白银换文献综述。

总而言之,综合评定下,我目前学术生涯的主要突破口是要想办法先变成大富翁。毕竟没有谁能不爱一个写程序带上注释的 AI。我将脚本附在下面,需要可以自取,只需要在文件开头填入 API 密钥和文件路径即可。需要安装 Python 和 OpenAI 的库——怎么装问 GPT 就好,Mac 上直接用 Code Runner 这类编辑器加系统自带的环境也能跑。注意这里正则匹配的是 PDF Expert 的导出样式,如果使用其他 PDF 软件,需要修改正则匹配的部分。

import openai
import re
import csv

# 将你的API密钥放在这里
openai.api_key = "sk-********"

# 将你的源文件路径放在这里
file_path = "/********"

# 将你的输出文件路径放在这里
output_path = "/********"


# 设置每段的字符限制
char_limit = 1500

def read_file(file_path):
    with open(file_path, "r", encoding="utf-8") as file:
        content = file.read()
    return content

def split_text(text, limit):
    text_chunks = []
    start = 0
    end = limit
    
    while start < len(text):
        if end >= len(text):
            end = len(text)
        else:
            while text[end] not in {".", "!", "?", "\n"} and end > start:
                end -= 1
            end += 1
        text_chunks.append(text[start:end].strip())
        start = end
        end += limit
        
    return text_chunks

def proofread_text(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "你是一个能校对文本的AI助手。"},
            {"role": "user", "content": prompt},
        ],
        max_tokens=1800,
        n=1,
        stop=None,
        temperature=0.5,
    )
    return response.choices[0].message['content'].strip()

def main():
    content = read_file(file_path)
    text_chunks = split_text(content, char_limit)

    proofread_chunks = []
    prompt_prefix = "以下文本出自OCR,其中可能存在错别字、错误的标点和多余的空格等问题,请校对并输出正确的版本。请注意,不要随意修改原文。"
    
    for chunk in text_chunks:
        prompt = f"{prompt_prefix}\n\n{chunk}"
        proofread_chunks.append(proofread_text(prompt))
        print("正在校对新段落")

    proofread_content = "\n".join(proofread_chunks)

    # 将校对后的文本输出到一个新的文件
    with open("./proofread_text.txt", "w", encoding="utf-8") as output_file:
        output_file.write(proofread_content)

    print("校对完成,结果已保存到proofread_text.txt")

if __name__ == "__main__":
    main()

# 读取纯文本文件
with open("./proofread_text.txt", "r", encoding="utf-8") as input_file:
    raw_data = input_file.readlines()
    
# 准备写入 CSV 文件
with open(output_path, "w", newline="", encoding="utf-8") as output_file:
    writer = csv.writer(output_file)
    writer.writerow(["Highlight", "Title", "Author", "URL", "Note", "Location", "Date"])
    
    # 初始化变量
    title, highlight, note, location = "", "", "", ""
    title_found = False
    
    for line in raw_data:
        # 搜索书名
        if line.startswith("#") and not title_found:
            title = line.strip()[1:].strip()
            title_found = True
        # 搜索高亮
        elif "*高亮" in line:
            highlight_match = re.search(r"\[页面\s(\d+)\]:\s*(.+)", line)
            if highlight_match:
                location = highlight_match.group(1)
                highlight = highlight_match.group(2).lstrip('*').strip()
        # 搜索笔记
        elif "*和笔记" in line:
            note_match = re.search(r"\[页面\s(\d+)\]:\s*(.+)", line)
            if note_match:
                note = note_match.group(2).lstrip('*').strip()
        # 存储高亮和笔记到 CSV 文件
        elif line.strip() == "":
            if highlight:
                writer.writerow([highlight, title, "", "", note, location, ""])
                highlight, note, location = "", "", ""
                
    # 如果有未存储的高亮和笔记,将其存储到 CSV 文件
    if highlight:
        writer.writerow([highlight, title, "", "", note, location, ""])
    
    print("收工啦,CSV 已经保存到 output.csv")