Python VS VBAは、Excelのテキストボックスコンポーネントを読み取ります

著者:リトルミン

PythonはExcelのテキストボックスを読み取ります

基本的なニーズ

今日、私は非常に奇妙な問題を見ました。次のようなExcelファイルのテキストボックス内のテキストを読むことです。

openxlpyは読み取れると思いましたが、openxlpyの公式ドキュメントを確認したところ、対応するAPIが見つかりませんでした。数人の大物に相談しましたが、同様の問題に対処していません。

悪党の下で、私は自分の強力なデータ分析能力を使用して、これらのものを自分で読み取る方法を書く準備ができています。

コードの処理

xlsxファイルの本質はxml形式の圧縮パッケージであり、解凍されたファイルを解析および抽出して、対応するデータを抽出できます。

もともと私はxmlを解析するためのxpathとしてlxmlを使用するつもりでしたが、実際のテストでは、これらのxmlファイルには多くの名前空間があり、解析は非常に複雑であることがわかりました。スムーズに解析できるいくつかの一般的なxml解析ライブラリを試しましたが、通常ほど便利ではないと思うので、最終的にxml解析に通常の式を使用することにしました。

最終的な処理コードは次のとおりです。

import re
import os
import shutil
from zipfile import ZipFile


def read_xlsx_textbox_text(xlsx_file):
    tempdir = tempfile.gettempdir()
    basename = os.path.basename(xlsx_file)
    xml_names = []
    with ZipFile(xlsx_file) as zip_file:
        for name in zip_file.namelist():
            if name.startswith("xl/drawings/drawing"):
                zip_file.extract(name, tempdir)
                destname = f"{tempdir}/{name}"
                xml_names.append(destname)
    result = []
    for xml_name in xml_names:
        with open(xml_name, encoding="utf-8") as f:
            text = f.read()
        lines = re.findall("<a:p>(.*?)</a:p>", text)
        for line in lines:
            runs = re.findall("<a:t>(.*?)</a:t>", line)
            result.append("".join(runs).replace('&lt;', '<').replace(
                '&gt;', '>').replace('&amp;', '&'))
    return "\n".join(result)

テストがあります:

result = read_xlsx_textbox_text("test.xlsx")
print(result)

結果:

什么是JSON?

就是一种数据格式;比如说,我们现在规定,有一个txt文本文件,用来存放一个班级的成绩;然后呢,我们规定,这个文本文件里的学生成绩的格式,是第一行,就是一行列头(姓名 班级 年级 科目 成绩),接下来,每一行就是一个学生的成绩。那么,这个文本文件内的这种信息存放的格式,其实就是一种数据格式。

学生 班级 年级 科目 成绩
张三 一班 大一 高数 90
李四 二班 大一 高数 80

ok,对应到JSON,它其实也是代表了一种数据格式,所谓数据格式,就是数据组织的形式。比如说,刚才所说的学生成绩,用JSON格式来表示的话,如下:

[{"学生":"张三", "班级":"一班", "年级":"大一", "科目":"高数", "成绩":90}, {"学生":"李四", "班级":"二班", "年级":"大一", "科目":"高数", "成绩":80}]

其实,JSON,很简单,一点都不复杂,就是对同样一批数据的,不同的一种数据表示的形式。

JSON的数据语法,其实很简单:如果是包含多个数据实体的话,比如说多个学生成绩,那么需要使用数组的表现形式,就是[]。对于单个数据实体,比如一个学生的成绩,那么使用一个{}来封装数据,对于数据实体中的每个字段以及对应的值,使用key:value的方式来表示,多个key-value对之间用逗号分隔;多个{}代表的数据实体之间,用逗号分隔。
...

このようにして、Excelファイルからテキストボックスのすべてのテキストを正常に実装し、読み取りました。

注:他に特別な要件がある場合は、実際の状況に応じてコードを変更するか、この記事の作成者(Xiaoming)に連絡して対応するカスタマイズを依頼することができます。

xlsファイルのテキストボックスの内容を読む

上記の方法は、xlsx形式のファイルの読み取りのみをサポートしています。xls形式を読み取る場合は、最初に形式変換を実行する必要があります。

完全なコード:

import win32com.client as win32


def read_xls_textbox_text(xls_file):
    excel_app = win32.gencache.EnsureDispatch('Excel.Application')
#     excel_app.DisplayAlerts = False
    try:
        wb = excel_app.Workbooks.Open(xls_file)
        xlsx_file = xls_file+"x"
        wb.SaveAs(xlsx_file, FileFormat=51)
    finally:
        excel_app.Quit()
    return read_xlsx_textbox_text(xlsx_file)

