他山之石,可以攻玉--python调用exe文件批量移除pdf密码

1,640 阅读6分钟
原文链接: zhuanlan.zhihu.com

笔者前阵子在抽取pdf文字时,碰到了一个棘手的问题:无法直接编辑加密的pdf文本。

带密码保护的pdf案例

什么是加密的pdf?加密的pdf 分为两类:第一种为pdf在打开需要密码;第二种是在pdf的操作受到了限制,需要密码才能解除限制。本文讨论第二种情况。

来看看一篇加密的pdf文档有哪些权利受到了限制(访问文档属性-->安全信息)。

连打印都不允许……这,有点过了,毕竟招股书是公开的文档。

那么究竟如何去除加密的pdf文档的密码?注意,这里是去除,而不是破解。文章讨论将带有密码保护的pdf转化为没有密码保护的pdf,而不是如何获取密码。

--方案1--

推介一个网站【I LOVE PDF

I LOVE PDF页面截图

利用这个网站,你只需要上传文件,就能对pdf进行切割、合并、破解、转化为word等操作……挺棒的,免费,无需注册!

但是,如果有超过10M的文件,网站是不受理的;如果你有100份文件,那么就得上传100次……尺有所长,尺有所短,网站已经尽力了,毕竟设计初衷不是为了大批量处理文件。涉及机密文件,建议不要上传……

--方案2--

对于切割合并等基础操作,使用库pypdf操作即可--详情可查看笔者之前的文章《利用python处理PDF文本》。

对于密码解除,笔者尝试了诸多方案。这里就不废话了,直接上干货--qpdf

以上是官网对于QPDF介绍。简言之,qpdf是一款可以移除密码的在命令行终端操作的软件。

qpdf 的优势:

  • 破除 256-bit encryption keys and AES
  • 破除 default RC4 with 40, 128 or 256 bit key lengths

命令行语法如下:

基本语法

其中:

  1. infilename 是指导入的文件的名字,也就是需要移除密码的文件
  2. outfilename:经过处理之后的需要导出的文件的名字,也就是最终你要的文件的名字
  3. options:需要使用的命令。下图是可以使用的命令的一小部分,其中黄色标注的的--decrypt 是我们需要的命令。这里很明显的提到:如果PDF打开时需要密码,则需要提供密码,这里仅仅移除文档的权限加密。
option选项

因此,在转到qpdf的安装目录下,你只需要在命令行终端收入一下命令:

qpdf --decrypt infilename outfilename

命令行终端示意图

好了,对于单个文件确实可以这么处理。那么对于100个文件该如何处理?只需要在命令行终端输入100遍即可?嗯,是的,如果你不嫌麻烦。但是,重复的劳动,应该交给机器来完成。实际上,写个批处理就可以轻松搞定这个任务。

if 你会批处理:

-----文章到此结束----

else:

-----请继续阅读----

--------------------不会的批处理的朋友请往下看-------------------

好了,当你在使用命令行进行pdf解密时,你实际上在打开一个进程。

接下来要做的事情已经很明了,就是利用python调用命令行终端,建立多进程直接同时对多个文件进行处理。

python调用命令行终端库主要有os、subprocess

python打开命令行终端的库及其关系

官网推荐,尽量不使用os.system;同时,o.popen也是调用subprocess.Popen,因此,推荐直接使用subprocess.Popen。subprocess.Popen语法如下:

class Popen(object):
    """ Execute a child program in a new process.

    For a complete description of the arguments see the Python documentation.

    Arguments:
      args: A string, or a sequence of program arguments.

      bufsize: supplied as the buffering argument to the open() function when
          creating the stdin/stdout/stderr pipe file objects

      executable: A replacement program to execute.

      stdin, stdout and stderr: These specify the executed programs' standard
          input, standard output and standard error file handles, respectively.

      preexec_fn: (POSIX only) An object to be called in the child process
          just before the child is executed.

      close_fds: Controls closing or inheriting of file descriptors.

      shell: If true, the command will be executed through the shell.

      cwd: Sets the current directory before the child is executed.

      env: Defines the environment variables for the new process.

      universal_newlines: If true, use universal line endings for file
          objects stdin, stdout and stderr.

      startupinfo and creationflags (Windows only)

      restore_signals (POSIX only)

      start_new_session (POSIX only)

      pass_fds (POSIX only)

      encoding and errors: Text mode encoding and error handling to use for
          file objects stdin, stdout and stderr.

    Attributes:
        stdin, stdout, stderr, pid, returncode
    """
    _child_created = False  # Set here since __del__ checks it

    def __init__(self, args, bufsize=-1, executable=None,
                 stdin=None, stdout=None, stderr=None,
                 preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
                 shell=False, cwd=None, env=None, universal_newlines=False,
                 startupinfo=None, creationflags=0,
                 restore_signals=True, start_new_session=False,
                 pass_fds=(), *, encoding=None, errors=None):
        """Create new Popen instance."""
        _cleanup()
        # Held while anything is calling waitpid before returncode has been
        # updated to prevent clobbering returncode if wait() or poll() are
        # called from multiple threads at once.  After acquiring the lock,
        # code must re-check self.returncode to see if another thread just
        # finished a waitpid() call.
        self._waitpid_lock = threading.Lock()

        self._input = None
        self._communication_started = False

#------------------------丑陋的界面--------------------

我们分步来分析subprocess.Popen

首先,Popen是一个类

其自带一个初始化函数

初始化参数里面有若干个参数,我们逐一展示

参数1
参数2

重点来看看shell参数。

如果直接通过命令行来执行,那么shell = True

官网对于shell的解释

简单来说,如果你的程序依赖于其他命令行输入,那么使用shell相当危险!由于我们不需要在命令行直接输入命令,因此这里并无风险。

为何我们要关注shell这个参数?好吧,如果shell= False,将直接导致程序错误,找不到文件([Errno 2] No such file or directory)!

好了,函数解析完毕,直接上代码

#批量破解pdf密码
import subprocess
import  os
path = r"F:\qpdf"
pre = r"C:\Users\tctctct\PycharmProjects\yxpl\files"  #初始pdf文件夹
final = r"D:\yxpl"     #存放破解的pdf的文件夹
files = os.listdir(pre)
exit_files = os.listdir(final)
for j  in files:
    if j not in exit_files:
        abs_file = os.path.join(pre,j)
        out_file = os.path.join(final,j)
        cmd = ["qpdf","--decrypt",abs_file,out_file]  #命令行终端命令
        sub = subprocess.Popen(args=cmd,cwd=path,shell=True)  #不要忘记cwd
        sub.wait()   #最好加上,否则可能由于多个进程同时执行带来机器瘫痪

后续:

  • 有兴趣朋友可以借助tkinter或者pyqt做一个用户界面,方便python用户使用。如果懂点pyinstaller,也可以将程序打包成exe文件,方便完全不懂python的小白使用。
  • pdf 文章有了,如何抽取里面的文字?请看《利用python处理PDF文本
  • 文字也有了, 如何识别出你要的内容或者进行转化?请学习自然语言处理+正则表达式
  • 拓展思路,命令行可以做的事,python可以直接做。再进一步,其他语言做好的事情,python来调用。例如,java库pdfbpox擅长处理pdf ,那么就写个pdfbox python接口,直接调用。