標籤:
Python處理海量手機號碼一、任務描述
上周,老闆給我一個小任務:批量產生手機號碼並去重。給了我一個Excel表,裡面是中國移動各個地區的可用手機號碼前7位(如),裡面有十三張表,每個表裡的電話號碼首碼估計大概是八千個,需要這些7位號碼產生每個都產生後4位組成11位手機號碼,也就說每一個格子裡面的手機號碼都要產生一萬個手機號。而且還有,本來伺服器已經使用了一部分手機號碼了,要在產生的號碼列表裡去掉已經使用過的那一批。已經使用過的這一批號碼已經匯出到了一批txt文本裡,約4000w,每個txt有10w個號碼,裡面有重複的,老闆估計實際大概是3000w左右。老闆可以給我分配使用一個16G記憶體、8核CPU的Windows伺服器來跑程式。
二、任務分析
要處理海量資料,所以程式的執行效率和佔用記憶體不能太高,應該能在開發機的4G記憶體下也大概跑得動,即關掉所有編程IDE和伺服器軟體,只用Notepad++和瀏覽器(用來查資料)的情況下不會卡機。這次任務可能會用到的技術有:程式Excel的處理,檔案的遍曆和讀寫,大型數組的操作,多線程並發。預估任務完成周期:一周(日常工作正常進行的前提下)。
三、技術分析
PHP:很熟悉,但是執行效率和記憶體佔用不夠好,可能會卡機,要實現多線程似乎有點複雜。(有待斟酌)
Javascript:較熟悉,執行效率和記憶體佔用不太清楚,但是弱類型通常都比較堪憂,各種回調比較幹擾思維不順手。(不考慮)
Java:略懂,學起來和寫起來都比較麻煩,開發效率比較慢。(有待斟酌)
C#:沒用過,較難學(比JAVA易,比指令碼難)。(有待斟酌)
C/C++:略懂,數組處理、多線程這兩個似乎比較難搞。(不考慮)
Python:沒用過,據說很容易學,有個研究生同學用它來做物理運算等,執行效率應該不低。(試試看)
於是就打著試試看的心態,開啟了菜鳥教程(Runoob)的Python教程大概看了一下,目錄中有幾個數組(List、元組、字典)、檔案IO、File和多線程,看了一下常式果然好簡單。再度娘了一下python處理Excel,果斷簡單快捷!於是開啟了玩蛇之路。
四、合并(./4000w/hebing.py)
本文開頭說到,有4000w的已用號碼列表,但是裡面是有重複的,而後面的處理都需要用到這些號碼。而看到這400多個txt檔案加起來大小約500M,所以全部讀進去再進行處理也可以承受。所以先把這些檔案讀進去合并去重,輸出成為一個txt檔案。最後得到的號碼有1800w多條,輸出txt檔案大小約209M。
hebing.py
其中對數組內的元素進行合并去重的那一句是 txtArr = list( set(txtArr) ) 。很神奇對吧,這兩個是什麼函數?其實這兩個都是轉換類型的函數。先把它轉換成了 set 類型,再轉換為 list 類型(列表/數組)。python的set(集合)類型是一個無序不重複的元素集,所以list轉換為set之後就自動去重了,當然同時順序也會被打亂了,不過這裡的順序不重要就不用管它啦。
最後數群組轉換為字串也是直接用字串拼接數組就轉換了,不要用for迴圈,非常非常耗時間的。
五、Excel處理(./preNum.py)
根據網上常式直接讀取Excel第一個表裡面的內容出來,合并成數組轉成字串存到文本裡面去。在轉成字串的時候發現報錯似乎是說資料類型不對,才知道原來python與PHP、JS不同,是強型別的=_=!於是先在Excel裡把表裡面的資料轉換成字串格式(excel裡準確叫文字格式設定),轉換後excel表裡面的資料格左上方是有綠色的小三角形的。每個表單獨處理產生一個檔案,一個檔案裡面大概有八千個手機號碼首碼。
preNum.py六、組建編號碼並去掉已用過的
首先來想想,有十三個表,一個表裡面有大概八千個號碼首碼,每個首碼產生一萬個號碼,每個號碼要與前面所說的1800萬的已用手機號碼進行比對去重。你會怎麼做呢???
↓↓
↓↓
我的想法是,分成十三次來做,每次一個表,每個表中八千多個號碼,使用多線程,八千多個線程,每個線程產生一萬個號碼並與那1800萬個號碼一 一比對。
其中產生和去重的核心代碼如下,每產生一個號碼的時間大概是0.5秒。所以估計了一下時間,10000*0.5s ≈ 83min,八千個線程大概要一個半小時左右。
#這裡是重複的號碼#fileobj = open(‘testBugNum.txt‘); #測試的fileobj = open(‘list-dataAll.txt‘); #實際的bugTxtStr = fileobj.read();fileobj.close();bugTxtArr = bugTxtStr.splitlines(); #已用過的號碼的列表while j < 10000: #產生一萬個同首碼號碼 newNum = str( int(txtNum)*10000+j ); j += 1;# print newNum; if not newNum in bugTxtArr: #如果不在已用過的號碼列表裡 numArr.append( ‘+86‘+newNum ); print ‘+86‘+newNum;
我在本機大概運行了一下,觀察了幾分鐘,似乎線程建立得比較慢,有些已經跑到了十幾個了,有的才剛建立線程。線程調度嘛,不按照順序嘛,除了輸出很亂以外,似乎也沒有什麼其它問題。於是就上傳到伺服器上去跑了,然後再過了一會就下班回家了。第二天回到公司,連上伺服器看看,出乎意料啊,看到輸出的資訊裡面,那些線程才跑到 三百多,天呐什麼時候才能跑到一萬啊。而且還有坑爹的是,偶爾就看到有些線程建立失敗⊙o⊙
七、思考思考(./doData.py)
從前面的運行資訊來看,這裡使用多線程似乎並沒有加快程式的運行啊,這是為什麼呢?如果不能用多線程,那麼產生比對的地方就要改成另外更高效的方式了,有嗎?
第一問,從網上找到答案,確實說在計算密集型程式中,多線程比單線程更糟,因為一個CPU就那麼幾個核,不同的線程還是一樣要佔用CPU資源,再加上線程調度的時間和空間,真是天坑。第二問,PHP中有從一個數組去除另一個數組的函數(官方說法叫做數組的差集 array_diff() ),那麼python應該也會有這樣的函數,先成一萬個號碼的數組再進行差集 會不會比原來 每個號碼對比再合并效率快呢?
實踐了一下,證明確實效率高了極多極多,python中的數組差集是這個樣子的 numArr = list( set(numArr) - set(bugTxtArr) ) 測了一下,大概三秒完成一批號碼(一批約等於一萬個號碼),之前是半秒一個號碼\( ^▽^ )/。但也注意現在產生的一萬個號碼排序是亂的,因為中間轉換成的set類型是無序的,如果需要從小到大排序,那還要再加個函數排序一下,問了老闆說不用按順序,那就直接這樣了。
期間調試的時候發現,使用多線程有時會報錯 Unhandled exception in thread started by sys.excepthook is missing 之類的,網上查資料說是因為主進程已經執行完畢,那麼其建立的線程就會被關掉。所以我的做法就是讓主進程最後為一直執行空語句,很像當年用C語言做單片機的做法呢→_→雖然最後不用多線程了,直接單線程處理,安全穩定。
doData.py八、合并整理(./hbData.py)
經過了大概六個小時的號碼產生,最後就是把一個Excel表產生的八千多個檔案整理,每十個檔案合成一個,每個檔案約十萬個號碼,每個號碼前面加上 “+86” 。就遍曆一下目錄,沒什麼技術點就不詳說了。
hbData.py
後話,總共只有十三個表,這些程式稍微改一下,執行十三次就行了。值得注意的是,我這裡的程式幾乎每個都有一個全域變數 tblIndex, 是以防一檔案裡面一個個修改目錄名和檔案名稱,疏忽有可能導致的資料覆蓋。
總結
- 使用指令碼語言有一個很重要的要點:要盡量用語言提供的函數,不要自己實現演算法,尤其是迴圈的那種,執行速度不在一個數量級。
- 處理大批量的資料,要拆分步驟,產生中間檔案。大量資料複雜操作要小批量小批量地慢慢調試,結果無誤才逐步切換成真實資料。
- 多線程在運算密集型的情境中是沒有用武之處的,就算CPU是多核也沒什麼用,反而會造成順序隨機不易觀察,線程不穩定容易出錯,線程間切換記憶體消耗加大等弊端。
- 據說GPU可以用來挖礦、暴力破解等,對本情境這種高並發、簡單邏輯的運算應該也非常適用,以後可能要用得上GPU編程(求推薦教程)。
Python處理海量手機號碼