LoginSignup
0
3

More than 1 year has passed since last update.

Pythonを利用した各種ファイルのワードクラウド化です。

Posted at

様々な形式のファイルをテキスト化してワードクラウドで分析します。

pythonを利用した、テキストファイルをワードクラウド表示するサンプルはいくつか、見つかります。
ところが、手元にある分析したいファイルは、エクセルだったり、ワード、パワーポイント,pdf,htmlなどテキスト以外の様々なファイルがあります。

これらをテキスト化する方法も個々に見つけられますが、個別に変換するのが面倒だったので、これらのファイルを解析してワードクラウドで分析するようなコードを書いてみました。

分析対象の具体的な拡張子は
.xlsx,.docx,.pptx,.pdf,.csv,.txt,.text,.md,.htm,.html
です。
また、テキストの拡張子を増やすのは、簡単に拡張できます。
(コードのコピペでなく、配列変数修正だけで拡張できるよう直すかもしれません。)

各種ファイルのライブラリを利用してます。

何かしたいときに、ライブラリが揃っているのは、pythonの強みですよね。
皆さんが、各種ライブラリを作って公開してくれているので、とても助かります。感謝感謝!

各ライブラリのインストール方法は、省略しますので、未導入のライブラリがあれば、各自調査して導入してください。

エクセル用:openpyxl
パワーポイント用:pptx
ワード用:docx (python-docx)
pdf用:pdfminer

更にテキストもキャラクターコードを考慮しないといけないので、
chardet

日本語の解析用:janomeまたは、Mecabとre
コード上は比較のため両方入れましたが、どちらかがあれば、動かせます。
(なお、MeCabは、reも併用してます。)
自分の環境に合わせてコードを修正ください。
コード修正が面倒な方は、両方入れてください。

そしてもちろん今回の主役、
ワードクラウド用:wordcloud
です。

後は、画面用にtkinter,threading
ファイル処理用にglob,os
等、常連のライブラリを使ってます。

コード解説

申し訳ありません。かなりごちゃごちゃしてます。

基本的には、指定されたフォルダ内のファイルを種類別にテキスト化し、単語を切り出してテキストファイルに出力していきます。
そして、ファイル毎にワードクラウドを作成し、最後にそのフォルダー内のすべてのテキストをまとめたワードクラウドを作成します。

特にpdfの解析、ワードの解析は、もっとスマートに行いたいのですが、あまりすっきりしてません。
pdfって、めんどくさいですね。ワードの表も良くわかりません。

また、日本語解析も見様見真似でコーディングしていて、最適化できているかは自信ないです。

pythonコード

下記、そのまま使えると思います。
必要なライブラリを導入してご利用ください。

★汎用版ワードクラウド分析.py
# %%
# 汎用版ワードクラウド分析
# 指定したフォルダ内のドキュメントの文章を単語にして、ワードクラウド表示する。

# 現在サポートしているファイル形式は、以下の通り
# .xlsx , .docx , .pptx , .pdf , .txt , .csv , .htm , .html , .md
# 副産物として、すべてのドキュメントの文書を一つのテキストファイルにまとめるので、全文検索がしやすくなる。
# ドキュメントが日別や月別に分かれてしまっていて、キーワードも曖昧で、探しているものが
# どのファイルにあるのかわからない場合など、この副産物が結構役に立つ。

# なお、多くのライブラリ、およびjanomeまたは、mecabの日本語の形態素解析が必要なので、
# 動作環境構築は面倒。(特に高機能なmecabはインストールが面倒)
# janomeにするか、mecabにするかは、一番下の方にあるメインルーチンで指定する。
# 色々試したかったので、両方残してますが、実際は不要な方をごっそり削除しても問題ないです。

# このプログラムの稼働には、WordCloudを日本語対応させるため、上記の日本語解析環境と、下記ライブラリの他に日本語フォントが必要です。
# これがないとワードクラウド内の日本語が文字化けするということで、「日本語フォントのインストールも必要」
# とネットには書いてたけど、元々入っているフォントで試したら、これでもできることが分かったので、最終的にこれにしました。
# f_path = "C:/Windows/Fonts/msgothic.ttc"  # とりあえず、Windowsなら標準で入っているMSゴシックフォントにしてみた。

# ちなみに、上記がうまくいかない場合、下記のフォントをインストールして利用した方がよさそうです。
# 指定した場所に、フォントが見つからなかったりすると、プログラムがエラーになります。
# なお、フォントファイルは、エクスプローラからは、C:/Windows/Fontsにあるように見えますが、実ファイルの場所が違う事があるので要注意です。
# (ぱっと見た目ではわからないので、右クリックしてプロパティがあればそこで、無ければ、開いてからプロパティで確認できます。)
# 私の環境では、以下のようになってました。
#    f_path = "C:/Windows/Fonts/YuGothM.ttc"
#    f_path = os.environ['LOCALAPPDATA']+"/Microsoft/Windows/Fonts/ipaexg.ttf"


# glogは、ファイルを扱うのに便利です。
from glob import glob

# フルパスから、ファイル名だけを抜き出す、ファイル存在チェック等に使う。
import os

# 画面用にtkinterライブラリを使う。使いこなすのが難しい・・・
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox

# マルチスレッドで実行中の表示をしようとしたが、うまくいかず、一旦はafterメソッドを使うことにした。
# tkinterはmainloopに戻るときだけ画面再表示が行われるらしい。
# afterは一度、mainloopに戻るので、そこで画面更新が行われる。
# この問題は、画面のUpdateで解決したが、「応答なし」になる問題を解消するため、
# 結局、マルチスレッドを使う事にした。
import threading

# ワードクラウドを使うためには、文書を単語に分解する必要がある。
# 文書解析は、MeCabと、janomeどちらか初期値で選べるようにしてみた。
# 日本語を単語に分解するのって難しそうですね。janomeとMeCabには感謝しかないです。
# 指定は、一番下の方のmainで行ってます。
# 自分の好きな方を使えばいいので、不要な解析はごっそり削除してください。
# それぞれに癖があるみたいで、どちらかが正確というのでなく、なんとなく結果に違いが出ます。

# janome用
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.tokenfilter import *
from janome.charfilter import *

# mecab用
import MeCab
import re        # reは、mecab解析の時使ってる。


# 今回の主役、ワードクラウドライブラリ
# こいつは、凄い!
# このプログラムは、ほぼ下処理だけです。このライブラリのおかげで、実際のワードクラウド作成は、ほぼ2行です!
# こういう、プログラムデザインも洗練された素晴らしいライブラリを作れる人って、凄い!
import wordcloud as wc

# pdfから日本語を含む文字を読み込むのは、思いのほかめんどくさい。
# pdfは、多様な表現ができるけど、スマートなつくりじゃないってことですかね。
# 必要なPdfminer.sixモジュールのクラスをインポート
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfinterp import PDFPageInterpreter, PDFResourceManager
from pdfminer.layout import LAParams, LTContainer, LTTextBox
from pdfminer.converter import PDFPageAggregator

# chardetによるエンコーディングの判定とテキストデータのデコード
# csvやtxtなどのテキストは、エンコードが結構バラバラなので、これで判定しないと半角文字ですらエラーになる事がある。
# utf-8とか、shift-jisだけでなく、BOM(Byte Order Mark)付きとか、utf-16とか、ビッグエンディアンとか、深ーい世界がテキストにはある。
from chardet import detect

# これらのライブラリで、OFFICE系のドキュメントを取り込む。
# こんなライブラリを無料で使わせてくれるって、みなさん偉い!。
import openpyxl
import pptx
import docx  # Python-docxのインポートは、なぜか、import python-docxではない。

# openpyxlsを使うと、意味のないwarrningが出てくることがあるので、表示を抑制することにした。
# これがなくても、動作は変わらない。
openpyxl.reader.excel.warnings.simplefilter('ignore')


