搜索引擎与爬虫

爬虫系列:读取 CSV、PDF、Word 文档

上一期我们讲解了使用 Python 读取文档编码的相关问题,本期我们讲解使用 Python 处理 CSV、PDF、Word 文档相关内容。

CSV

我们进行网页采集的时候,你可能会遇到 CSV 文件,也可能项目需要将数据保存到 CSV 文件。Python 有一个超赞的标准库可以读写 CSV 文件。虽然这个库可以处理各种 CSV 文件,但是我们这里重点介绍标准 CSV 格式。

读取 CSV 文件

Python 的 CSV 主要是面向本地用户,也就是说你的 CSV 文件得保存到你的电脑上。而经行网络数据采集的时候,很多文件都是在线的。不过有一些方法可以解决这个问题:

  • 手动把 CSV 文件下载到本机,然后用 Python 定位文件位置;

  • 写 Python 程序下载文件,读取之后把源文件删除;

  • 从网上直接把文件读取成一个字符串,然后转换成一个 StringIO 对象,使它具有文件的属性。

虽然前两个方法也可以用,但是既然你可以轻易的把 CSV 文件保存到内存里,就不要下载到本地占用硬盘空间了。直接把文件读取成字符串,然后封装成 StringIO 对象,让 Python 把他当作文件来处理,就不需要保存成文件了。下面的程序就是从网上获取一个 CSV 文件,然后把每一行都打印到命令行里:

import requests
from io import StringIO
import csv


class ProcessCSVPDFDOCX(object):
    def __init__(self):
        self._csv_path = 'https://image.pdflibr.com/crawler/blog/country.CSV'
        self._session = requests.Session()

    def read_csv(self):
        response = self._session.get(self._csv_path)
        # 将文本设置成 utf-8 的编码方式
        response.encoding = 'utf-8'
        response_text = response.text
        data_file = StringIO(response_text)
        dict_reader = csv.DictReader(data_file)

        print(dict_reader.fieldnames)

        for row in dict_reader:
            print(row)


if __name__ == '__main__':
    ProcessCSVPDFDOCX().read_csv()

csv.DictReader 会返回把 CSV 文件每一行转化成 Python 的字典对象返回,而不是列表对象,并把字段列表保存到变量 dict_reader.fieldnames 里,字段同时作为字典对象的键。

PDF

从某种意义上来说, Adobe 在 1993 年发明 PDF 格式(Protable Document Format,便携式文档格式)是一种技术革命。PDF 可以让用户在不同系统上使用同样的方式查看图片和文本文档,无论这种文件是在那种系统上制作的。

虽然把 PDF 显示在网页上已经过时了(你已经可以把内容显示成 HTML 了,为什么还要这种静态、加载速度超慢的格式呢?),但是 PDF 仍然无处不在,尤其是在处理商务报表和表单的时候。

目前很多 PDF 解析库都是 Python 2.x 版本建立的,还没有迁移到 Python 3.x 版本。但是,因为 PDF 比较简单,而且开源的文档格式,所以一些给力的 Python 可以读取 PDF 文件,而且支持 Python 3.x 版本。

PDFMiner3K 就是一个非常好用的库(是 PDFMiner 的 Python 3.x 移植版)。他非常灵活,可以通过命令行使用,也可以整合到代码中。还可以处理不同的语言编码,而且对网络文件的处理也非常的方便。

