這是之前寫文獻綜述的時候,為了方便整理文獻,寫了一個小工具,可以將 SCI 文獻的 PDF 轉換為 Markdown 格式。這樣就可以直接在 Markdown 編輯器中編輯文獻,方便整理、翻譯和寫作。
具體的代碼如下:
import requests
import random
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
import scipdf
import string
from datetime import datetime
# 自定義異常,用於處理 GROBID 服務的異常情況
class GROBID_OFFLINE_EXCEPTION(Exception):
pass
class PDFToMarkdown:
def __init__(self, input_path, grobid_urls=None):
"""
初始化 PDFToMarkdown 實例。
Args:
input_path (str): 要處理的文件或文件夾路徑。
grobid_urls (list): 可選,GROBID 伺服器 URLs 列表。默認為預設的 URLs 列表。
"""
self.input_path = input_path
# 使用自定義 GROBID 伺服器,如果沒有則使用默認伺服器
self.grobid_urls = grobid_urls if grobid_urls is not None else [
"https://qingxu98-grobid.hf.space",
"https://qingxu98-grobid2.hf.space",
# ... (其他伺服器 URL)
"https://qingxu98-grobid8.hf.space",
]
def get_avail_grobid_url(self):
"""獲取可用的 GROBID 伺服器 URL"""
if not self.grobid_urls:
return None
while self.grobid_urls:
_grobid_url = random.choice(self.grobid_urls) # 隨機選擇一個 GROBID URL
if _grobid_url.endswith('/'):
_grobid_url = _grobid_url.rstrip('/')
try:
# 檢查伺服器是否在線
res = requests.get(f"{_grobid_url}/api/isalive", timeout=5)
if res.text == 'true':
return _grobid_url # 返回可用的 URL
except (requests.ConnectionError, requests.Timeout):
# 如果連接錯誤或超時,從列表中移除此 URL
self.grobid_urls.remove(_grobid_url)
return None # 如果沒有可用的伺服器,返回 None
@staticmethod
def dict_to_markdown(article_json):
"""將文章字典轉換為 Markdown 格式字符串"""
markdown_lines = []
markdown_lines.append(f"# {article_json.get('title', '無標題')} \n") # 標題
markdown_lines.append(f"> doi:{article_json.get('doi', '')} \n") # DOI
markdown_lines.append(f"+ authors\n{article_json.get('authors', ['無作者'])} \n") # 作者
markdown_lines.append(f"+ abstract\n{article_json.get('abstract', '無摘要')} \n") # 摘要
# 處理各章節內容
if 'sections' in article_json:
for section in article_json['sections']:
markdown_lines.append(f"+ {section['heading']}\n{section['text']}\n") # 章節標題與內容
return "\n".join(markdown_lines) # 返回合併後的 Markdown 字符串
@staticmethod
def save_markdown_file(filename, content):
"""將內容寫入到 Markdown 文件"""
with open(filename, 'w', encoding='utf-8') as f:
f.write(content) # 寫入內容到文件
def parse_pdf(self, pdf_path, grobid_url):
"""解析單個 PDF 文件,返回文章字典"""
if not os.path.isfile(pdf_path):
raise FileNotFoundError(f"指定路徑下沒有找到 PDF 文件: {pdf_path}") # 檢查文件是否存在
if grobid_url.endswith('/'):
grobid_url = grobid_url.rstrip('/')
try:
# 使用 GROBID 解析 PDF
return scipdf.parse_pdf_to_dict(pdf_path, grobid_url=grobid_url)
except GROBID_OFFLINE_EXCEPTION:
raise GROBID_OFFLINE_EXCEPTION("GROBID 服務不可用,檢查配置中的 GROBID_URL。")
except RuntimeError:
raise RuntimeError("解析 PDF 失敗,請檢查 PDF 是否損壞。")
def process_pdf_file(self, pdf_path, grobid_url):
"""處理單個 PDF 文件,返回 Markdown 內容"""
print(f"正在解析: {pdf_path}")
try:
pdf_article_dict = self.parse_pdf(pdf_path, grobid_url) # 解析 PDF 文件
return self.dict_to_markdown(pdf_article_dict) # 轉換為 Markdown
except Exception as e:
print(f"處理文件 {pdf_path} 時發生錯誤: {e}")
return None # 出現錯誤時返回 None
def process(self):
"""處理輸入文件或文件夾,並返回生成的 Markdown 文件路徑"""
markdown_contents = [] # 存儲所有 Markdown 內容
grobid_url = self.get_avail_grobid_url()
if grobid_url is None:
raise RuntimeError("沒有可用的 GROBID 服務,請檢查您的伺服器配置。")
# 根據輸入路徑判斷是文件還是文件夾
if os.path.isfile(self.input_path):
pdf_files = [self.input_path] # 單個文件
elif os.path.isdir(self.input_path):
# 收集文件夾中的所有 PDF 文件
pdf_files = [os.path.join(dirpath, filename)
for dirpath, _, filenames in os.walk(self.input_path)
for filename in filenames if filename.endswith('.pdf')]
else:
raise ValueError("輸入路徑既不是文件也不是文件夾。")
# 使用線程池並行處理 PDF 文件
with ThreadPoolExecutor(max_workers=5) as executor:
future_to_file = {executor.submit(self.process_pdf_file, pdf, grobid_url): pdf for pdf in pdf_files}
# 收集生成的 Markdown 內容
for future in as_completed(future_to_file):
result = future.result()
if result:
markdown_contents.append(result)
# 如果有有效的 Markdown 內容,將其保存到文件
if markdown_contents:
# 生成時間戳
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# 生成兩位隨機字母
random_suffix = ''.join(random.choices(string.ascii_lowercase, k=2))
output_filename = f"{timestamp}_{random_suffix}.md"
self.save_markdown_file(output_filename, "\n\n".join(markdown_contents)) # 合併並保存為 Markdown 文件
print(f"所有 Markdown 文件已合併並保存為 {output_filename}")
return output_filename # 返回生成文件路徑
else:
print("沒有有效的 Markdown 內容生成。")
return None
# 如果直接運行此腳本,提供使用示例
if __name__ == "__main__":
input_path = 'your_file_or_directory_path' # 替換為你的文件或目錄路徑
custom_grobid_urls = [
"https://your-custom-grobid-server.com",
"https://another-custom-grobid-server.com",
]
pdf_to_markdown = PDFToMarkdown(input_path, grobid_urls=custom_grobid_urls)
output_file = pdf_to_markdown.process() # 處理 PDF 文件並生成 Markdown 文件
print("生成的文件路徑:", output_file) # 輸出生成的文件路徑
注意需要指定安裝了以下 Python 庫:
pip install git+https://github.com/titipata/scipdf_parser
使用時,替換 input_path
為你的文件或目錄路徑,這個腳本是多線程的,可以處理文件夾中的所有 PDF 文件。如果你有自己的 GROBID 伺服器,可以將其添加到 custom_grobid_urls
列表中,否則會使用默認的 GROBID 伺服器。最終會生成一個 Markdown 文件,其中包含所有 PDF 文件的內容。
參考: