經常看到公眾號推什么“學姐一年發五篇SCI,原來是靠它”之類的文章,點進去全是Python的安利,然而跟著廣告進去也不一定能學會。正好時值學校要求寫文獻閱讀報告,然而至少一萬字的報告實在是讓人望而卻步,于是想到了自己使用Python去實現自動讀取英文文獻,并翻譯生成綜述(當然,只適用于湊字數的情況,如果需要寫綜述,還是建議自己總結歸納)。這里分享給大家,希望大家都能學廢(不是)
如果不想了解技術細節,只想直接拿來用,可以直接跳過代碼編寫部分,直達最后代碼使用部分。
本代碼免費開源,已發布release,可在GitHub上進行下載
,如果你覺得好用,希望能夠給我一個贊(能GitHub給一顆Star更好),也歡迎去github發表意見建議。
代碼實現效果如下:
開發環境
- Windows 10
- Sublime Text 3
- Python 3.7
- Pdfminer、requests
- 有道翻譯API
事前準備
接口申請
本代碼使用了有道翻譯的API,因此,如需使用,需要去有道翻譯接口官方申請APP Key和Secret key,直接按照其官方教程申請即可,后續需要在代碼中配置。接口申請完全免費,初始會送100元的面值,用完需要續費,不過一般情況100元可以用很久了。
安裝pdfminer和requests
同時按下鍵盤win
+R
,在彈出的輸入框內輸入cmd
按下回車,在彈出的黑框中進行下面操作。
因為我使用的是python3,因此輸入以下命令安裝:
pip install pdfminer3k
pip install requests
需求分析
這里分析了本代碼實現的關鍵點:
- 文獻是已經下載下來的pdf文件
- 文獻中,需要提取的部分主要為:
- 標題
- 作者
- 摘要
- 結論
因此,本代碼的思路是讀取本地文件夾內的pdf文件,然后讀取并識別出其關鍵元素,調用有道翻譯的API進行翻譯,并進行有機組合,寫入TXT文件中。
代碼編寫
讀取pdf文件
依次讀取文件夾內的文件,如果后綴為pdf,則寫入文件元祖:
def getFileName(filepath):
file_list = []
for root,dirs,files in os.walk(filepath):
for filespath in files:
if 'pdf' in filespath.split('.')[1]:
file_list.append(os.path.join(root,filespath))
return file_list
讀取文件內容并提取標題、作者、摘要和結論
def parse(DataIO, save_path, appKey, appSecret):
#用文件對象創建一個PDF文檔分析器
parser = PDFParser(DataIO)
#創建一個PDF文檔
doc = PDFDocument()
#分析器和文檔相互連接
parser.set_document(doc)
doc.set_parser(parser)
#提供初始化密碼,沒有默認為空
doc.initialize()
#檢查文檔是否可以轉成TXT,如果不可以就忽略
if not doc.is_extractable:
raise PDFTextExtractionNotAllowed
else:
#創建PDF資源管理器,來管理共享資源
rsrcmagr = PDFResourceManager()
#創建一個PDF設備對象
laparams = LAParams()
#將資源管理器和設備對象聚合
device = PDFPageAggregator(rsrcmagr, laparams=laparams)
#創建一個PDF解釋器對象
interpreter = PDFPageInterpreter(rsrcmagr, device)
last_para = '' # 記錄上一段文本
count = 0 # 對文本塊進行計數,方便后續查找標題和作者
author = '' # 記錄作者
ab_count = 0 # 記錄已識別的摘要的數量,避免提取文中的abstract
fanyi = YouDaoFanyi(appKey, appSecret)
#循環遍歷列表,每次處理一個page內容
#doc.get_pages()獲取page列表
for page in doc.get_pages():
interpreter.process_page(page)
#接收該頁面的LTPage對象
layout = device.get_result()
#這里的layout是一個LTPage對象 里面存放著page解析出來的各種對象
#一般包括LTTextBox,LTFigure,LTImage,LTTextBoxHorizontal等等一些對像
#想要獲取文本就得獲取對象的text屬性
for x in layout:
try:
if(isinstance(x, LTTextBoxHorizontal)):
with open('%s' % (save_path), 'a', encoding='utf-8') as f:
result = x.get_text() # 每塊的內容
# print(result)
# 提取標題
if count==0:
# 如果是researchgate的文章,直接翻頁
if re.findall('^see discussions', result.lower())!=[]:
break
# 如果第一行是各種頁眉等干擾信息,直接略過
if re.findall('(^[0-9])|(^(research )?article)|(unclassified)|(www.)|(accepted (from|manuscript))|(proceedings of)|(vol.)|(volume \d)|(https?://)|(^ieee)|(sciencedirect)|(\d{4}\)$)|(\d{1,4} – \d{1,4}$)|(cid:)',re.split('\s+$',result.lower())[0])!=[] or '':
count -= 1
else:
# 將結果寫入TXT
f.write('\n'+result.replace('\n', '')+'\n')
# 提取作者
elif count==1:
# 只取第一作者
author = result.split('\n')[0].split(',')[0].split(' and ')[0]
author = generate_author(author)
print('author '+ author)
# 去掉pdf文件讀取的各種換行符
result = result.replace('\n', '')
try:
# 轉為小寫,去掉空格,方便正則識別
last_para = last_para.lower().replace(' ', '')
# print(result)
# 匹配Abstract和摘要內容分開的情況
if re.findall('abstract$', last_para)!=[]:
# 去掉關鍵詞
oringin_result = re.split('(K|k)(eyword|EYWORD)[sS]?',result)[0]
# 翻譯并轉換人稱
trans_result = fanyi.translate(oringin_result).replace('我們', '他們')
# print(result)
# 組織語言寫入TXT
write_cont = author + '等人提出:' + trans_result + '\n'
ab_count += 1
f.write(write_cont)
# 匹配Abstract和摘要內容位于同一行的情況
elif re.findall('^abstract', result.lower().replace(' ', ''))!=[] and re.findall('abstract$', result.lower().replace(' ', ''))==[]:
# 確保摘要只匹配一次,不匹配文中的Abstract字眼
if ab_count==0:
# 去掉Abstract字眼及其后續的符號
oringin_result = re.sub('(a|A)(bstract|BSTRACT)[- —.]?','', result)
# 去掉關鍵詞
oringin_result = re.split('(K|k)(eyword|EYWORD)[sS]?',oringin_result)[0]
# 翻譯并轉換人稱
trans_result = fanyi.translate(oringin_result).replace('我們', '他們')
# print(result)
# 組織語言寫入TXT
write_cont = author + '等人提出:' + trans_result + '\n'
ab_count += 1
f.write(write_cont)
# 匹配結論
elif re.findall('(^(i|v|x|\d)*\.?conclusions?)|(conclusions?$)', last_para)!=[]:
# 避免因圖表在標題下方導致的識別錯誤
if re.findall('^fig', result.lower()):
continue
# 翻譯
trans_result = fanyi.translate(result)
# print(result)
# 轉換人稱
write_cont = trans_result.replace('我們', '他們') + '\n'
# 寫入TXT
f.write(write_cont)
except Exception as e:
print(e)
last_para = result
count += 1
except Exception as e:
print('out'+str(e))
else:
continue
with open('%s' % (save_path), 'a', encoding='utf-8') as f:
f.write('\n')
按照引用的格式生成作者信息
def generate_author(author):
# 過濾掉作者名后面的各種符號,并生成引用的格式
# print(author)
author = re.sub('by |[\s\d\*?\/@?\(\&\)]+$', '', author)
author_list = re.split('\s+',author)
author_str = author_list[len(author_list)-1]
for i in range(0,len(author_list)-1):
author_str = author_str + ' ' + author_list[i][0]
return author_str
翻譯接口
其實直接抄有道官網文檔就可以了,這里在其基礎上做了更改:
class YouDaoFanyi:
def __init__(self, appKey, appSecret):
self.YOUDAO_URL = 'https://openapi.youdao.com/api/'
self.APP_KEY = appKey # 應用id
self.APP_SECRET = appSecret # 應用密鑰
self.langFrom = 'en' # 翻譯前文字語言,auto為自動檢查
self.langTo = 'zh-CHS' # 翻譯后文字語言,auto為自動檢查
self.vocabId = "您的用戶詞表ID" #非必填項,可以不寫
def encrypt(self,signStr):
hash_algorithm = hashlib.sha256()
hash_algorithm.update(signStr.encode('utf-8'))
return hash_algorithm.hexdigest()
def truncate(self,q):
if q is None:
return None
size = len(q)
return q if size <= 20 else q[0:10] + str(size) + q[size - 10:size]
def do_request(self,data):
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
return requests.post(self.YOUDAO_URL, data=data, headers=headers)
def translate(self,q):
data = {}
data['from'] = self.langFrom
data['to'] = self.langTo
data['signType'] = 'v3'
curtime = str(int(time.time()))
data['curtime'] = curtime
salt = str(uuid.uuid1())
signStr = self.APP_KEY + self.truncate(q) + salt + curtime + self.APP_SECRET
sign = self.encrypt(signStr)
data['appKey'] = self.APP_KEY
data['q'] = q
data['salt'] = salt
data['sign'] = sign
data['vocabId'] = self.vocabId
response = self.do_request(data)
contentType = response.headers['Content-Type']
result = json.loads(response.content.decode('utf-8'))['translation'][0]
print(result)
return result
最后,書寫主函數進行調用:
if __name__ == '__main__':
#解析本地PDF文本,保存到本地TXT
folder = '文件夾路徑' # 需要讀取pdf的文件夾的路徑,注意為絕對路徑,如:E:/論文
write_txt_file = 'result.txt' # 保存結果的文件
appKey = '應用ID' # 應用id
appSecret = '應用秘鑰' # 應用密鑰
success_count = 0 # 統計成功的次數
fail_count = 0 #統計失敗的次數
# 單次調用,供開發測試
# pdf_filename = folder+'文件名'
# with open(pdf_filename,'rb') as pdf_html:
# try:
# parse(pdf_html, folder + write_txt_file, appKey, appSecret)
# success_count+=1
# except Exception as e:
# print(pdf_filename)
# fail_count+=1
pdf_list = getFileName(folder)
# 依次讀取元祖,獲取pdf文件位置
for file_item in pdf_list:
with open(file_item,'rb') as pdf_html:
try:
print(file_item)
parse(pdf_html, folder + write_txt_file, appKey, appSecret)
success_count+=1
except Exception as e:
# 文件讀取或翻譯失敗則將錯誤信息寫入TXT
print('文檔讀取失敗:' + str(e) +',路徑為:' + file_item)
with open('%s' % (folder + write_txt_file), 'a', encoding='utf-8') as f:
f.write('\n'+'文檔讀取失敗:' + str(e) +',路徑為:' + file_item + '\n')
fail_count+=1
print('共讀取pdf文件' + str(success_count+fail_count) + '個,其中成功讀取并翻譯' + str(success_count) + '個,失敗' + str(fail_count) + '個')
至此,代碼編寫完畢
使用
本工具已發布release,可在GitHub上進行下載,直接雙擊使用即可,后面的可以不用看了。
也可下載源碼使用,代碼可在Github上下載
配置代碼
更改代碼主函數的配置變量(其中的應用ID和應用秘鑰需要事先申請,見上文事前準備一節):
if __name__ == '__main__':
#解析本地PDF文本,保存到本地TXT
folder = '文件夾路徑' # 需要讀取pdf的文件夾的路徑,注意為絕對路徑,如:E:/論文/
write_txt_file = 'result.txt' # 保存結果的文件
appKey = '應用ID' # 應用id
appSecret = '應用秘鑰' # 應用密鑰
運行代碼
運行代碼之前,需要安裝python3以及按照事前準備中的描述安裝pdfminer和requests
在代碼所在的根目錄下的命令行中輸入以下命令即可:
python pdfprocessor.py
運行結果
僅花了38秒的時間,就提取并翻譯完成了14個pdf文件,翻譯生成的字數合計6812個字:
試了一下45個文件,花了大概兩分鐘,生成了一萬多字
最后看一下翻譯結果對比: