Python3批量轉換文件編碼

Python3批量轉換文件編碼

| 背景: 我這個程序員菜鳥有一天突然發現,自己的某個很菜鳥的項目,所有文件編碼都是混亂的。這該怎麼辦?急,在線等。

可惜,我終於沒有等到大佬給我推薦什麼好使喚的軟件。於是我覺得我是不是可以自己批量解決一下。

準備工作

  • python3
  • pip install chardet (檢測編碼)

檢測文件編碼

“凡事預則立,不預則廢”,編碼混亂的文件實在太多,還是的好好計劃下:首先,我們檢測一下各個文件的編碼狀況,然後纔可以動工修正。
檢測文件編碼,我們可以使用 chardet 開源庫,用法很簡單,直接將 bytes 傳入即可:

import chardet

f_file = open(path, "rb")
content = f_file.read()
# 結果是一個字典,包含了猜測的編碼與概率
guess_encode = chardet.detect(content)

獲取要檢測編碼的所有文件

“有子存焉,子又生孫,孫又生子,子又有子,子又有孫,子子孫孫無窮匱也”——對於一些個文件夾而言,真的是有非常有深度,它們有非常深的目錄結構。

無論是檢測編碼,還是修正文件編碼,都應先將這許多個文件先查找出來。如何查找?

一般我們想到的是遞歸,但其實針對文件的這個情況,python 的os 模塊已經做好了準備,使用os.walk即可:

import os
import re

    # 深度遞歸遍歷所有文件夾下的文件
    def walk_files(path, regex=r"."):
        if not os.path.isdir(path):
            return [path]
        file_list = []
        for root, dirs, files in os.walk(path):
            for file in files:
                file_path = os.path.join(root, file)
                if re.match(regex, file_path):
                    file_list.append(file_path)

        return file_list
        

使用正則表達式(re模塊),是爲了方便過濾,總有些文件是可能不需要檢測或修改的。
既然獲取了文件列表,那麼遍歷讀取並檢測編碼並不是難事,只需要加上一個循環即可,在循環中我們記錄下編碼的猜測結果,或是打印,或是暫存到最後寫入到報告文件中,不再贅述。

修改文件編碼

python2 的字符串可以說設計得比較糟糕,二進制bytes類型也算是字符串,導致了一系列的混亂。

python3 對這方面做了改進,byte編碼轉換隻需要如下進行即可:

# byte解碼爲字符串
contentStr = content.decode(original)
# 轉爲目標編碼bytes
targetBytes = bytes(contentStr, target)

當然,記得加上try,bytes的解碼需要按照正確方法進行,否則會拋出異常,這相當於是一個解密的過程,用錯了鑰匙將無法打開大門(比如本來是 utf-8 編碼的內容,錯用了 gbk 解碼)

獲取修改完編碼方式的bytes後,我們還需要保存文件:

f_file.seek(0)
f_file.truncate()
f_file.write(targetBytes)

先將文件指針移動到最前面,接着使用 f_file.truncate() 清空指針後所有內容,最後寫入。

終章(實例代碼和截圖)

上文大部分都是在敘述思路,代碼並不完整。不過,最重要的是——進行任何批量操作前,請先備份。但我沒有實現,可以考慮使用 shutil.copytree(原文件夾,新文件夾) 進行備份。

在這裏插入圖片描述
如上圖,chardet 的猜測不一定是正確的,所以需要備份,需要針對某些文件進行一些微調,直到IDE能夠正常顯示或運行。

下面是完整的測試代碼:

# -*- coding: utf-8 -*-
# @Date:2020/1/12 19:04
# @Author: Lu
# @Description

import os
import copy
import re
import chardet


class FileUtil():

    # 深度遞歸遍歷所有文件夾下的文件
    def walk_files(path, regex=None):
        if not os.path.isdir(path):
            return [path]
        file_list = []
        for root, dirs, files in os.walk(path):
            for file in files:
                file_path = os.path.join(root, file)
                if re.match(regex, file_path):
                    file_list.append(file_path)

        return file_list


class EncodeTask():

    def __init__(self):
        self.default_config = {
            "workpaths": [u"./"],
            "filefilter": r"."
        }
        self.config = copy.deepcopy(self.default_config)
        self.work_files = []
        self.workpaths = []

    def update(self, config, fill_default_value=False):
        cache = copy.deepcopy(config)
        for k in self.default_config.keys():
            if cache.get(k):
                self.config[k] = cache[k]
            elif fill_default_value:
                self.config[k] = self.default_config[k]
        self.__gen_files(self.config["workpaths"])
        return self

    def __gen_files(self, workpaths):
        self.work_files.clear()
        for workpath in workpaths:
            self.work_files += FileUtil.walk_files(workpath, self.config["filefilter"])

    def check_encoding(self):
        encoding_report = {"stat": {}, "reports": []}
        for path in self.work_files:
            f_file = open(path, "rb")
            content = f_file.read()
            guess_encode = chardet.detect(content)

            encoding = guess_encode.get("encoding")
            encoding_report["reports"].append([path, guess_encode])
            if not encoding_report["stat"].get(encoding):
                encoding_report["stat"][encoding] = 1
            else:
                encoding_report["stat"][encoding] += 1

            f_file.flush()
            f_file.close()

        reportfile = open(u"./encoding_report.txt", "w",encoding="utf-8")
        reportContent = u"{}\n".format(encoding_report["stat"])

        for item in encoding_report["reports"]:
            reportContent += u"\n{}    {}".format(item[0], item[1])

        reportfile.write(reportContent)
        reportfile.flush()
        reportfile.close()
        print(encoding_report)

    def change_encoding(self, original, target):
        for path in self.work_files:
            print(u"\n{}\nchange {} to {}".format(path, original, target))
            f_file = open(path, "rb+")
            content = f_file.read()
            try:
                # byte解碼爲字符串
                contentStr = content.decode(original)
                # 字符串編碼爲uniccode str
                # unicodeBytes = contentStr.encode("unicode_escape")

                # 轉爲目標編碼bytes
                targetBytes = bytes(contentStr, target)

                # print(targetBytes)

                f_file.seek(0)
                f_file.truncate()
                f_file.write(targetBytes)

            except Exception as e:
                print(u"Error:可能編碼有誤\n{}".format(e))

            finally:
                f_file.flush()
                f_file.close()


def task():
    print("""You can use it like this code:
# -*- coding: utf-8 -*-

    from conver_encode import EncodeTask

    EncodeTask().update({
        "workpaths": [u"./test"],
        "filefilter": r".*\.(?:java)"
    }).check_encoding()

    EncodeTask().update({
        "workpaths": [u"./test"],
        "filefilter": r".*\.(?:java)"
    }).change_encoding("gb18030", "utf-8")

    # }).change_encoding("utf-8", "gb18030")
    # }).change_encoding("Windows-1252", "utf-8")
    """);
    pass


if __name__ == '__main__':
    task()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章