同じ名前のxlsxファイルがあるときにプロンプ​​トが表示されないようにする場合は、コメントを閉じてください。

テスト読み取り:

print(read_xls_textbox_text(r"E:\tmp\test2.xls"))

結果:

我们的数据从哪里来?
互联网行业:网站、app、系统(交易系统。。)
传统行业:电信,人们的上网、打电话、发短信等等数据

数据源:网站、app

都要往我们的后台去发送请求,获取数据,执行业务逻辑;app获取要展现的商品数据;发送请求到后台进行交易和结账

后台服务器,比如Tomcat、Jetty;但是,其实在面向大量用户,高并发(每秒访问量过万)的情况下,通常都不会直接是用Tomcat来接收请求。这种时候,通常,都是用Nginx来接收请求,并且后端接入Tomcat集群/Jetty集群,来进行高并发访问下的负载均衡。

比如说,Nginx,或者是Tomcat,你进行适当配置之后,所有请求的数据都会作为log存储起来;接收请求的后台系统(J2EE、PHP、Ruby On Rails),也可以按照你的规范,每接收一个请求,或者每执行一个业务逻辑,就往日志文件里面打一条log。
网站/app会发送请求到后台服务器,通常会由Nginx接收请求,并进行转发
...

xls形式をxlsxにバッチ変換します

xlsファイルのバッチがあり、それらをバッチでxlsxに変換したいとします。

画像-20210227173120785

私のアプローチは、フォルダ全体が変換された後にアプリケーションを閉じることです。これは比較的高速ですが、より多くのメモリを消費する可能性があります。コードは次のとおりです。

import win32com.client as win32  # 导入模块
from pathlib import Path
import os

def format_conversion(xls_path, output_path):
    if not os.path.exists(output_path):
        os.makedirs(output_path)
    excel_app = win32.gencache.EnsureDispatch('Excel.Application')
    try:
        for filename in Path(xls_path).glob("[!~]*.xls"):
            dest_name = f"{output_path}/{filename.name}x"
            wb = excel_app.Workbooks.Open(filename)
            wb.SaveAs(dest_name, FileFormat=51)
            print(dest_name, "保存完成")
    finally:
        excel_app.Quit()

テストがあります:

excel_path = r"F:\excel文档"
output_path = r"E:\tmp\excel"
format_conversion(excel_path, output_path)

結果:

E:\tmp\excel/008.离线日志采集流程.xlsx 保存完成
E:\tmp\excel/009.实时数据采集流程.xlsx 保存完成
E:\tmp\excel/011.用户访问session分析-模块介绍.xlsx 保存完成
E:\tmp\excel/012.用户访问session分析-基础数据结构以及大数据平台架构介绍.xlsx 保存完成
E:\tmp\excel/013.用户访问session分析-需求分析.xlsx 保存完成
E:\tmp\excel/014.用户访问session分析-技术方案设计.xlsx 保存完成
E:\tmp\excel/015.用户访问session分析-数据表设计.xlsx 保存完成
E:\tmp\excel/018.用户访问session分析-JDBC原理介绍以及增删改查示范.xlsx 保存完成
E:\tmp\excel/019.数据库连接池原理.xlsx 保存完成
...

xlsxファイルのテキストボックステキストをバッチ抽出

上記ではxlsxファイルのフォルダーを取得しました。以下では、このフォルダー内の各xlsxファイルのテキストボックスの内容を抽出し、対応するtxt形式で保存する必要があります。

処理コード:

from pathlib import Path

xlsx_path = r"E:\tmp\excel"
for filename in Path(xlsx_path).glob("[!~]*.xlsx"):
    filename = str(filename)
    destname = filename.replace(".xlsx", ".txt")
    print(filename, destname)
    txt = read_xlsx_textbox_text(filename)
    with open(destname, "w") as f:
        f.write(txt)

実行後、対応するtxtファイルが正常に取得されました。

画像-20210227175246324

デマンドアップグレード

上記の読み取り方法は、Excelファイル全体のすべてのテキストボックスの内容をマージすることですが、Excelファイルの複数のシートにテキストボックスがある場合があります。異なるシートを区別できるようにしたいと考えています。

画像-20210227181350761

各シート名に対応するテキストボックスのテキストを返すように読み取りメソッドを改善して、最初にテストしてみましょう。

まず、必要なファイルを解凍します。

from zipfile import ZipFile
from pathlib import Path
import shutil
import os
import tempfile
import re

xlsx_file = "test3.xlsx"

