懶人福音:用Python讓Excel數(shù)據(jù)躲貓貓!

全文約2000字膜廊,閱讀大約需要10分鐘

在日常工作中,我們經(jīng)常會(huì)遇到需要處理大量文件并將數(shù)據(jù)存儲(chǔ)至數(shù)據(jù)庫(kù)或整合到一個(gè)文件的需求淫茵。這個(gè)任務(wù)對(duì)于人力和時(shí)間來(lái)說(shuō)都是一大挑戰(zhàn)爪瓜。幸運(yùn)的是,我們有Python這個(gè)神奇的工具匙瘪,可以幫助我們自動(dòng)化這個(gè)任務(wù)铆铆,省時(shí)又省力!現(xiàn)在辆苔,我將向你展示如何使用Python處理Excel文件并將數(shù)據(jù)存儲(chǔ)到PostgreSQL數(shù)據(jù)庫(kù)中算灸。

先打個(gè)底:以理解為主,不夠嚴(yán)謹(jǐn)驻啤,如果看完還是不會(huì),那一定是我講的不夠好荐吵,千萬(wàn)別影響你們探索Python的興趣骑冗。

在我們的奇妙冒險(xiǎn)中,如果你想將多個(gè)excel文件整合到一個(gè)表中先煎,需要滿足一個(gè)前置條件——每個(gè)excel文件的格式和列對(duì)應(yīng)的含義順序必須一致贼涩。但是,如果表頭不一樣也沒(méi)關(guān)系薯蝎,我們可以用程序來(lái)解決這個(gè)問(wèn)題遥倦。本文將帶你進(jìn)入Python的魔法世界,教你如何處理Excel文件并將數(shù)據(jù)存儲(chǔ)到PostgreSQL數(shù)據(jù)庫(kù)中。在開始之前袒哥,我們需要安裝一些神奇的庫(kù):

  • pandas:用于處理Excel文件中的數(shù)據(jù)
  • sqlalchemy:用于連接和操作PostgreSQL數(shù)據(jù)庫(kù)
    安裝方法這里就不再重點(diǎn)講了了缩筛,直接搜網(wǎng)上的教程安裝即可。

1.日志記錄

開局先送送你一串Python日志記錄的代碼堡称,可在任何場(chǎng)景下復(fù)用瞎抛,它能夠?qū)崟r(shí)監(jiān)測(cè)程序的運(yùn)行狀態(tài),輕松解決測(cè)試和問(wèn)題排查的難題却紧。

注意:log_home需要改為自己本地路徑

# 定義日志記錄器
log_home = '/home/xusl/log/excel'  # 請(qǐng)將此路徑改為你自己的本地路徑
log_level = logging.INFO
log_to_console = True
log_config = {
    'version': 1,
    'formatters': {
        'generic': {
            'format': '%(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s',
        },
        'simple': {
            'format': '%(asctime)s %(levelname)-5.5s %(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'generic',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': os.path.join(log_home, 'excel_to_data.log'),
            'encoding': 'utf-8',
            'formatter': 'generic',

        },
    },
    'root': {
        'level': log_level,
        'handlers': ['console', 'file', ] if log_to_console else ['file', ],
    }
}
logging.config.dictConfig(log_config)
logger = logging.getLogger(__name__)

2.數(shù)據(jù)庫(kù)連接

接下來(lái)桐臊,我們需要配置自己的數(shù)據(jù)庫(kù)信息。

# 建立與PostgreSQL數(shù)據(jù)庫(kù)的連接 此處需要更改為自己的數(shù)據(jù)庫(kù)配置
db_user = 'dps'
db_password = 'DPS888'
db_host = '10.12.8.88'
db_port = '5432'
db_name = 'dpstest'


def get_conn():
    conn_url = 'postgresql+psycopg2://{user}:{password}@{host}:{port}/{database}'
    engine = create_engine(conn_url.format(database=db_name, user=db_user, password=db_password, host=db_host, port=db_port),
                                 pool_size=20,
                                 pool_recycle=7200,
                                 connect_args={'connect_timeout': 30})
    try:
        with engine.connect():
            logger.info('成功連接到數(shù)據(jù)庫(kù)')
    except Exception as e:
        logger.error('無(wú)法連接到數(shù)據(jù)庫(kù):', str(e))
    return engine

3.設(shè)計(jì)及創(chuàng)建表結(jié)構(gòu)