def janome_str(text, ext):
    """日本語文節解析
    Args:
        text (str): 分析したいドキュメント全体の文書
        ext (str):拡張子(pdfの場合のみcidを除外しなきゃいけなかったので、苦肉の策)
    Returns:
        str: 分析結果
    """
    '''
    日本語の解析用です。解析の仕方でずいぶん変わる。
    参考にしたページ
    https://ohke.hateblo.jp/entry/2017/11/02/230000
    https://mocobeta.github.io/janome/
    https://nanjamonja.net/archives/793
    https://qiita.com/kromiii/items/42273d805c7ddf5a64d2
    '''

    words = []

    tokenizer = Tokenizer()
    char_filters = [UnicodeNormalizeCharFilter(), RegexReplaceCharFilter(
        r"[?.*/~=()〝 <>::《°!!!?()-]+", "")]
    token_filters = [POSKeepFilter(["名詞"]), POSStopFilter(
        ["名詞,非自立", "名詞,数", "名詞,代名詞", "名詞,接尾"]), LowerCaseFilter()]

    a = Analyzer(char_filters=char_filters, tokenizer=tokenizer,
                 token_filters=token_filters)

    for token in a.analyze(text):
        # pdfを分析すると、cidという文章と関係ない文字列が拾われてしまう。
        # CID (Character Identifier) は、Adobe Systems が開発したフォントエンコーディング方式であり、PDF ファイル内のテキスト表示に使用
        # なぜか、テキストとして取り出されてしまうので、やむを得ずpdfの時だけ"cid"という単語を除去している。
        if ext == 'pdf' and token.base_form == 'cid':
            continue
        words.append(token.base_form)
    return ' '.join(words)


# mecabで文字区切り分析する場合(こっちの方がいろいろ設定できるらしい)
# しかし、インストールが面倒なので、自分以外の環境で動かすには適さない。
# だったけど、wrapperのおかげで、installが簡単になったらしい。
def mecab_str(text, ext):
    """日本語文節解析
    Args:
        text (str): 分析したいドキュメント全体の文書
        ext (str):拡張子(pdfの場合のみcidを除外しなきゃいけなかったので、苦肉の策)
    Returns:
        str: 分析結果
    """
    mecab = MeCab.Tagger()
    parsed = mecab.parse(text)
    lines = parsed.split('\n')
    lines = lines[0:-2]
    word_list = []

    for line in lines:
        tmp = re.split('\t|,', line)    # tabで区切ってるテキストを、切り離す

        # 名詞のみ対象
        if tmp[1] in ["名詞"]:
            # さらに絞り込み
            if tmp[2] in ["一般", "固有名詞"]:
                # pdfを分析すると、cidという文章と関係ない文字列が拾われてしまう。
                # CID (Character Identifier) は、Adobe Systems が開発したフォントエンコーディング方式であり、PDF ファイル内のテキスト表示に使用
                # なぜか、テキストとして取り出されてしまうので、やむを得ずpdfの時だけ"cid"という単語を除去している。
                if ext == 'pdf' and tmp[0] == 'cid':
                    continue
                else:
                    word_list.append(tmp[0])

    return " " . join(word_list)


def pptx2cloud(scan_folder, out_folder):
    """PowerPointドキュメントのワードクラウド化
    Args:
        scan_folder (str): スキャンするフォルダー
        out_folder (str): ワードクラウド分析の結果のpng形式のファイルを保存するフォルダー
    Returns:
        int: ワードクラウド分析したファイルの数
    """

    print("■これから、パワーポイントをファイル毎に解析します。")
    print("■解析中のファイル名を表示します。")

    number_of_files = 0
    scan_path = scan_folder + '*.pptx'
    filepaths = glob(scan_path)  # globで一気に全ファイル名を取得できる。

    for filepath in filepaths:
        print(filepath)
        results = ''
        try:
            presentation = pptx.Presentation(filepath)
        except:
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            print('★★★' + filepath + 'は開けない(多分PW付)のでスキップしました。★★★')
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            continue

        number_of_files += 1

        # すべてのスライドを解析して文字を取り出す。
        for slide in presentation.slides:
            for shape in slide.shapes:
                # 文字列以外は読み飛ばす
                if not shape.has_text_frame:
                    continue
                for par in shape.text_frame.paragraphs:
                    for run in par.runs:
                        results += run.text

        if results != '':
            text2wordcloud(results, filepath, out_folder, 'pptx')

    return number_of_files