你可以下载这个模块的源文件(https://pypi.org/project/pdfminer3k/),解压并用下面命令安装:

python setup.py install

我们也可以使用 pip 的方式安装:

pip install pdfminer3k

下面的例子可以把任意 PDF 读成字符串,然后使用 StringIO 转换成文件对象:

import requests
from io import StringIO
import csv
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
from pdfminer.layout import LAParams
from pdfminer.converter import TextConverter
from urllib.request import urlopen


class ProcessCSVPDFDOCX(object):
    def __init__(self):
        self._session = requests.Session()
        self._pdf_path = 'https://image.pdflibr.com/crawler/blog/markdown-cheatsheet-online.pdf'


    def read_pdf(self, pdf_file):
        rscmgr = PDFResourceManager()
        retstr = StringIO()
        laparames = LAParams()
        device = TextConverter(rscmgr, retstr, laparams=laparames)
        process_pdf(rscmgr, device, pdf_file)
        device.close()

        content = retstr.getvalue()
        retstr.close()
        return content

    def read_pdf_main(self):
        pdf_file = urlopen(self._pdf_path)
        output_string = self.read_pdf(pdf_file)
        print(output_string)
        pdf_file.close()


if __name__ == '__main__':
    ProcessCSVPDFDOCX().read_pdf_main()

readPDF 最大的好处是,如果你的 PDF 文件在电脑里,你就可以直接把 urlopen 返回的对象 pdf_file 换成普通的 open() 文件对象。

输入的结果可能不是很完美,尤其是当文件中包含图片、各种各样的文本格式,或者带有表格和数据图的时候。但是,对于大多数只包含纯文本内容的 PDF 而言,其输出结果与纯文本并没有什么区别。

微软 Word 和 .docx

网上有很多对 Word 吐槽的网友,Word 的特意功能就是把那些因该写成简单 TXT 或 PDF 格式的文件,变成了即大又慢且难以打开的怪兽,它们经常在系统切换和版本切换中出现格式不兼容,而且应为某些原因在文件内容已经定稿后仍处于可编辑状态。Word 文档从未打算让人频繁传递。不过他们在一些网站上很流行,包括重要的文档、信息,甚至图表和多媒体;总之,那些内容都应该使用 HTML 代替。

大约在 2008 年以前,微软 Office 产品中 Word 用 .doc 文件格式。这种二进制格式很难读取,而且能够读取 word 格式的软件很少。为了跟上时代,让自己的软件符合主流软件的标准,微软决定使用 Open Office 的类 XML 格式标准,此后新版本 Word 才能与其他文字处理软件兼容,这个格式就是 .docx

不过,Python 对这种 Google Docs、Open Office 和 Microsoft Office 都在使用的 .docx 格式支持还不够好。虽然有一个 python-docx 库,但是只支持创建和读取一些基本的数据,入文件大小和文件标题,不支持正文读取。如果想读取 Microsoft Office 文件的正文内容,我们需要自己动手找方法。

第一步是从文件读取 XML:

import requests
from io import StringIO
import csv
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
from pdfminer.layout import LAParams
from pdfminer.converter import TextConverter
from urllib.request import urlopen
from io import open, BytesIO
from zipfile import ZipFile


class ProcessCSVPDFDOCX(object):
    def __init__(self):
        self._csv_path = 'https://image.pdflibr.com/crawler/blog/country.CSV'
        self._session = requests.Session()
        self._pdf_path = 'https://image.pdflibr.com/crawler/blog/markdown-cheatsheet-online.pdf'
        self._docx_path = 'https://image.pdflibr.com/crawler/blog/test_document.docx'


    def convert_docx_to_xml(self):
        word_file = urlopen(self._docx_path).read()
        word_file = BytesIO(word_file)
        document = ZipFile(word_file)
        xml_content = document.read('word/document.xml')
        print(xml_content.decode('utf-8'))


if __name__ == '__main__':
    ProcessCSVPDFDOCX().convert_docx_to_xml()

这段代码把远程 Word 读取成一个二进制文件对象(BytesIO 与上面使用的 StringIO 类似),再使用 Python 的标准库 zipfile 解压(所有的 .docx 文件为了节省空间都进行了压缩),然后对读取这个解压文件,就变成了 XML 了。

解压后的 XML 文件包含了大量信息,好在所有的内容都包含在 <w:t>标签里面,标题内容也是如此,这样就容易处理多了。

import requests
from io import StringIO
import csv
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
from pdfminer.layout import LAParams
from pdfminer.converter import TextConverter
from urllib.request import urlopen
from io import open, BytesIO
from zipfile import ZipFile
from bs4 import BeautifulSoup


class ProcessCSVPDFDOCX(object):
    def __init__(self):
        self._csv_path = 'https://image.pdflibr.com/crawler/blog/country.CSV'
        self._session = requests.Session()
        self._pdf_path = 'https://image.pdflibr.com/crawler/blog/markdown-cheatsheet-online.pdf'
        self._docx_path = 'https://image.pdflibr.com/crawler/blog/test_document.docx'


    def convert_docx_to_xml(self):
        word_file = urlopen(self._docx_path).read()
        word_file = BytesIO(word_file)
        document = ZipFile(word_file)
        xml_content = document.read('word/document.xml')
        print(xml_content.decode('utf-8'))
        word_obj = BeautifulSoup(xml_content.decode('utf-8'), features="html.parser")
        text_string = word_obj.findAll("w:t")
        for text_ele in text_string:
            print(text_ele.text)


if __name__ == '__main__':
    ProcessCSVPDFDOCX().convert_docx_to_xml()

这段代码显示的结果可能并不完美,但是已经差不多了,一行打印一个 <w:t> 标签。

总结

这篇文章主要讲解了使用 Python 如何处理在线 CSV、PDF、Word 文档,由于 docx 文档并没有很好的库,如何曲线解析 docx 文件,通过这篇文章可以处理互联网上大部分文档内容。

这篇文章的所有源代码已经托管于 Github: https://github.com/sycct/Scrape_1_1.git

如果有任何问题,欢迎大家 issue。