古人云书中自由黄金物,殊不知读书也是销金窟。手头研究搞了一段时间发现一时结束不了之后,心里松懈下来开始看各种怪东西,偶有和此前研究或者别的想做的题目相关的东西,偏偏也没有流式排版的文件可以直接划线阅读,只能识别 PDF 之后再导出划线和批注,稍作处理后又做成 CSV 导入给 Readwise 存档。整个流程麻烦与否且不论,最大的问题就是光学识别过后的 PDF 文件划出来的线并不干净。
什么叫不干净呢?会存在多余的空格、全半角标点不对、字词识别错误、翻页被截断、页边码乱入正文等等情况。快速浏览还行,但要是拿来存档肯定不行,引用的时候也要手动处理。当然也可以用程序处理,例如将文段中的空格批量移除、将标点全部替换成全角、将引号都换成直角——问题在于各种边缘情况很难处理,例如批量替换空格,那么中英文之间是否要再加空格,英文两个词之间的空格也可能被不小心消除,真要一个个把边缘情况处理掉,那真是写到精神崩溃。
前几天偶然想到直接让 GPT 处理,考虑上下文限制,加个分段应该就可以。再进一步,连 Python 也懒得写了,直接让 GPT-4 按要求写了一个,再手动指挥它 debug 两轮,出来的脚本虽说有点慢,但也就能用了。佛祖云:能用是福。测试一个简单文件能用后,我也就不再折腾它,直接开始给它上强度,让它帮我处理一本从繁体竖排 OCR 来的书:《推理小说这样读》。最终结果是它吞掉了一些 Markdown 标记,偶尔会搞不懂需求开始阐释文本或是胡编一些内容,但大体上没问题,文本出来非常干净,人肉稍微再处理一下就算是够用的程度。
随后我又想到应该再做个程序把校对后的文本直接处理成 CSV,此处用 GPT3.5 调试了几轮,均不得善终;最后换 GPT4 一次性成功,再提示它修改几个细节后就完全可用了。最后就是将俩脚本拼接一下,剪刀浆糊一糊弄,全自动小书僮就做好了,除了有点花钱没有别的什么缺点。配合 Readwise 每天回顾的时候还可以顺便再人肉校对一下,同时用官方插件也可以将整个记录同步到 Obsidian 存档方便整体检索,基本上算是没有添加额外负担的整理方法。且多端(Readwise、Obsidian 和 PDF)都保留了记录,满足数字仓鼠患者的需求。
也许会有人说我要求太低,毕竟这东西错误率还是有一些,但真实情况却是 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")