根據(jù)文件內(nèi)容來(lái)設(shè)計(jì)和創(chuàng)建表結(jié)構(gòu)晓殊,當(dāng)然你也可以用中文

# 創(chuàng)建存儲(chǔ)數(shù)據(jù)的表
table_name = 'public.excel_data'
ddl = """
DROP TABLE IF EXISTS public.excel_data;
CREATE TABLE IF NOT EXISTS public.excel_data (
    file_nm VARCHAR(255),
    cust_nm VARCHAR(255),
    cert_no VARCHAR(255),
    prod_nm VARCHAR(255),
    amt numeric(20,2),
    crt_dtm timestamp NOT NULL DEFAULT now() -- 創(chuàng)建時(shí)間
);
"""

4.處理數(shù)據(jù)

思路如下:

  • 提取文件名

  • 讀取Excel文件數(shù)據(jù)并提取前4列

  • 列名重命名

  • 根據(jù)條件過(guò)濾末尾的空行

  • 將數(shù)據(jù)存儲(chǔ)到PostgreSQL表中

  • 處理成功后將Excel文件移動(dòng)到end目錄

重點(diǎn)講下to_sql()函數(shù):

  • name:SQL 表名

  • con:與數(shù)據(jù)庫(kù)鏈接的?式断凶,推薦使?sqlalchemy的engine類型

  • schema:相應(yīng)數(shù)據(jù)庫(kù)的引擎,不設(shè)置則使?數(shù)據(jù)庫(kù)的默認(rèn)引擎巫俺,如mysql中的innodb引擎

  • if_exists:當(dāng)數(shù)據(jù)庫(kù)中已經(jīng)存在數(shù)據(jù)表時(shí)對(duì)數(shù)據(jù)表的操作认烁,有replace替換、append追加识藤,fail則當(dāng)表存在時(shí)提?

  • index:對(duì)DataFrame的index索引的處理砚著,為True時(shí)索引也將作為數(shù)據(jù)寫?數(shù)據(jù)表

  • index_label:當(dāng)上?個(gè)參數(shù)index為True時(shí),設(shè)置寫?數(shù)據(jù)表時(shí)index的列名稱

  • chunsize:設(shè)置整數(shù)痴昧,如20000稽穆,?次寫?數(shù)據(jù)時(shí)的數(shù)據(jù)?數(shù)量,當(dāng)數(shù)據(jù)量很?時(shí)赶撰,需要設(shè)置舌镶,否則會(huì)鏈接超時(shí)寫?失敗。

  • dtype:列名到 SQL 類型的字典豪娜,默認(rèn)無(wú);可選地指定列的數(shù)據(jù)類型

完整代碼如下:

import os
import pandas as pd
import logging.config
import shutil
import datetime

from sqlalchemy import create_engine

_tb_nm = 'excel_to_data'
_tb_nm_cn = "excel數(shù)據(jù)入庫(kù)"
_service_code = _tb_nm
# 日志目錄
log_home = '/home/xusl/log/excel'

# 日志level
log_level = logging.INFO

# 日志打印到控制臺(tái)
log_to_console = True

# 配置日志記錄器
log_config = {
    'version': 1,
    'formatters': {
        'generic': {
            'format': '%(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s',
        },
        'simple': {
            'format': '%(asctime)s %(levelname)-5.5s %(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'generic',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': os.path.join(log_home, _tb_nm + '.log'),
            'encoding': 'utf-8',
            'formatter': 'generic',

        },
    },
    'root': {
        'level': log_level,
        'handlers': ['console', 'file', ] if log_to_console else ['file', ],
    }
}
logging.config.dictConfig(log_config)
logger = logging.getLogger(_tb_nm)


# 建立與PostgreSQL數(shù)據(jù)庫(kù)的連接 39數(shù)據(jù)庫(kù)
db_user = 'dps'
db_password = 'DPS888'
db_host = '10.12.8.88'
db_port = '5432'
db_name = 'dpstest'



def get_conn():
    conn_url = 'postgresql+psycopg2://{user}:{password}@{host}:{port}/{database}'
    engine = create_engine(conn_url.format(database=db_name, user=db_user, password=db_password, host=db_host, port=db_port),
                                 pool_size=20,
                                 pool_recycle=7200,
                                 connect_args={'connect_timeout': 30})
    try:
        with engine.connect():
            print('成功連接到數(shù)據(jù)庫(kù)')
    except Exception as e:
        print('無(wú)法連接到數(shù)據(jù)庫(kù):', str(e))
    return engine

