深入学习Python解析并解密PDF文件内容的方法
前面学习了解析PDF文档,并写入文档的知识,那篇文章的名字为深入学习Python解析并读取PDF文件内容的方法。
链接如下:https://www.cnblogs.com/wj-1314/p/9429816.html
但是最近出现了一个新问题,就是上面使用pdfminer这个库只能解析正常的PDF内容,然而在实际情况中,公司的一些文档可能是加密的,那么如何处理加密的PDF文件,就是本文学习的重点。
在网上查找资料,发现pypdf2可以实现对pdf文件进行加密,解密,所以就学习了一下这个库,并留下笔记。
首先说明pypdf2是Python3版本的,在之前的Python2版本有一个对应的pypdf库,但是本文下载了pypdf2这个库,在Python2 运行时没有报错的。
注意:所有修改操作均无法再原文件中操作,只能将修改的结果写入新文件中。
一:PyPDF2介绍
PyPDF2是源自pyPdf项目的纯python PDF工具包。它目前由Phaseit,Inc。维护。PyPDF2可以从PDF文件中提取数据,或者操纵现有的PDF来生成新文件。PyPDF2与Python版本2.6,2.7和3.2 - 3.5兼容。
作为PDF工具包构建的Pure-Python库。它能够:
- 提取文档信息(标题,作者,......)
- 逐页拆分文档
- 逐页合并文档
- 裁剪页面
- 将多个页面合并为一个页面
- 加密和解密PDF文件
通过Pure-Python,它应该在任何Python平台上运行,而不依赖于外部库。它也可以完全在StringIO对象而不是文件流上工作,允许在内存中进行PDF操作。因此,它是管理或操作PDF的网站的有用工具。
而本文主要学习加密解密PDF文件。
二:PyPDF2安装
2.1 下载
在https://pypi.org/project/PyPDF2/ 中搜索PyPDF2 1.26.0可以安装包。
2.2 在Linux安装压缩包命令如下:
cd /data && tar -xvf PyPDF2-1.26.0.tar.gz cd PyPDF2-1.26.0 python setup.py install
2.3 直接安装
pip install pypdf2
2.4 PyPDF的官方文档:https://pythonhosted.org/PyPDF2/
三:PyPDF 的使用目的
首先 我这里有一个加密的PDF文件:
那么我使用上一篇文章的代码(如下):
#coding:utf-8
import importlib
import sys
import time importlib.reload(sys)
time1 = time.time() from pdfminer.pdfparser import PDFParser, PDFDocument
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTTextBoxHorizontal, LAParams
from pdfminer.pdfinterp import PDFTextExtractionNotAllowed text_path = r'5b931164edc09a226b3a12c4.pdf' def parse():
'''解析PDF文本,并保存到TXT文件中'''
fp = open(text_path, 'rb')
# 用文件对象创建一个PDF文档分析器
parser = PDFParser(fp)
# 创建一个PDF文档
doc = PDFDocument()
# 连接分析器,与文档对象
parser.set_document(doc)
doc.set_parser(parser) # 提供初始化密码,如果没有密码,就创建一个空的字符串
doc.initialize() # 检测文档是否提供txt转换,不提供就忽略
if not doc.is_extractable:
raise PDFTextExtractionNotAllowed
else:
# 创建PDF,资源管理器,来共享资源
rsrcmgr = PDFResourceManager()
# 创建一个PDF设备对象
laparams = LAParams()
device = PDFPageAggregator(rsrcmgr, laparams=laparams)
# 创建一个PDF解释其对象
interpreter = PDFPageInterpreter(rsrcmgr, device) # 循环遍历列表,每次处理一个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:
if (isinstance(x, LTTextBoxHorizontal)):
with open(r'2.txt', 'a') as f:
results = x.get_text()
print(results)
f.write(results + "\n") if __name__ == '__main__':
parse()
time2 = time.time()
print("总共消耗时间为:", time2 - time1)
解析的时候,会主动触发异常(如下):
那么,打开文件,我们会发现,实际情况是这样的:
既然文件已经加密,那么正常渠道解析,肯定会触发异常,所以此时的重中之重就是解密PDF文件,然后再去解析即可。
如何解密呢? 话不多说,直接看代码。
如果不知道密码,最好设置为空,这样的话 大多数就可以解析,代码如下:
# coding:utf-8
import os
from PyPDF2 import PdfFileReader
from PyPDF2 import PdfFileWriter def get_reader(filename, password):
try:
old_file = open(filename, 'rb')
print('run jiemi1')
except Exception as err:
print('文件打开失败!' + str(err))
return None # 创建读实例
pdf_reader = PdfFileReader(old_file, strict=False) # 解密操作
if pdf_reader.isEncrypted:
if password is None:
print('%s文件被加密,需要密码!' % filename)
return None
else:
if pdf_reader.decrypt(password) != 1:
print('%s密码不正确!' % filename)
return None
if old_file in locals():
old_file.close()
return pdf_reader def decrypt_pdf(filename, password, decrypted_filename=None):
"""
将加密的文件及逆行解密,并生成一个无需密码pdf文件
:param filename: 原先加密的pdf文件
:param password: 对应的密码
:param decrypted_filename: 解密之后的文件名
:return:
"""
# 生成一个Reader和Writer
print('run jiemi')
pdf_reader = get_reader(filename, password)
if pdf_reader is None:
return
if not pdf_reader.isEncrypted:
print('文件没有被加密,无需操作!')
return
pdf_writer = PdfFileWriter() pdf_writer.appendPagesFromReader(pdf_reader) if decrypted_filename is None:
decrypted_filename = "".join(filename.split('.')[:-1]) + '_' + 'decrypted' + '.pdf' # 写入新文件
pdf_writer.write(open(decrypted_filename, 'wb')) decrypt_pdf(r'5b931164edc09a226b3a12c4.pdf', '')
运行结果如下:
新生成的文件如下:
打开是这样的:
所以 ,这样的话 就可以打开了,也可以解析了,下面继续使用PDF解析文件解析,代码是上面的,结果如下:
解析成功,那么会保存为txt格式。
但是这里要注意,我给解密的代码,把密码设置为abc,如下:
那么会触发异常,代码结果表示如下:
代码如下:
# coding:utf-8
import os
from PyPDF2 import PdfFileReader
from PyPDF2 import PdfFileWriter def get_reader(filename, password):
try:
old_file = open(filename, 'rb')
print('run jiemi1')
except Exception as err:
print('文件打开失败!' + str(err))
return None # 创建读实例
pdf_reader = PdfFileReader(old_file, strict=False) # 解密操作
if pdf_reader.isEncrypted:
if password is None:
print('%s文件被加密,需要密码!' % filename)
return None
else:
if pdf_reader.decrypt(password) != 1:
print('%s密码不正确!' % filename)
return None
if old_file in locals():
old_file.close()
return pdf_reader def decrypt_pdf(filename, password, decrypted_filename=None):
"""
将加密的文件及逆行解密,并生成一个无需密码pdf文件
:param filename: 原先加密的pdf文件
:param password: 对应的密码
:param decrypted_filename: 解密之后的文件名
:return:
"""
# 生成一个Reader和Writer
print('run jiemi')
pdf_reader = get_reader(filename, password)
if pdf_reader is None:
return
if not pdf_reader.isEncrypted:
print('文件没有被加密,无需操作!')
return
pdf_writer = PdfFileWriter() pdf_writer.appendPagesFromReader(pdf_reader) if decrypted_filename is None:
decrypted_filename = "".join(filename.split('.')[:-1]) + '_' + 'decrypted' + '.pdf' # 写入新文件
pdf_writer.write(open(decrypted_filename, 'wb')) decrypt_pdf(r'5b931164edc09a226b3a12c4.pdf', 'abc')
四:PyPDF2的理论介绍
PyPDF2 包含了 PdfFileReader PdfFileMerger PageObject PdfFileWriter 四个常用的主要 Class。
具体分析:
PyPDF2 将读与写分成两个类来操作:
from PyPDF2 import PdfFileWriter, PdfFileReader writer = PdfFileWriter()
reader = PdfFileReader(open("document1.pdf", "rb"))
官方实例:
from PyPDF2 import PdfFileWriter, PdfFileReader output = PdfFileWriter()
input1 = PdfFileReader(open("document1.pdf", "rb")) # print how many pages input1 has:
print "document1.pdf has %d pages." % input1.getNumPages() # add page 1 from input1 to output document, unchanged
output.addPage(input1.getPage(0)) # add page 2 from input1, but rotated clockwise 90 degrees
output.addPage(input1.getPage(1).rotateClockwise(90)) # add page 3 from input1, rotated the other way:
output.addPage(input1.getPage(2).rotateCounterClockwise(90))
# alt: output.addPage(input1.getPage(2).rotateClockwise(270)) # add page 4 from input1, but first add a watermark from another PDF:
page4 = input1.getPage(3)
watermark = PdfFileReader(open("watermark.pdf", "rb"))
page4.mergePage(watermark.getPage(0))
output.addPage(page4) # add page 5 from input1, but crop it to half size:
page5 = input1.getPage(4)
page5.mediaBox.upperRight = (
page5.mediaBox.getUpperRight_x() / 2,
page5.mediaBox.getUpperRight_y() / 2
)
output.addPage(page5) # add some Javascript to launch the print window on opening this PDF.
# the password dialog may prevent the print dialog from being shown,
# comment the the encription lines, if that's the case, to try this out
output.addJS("this.print({bUI:true,bSilent:false,bShrinkToFit:true});") # encrypt your new PDF and add a password
password = "secret"
output.encrypt(password) # finally, write "output" to document-output.pdf
outputStream = file("PyPDF2-output.pdf", "wb")
output.write(outputStream)
五 :PdfFileReader类
class PyPDF2.PdfFileReader(stream,strict = True,warndest = None,
overwriteWarnings = True )
初始化PdfFileReader对象。此操作可能需要一些时间,因为PDF流的交叉引用表被读入内存。
参数:
stream - File对象或支持类似于File对象的标准读取和搜索方法的对象。也可以是表示PDF文件路径的字符串。
- strict(bool) - 确定是否应该警告用户所有问题并且还会导致一些可纠正的问题致命。默认为
True
。 - warndest - 记录警告的目的地(默认为
sys.stderr
)。 - overwriteWarnings(bool) - 确定是否
warnings.py
使用自定义实现覆盖Python的 模块(默认为True
)。
decrypt(密码)
使用带有PDF标准加密处理程序的加密/安全PDF文件时,此功能将允许解密文件。它根据文档的用户密码和所有者密码检查给定的密码,如果密码正确,则存储生成的解密密钥。
哪个密码匹配无关紧要。两个密码都提供了正确的解密密钥,允许文档与此库一起使用。
参数:password(str) 要匹配的密码
返回:0
如果密码失败,1
密码是否与用户密码匹配,密码2
是否与所有者密码匹配。
返回类型: INT
引发NotImplementedError:如果文档使用不受支持的加密方法。
documentInfo
访问给定Destination对象的页码
getDestinationPageNumber(destination)
检索PDF文件的文档信息字典(如果存在)。请注意,某些PDF文件使用元数据流而不是docinfo词典,此功能不会访问这些元数据流。
返回:页码或者如果找不到页面的话 则为-1
返回类型:INT
getDocumentInfo()
检索PDF文件的文档信息字典(如果存在)。请注意,某些PDF文件使用元数据流而不是docinfo词典,此功能不会访问这些元数据流。
返回:该PDF文件的文档信息
返回类型:DocumentInformation
或者None
如果不存在。
getFields
(tree = None,retval = None,fileobj = None )
如果此PDF包括交互式表单字段,则提取字段数据,该树和retval的参数是递归使用。
参数:fileobj 用于在找到的所有交互式表单字段上写入报告的文件对象(通常是文本文件)
返回:一个字典,其中每个键是一个字段名称,每个值都是一个个Field
对象。默认情况下,映射名称用于键。
返回类型:dict 或者None无法找到表单数据。
getFormTextFields()
使用文本数据从文档中检索表单域(输入,下拉列表)
getNameDestinations(tree=None,retval=None)
检索文档中存在的指定目标
返回:将名称映射到的字典 Destinations
。
返回类型:字典
getNumPages()
计算此PDF文件中的页面。
返回:页面
返回类型:INT
引发PDFReadError:如果文件已加密且限制阻止此操作。
getOutlines(node=None,outlines=None)
检查文档中存在的文档大纲。
返回:一个PageObject
实例。
返回类型:PageObject
getPageLayout()
获取页面布局,有关setPageLayout()
有效布局的说明,请参阅参考资料。
返回:目前正在使用的页面布局
返回类型:str None如果没有指定。
getPageMode()
获取页面布局,有关setPageMode()
有效模式的说明,请参阅。
返回:目前正在使用的页面模式。
返回类型:str
,None
如果没有指定。
getPageNumber()
检索给定PageObject的页面。
参数:page(PageObject) - 获取页码的页面。应该是一个实例PageObject
返回:页码或如果找不到页面,则为-1
返回类型:INT
getXmpMetadata()
从PDF文档跟目录中检索XMP(可扩展元数据平台)数据。
返回:XmpInformation
可用于从文档访问XMP元数据的实例
返回类型:XmpInformation
或者 None
如果在文档根目录中未找到元数据。
isEncrypted
只读布尔属性,显示此PDF文件是否已加密。请注意,即使decrypt()
调用该方法,此属性(如果为true)仍将保持为true 。
namedDestinations
访问该getNamedDestinations()
函数的只读属性 。
numPages
访问该getNumPages()
函数的只读属性 。
outlines
- 只读属性访问
getOutlines()
功能。
pageLayout
访问该getPageLayout()
方法的只读属性 。
pageMode
访问该getPageMode()
方法的只读属性 。
pages
只读属性,它根据getNumPages()
和 getPage()
方法模拟列表 。
xmpMetadata
访问该getXmpMetadata()
函数的只读属性 。
PDF读取操作:
# encoding:utf-8
from PyPDF2 import PdfFileReader, PdfFileWriter readFile = 'C:/ learn.pdf'
# 获取 PdfFileReader 对象
pdfFileReader = PdfFileReader(readFile)
# 或者这个方式:pdfFileReader = PdfFileReader(open(readFile, 'rb'))
# 获取 PDF 文件的文档信息
documentInfo = pdfFileReader.getDocumentInfo()
print('documentInfo = %s' % documentInfo)
# 获取页面布局
pageLayout = pdfFileReader.getPageLayout()
print('pageLayout = %s ' % pageLayout) # 获取页模式
pageMode = pdfFileReader.getPageMode()
print('pageMode = %s' % pageMode) xmpMetadata = pdfFileReader.getXmpMetadata()
print('xmpMetadata = %s ' % xmpMetadata) # 获取 pdf 文件页数
pageCount = pdfFileReader.getNumPages() print('pageCount = %s' % pageCount)
for index in range(0, pageCount):
# 返回指定页编号的 pageObject
pageObj = pdfFileReader.getPage(index)
print('index = %d , pageObj = %s' % (index, type(pageObj)))
# <class 'PyPDF2.pdf.PageObject'>
# 获取 pageObject 在 PDF 文档中处于的页码
pageNumber = pdfFileReader.getPageNumber(pageObj)
print('pageNumber = %s ' % pageNumber)
六: PDFFileWriter类
这个类支持PDF文件,给出其他类生成的页面。
属性和方法 | 描述 |
---|---|
addAttachment(fname,fdata) | 在 PDF 中嵌入文件 |
addBlankPage(width= None,height=None) | 追加一个空白页面到这个 PDF 文件并返回它 |
addBookmark(title,pagenum,parent=None, color=None,bold=False,italic=False,fit=’/fit,*args’) |
|
addJS(javascript) | 添加将在打开此 PDF 是启动的 javascript |
addLink(pagenum,pagedest,rect,border=None,fit=’/fit’,*args) | 从一个矩形区域添加一个内部链接到指定的页面 |
addPage(page) | 添加一个页面到这个PDF 文件,该页面通常从 PdfFileReader 实例获取 |
getNumpages() | 页数 |
getPage(pageNumber) | 从这个 PDF 文件中检索一个编号的页面 |
insertBlankPage(width=None,height=None,index=0) | 插入一个空白页面到这个 PDF 文件并返回它,如果没有指定页面大小,就使用最后一页的大小 |
insertPage(page,index=0) | 在这个 PDF 文件中插入一个页面,该页面通常从 PdfFileReader 实例获取 |
removeLinks() | 从次数出中删除连接盒注释 |
removeText(ignoreByteStringObject = False) | 从这个输出中删除图像 |
write(stream) | 将添加到此对象的页面集合写入 PDF 文件 |
PDF写入操作
def addBlankpage():
readFile = 'C:/study.pdf'
outFile = 'C:/copy.pdf'
pdfFileWriter = PdfFileWriter() # 获取 PdfFileReader 对象
pdfFileReader = PdfFileReader(readFile) # 或者这个方式:pdfFileReader = PdfFileReader(open(readFile, 'rb'))
numPages = pdfFileReader.getNumPages() for index in range(0, numPages):
pageObj = pdfFileReader.getPage(index)
pdfFileWriter.addPage(pageObj) # 根据每页返回的 PageObject,写入到文件
pdfFileWriter.write(open(outFile, 'wb')) pdfFileWriter.addBlankPage() # 在文件的最后一页写入一个空白页,保存至文件中
pdfFileWriter.write(open(outFile,'wb'))
结果是:在写入的 copy.pdf 文档的最后最后一页写入了一个空白页。
分割文档(取第五页之后的页面)
def splitPdf():
readFile = 'C:/learn.pdf'
outFile = 'C://copy.pdf'
pdfFileWriter = PdfFileWriter() # 获取 PdfFileReader 对象
pdfFileReader = PdfFileReader(readFile)
# 或者这个方式:pdfFileReader = PdfFileReader(open(readFile, 'rb'))
# 文档总页数
numPages = pdfFileReader.getNumPages() if numPages > 5:
# 从第五页之后的页面,输出到一个新的文件中,即分割文档
for index in range(5, numPages):
pageObj = pdfFileReader.getPage(index)
pdfFileWriter.addPage(pageObj)
# 添加完每页,再一起保存至文件中
pdfFileWriter.write(open(outFile, 'wb'))
合并文档
def mergePdf(inFileList, outFile):
'''
合并文档
:param inFileList: 要合并的文档的 list
:param outFile: 合并后的输出文件
:return:
'''
pdfFileWriter = PdfFileWriter()
for inFile in inFileList:
# 依次循环打开要合并文件
pdfReader = PdfFileReader(open(inFile, 'rb'))
numPages = pdfReader.getNumPages()
for index in range(0, numPages):
pageObj = pdfReader.getPage(index)
pdfFileWriter.addPage(pageObj) # 最后,统一写入到输出文件中
pdfFileWriter.write(open(outFile, 'wb'))
PageObject类
class PyPDF2.pdf.PageObject(pdf = None,indirectRef = None )
此类表示 PDF 文件中的单个页面,通常这个对象是通过访问 PdfFileReader 对象的 getPage() 方法来得到的,也可以使用 createBlankPage() 静态方法创建一个空的页面。
参数:
- pdf : 页面所属的 PDF 文件。
- indirectRef:将源对象的原始间接引用存储在其源 PDF 中。
PageObject 对象的属性和方法
属性或方法 | 描述 |
---|---|
static createBlankPage(pdf=None,width=None,height=None) | 返回一个新的空白页面 |
extractText() | 找到所有文本绘图命令,按照他们在内容流中提供的顺序,并提取文本 |
getContents() | 访问页面内容,返回 Contents 对象或 None |
rotateClockwise(angle) | 顺时针旋转 90 度 |
scale(sx,sy) | 通过向其内容应用转换矩阵并更新页面大小 |
粗略读取PDF文本内容
def getPdfContent(filename):
pdf = PdfFileReader(open(filename, "rb"))
content = ""
for i in range(0, pdf.getNumPages()):
pageObj = pdf.getPage(i) extractedText = pageObj.extractText()
content += extractedText + "\n"
# return content.encode("ascii", "ignore")
return content
深入学习Python解析并解密PDF文件内容的方法的更多相关文章
- 深入学习python解析并读取PDF文件内容的方法
这篇文章主要学习了python解析并读取PDF文件内容的方法,包括对学习库的应用,python2.7和python3.6中python解析PDF文件内容库的更新,包括对pdfminer库的详细解释和应 ...
- python解析VOC的xml文件并转成自己需要的txt格式
在进行神经网络训练的时候,自己标注的数据集往往会有数据量不够大以及代表性不强等问题,因此我们会采用开源数据集作为训练,开源数据集往往具有特定的格式,如果我们想将开源数据集为我们所用的话,就需要对其格式 ...
- 利用Python将多个PDF文件合并
from PyPDF2 import PdfFileMerger import os files = os.listdir()#列出目录中的所有文件 merger = PdfFileMerger() ...
- robotframework 测试工具添加PDF文件内容匹配插件
robotframework 这个需要了解的请度娘.本文实现的是一个小功能.大体分为如下几个步骤 1)给定一个pdf文件. 2)读取pdf文件内容,并解析为文本内容. 3)通过给定的内容,比对pdf ...
- 如何修改PDF文件内容,PDF怎么添加背景
很多的情况下,大家都会遇到PDF文件,不管是在学习中还是在工作中,对于PDF文件,文件的修改编辑是需要用到PDF编辑软件的,在编辑文件的时候,发现文件的页面是有背景颜色的,又该如何修改背景颜色呢,不会 ...
- pdf文件内容查看器 -- 采用wpf开发
前言 pdf是一种应用非常广的版式文档格式,已成为事实上的国际标准.关于pdf格式的文章汗牛充栋,本文也是关于pdf格式的文章,但是本文不是纸上谈兵:本人这几周一直研究pdf格式内容,不但对pfd格式 ...
- 怎么编辑PDF文件内容,PDF文件编辑方法
怎样编辑PDF文件内容?这是一个常常困扰我们的问题,工作当中我们经常会收到PDF格式的文件,但有时的文件内容不是我们想要的或者是觉得不合理的需要改掉.但是每次有这样的问题时都没有什么好的解决方法,每次 ...
- 编辑方法分享之如何编辑PDF文件内容
我们现在在工作中会经常使用到PDF文件,还会有遇到需要编辑PDF文件的时候,PDF文件的编辑问题一直是个大难题.很多朋友在面对PDF文件的时候束手无策,不知道该怎么对它进行编辑.下面小编就教给大家一个 ...
- linux几种快速清空文件内容的方法
linux几种快速清空文件内容的方法 几种快速清空文件内容的方法: $ : > filename #其中的 : 是一个占位符, 不产生任何输出. $ > filename $ echo & ...
随机推荐
- 201621123002《JAVA程序设计》第二周学习总结
1.本周学习总结 1.重点String类 2.Java的数据类型 3.Java中的引用类,包装类 for(类型 元素变量名(任取):遍历对象(数组名)) 2.书面作业 1.String-使用Eclip ...
- GUI学习之〇——PyQt5安装
GUI(Graphical User Interface)是程序和软件使用者的接口,好的GUI是一个良好的软件的前提,在这里演示一下用PyQt5做一个GUI的方法 软件需求:python3.6 用的是 ...
- java 线程池简单例子
package com.hra.riskprice; import com.hra.riskprice.SysEnum.Factor_Type; import com.hra.riskprice.po ...
- AX_Dialog
Dialog dialog = new Dialog("@SYS1052"); DialogField ...
- flask通过form表单一次上传多个文件
基本上,用了flask官网的示例代码(中文版,英文版),稍微做了修改. import os from flask import Flask, flash, request, redirect, url ...
- 【MySQL】-2 函数、分组、子查询、联合查询
函数 Mysql的函数特性没有SQL可移植性强. 大多数情况下支持的函数: 处理文本串的函数: RTrim():处理列值右边的空格 LTrim():处理列值左边的空格 Trim():处理列值的左右 ...
- JavaScript:void(0)使用介绍
1.点击链接后不做任何事情(为防止点击链接后跳转到页首,onclick事件return false即可) <a href="javascript:void(0);" > ...
- sock5协议转换http协议工具polipo使用笔记(Centos7)
一.安装 Shadowsocks使用socks5协议,而终端很多工具目前只支持http和https等协议,所以我们为终端设置Shadowsocks的思路就是将socks5协议转换成http协议,然后为 ...
- mac开发常用工具和插件记录
1.alfred 是 Mac 系统上一款专注于效率提升的著名应用,它能帮你快速打开网页.快速进行自定义搜索.查看剪贴板历史.快速查询单词等等.Alfred 提供的功能虽然很多,但目的只有一个 —— 那 ...
- UML系统建模的分析和应用
一.基本信息 标题:UML系统建模的分析和应用 时间:2016 出版源:无线互联科技 领域分类:统一建模语言 二.研究背景 问题定义:统一建模语言的分析应用 难点:掌握和理解相关系统的业务环境,掌握良 ...