def text2cloud(scan_folder, out_folder, ext):
    """text形式のファイルワードクラウド化
        TXTとCSVどちらもこのサブルーチンで処理するようにした。
        そのため、拡張子も入力としている。
        テキストファイルならどんな拡張子でも対応可能。
    Args:
        scan_folder (str): スキャンするフォルダー
        out_folder (str): ワードクラウド分析の結果のpng形式のファイルを保存するフォルダー
        ext(str):拡張子
    Returns:
        int: ワードクラウド分析したファイルの数
    """
    print("■これから、" + ext + "ファイルを解析します。")
    print("■解析中のファイル名を表示します。")

    number_of_files = 0
    scan_path = scan_folder + '*.' + ext
    filepaths = glob(scan_path)  # globで一気に全ファイル名を取得できる。

    for filepath in filepaths:
        number_of_files += 1
        print(filepath)

        # ファイルのキャラクターコードを調べるために、一度バイナリーモードで読む。
        # 一部読んで調べる方法もあるけど、こっちが簡単だった。
        with open(filepath, 'rb') as f:
            b = f.read()
        enc = detect(b)

        with open(filepath, encoding=enc['encoding']) as f:
            text = f.read()
            text2wordcloud(text, filepath, out_folder, 'txt')

    return number_of_files


def get_text_include(layout):
    """pdfを分解して、内部の分を取り出すサブルーチン
    Args:
        layout (str): 取り出したいpdfの塊?
    Returns:
        str: 取り出した結果(内包したテキストを含む場合もある)
    """
    # テキストならそのまま返す
    if isinstance(layout, LTTextBox):
        #        return [layout.get_text()]
        return layout.get_text()

    # Containerはテキストなどを内包ため再帰探索
    if isinstance(layout, LTContainer):
        text_list = ''
#        text_list = []
        for child in layout:
            #            text_list.extend(get_text_include(child))
            text_list += get_text_include(child)
        return text_list
    return ''
#    return []


def pdf2cloud(scan_folder, out_folder):
    """PDF形式のワードクラウド化
    Args:
        scan_folder (str): スキャンするフォルダー
        out_folder (str): ワードクラウド分析の結果のpng形式のファイルを保存するフォルダー
    Returns:
        int: ワードクラウド分析したファイルの数
    """
    print("■これから、PDFファイルを解析します。")
    print("■解析中のファイル名を表示します。")
#    text = ''

    # ここら辺は、pdfminer.six用のおまじない。意味がよくわからない。
    laparams = LAParams(detect_vertical=True)
    resource_manager = PDFResourceManager()
    device = PDFPageAggregator(resource_manager, laparams=laparams)
    interpreter = PDFPageInterpreter(resource_manager, device)

    number_of_files = 0
    scan_path = scan_folder + '*.pdf'
    filepaths = glob(scan_path)  # globで一気に全ファイル名を取得できる。

    for filepath in filepaths:
        print(filepath)
        text = ''

        try:
            file = open(filepath, "rb")  # リードオンリー&バイナリーのモードで開きます。
        except:
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            print('★★★' + filepath + 'は開けない(多分PW付)のでスキップしました。★★★')
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            continue

        for page in PDFPage.get_pages(file, caching=True, check_extractable=False):
            try:
                interpreter.process_page(page)
            except:
                # 訳の分からないエラーになることがあったので、やむを得ず、スキップすることにしました。
                print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
                print('★★★' + filepath + '★★★')
                print('は、interpreter.process_page(page)がエラーになるのでスキップします。')
                print('ページ情報「' + str(page) + '')
                print('これは、pythonのpdfライブラリ(pdfminer)、もしくはpdfそのものの問題と思われます。')
                print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
                continue
            layout = device.get_result()
            text += get_text_include(layout)

        if text != '':
            number_of_files += 1
            text2wordcloud(text, filepath, out_folder, 'pdf')

    return number_of_files