tempdir = tempfile.gettempdir()
basename = os.path.basename(xlsx_file)
xml_names = []
sheets_names = None
ids = []
with ZipFile(xlsx_file) as zip_file:
    for name in zip_file.namelist():
        if name.startswith("xl/drawings/drawing"):
            zip_file.extract(name, tempdir)
            destname = f"{tempdir}/{name}"
            xml_names.append(destname)
        elif name == "xl/workbook.xml":
            zip_file.extract(name, tempdir)
            sheets_names = f"{tempdir}/{name}"
        elif name.startswith("xl/worksheets/_rels/sheet"):
            tmp = name.lstrip("xl/worksheets/_rels/sheet")
            ids.append(int(tmp[:tmp.find(".")])-1)
print(xml_names, sheets_names, ids)

結果:

['C:\\Users\\Think\\AppData\\Local\\Temp/xl/drawings/drawing1.xml', 'C:\\Users\\Think\\AppData\\Local\\Temp/xl/drawings/drawing2.xml', 'C:\\Users\\Think\\AppData\\Local\\Temp/xl/drawings/drawing3.xml', 'C:\\Users\\Think\\AppData\\Local\\Temp/xl/drawings/drawing4.xml', 'C:\\Users\\Think\\AppData\\Local\\Temp/xl/drawings/drawing5.xml'] C:\Users\Think\AppData\Local\Temp/xl/workbook.xml [0, 1, 2, 4, 5]

シート名を読む:

with open(sheets_names, encoding="utf-8") as f:
    text = f.read()
sheet_names = re.findall(
    '<sheet .*?name="([^"]+)" .*?/>', text)
tmp = []
for inx in ids:
    tmp.append(sheet_names[inx])
sheet_names = tmp
sheet_names

結果:

['JSON', '数据库连接池', '实时数据采集', '工厂设计模式', '页面转化率']

分析:

result = {
    
    }
for sheet_name, xml_name in zip(sheet_names, xml_names):
    with open(xml_name, encoding="utf-8") as f:
        xml = f.read()
    lines = re.findall("<a:p>(.*?)</a:p>", xml)
    tmp = []
    for line in lines:
        runs = re.findall("<a:t>(.*?)</a:t>", line)
        tmp.append("".join(runs).replace('&lt;', '<').replace(
            '&gt;', '>').replace('&amp;', '&'))
    result[sheet_name] = "\n".join(tmp)
result

結果(ほとんどのテキストは省略):

{'JSON': '什么是JSON?....',
 '数据库连接池': 'java程序\n数据库连接\n数据库连接\n数据库连接\nMySQL...',
 '实时数据采集': '...实时数据,通常都是从分布式消息队列集群中读取的,比如Kafka....',
 '工厂设计模式': '如果没有工厂模式,可能会出现的问题:....',
 '页面转化率': '用户行为分析大数据平台\n\n页面单跳转化率,....'}

各シートに対応するテキストボックスの内容が正常に読み取られ、1対1で対応していることがわかります。

各シートの対応するテキストボックスのテキストを個別に読む

上記のプロセスをメソッドとして統合してカプセル化します。

import re
import os
from zipfile import ZipFile
import tempfile


def read_xlsx_textbox_text(xlsx_file, combine=False):
    tempdir = tempfile.gettempdir()
    basename = os.path.basename(xlsx_file)
    xml_names = []
    sheets_names = None
    ids = []
    with ZipFile(xlsx_file) as zip_file:
        for name in zip_file.namelist():
            if name.startswith("xl/drawings/drawing"):
                zip_file.extract(name, tempdir)
                destname = f"{tempdir}/{name}"
                xml_names.append(destname)
            elif name == "xl/workbook.xml":
                zip_file.extract(name, tempdir)
                sheets_names = f"{tempdir}/{name}"
            elif name.startswith("xl/worksheets/_rels/sheet"):
                tmp = name.lstrip("xl/worksheets/_rels/sheet")
                ids.append(int(tmp[:tmp.find(".")])-1)
    with open(sheets_names, encoding="utf-8") as f:
        text = f.read()
    sheet_names = re.findall(
        '<sheet .*?name="([^"]+)" .*?/>', text)
    tmp = []
    for inx in ids:
        tmp.append(sheet_names[inx])
    sheet_names = tmp
    result = {
    
    }
    for sheet_name, xml_name in zip(sheet_names, xml_names):
        with open(xml_name, encoding="utf-8") as f:
            xml = f.read()
        lines = re.findall("<a:p>(.*?)</a:p>", xml)
        tmp = []
        for line in lines:
            runs = re.findall("<a:t>(.*?)</a:t>", line)
            tmp.append("".join(runs).replace('&lt;', '<').replace(
                '&gt;', '>').replace('&amp;', '&'))
        result[sheet_name] = "\n".join(tmp)
    if combine:
        return "\n".join(result.values())
    return result

