最近在爬一个网站,而后爬到详情页的时候发现,目标内容是用pdf在线预览的html
好比以下网站:python
https://camelot-py.readthedocs.io/en/master/_static/pdf/foo.pdfgit
根据个人分析发现,这样的在线预览pdf的采用了pdfjs加载预览,用爬虫的方法根本没法直接拿到pdf内的内容的,对的,你注意到了我说的【根本没法直接拿到】中的直接两个字,确实直接没法拿到,怎么办呢?只能把pdf先下载到本地,而后用工具转了,通过我查阅大量的相关资料发现,工具仍是有不少:github
1.借用第三方的pdf转换网站转出来json
2.使用Python的包来转:如:pyPdf,pyPdf2,pyPdf4,pdfrw等工具浏览器
这些工具在pypi社区一搜一大把:多线程
可是效果怎么样就不知道了,只能一个一个去试了,到后面我终于找到个库,很是符合个人需求的库 ——camelot工具
camelot能够读取pdf文件中的数据,而且自动转换成pandas库(数据分析相关)里的DataFrame类型,而后能够经过DataFrame转为csv,json,html都行,个人目标要的就是转为html格式,好,废话很少说,开始搞网站
1.安装camelot:编码
pip install camelot-py
pip install cv2 (由于camelot须要用到这个库)
2.下载pdf:由于在线的pdf其实就是二进制流,因此得按照下载图片和视频的方式下载,而后存到本地的一个文件里,这个步骤就很少说了
3.解析:
import camelot file = 'temp.pdf' table = camelot.read_pdf(file,flavor='stream') table[0].df.to_html('temp.html')
以上的temp.html就是我但愿获得的数据了,而后根据个人分析发现,在read_pdf方法里必定带上参数 【flavor='stream'】,否则的话就报这个错:
RuntimeError: Please make sure that Ghostscript is installed
缘由就是,read_pdf默认的flavor参数是lattice,这个模式的话须要安装ghostscript库,而后你须要去下载Python的ghostscript包和ghostscript驱动(跟使用selenium须要下载浏览器驱动一个原理),而默认咱们的电脑确定是没有安装这个驱动的,因此就会报上面那个错。我试着去装了这个驱动和这个包,去read_pdf时其实感受没有本质区别,是同样的,因此带上参数flavor='stream'便可,固然若是你硬要用lattice模式的话,安装完ghostscript包和ghostscript驱动以后,记得在当前py文件用 【import ghostscript】导入下这个包,否则仍是会报如上错误
继续走,发现能拿到我想要的数据了,很是nice,而后忽然的,报了以下错误:
PyPDF2.utils.PdfReadError: EOF marker not found
当时就是卧槽,这什么状况,我开始去研究EOF marker是什么意思,可是我直接打开这个pdf文件又是正常的
很是诡异,网上查阅了一堆,大概意思就是说,没有EOF结束符,这个东西在以前我作js开发的时候遇到过,js的语句体{},少了最后的【}】,
我又去了解了下EOF到底在二进制文件指的什么,而后看到老外的这个帖子:
我用一样的方法查看数据的前五个字符和后五个字符:
好像有了眉目,我以文本的方式打开了我下载到本地的一个pdf,在%%EOF结尾以后还有不少的null
难道是NULL的问题?我手动删掉null以后,单独对这个修改过的pdf用pdf查看器打开,正常打开,没有问题,我接着用代码针对这个文件执行read_pdf,发现很是神奇的不会报错了,那还真是结尾的NULL元素了。
而后我在从网上读取到pdf以后的二进制部分用字符串的strip()方法,觉得用strip能够去除那些null,结果发现仍是如此
-------------------------------------
那就只有先锁定 %%EOF 所在位置,而后切片操做了,部分代码以下,果真问题解决,但同时又报了一个新的错,这个就是个编码问题了,相信搞爬虫的朋友们对这个问题很是熟悉了
先暂时无论这个问题,我又改了下目标网站的指定页码
pdfminer.psparser.SyntaxError: Invalid dictionary construct: [/'Type', /'Font', /'Subtype', /'Type0', /'BaseFont', /b"b'", /"ABCDEE+\\xcb\\xce\\xcc\\xe5'", /'Encoding', /'Identity-H', /'DescendantFonts', <PDFObjRef:11>, /'ToUnicode', <PDFObjRef:19>]
发现问题愈来愈严重了,我鼓捣了一番以后,又查了一堆资料,将utf-8改为gb18030仍是报错,我发现我小看这个问题了,接着查阅,而后发现github上camelot包的issues也有人提了这个报错,
https://github.com/atlanhq/camelot/issues/161
而后这里有我的说能够修复下pdf文件:
我查了下,须要安装一个软件mupdf,而后在终端用命令 修复
mutool clean 旧的.pdf 新的.pdf
首先这并非理想的解决方法,在python代码中,是能够调用终端命令,用os和sys模块就能够,可是万一由于终端出问题还很差找缘由,因此我并无去修复,以后我发现我这个决定是对的
接着看,发现issue里不少人都在反馈这个问题,最后看到这个老哥说的
大概意思就是说pypdf2没法完美的处理中文文档的pdf,而camelot对pdf操做就基于pypdf2,卧槽,这个就难了。
而后我又查到这篇文章有说到这个问题:https://blog.csdn.net/kmesky/article/details/102695520
那只能硬改源码了,改就改吧,毕竟这也不是我第一次改源码了
注意:若是你不知道的状况下,千万不要改源码,这是一个大忌,除非你很是清楚你要作什么
C:\Program Files\Python37\Lib\site-packages\pandas\io\formats\format.py该文件的第846行
由这样:
改为这样:
File "D:\projects\myproject\venv\lib\site-packages\PyPDF2\generic.py", 该文件的第484行
Lib/site-packages/PyPDF2/utils.py 第238行
再运行:以前那些错误已经没有了
但同时又有了一个新的错
其实这个超出索引范围的报错的根本是上面的警告:UserWarning:page-1 is image-based,camelot only works on text-based pages. [streams.py:443]
由于源数据pdf的内容是个图片,再也不是文字,而camelot只能以文本形式提取数据,因此数据为空,因此 table[0]会报索引超出范围
针对图片的处理,我网上查阅了一些资料,以为这篇文章写的不错,能够提取pdf中的图片
http://www.javashuo.com/article/p-sxccfttj-ho.html
可是,个人目标是但愿拿到pdf中的内容,而后转成html格式,在以前,我已经由在线pdf->本地pdf->提取表格->表格转html,这是第一种。
若是要提取图片的话,那步骤就是第二种:在线pdf->本地pdf->提取图片->ocr提取表格->验证对错->表格转html,这样就会多些步骤,想一想,我为了拿到一个网站的数据,每一个网页就要作这些操做,并且还要判断是图片就用用第二种,是表格就用第一种,两个方法加起来的话,爬一个网站的数据要作的操做的就多了,虽然这些都属于IO操做型,可是到后期开启多线程,多进程时,与那些直接就能从源网页提取的相比就太耗时间了。
这样不是不行,是真的耗时间,因此我暂时放弃对图片的提取了,只提取table,先对pdf二进制数据判断是不是图片,是图片就跳过了
原理就是,根据上面那片博客里的:
不过通过个人验证,发现这个方法正确率不能百分之百,少部分的即便是表格仍是有/Image和/XObject相关的字符串
那没办法了,有多少是多少吧
部分代码实现:
fujian_data = requests.get(fujian_url, headers=headers).content fujian_index = fujian_data.index(b'%%EOF') fujian_data = fujian_data[:fujian_index + len(b'%%EOF')] checkXO = rb"/Type(?= */XObject)" checkIM = rb"/Subtype(?= */Image)" isXObject = re.search(checkXO, fujian_data) isImage = re.search(checkIM, fujian_data) if isXObject and isImage: # 是图片跳过 pass f = open('temp.pdf', 'wb') f.write(fujian_data) f.close() tables = camelot.read_pdf('temp.pdf', flavor='stream') if os.path.exists('temp.pdf'): os.remove('temp.pdf') # 删除本地的pdf tables[0].df.to_html('foo.html', header=False, index=False)
至此完毕,固然,你也能够用camelot 的to_csv 和 to_json方法转成你但愿的,具体就本身研究了
以上就是Python处理在线pdf的全部内容