def docx2cloud(scan_folder, out_folder):
    """ワードドキュメントのワードクラウド化
    Args:
        scan_folder (str): スキャンするフォルダー
        out_folder (str): ワードクラウド分析の結果のpng形式のファイルを保存するフォルダー
    Returns:
        int: ワードクラウド分析したファイルの数
    """
    print("■これから、ワードファイルを解析します。")
    print("■解析中のファイル名を表示します。")

    number_of_files = 0
    scan_path = scan_folder + '*.docx'
    filepaths = glob(scan_path)  # globで一気に全ファイル名を取得できる。

    for filepath in filepaths:
        try:
            doc = docx.Document(filepath)
        except:
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            print('★★★' + filepath + 'は開けない(多分PW付)のでスキップしました。★★★')
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            continue

        print(filepath)
        number_of_files += 1
        results = []
        for para in doc.paragraphs:
            results.append(para.text)

        # ワード内の表からもデータを取得することにした。
        # セル結合されていると、何度も同じ情報が出てくるので、
        # 簡易的に、ひとつ前と同じ内容は取得しないようにした。
        # 正確ではないが、表のデータをワードクラウド分析しないよりもマシかな。
        # もっといい方法が分かったら教えてください。
        for tbl in doc.tables:
            last_text = ''
            for row in tbl.rows:
                for cell in row.cells:
                    new_text = cell.text
                    # テキストが空ならスキップ
                    if new_text == '':
                        continue
                    # ひとつ前と同じテキストならスキップ
                    if new_text == last_text:
                        continue
                    results.append(new_text)
                    last_text = new_text

        text = '\n'.join(map(str, results))
        text2wordcloud(text, filepath, out_folder, 'docx')

    return number_of_files


def xlsx2cloud(scan_folder, out_folder):
    """エクセルドキュメントのワードクラウド化
    Args:
        scan_folder (str): スキャンするフォルダー
        out_folder (str): ワードクラウド分析の結果のpng形式のファイルを保存するフォルダー
    Returns:
        int: ワードクラウド分析したファイルの数
    """
    scan_path = scan_folder + '*.xlsx'
    filepaths = glob(scan_path)  # globで一気に全ファイル名を取得できる。

    print("■これから、エクセルファイルのすべてのシートを読んで、解析します。")
    print("■解析中のファイル名、シート名を表示します。")

    number_of_files = 0

    for filepath in filepaths:
        results = []
    # 今回はセル単位に処理したいのではないため、openpyxlsの方がよさそう!
    # エクセルからデータフレームに読み込む
        try:
            wb = openpyxl.load_workbook(filepath, read_only=True)
        except:
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            print('★★★' + filepath + 'は開けない(多分PW付)のでスキップしました。★★★')
            print('★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★')
            continue

        print(filepath)
        number_of_files += 1
        for ws in wb.worksheets:
            print('シート [' + ws.title + ']')
            for rows in ws.iter_rows():
                for cell in rows:
                    if cell.value is not None:
                        results.append(str(cell.value))
        text = ','.join(map(str, results))
        text2wordcloud(text, filepath, out_folder, 'xlsx')

    return number_of_files


def text2wordcloud(text, filepath, out_folder, ext):
    """テキストをワードクラウド分解して、png形式の画像として出力する。
    Args:
        text (str): 分解したい文書
        filepath (str): 元のファイル(フルパス)
        out_folder (str): 結果出力するフォルダー
        ext(str):ファイル種別
    """

    # f_path がないとワードクラウド内の日本語が文字化けする。
    # 下記どちらでもいいけど、後者の方が良く紹介されてる。なぜか、インストール場所が違う。
    # プロパティーで見ないと、ファイルのパス(場所)もファイル名もわからない。
    f_path = "C:/Windows/Fonts/YuGothM.ttc"  # 見た目は「Yu Gothic UIのYu Gothic UI標準」
    f_path = os.environ['LOCALAPPDATA'] + \
        "/Microsoft/Windows/Fonts/ipaexg.ttf"  # 見た目は「IPAexゴシック 標準」
    # 元々入っているフォントで試したら、これでもできることが分かったので、最終的にこれにしました。
    f_path = "C:/Windows/Fonts/msgothic.ttc"  # とりあえず、Windowsなら標準で入っているフォントにしてみた。

    total_filepath = out_folder + SUM_FILE
    # シフトJISに出来ない文字は無視してしまえ!(encode出来ないと?になるのを利用して、?に変換した?。)
    # という乱暴なやり方にします。
    text = text.encode('cp932', 'replace')
    text = text.decode('cp932')

    if filepath != '':
        # 個別ファイル集計時に、全ファイル用のデータに書き出しておく

        # 日本語文節解析エンジンは、mainの初期値で設定。
        if ANALYZE_ENGINE == 'janome':
            text = janome_str(text, ext)
        else:
            text = mecab_str(text, ext)

