
Tömeges állományok létrehozása táblázatból Pythonban
Valós eset Fájl műveletek Excel Python
2023. junius 14.
Korábban írtam egy esetről, amikor tömeges állományokat kellett létrehozni, majd ehhez elkészült egy kis NodeJS csomag is, amihez szintén volt egy bejegyzés Tömeges állományok létrehozása táblázatból címen.
Ahogy a korábbi programban is az állománynevek következetesek és könnyen előállíthatóak voltak Excelben, ezért készült a program, úgy hogy ezt a forrást használja és így generálja le az állományokat. Most egy ugyanilyen program elkészítését be is mutatom csak Pythonba. Fontosnak tartom, hogy egy probléma megoldásra több irányt is tudjak mutatni.
Felkészülés
Az első lépésünk, hogy megnézzük, milyen kész csomagok állnak rendelkezésre. Tudjuk, hogy táblázatot kell beolvasni és azt feldolgozni. Ehhez a legkézenfekvőbb a pandas
nevű csomag volt, ami igazából adat feldolgozására és elemzésére szolgál, éppen ebből adódóan nagyon sok fajta állományt betud olvasni, és fel tudja dolgozni annak a tartalmát. Külön táblázat beolvasási függénybe is van (read_excel()
), ami kezeli a munkalapokat és egy a fejlécből előállított kulcsokkal ellátott listát ad vissza, ez pont az, amire szükségük van.
Ezen túl fontos, hogy át kell gondolni már előre, hogy mik azok a paraméterek, amiket szeretnénk, ha változók lennének. Ami a legfontosabb, hogy a forrásfájl átadható legyen, hiszen szükségünk van arra, hogy bármilyen táblázatot meg tudjunk adni a programnak anélkül, hogy a kódhoz hozzá kelljen nyúlni. Ezen túl megadható legyen a célkönyvtár és pár opcionális paraméter. Ezek valójában nem térnek el a korábbi bejegyzésben is használt paraméterektől.
Lássuk mik is ezek pontosan:
Megnevezés | Paraméter | Paraméter röviden | Alapértelmezett érték | Kötelező mező | Leírás |
---|---|---|---|---|---|
Forrásállomány | —source | -s | nincs | kötelező | Forrás fájl elérése |
Célkönyvtár | —target | -t | dist/ | nem kötelező | Célkönyvtár, ahová menteni szeretnénk |
Könyvtárak létrehozás | —dirs | -d | true | nem kötelező | Minden állomány saját könyvtárat kapjon |
Fejléc hozzáadása | —addheaderkey | -a | true | nem kötelező | A fejléc értékének hozzáadása a tartalomhoz. |
Elválasztó | —join | -j | = | nem kötelező | A célfájlban a fejléc és az érékének az elválaszója |
Szuper, ha ezt már összeszedük, akkor nincs más teendőnk, mit bekérni őket.
Értékek beolvasása
Szernecsére erre is van egy szuper jó csomag, aminek a neve argparse
. Ennek előnye, hogy csak fel kell paraméterezni, hogy milyen értékeket várunk és minden mást lekezel. Kezeli, ha egy paraméter kötelező, de nem adtuk meg, vagy adhatunk alapértelmezett értékeket és akkor azt kapjuk vissza abban az esetben, ha nem adott mást meg a felhasználó. Mindenféle többi bonyolult paraméterezést is lehet adni neki, de nekünk nagyjából ennyire van szükségünk. A cél, hogy ha valami tényleg jól megvan már írva és működik, akkor ne írjuk meg újra csak használjuk. Lássuk, hogy is működik.
Először is telepítsük a csomagot.
$ pip install argparse
Majd hívjuk be a programunkba. Ide most beteszem összes csomagot, amit használni fogunk.
import os
import argparse
import pandas as pd
A következő és nagyon fontos lépés, hogy paraméterezzük fel a függvényünket, hogy meg tudjuk neki mondani, hogy mit várunk pontosan.
# Argumentek beolvasása
parser = argparse.ArgumentParser(description="Get parameters", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-t", "--target", help="Target directory when saved all file", default= "dist/")
parser.add_argument("-d", "--dirs", help="Create directory every files", default="true", choices=["true", "false"])
parser.add_argument("-a", "--addheaderkey", help="Add header key in content with join separator", default="true", choices=["true", "false"])
parser.add_argument("-j", "--join", help="The target file content line and header separator", default= "=")
parser.add_argument("source", help="Source location")
Létre kell hozni egy parser
-t, ami kezeli ezt az egészet, majd láthatjuk, hogy csak simán ehhez hozzá kell adni a paramétereket. Ez a függvény vár bemeni értékeket, ilyen érték a rövid kapcsoló (-), majd ezt követi a hosszú is(—). Ezen túl lehet neki adni egy help
szövegest, ami leírásként segítséget nyújthat a paraméterezés során, és ezen túl egy default
értéket, hogy mi legyen az alapértelmezett érék, abban az esetben, ha nem lett átadva hozzá semmi.
Majd ezektől kicsit eltérően a forrást is hozzá adjuk. Mivel ez egy kötelező mező, így nincs kapcsolója, csak egy neve, amire majd hívatkozhatunk és egy leírása.
Amennyiben ezzel megvagyunk akkor mondhatjuk, hogy fel is van készítve a programunk a paraméterek bekérésre. Már csak ki kell kérni a megkapott értékeket.
args = parser.parse_args()
arguments = vars(args)
Ezzel már biztosítva is van a paraméterek beolvasása. Abban az esetben, ha minden kötelező paraméter ki van töltve, akkor az arguments
nevű változóban lesz az összes bejövő változó. Itt megtalálunk mindent, amire szükségünk van. Ezeket vegyük külön változókba és alakítsuk át úgy, hogy nekünk később csak használni kelljen.
source=arguments["source"]
targetDir=arguments["target"] if arguments["target"].endswith("/") else f'{arguments["target"]}/'
createDirs = True if arguments['dirs'] == "true" or arguments['dirs'] == "True" else False
addkey = True if arguments['addheaderkey'] == "true" or arguments['addheaderkey'] == "True" else False
join =arguments['join']
A forrás csak vegyük ki, az úgy jó ahogy van. A cél könyvtár végére tegyünk egy /
jelet, ha nincs, hiszen ez egy könyvtár. Így késöbb csak használni kell és az állomány nevét is csak utána kell tenni. A következő pedig, hogy megnézzük, hogy a könyvtárakat létre kell-e hozni vagy sem. Itt egy eldöntendő boolean
igaz/hamis (True/False
) értéket várunk, azonban a bejövő érték az egy szöveg és ezért ezt át kell alakítani. Így ha az értéke szöveges true
vagy True
tehát igaz, akkor az értékünk is igaz minden más esetben hamis. Ugyanezt a metodikát használjuk a fejléc hozzáadásnál is. Aztán a legvégén az elválasztót is csak simán kitesszük egy változóba, mert azzal fűzzük össze, amit kaptunk.
Célkönyvtár ellenőrzés
Ellenőrizni kell, hogy a forrás és a célkönyvtár létezik-e vagy sem, mert ha nem akkor garantált a hiba, ha dolgozni szeretnénk benne. Tehát ha nem létezik a forrás, akkor be kell fejezni a program futását és jelezni. Abban az esetben, ha a forrás létezik, de a célkönyvtár nem, akkor csak létre kell hozni számára a könyvtárat.
if not os.path.isfile(source):
exit("The source is not exist: "+source)
if not os.path.isdir(targetDir):
os.mkdir(targetDir)
Táblázat beolvasása
Ha ezzel megvagyunk, akkor be kell olvasni a táblázatot, hogy az adatokat fel tudjuk dolgozni. Igen ám, de elötte meg kell nézni, hogy hány munkalap van összesen, hogy mindet fel tudjuk dolgozni. És hozzunk létre egy számlálót is, hogy az elöző alapján itt is tudjunk egy kimutatást kiírni a végén.
sheet_names = (pd.ExcelFile(source)).sheet_names
print("Table read is complete.")
fileCount=0
Ha ez megvan, akkor már tényleg csak a táblázat beolvasása maradt munkalaponként.
for sheet in sheet_names:
sheetDir=f'{targetDir}{sheet}/'
if not os.path.isdir(sheetDir):
os.mkdir(sheetDir)
data = pd.read_excel(source, sheet)
json_data = data.to_dict(orient="records")
Nézzük mi történik. Végig megyünk az összes munkalapon és létrehozzuk neki a könyvtárat a munkalap nevéből, ha még ilyen nem létezik. Aztán az egészet beolvassuk és egy dictionary
-t (nem szép de leegyszerűsítve egy tömbböt) hozunk létre az adatokkal. Majd a json_data
ditionary-nek az értékéet ki kell írni a célállományba. Ehhez definiáljunk egy metódust, aminek a neve legyen writeFiles
, két bemenő paramétert vár, az egyik a beolvasott adat a másik a célkönyvtár. Ezt adjuk hozzá a kódunk elejéhez, mindenféleképpen a meghívás elött kell lennie, különben nem létezik és hibát kapunk.
writeFiles(json_data, sheetDir)
A kiíró metódus és az adatok feldolgozása
Itt már nincs más teendőnk, mint feldolgozni az adatokat és kiírni azokat.
def writeFiles(dataRows, target):
global fileCount
for data in dataRows:
if "name" not in data:
print("Missing name attribute!")
continue
fileTarget = target
if createDirs == True:
fileTarget = f'{target}{data["name"]}/'
if not os.path.isdir(fileTarget):
os.mkdir(fileTarget)
fileName=f'{fileTarget}{data["name"]}.{data["extension"]}'
del data["name"]
del data["extension"]
content=[]
for key, value in data.items():
content.append(f'{key}{join}{value}' if addkey else str(value))
f = open(fileName, "w", encoding='utf-8')
f.write("\n".join(content))
f.close()
fileCount=fileCount+1
Elöször is a global
utasítással a metóduson belül elérhetővé tesszük a count
számláló változót. Majd végig megyünk az összes soron és ellenőrizzük, hogy van-e benne name
kulcs, azaz a fájlnév értéke. Ha nincs jelezzük, ha van megyünk tovább. Itt is megnézzük, hogy szeretnénk-e az összes állományt külön könyvtárba tenni. Amennyiben igen, akkor a fájllal megegyező nevű könyvtárat létre kell hozni, abban az esetben, ha az nem létezik. Ha pedig nem szeretnénk külön könyvtárazni, akkor csak a munkalappal megegyező könyvtárban ömlesztve lesznek állomány, tehát itt nincs teendő. Ezt követően módosítjuk a célkönyvtárát. Majd a fileName
változóba összerakjuk a végleges állomány nevet a célkönyvtárral együtt, tehát a teljes elérési utat.
Majd a del
utasítással a nevet és a kiterjesztést egyszerűen töröljük az adatok közül, mert többet nincs rá szükségünk. Az állomány tartalmát egy tömbbe tesszük, minden sort bele teszünk az alapján, hogy szükség van-e a fejlécre vagy csak sima szöveges állományt hozunk létre.
Amikor a tartalom is előállt, akkor nincs más teendő, mint ki írni a tartalmat. Minden tömbértéket egy új sorba, erre hívatott a join
, ami összefűzi a tömbböt szöveggé a \n
azaz új sor karakterrel. Majd növeljük a kiírt állományok számát.
Már csak egy dolog maradt, jelezzünk vissza, ha kész és ha tudunk adjunk plusz információt is főleg, ha már számoltunk.
print(f'Finish. Created {len(sheet_names)} main liblaries ({", ".join(sheet_names)}) and {fileCount} files.')
Futtasuk és teszteljük is a programot
$ py main.py sorce.xlsx -t dist/
Lássuk egyben a kódot
A teljes szoftvert feltettem a GitHub tárolóba. Itt megtaláltok több futattási példát és minta kimeneteket is.
https://github.com/dovicsin/python-bulk-file-creator
Maga a kód
import os
import argparse
import pandas as pd
# Argumentek beolvasása
parser = argparse.ArgumentParser(description="Get parameters", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-t", "--target", help="Target directory when saved all file", default= "dist/")
parser.add_argument("-d", "--dirs", help="Create directory every files", default="true", choices=["true", "false"])
parser.add_argument("-a", "--addheaderkey", help="Add header key in content with join separator", default="true", choices=["true", "false"])
parser.add_argument("-j", "--join", help="The target file content line and header separator", default= "=")
parser.add_argument("source", help="Source location")
args = parser.parse_args()
arguments = vars(args)
source=arguments["source"]
targetDir=arguments["target"] if arguments["target"].endswith("/") else f'{arguments["target"]}/'
createDirs = True if arguments['dirs'] == "true" or arguments['dirs'] == "True" else False
addkey = True if arguments['addheaderkey'] == "true" or arguments['addheaderkey'] == "True" else False
join =arguments['join']
if not os.path.isfile(source):
exit("The source is not exist: "+source)
if not os.path.isdir(targetDir):
os.mkdir(targetDir)
sheet_names = (pd.ExcelFile(source)).sheet_names
print("Table read is complete.")
fileCount=0
def writeFiles(dataRows, target):
global fileCount
for data in dataRows:
if "name" not in data:
print("Missing name attribute!")
continue
fileTarget = target
if createDirs == True:
fileTarget = f'{target}{data["name"]}/'
if not os.path.isdir(fileTarget):
os.mkdir(fileTarget)
fileName=f'{fileTarget}{data["name"]}.{data["extension"]}'
del data["name"]
del data["extension"]
content=[]
for key, value in data.items():
content.append(f'{key}{join}{value}' if addkey else str(value))
f = open(fileName, "w", encoding='utf-8')
f.write("\n".join(content))
f.close()
fileCount=fileCount+1
for sheet in sheet_names:
sheetDir=f'{targetDir}{sheet}/'
if not os.path.isdir(sheetDir):
os.mkdir(sheetDir)
data = pd.read_excel(source, sheet)
json_data = data.to_dict(orient="records")
writeFiles(json_data, sheetDir)
print(f'Finish. Created {len(sheet_names)} main liblaries ({", ".join(sheet_names)}) and {fileCount} files.')