# engine = create_engine(f'postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}')


# 創(chuàng)建存儲(chǔ)數(shù)據(jù)的表
table_name = 'public.excel_data'

ddl = """
DROP TABLE IF EXISTS public.excel_data;
CREATE TABLE IF NOT EXISTS public.excel_data (
    file_nm VARCHAR(255),
    cust_nm VARCHAR(255),
    cert_no VARCHAR(255),
    prod_nm VARCHAR(255),
    amt numeric(20,2),
    crt_dtm timestamp NOT NULL DEFAULT now() -- 創(chuàng)建時(shí)間
);
"""


# 遍歷指定目錄下的所有Excel文件
excel_dir = '/home/xusl/data'
src_excel = '/home/xusl/data/src'
end_excel = '/home/xusl/data/end'
src_dir = 'src'
end_dir = 'end'


def deal(conn):
    for filename in os.listdir(src_excel):
        if not filename.endswith('.xlsx'):
            logging.info('沒(méi)有excel文件餐胀!')
            continue
        else:
            logging.info('')
            logging.info('')
            excel_file = os.path.join(src_excel, filename)

            # 提取文件名
            file_nm = os.path.basename(excel_file)
            func_name = file_nm
            logging.info('start %s' % func_name)
            logging.info(f'Reading data from {excel_file}')
            d0 = datetime.datetime.now()

            # 讀取Excel文件數(shù)據(jù)并提取前4列
            try:
                df = pd.read_excel(excel_file, usecols=[0, 1, 2, 3])
                logging.info('df讀取內(nèi)容:%s ' % df)
            except Exception as e:
                logging.error(f'Error reading file {excel_file}: {str(e)}')
                continue

            # 修改列名
            df.columns = ['cust_nm', 'cert_no', 'prod_nm', 'amt']
            logging.info('df修改后內(nèi)容:%s ' % df)

            # 根據(jù)條件過(guò)濾末尾的空行
            if not df.empty and df.iloc[-1].isnull().all():
                df = df[:-1]
                logging.debug('df刪減末尾后:%s ' % df)

            # 將數(shù)據(jù)存儲(chǔ)到PostgreSQL表中
            df['file_nm'] = file_nm
            df = df[['file_nm', 'cust_nm', 'cert_no', 'prod_nm', 'amt']]

            try:
                # 將整個(gè)DF導(dǎo)入數(shù)據(jù)庫(kù)中
                df.to_sql(name='excel_data', schema='public', con=conn, if_exists="append", index=False)
                d1 = datetime.datetime.now()
                s = (d1 - d0).total_seconds()
                logging.info('... end %s, 耗時(shí): %s 秒. ' % (func_name, s))
            except Exception as e:
                logging.error(f'Error inserting data from file {excel_file}: {str(e)}')
                continue

            # 處理成功后將Excel文件移動(dòng)到end目錄
            src_file = os.path.join(src_excel, filename)
            end_file = os.path.join(end_excel, filename)
            try:
                shutil.move(src_file, end_file)
            except Exception as e:
                logging.error(f'Error moving file {src_file} to {end_file}: {str(e)}')

    # 關(guān)閉數(shù)據(jù)庫(kù)連接
    # engine.dispose()


if __name__ == '__main__':
    engine = get_conn()
    deal(engine)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘤载,隨后出現(xiàn)的幾起案子否灾,更是在濱河造成了極大的恐慌,老刑警劉巖鸣奔,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墨技,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挎狸,警方通過(guò)查閱死者的電腦和手機(jī)扣汪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锨匆,“玉大人崭别,你說(shuō)我怎么就攤上這事。” “怎么了茅主?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵舞痰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我暗膜,道長(zhǎng)匀奏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任学搜,我火速辦了婚禮娃善,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瑞佩。我一直安慰自己聚磺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布炬丸。 她就那樣靜靜地躺著瘫寝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稠炬。 梳的紋絲不亂的頭發(fā)上焕阿,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音首启,去河邊找鬼暮屡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛毅桃,可吹牛的內(nèi)容都是我干的褒纲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钥飞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼莺掠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起读宙,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤彻秆,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后结闸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掖棉,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年膀估,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耻讽。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡察纯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饼记,我是刑警寧澤香伴,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站具则,受9級(jí)特大地震影響即纲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜博肋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一低斋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匪凡,春花似錦膊畴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至衬衬,卻和暖如春买猖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滋尉。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工玉控, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兼砖。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓奸远,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親讽挟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子懒叛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容