# なぜか、pdfには変な "改行もどき" がある場合があるので削除する。
        text = text.replace('\ n', '')

        # ファイル名も特殊文字がある場合があるので、標準文字だけにしないと、書き込めない。
        file_name_memo = os.path.basename(filepath)
        file_name_memo = file_name_memo.encode('cp932', 'replace')
        file_name_memo = file_name_memo.decode('cp932')

        ttl_file = open(total_filepath, mode='a', encoding='cp932')
        ttl_file.write(file_name_memo + text + '\n')
        ttl_file.close()

        if DEBUG_MODE:
            dbg_name = os.path.basename(filepath) + '_dbg_.txt'
            dbg_filepath = out_folder + dbg_name
            dbg_file = open(dbg_filepath, mode='a', encoding='cp932')
            dbg_file.write(text)
            dbg_file.close()

    elif os.path.exists(total_filepath):
        # 最後にファイル指定なしで、呼ばれる。
        # その場合は、全てのファイルのキーワードを分析する。
        # しかし、フォルダ内のファイルに一つも文字がない場合、total_filepathは存在しない。
        ttl_file = open(total_filepath, mode='r', encoding='cp932')
        text = ttl_file.read()
        ttl_file.close()
        filepath = total_filepath

    else:
        # 指定したフォルダーに一つもテキストがなかった場合ここに来る
        # 一つもないのにワードクラウドを起動するとエラーになる。
        return

    if text != '':
        # これだけでワードクラウドにしてしまうライブラリに感謝!
        # サイズは、最近のPCの画面サイズと同じにしてみました。
        word_cloud = wc.WordCloud(
            width=1920, height=1080, font_path=f_path, collocations=False).generate(text)
        png_name = os.path.basename(filepath) + '_wc.png'
        png_filepath = out_folder + "/" + png_name
        word_cloud.to_file(png_filepath)

    return


def help_screen():
    """説明画面
    この画面は、それほど邪魔な画面とは思えないので、常に表示することにしました。
    """
    sub_window = tk.Toplevel()
    sub_window.title("仕様などの説明")
    sub_window.geometry("700x400")
# sub_windowsの大きさを変えたら、help_txのwidth,heightも変えないと見た目がおかしくなることがある。

# サブフレームの作成と設置
    sub_frame = ttk.Frame(sub_window)
    sub_frame.grid(column=0, row=0, sticky=tk.NSEW, padx=5, pady=10)

    help_tx = tk.Text(sub_frame, width=90, height=25,
                      padx=10, pady=10, wrap=tk.NONE)

    help_hsb = tk.Scrollbar(sub_frame, orient='h', command=help_tx.xview)
    help_tx.configure(xscrollcommand=help_hsb.set)    # 水平スクロールバーをアタッチ
    help_vsb = tk.Scrollbar(sub_frame, orient='v', command=help_tx.yview)
    help_tx.configure(yscrollcommand=help_vsb.set)    # 垂直スクロールバーをアタッチ

# 画面の場所はEast West South Northで表すそうです。
    help_tx.grid(column=0, row=5, sticky=('e', 'w'))    # 密着するよう配置
    help_hsb.grid(column=0, row=6, sticky=('e', 'w'))
    help_vsb.grid(column=1, row=5, sticky=('n', 's', 'w'))

# これ、ヘルプの文書を書きやすい!
    help_tx.insert('1.0', '''
※このアプリの説明

【たくさんある文書の出現頻度が分かるように、ワードクラウドを使って画像で表示する。】

  【使い方】

   1.分析したいファイル(※)を一つのフォルダに分けて入れて、
     そのフォルダを指定する。
   ※エクセル、ワード、パワーポイント、PDF、CSV、TXT
   ※下記の仕様に書いた通り、エクセルは「.xlsx形式」、ワードは「.docx形式」、
   パワーポイントは「*.pptx形式」である必要がある。

   2.フォルダを指定したら「集計開始」ボタンを押す。

   3.指定したフォルダに「output_ほにゃらら」というフォルダが出来て、そこに集計結果が集計される。
   「ほにゃらら」は、日本語解析つーつのmecabまたはjanome。プログラムで指定してください。


  【仕様】

   集計結果は常に上書きします。

   なお、本プログラムは、「.___x形式」のオフィスファイルしか参照しない。
   したがって、古い形式のオフィスファイルは、あらかじめ新しくしておく必要があります。

   エクセル用には、別途、「xls2xlsx.xlsm」というエクセルマクロも用意しています。
   使い方は、簡単で、変換したいファイルがあるフォルダに上記のマクロエクセルを
   コピーして実行すると、同じフォルダ内の旧エクセル形式を新形式にして、
   旧形式のファイルを削除します。

   ワード、パワーポイントも最新の「.___x」形式しか読めませんが、変換プログラムは作ってません。


   (プログラムの全体の流れの概要説明)
   1.データがあるフォルダ名を選択。
   2.次に上記で選択したフォルダから各フォーマット別に1ファイルずつ読み込んで、テキスト化し、
       ワードクラウドを使って、利用頻度が多い文字を大きくした画像ファイルに出力する。
   3.上記、2を全ファイル分繰り返す。
   4.最後に、すべての文字をワードクラウド分析する。

   P.S.この画面は、それほど邪魔な画面とは思えないので、常に表示することにしました。
    '''[1:-1])


