背景
公司項目的廣告展示率太低,需要查找原因,之前做了統計埋點,運維給出過濾后的數據,一個 txt 文件 500M 以上,文件打開非常亂。
特別注意:很多人學Python過程中會遇到各種煩惱問題,沒有人解答容易放棄。為此小編建了個Python全棧免費答疑.裙 :七衣衣九七七巴而五(數字的諧音)轉換下可以找到了,不懂的問題有老司機解決里面還有最新Python實戰教程免非下,,一起相互監督共同進步!

詢問運維是否有工具轉換,回復說沒有,然后想到之前重學 Python3 的初衷,就是為了能寫一些腳本方便處理一些事情。
這個數據主要是 HTTP 的 GET 請求,需要轉碼,然后把參數拿到,目標是寫到 Execl 文件中。
因為一直沒用,語法也記不清楚,只能不停回看筆記,關於 Execl 這塊也沒用過,臨時網上查找。
讀文件
由於原文件太大,所以先復制了一部分存入當前目錄的 test.txt
文件中。
import os file = open('./test.txt') # 打開文件 for line in file: # 按行遍歷文件 ... file.close() # 最后要關閉
遍歷每一行數據后,轉碼,分割,轉成字典結構
import urllib.parse # 定義個方法將一行數據轉成字典 def getOneDict(line): params = dict() # 創建字典保存數據 pre = 'timeAnalysis?' index = line.find(pre) # 找到這個位置,后面的數據是需要的參數 if index > -1: line = line[index+len(pre):].split()[0] # 先截子串然后選空格之前的 line = urllib.parse.unquote(line) # 網上搜到的用於將 HTTP 編碼后的文字解碼 paramList = line.split('&') # 得到類似 s=android 的內若 for item in paramList: param = item.split('=') if (len(param) > 1): # 如果等於號后面有值 params[param[0]] = param[1] # 字典加個數據 return params
創建 Execl 文件
有了每一行對應的字典,就想把這內容寫到 Execl 文檔里。要先切換到 pip3 目錄,執行 sudo pip3 install openpyxl
安裝 openpyxl 模塊,最新的語法參考 http://openpyxl.readthedocs.io/en/stable/
import openpyxl def createExcel(): if 'time.xlsx' in os.listdir('./'): # 如果有了這個文件,就不要再創建了 return wb = openpyxl.Workbook() # 創建 Execl wb.active.title = 'Splash' # 默認有一個活動的 Sheet,把名字改成 Splash wb.create_sheet(title='Home') # 再創建一個 Sheet wb.save('time.xlsx') # 最后一定不能忘了這句
結果本地生成了文件 time.xlsx
,打開后有兩個 Sheet。

向 Execl 中插入數據
看 Execl,橫向是 A、B、C... 這樣編號,縱向是 1、2、3... 這樣編號,看文檔可以通過兩個編號組合來定位。我這里沒用這個函數。
我需要把字典里的 key 作為第一行用作標題,然后每來一個字典里的數據,先尋找是否有了這個 key,有了就往下一行添加對應的 value,沒有的話先添加一列,行數是第一行,值是字典里的這個 key,然后再添加 value。
def writeToExecl(params): wb = openpyxl.load_workbook('time.xlsx') # 打開 Execl 文件 splashSheet = wb['Splash'] # 找到兩個 Sheet homeSheet = wb['Home'] if params['pid'] == '04': # 如果字典里有一個 pid=04 的,要放到 Splash 表里 writeToSheet(splashSheet, wb, params) elif params['pid'] == '00': writeToSheet(homeSheet, wb, params)
def getValue(t): return t.value def writeToSheet(sheet, wb, params): rows = tuple(sheet.rows) # 拿到所有行轉成元組 index = len(rows) + 1 # 在原有的行數的下一行插入數據 print('rows ' + str(index)) titles = list(map(getValue, rows[0])) # 用高階函數 map 轉換第一行,拿到里面的 value,然后要轉成 list if titles == [None]: # 測試發現列表不是空的,長度是 1,里面是 None,所以判斷一下 titles = [] for k,v in params.items(): # 遍歷字典 if k in titles: # 如果第一行有這個標題,直接插值 sheet.cell(row = index, column = titles.index(k) + 1).value = v # 行數為 index,列為這個 key 在的行,cell 里的索引從 1 開始 else: sheet.cell(row = 1, column = len(titles) + 1).value = k # 先插標題,先插了一列 sheet.cell(row = index, column = len(titles) + 1).value = v # 再插字典的 value titles.append(k) # 標題多了一個,要更新 wb.save('time.xlsx') # 保存以生效
遍歷文件
將要分析的文件放入當前目錄的 web 文件夾下,然后遍歷,對每一個文件進行讀取。
for file in os.listdir('./web'): print('read file ' + os.path.basename(file)) readAFile('./web/' + file) def readAFile(filename): file = open(filename) for line in file: params = getOneDict(line) if (len(params) == 0): # 如果字典為空,繼續下一行 continue writeToExecl(params) file.close()
其它
運行時報出了一個錯誤
... self._buffer_decode(data, self.errors, final) UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 3131: invalid start byte ...
網上搜了下,是編碼的問題,沒去特地解決,一兩行數據不影響統計比例,找到一種方法,打開文件時忽略錯誤。
file = open(filename, errors='ignore')
還有生成的文件名,要分析文件的路徑等可以通過命令行參數傳入,這樣可用性高些,就不搞了。
整體做下來,其實功能很簡單,但是由於對語言的不熟悉,花了不少時間,map 這個高級函數還是因為 Kotlin,Rx 里都有類似的,所以搜了下才知道,有些不知道的可能就寫繁瑣了。
文件太大,寫入時間太長,只運行了一會看了一些數據,分析出大概的原因,最終還是讓運營再去過濾分析比例的。
結果
源代碼
import os import urllib.parse import openpyxl import sys # 將一行數據轉成字典 def getOneDict(line): params = dict() # 創建字典保存數據 pre = 'timeAnalysis?' index = line.find(pre) if index > -1: line = line[index+len(pre):].split()[0] line = urllib.parse.unquote(line) paramList = line.split('&') for item in paramList: param = item.split('=') if (len(param) > 1): params[param[0]] = param[1] return params def createExcel(): if 'time.xlsx' in os.listdir('./'): return wb = openpyxl.Workbook() # 創建Execl wb.active.title = 'Splash' wb.create_sheet(title='Home') wb.save('time.xlsx') def writeToExecl(params): wb = openpyxl.load_workbook('time.xlsx') splashSheet = wb['Splash'] homeSheet = wb['Home'] if params['pid'] == '04': # 開屏 writeToSheet(splashSheet, wb, params) elif params['pid'] == '00': # 首頁 writeToSheet(homeSheet, wb, params) def getValue(t): return t.value def writeToSheet(sheet, wb, params): rows = tuple(sheet.rows) index = len(rows) + 1 # 從這一行開始插入數據 titles = list(map(getValue, rows[0])) # 拿到第1行的內容,做標題用 if titles == [None]: titles = [] for k,v in params.items(): if k in titles: sheet.cell(row = index, column = titles.index(k) + 1).value = v else: sheet.cell(row = 1, column = len(titles) + 1).value = k # 先插標題 sheet.cell(row = index, column = len(titles