メソッドの呼び出し:

result = read_xlsx_textbox_text("test3.xlsx")
print(result)

Combine = Trueを渡して、シートの結果を1つのテキストにマージできますが、以前に記述したメソッドを直接呼び出すことをお勧めします。

テキストボックスのテキストをバッチ抽出し、別のシートに保存します

以下では、xlsxファイルごとに同じ名前のフォルダーを作成し、各フォルダーの下のシート名に従ってテキストボックスにテキストを個別に保存する必要があります。

処理コード:

from pathlib import Path
import os

xlsx_path = r"E:\tmp\excel"
for filename in Path(xlsx_path).glob("[!~]*.xlsx"):
    dest = filename.with_suffix("")
    if not os.path.exists(dest):
        os.mkdir(dest)
    filename = str(filename)
    print(filename, dest)
    result = read_xlsx_textbox_text(filename)
    for txtname, txt in result.items():
        with open(f"{dest}/{txtname}", "w") as f:
            f.write(txt)
        print(f"\t{dest}/{txtname}")

テスト後、Excelファイルごとにディレクトリが正常に作成されました。各ディレクトリには、テキストボックスに存在するシートに応じた対応するシート名ファイルがあります。

Pythonを使用してVBAを呼び出し、最終的な需要を解決します

VBAの公式​​ドキュメントアドレス:https//docs.microsoft.com/zh-cn/office/vba/api/overview/excel

全体として、xmlを解析する上記の方法自体はまだかなり面倒です。上記の方法を書いた後、ブレインストーミングがあります。VBAにはテキストボックスを読み取るための既成の方法がありませんか?また、Pythonは完全に互換性のあるVBAコードを記述できるため、問題は単純です。VBAを使用すると、コードが単純であるだけでなく、フォーマット変換の問題を考慮せずに問題を直接解決できます。読み取りコードは次のとおりです。

import win32com.client as win32


def read_excel_textbox_text(excel_file, app=None, combine=False):
    if app is None:
        excel_app = win32.gencache.EnsureDispatch('Excel.Application')
    else:
        excel_app = app
    wb = excel_app.Workbooks.Open(excel_file)
    result = {
    
    }
    for sht in wb.Sheets:
        if sht.Shapes.Count == 0:
            continue
        lines = []
        for shp in sht.Shapes:
            try:
                text = shp.TextFrame2.TextRange.Text
                lines.append(text)
            except Exception as e:
                pass
        result[sht.Name] = "\n".join(lines)
    if app is None:
        excel_app.Quit()
    if combine:
        return "\n".join(result.values())
    return result

テスト読み取り:

result = read_excel_textbox_text(r'F:\jupyter\test\提取word图片\test3.xlsx')
print(result)

画像-20210227214136565

結果をスムーズに読んでください。

バッチ処理:

from pathlib import Path
import os

xlsx_path = r"E:\tmp\excel"
app = win32.gencache.EnsureDispatch('Excel.Application')
try:
    for filename in Path(xlsx_path).glob("[!~]*.xls"):
        dest = filename.with_suffix("")
        if not os.path.exists(dest):
            os.mkdir(dest)
        filename = str(filename)
        print(filename, dest)
        result = read_excel_textbox_text(filename, app)
        for txtname, txt in result.items():
            with open(f"{dest}/{txtname}", "w") as f:
                f.write(txt)
            print(f"\t{dest}/{txtname}")
finally:
    app.Quit()

テスト後、VBA処理の欠点も明らかです。63ファイルの場合は最大25秒かかりますが、xmlを直接解析するのにかかる時間はわずか259ミリ秒であり、パフォーマンスの違いは桁違いではありません。

総括する

Excelでデータを読み取ると、基本的にVBAで実行できないことはありません。PythonでVBAを呼び出すことも非常に簡単で、pywin32を直接使用するだけです。もちろん、2007 xlsxは本質的にxml形式の圧縮パッケージです。xmlテキストを解析するときに読み取り不可能なデータはありませんが、コードを書くのは非常に面倒です。もちろん、xlsxのストレージ原理をよりよく理解する必要があります。 。

このように、VBAと直接xml解析の長所と短所は非常に明白です。

  • VBAは、Excelアプリケーションで直接サポートされているAPIです。コードの記述は比較的簡単ですが、実行効率は低くなります。
  • xmlファイルを直接解析するには、Excelのストレージ形式をよりよく理解する必要があります。これは、コーディングに非常に手間がかかりますが、実行効率は非常に高くなります。

読者としてのあなたの意見は何ですか?以下のメッセージエリアでご意見をお聞かせください。

おすすめ

転載: blog.csdn.net/as604049322/article/details/114187638