class Application():
    """これが、メインルーティンに相当する。
    勉強のため、クラス記述してみたけど、クラスにした意味はない。
    """

    def __init__(self):

        self.root = tk.Tk()

# メインウィンドウの設定
        self.root.title("【ワードクラウド集計】")
        self.root.geometry("600x290")

# メインフレームの作成と設置
        self.frame = ttk.Frame(self.root)
        self.frame.grid(column=0, row=0, sticky=tk.NSEW, padx=5, pady=10)

# 各種ウィジェットの作成
        self.button_folder = ttk.Button(
            self.frame, text="参照", command=self.ask_folder)
        self.button_folder.grid(row=2, column=2, sticky=tk.E)

        self.label_folder = ttk.Label(self.frame, text="フォルダ指定:")
        self.label_folder.grid(row=2, column=0, padx=10, pady=2, sticky=tk.E)

        self.entry_folder = tk.StringVar()
        self.entry_exec_folder = ttk.Entry(
            self.frame, textvariable=self.entry_folder, width=60)
        self.entry_exec_folder.grid(row=2, column=1, sticky=tk.W)

        self.button_execute = ttk.Button(
            self.frame, text="集計開始", command=self.click_execute)
        self.button_execute.grid(row=30, column=1, pady=10)

        help_screen()

        self.root.mainloop()

    def ask_folder(self):
        """
        entry_folderにチェックする親のディレクトリをセットする。
        """
        if now_processing:  # 実行中は無効にする。
            return
        fld = filedialog.askdirectory()
        self.entry_folder.set(fld)

    def click_execute(self):
        """実行ボタンで実行するメソッド(関数)
        フォルダー名を画面からgetし、実行可能な状態かチェックし、
        OKであれば、別スレッドとして集計・出力(main_syuukei)を起動する。
        時間がかかるプログラムは、別のスレッドとする。
        画面と別のスレッドにしないと、タイムアウトすることがあるらしい。
        """

        if now_processing:  # 実行中は無効にする。
            return

        scan_folder = self.entry_exec_folder.get() + '/'
        out_folder = scan_folder + 'output_' + ANALYZE_ENGINE + '/'

        if scan_folder == "/":
            messagebox.showerror('エラー', 'フォルダ指定が必要です。')
            return

        if not os.path.exists(scan_folder):    # ディレクトリがない場合もエラー
            messagebox.showerror('エラー', 'フォルダ指定が存在しません。')
            return

        self.button_execute["text"] = "集計中..."
        self.label_exec = ttk.Label(self.frame, text="集計を実施中です")
        self.label_exec.grid(row=40, column=1, pady=10)

        self.progress_bar = ttk.Progressbar(
            self.frame, maximum=10, value=0, length=100, mode='indeterminate')
        self.progress_bar.grid(
            row=40, column=1, sticky=(tk.N, tk.E, tk.S, tk.W))
        self.progress_bar.start(200)

        main_thread = threading.Thread(target=self.main_syuukei, args=(
            [scan_folder, out_folder], ))
        main_thread.start()

    def main_syuukei(self, list_args):
        """ファイルを種類別にすべて分析させるメインの部分。
        afterを使うときは、パラメータは1つしか渡せないので、対応できるようLIST形式にしている。

        Args:
            list_args (list): スキャンするフォルダのパス、結果を出力するフォルダ
        """

        global now_processing
        now_processing = True

        scan_folder = list_args[0]
        out_folder = list_args[1]

        if not os.path.exists(out_folder):    # 出力用ディレクトリがない場合、作成する
            print("出力用ディレクトリを作成します")
            os.makedirs(out_folder)

        # 集計用ファイルに前回の結果が残っていたら削除する。
        total_filepath = out_folder + SUM_FILE
        if os.path.exists(total_filepath):
            os.remove(total_filepath)

        # 表示変わるかなあ?・・・変わらない・・・Threadingしたら変わるようになった!なぜ?

        number_of_all_files = 0

        self.button_execute["text"] = " エクセル集計中..."
        self.root.update()
        number_of_all_files += xlsx2cloud(scan_folder, out_folder)

        self.button_execute["text"] = " ワード集計中..."
        self.root.update()
        number_of_all_files += docx2cloud(scan_folder, out_folder)

        self.button_execute["text"] = " パワーポイント集計中..."
        self.root.update()
        number_of_all_files += pptx2cloud(scan_folder, out_folder)

        self.button_execute["text"] = " PDF集計中..."
        self.root.update()
        number_of_all_files += pdf2cloud(scan_folder, out_folder)

        text_ext = "csv"
        self.button_execute["text"] = text_ext + "集計中..."
        self.root.update()
        number_of_all_files += text2cloud(scan_folder, out_folder, text_ext)

        text_ext = "txt"
        self.button_execute["text"] = text_ext + "集計中..."
        self.root.update()
        number_of_all_files += text2cloud(scan_folder, out_folder, text_ext)

        text_ext = "text"
        self.button_execute["text"] = text_ext + "集計中..."
        self.root.update()
        number_of_all_files += text2cloud(scan_folder, out_folder, text_ext)

        text_ext = "md"
        self.button_execute["text"] = text_ext + "集計中..."
        self.root.update()
        number_of_all_files += text2cloud(scan_folder, out_folder, text_ext)

        text_ext = "htm"
        self.button_execute["text"] = text_ext + "集計中..."
        self.root.update()
        number_of_all_files += text2cloud(scan_folder, out_folder, text_ext)

        text_ext = "html"
        self.button_execute["text"] = text_ext + "集計中..."
        self.root.update()
        number_of_all_files += text2cloud(scan_folder, out_folder, text_ext)


# 他に、集計したいテキストファイルがあれば、以下の”XXX"の部分を変えて
# コメント外せば、集計できるようになります。

#        text_ext = "XXX"
#        self.button_execute["text"] = text_ext + "集計中..."
#        self.root.update()
#        number_of_all_files += text2cloud(scan_folder, out_folder, text_ext)

        self.button_execute["text"] = " 全データ集計中..."
        self.root.update()
        text2wordcloud('', '', out_folder, 'txt')

        print("finish!")
        self.progress_bar.grid_remove()
        self.button_execute["text"] = "集計完了"
        self.label_exec = ttk.Label(
            self.frame, text="                集計を完了しました               ")
        self.label_exec.grid(row=40, column=1, pady=10)

        end_message = 'お待たせしました。集計完了しました。'
        messagebox.showinfo('終了', end_message)
        now_processing = False
        self.root.destroy()

# これが、python流のメインを呼ぶ儀式
# 直接起動した場合にメインルーティンを起動するようになっている。


if __name__ == "__main__":
  # 委託の有無が必要ならTrueに変更する。
    now_processing = False

    # 文節解析の方法によって結果が変わるので、有名どころのMecab,janomeどちらかを選択できるようにしてある。
    # Mecabはインストールが面倒なので、他の環境でも使いやすくするには、
    # janomeが楽だけど、比較すると10倍くらい遅いらしい。
    # 最近はwrapper等が充実してきてmecabもpipだけで、簡単にインストール出来そうなので、janomeを使う意味ないかも。
    # とはいえ、比較すると違いは出てきます。どちらが正確という感じではないですが。

    ANALYZE_ENGINE = 'mecab'
    # janomeの方が10倍くらい遅いそうです。
    # 遅くてもいいから、janomeを使いたい場合は、次の行をコメントから有効にしてください。
    # ANALYZE_ENGINE = 'janome'

    SUM_FILE = '全データ集計.txt'

    DEBUG_MODE = False
#    DEBUG_MODE = True

    app = Application()

# %%
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3