玩蛇记--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。
#!/usr/bin/python # -*- coding: UTF-8 -*-#把所有的文本都读出来 i = 1; txtStrAll=''; while i<= 402: #402fileobj = open('list-data- ('+ str(i) +').txt');txtStr = fileobj.read();txtStrAll += txtStr+'\n';fileobj.close();i+=1;txtArr = txtStrAll.splitlines(); #分割成数组 print len( txtArr ); #39905386 txtArr = list(set(txtArr)); #去重 print len( txtArr ); #18316857#合并成字符串,for in会要很久很久 newStr = ''; newStr = '\n'.join( txtArr );#写入txt newFileObj = open('list-dataAll.txt', "wb"); newFileObj.write( newStr ); newFileObj.close();
hebing.py
其中对数组内的元素进行合并去重的那一句是 txtArr = list( set(txtArr) ) 。很神奇对吧,这两个是什么函数?其实这两个都是转换类型的函数。先把它转换成了 set 类型,再转换为 list 类型(列表/数组)。python的set(集合)类型是一个无序不重复的元素集,所以list转换为set之后就自动去重了,当然同时顺序也会被打乱了,不过这里的顺序不重要就不用管它啦。
最后数组转换为字符串也是直接用字符串拼接数组就转换了,不要用for循环,非常非常耗时间的。
五、Excel处理(./preNum.py)
根据网上例程直接读取Excel第一个表里面的内容出来,合并成数组转成字符串存到文本里面去。在转成字符串的时候发现报错似乎是说数据类型不对,才知道原来python与PHP、JS不同,是强类型的=_=!于是先在Excel里把表里面的数据转换成字符串格式(excel里准确叫文本格式),转换后excel表里面的数据格左上角是有绿色的小三角形的。每个表单独处理生成一个文件,一个文件里面大概有八千个手机号码前缀。
#!/usr/bin/python # -*- coding: UTF-8 -*-#读取Excel,把每个表的 7位 号码都读取出来,存入文件 #共有13个表(三位数前缀),#到python官网下载http://pypi.python.org/pypi/xlrd模块安装。 #安装方法:进入下载的 xlrd的文件夹,执行命令 python setup.py install import sys reload(sys) sys.setdefaultencoding('utf-8'); #解决'ascii' codec can't encode character, Python2默认是GB2312编码import xlrdexel = xlrd.open_workbook('cellnum.xlsx');tblIndex = 1; #第几个表 table1 = exel.sheets()[tblIndex]; #第N个表 ncols = table1.ncols; #列数 #print ncols; i=0; dataAll = []; while( i< ncols):dataArr = table1.col_values( i ); # print dataArr;for num in dataArr[:]: #每列中的数字格if not num.isdigit() :dataArr.remove(num);dataAll.append( dataArr );i += 1;#dataAll 是一个表的数据List #print dataAll;#写入txt newStr = ''; for eCol in dataAll:colStr = '\n'.join( eCol );newStr += colStr+'\n';newFileObj = open('exel-dataAll'+str(tblIndex)+'.txt', "wb"); newFileObj.write( newStr ); newFileObj.close();
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语言做单片机的做法呢→_→虽然最后不用多线程了,直接单线程处理,安全稳定。
#!/usr/bin/python # -*- coding: UTF-8 -*- import thread from time import sleep#生成号码的函数,传入号码前缀 def generate(txtNum): # print txtNum;numArr = []; #用来存号码数组的numArrStr = ''; #用来写入文件的global doneNum;global bugTxtArr;global totalNum;global tblIndex;j = 0;while j < 10000: #生成一万个同前缀号码newNum = str( int(txtNum)*10000+j );j += 1; # print newNum; numArr.append( newNum );#每个对比,但是非常耗时间 # if not newNum in bugTxtArr: #如果不在已用过的号码列表里 # numArr.append( '+86'+newNum ); # #print '+86'+newNum;# 先全部合并,再数组去除(list 差集运算) # numArr = list( set(numArr).difference(set(bugTxtArr)) );numArr = list( set(numArr) - set(bugTxtArr) );numArrStr = '\n'.join(numArr);#存入文件newFileObj = open('cellNum'+str(tblIndex)+'/'+str(txtNum)+'.txt', "wb");newFileObj.write( numArrStr );newFileObj.close();print '-----TASK--------'+str(txtNum)+'--OK-------------';doneNum += 1; #完成的任务数加一if doneNum == totalNum:print '++++++---ALL---TASK---DONE---+++++++++++++++'; #完成所有任务#########################################函数结束######################################定义要处理的表 tblIndex = 9;#把一个表合并后的数据读出来 fileobj = open('exel-dataAll'+str(tblIndex)+'.txt'); txtStr = fileobj.read(); fileobj.close(); txtArr = txtStr.splitlines(); #分割成数组 totalNum = len( txtArr ); #print txtArr;#这里是重复的号码 #fileobj = open('testBugNum.txt'); #测试的 fileobj = open('list-dataAll.txt'); #实际的 bugTxtStr = fileobj.read(); fileobj.close(); bugTxtArr = bugTxtStr.splitlines(); #已用过的号码的列表 #print bugTxtArr;print totalNum; doneNum = 0; num = 0; for txtNum in txtArr:if txtNum.isdigit() :generate(txtNum); #生成号码#多线程,有多少个前缀就多少个线程,每个线程生成一万个号码 #测试发现执行效率没有提高,而内存消耗大很多,查资料了解到计算密集型任务用多线程没帮助的 # try: # thread.start_new_thread( generate, (txtNum,) ) # except: # print "Error: "+str(txtNum)+" no Thread"; # errfileobj = open('error-log'+str(tblIndex)+'.txt', 'a'); # errfileobj.write( "Error: "+str(txtNum)+" no Thread\n" ); # errfileobj.close();# num += 1; # if num > 10: # break;#sleep(1); #print 'sleep end'; #while 1==1 : #主程序一直执行,以防线程提早结束 # pass;
doData.py
八、合并整理(./hbData.py)
经过了大概六个小时的号码生成,最后就是把一个Excel表生成的八千多个文件整理,每十个文件合成一个,每个文件约十万个号码,每个号码前面加上 “+86” 。就遍历一下目录,没什么技术点就不详说了。
#!/usr/bin/python # -*- coding: UTF-8 -*- import osdef hebing( filesArr ):global tblIndex;i = 0;newStr = '';filesArrLen = len(filesArr);#print filesArr;for file in filesArr:txtStrAll = '';fileobj = open('cellNum'+str(tblIndex)+'/'+filesArr[i]);txtStr = fileobj.read();fileobj.close();txtStrAll += txtStr+'\n';txtArr = txtStrAll.splitlines(); #分割成数组#全部在开头加上+86arrLen = len(txtArr);#print arrLen;for j in range(0, arrLen):txtArr[j] = "+86"+txtArr[j];#转成字符串newStr += '\n'.join(txtArr);i += 1;#每10个文件写入txt,注意最后的不足十个的时候if not i%10 : #print i;newFileObj = open('resList'+str(tblIndex)+'/'+str(i)+'.txt', "wb");newFileObj.write( newStr );newFileObj.close();newStr = '';elif i==filesArrLen :#print i;newFileObj = open('resList'+str(tblIndex)+'/'+str(i)+'.txt', "wb");newFileObj.write( newStr );newFileObj.close();newStr = '';#把所有的文本都读出来 tblIndex = 1; rootDir = os.getcwd()+"\cellNum"+str(tblIndex); #print rootDir; for parent,dirs,files in os.walk(rootDir): # print files; #得到一个文件名List,按文件名排序的 # hebing( files[0:15] ); #测试 hebing( files );""" i = 1; txtStrAll=''; while i<= 402: #402fileobj = open('list-data- ('+ str(i) +').txt');txtStr = fileobj.read();txtStrAll += txtStr+'\n';fileobj.close();i+=1;txtArr = txtStrAll.splitlines(); #分割成数组 print len( txtArr ); #39905386 txtArr = list(set(txtArr)); #去重 print len( txtArr ); #18316857#合并成字符串,for in会要很久很久 newStr = ''; newStr = '\n'.join( txtArr );#写入txt newFileObj = open('list-dataAll.txt', "wb"); newFileObj.write( newStr ); newFileObj.close(); """
hbData.py
后话,总共只有十三个表,这些程序稍微改一下,执行十三次就行了。值得注意的是,我这里的程序几乎每个都有一个全局变量 tblIndex, 是以防一文件里面一个个修改目录名和文件名,疏忽有可能导致的数据覆盖。
总结
- 使用脚本语言有一个很重要的要点:要尽量用语言提供的函数,不要自己实现算法,尤其是循环的那种,执行速度不在一个数量级。
- 处理大批量的数据,要拆分步骤,生成中间文件。大量数据复杂操作要小批量小批量地慢慢调试,结果无误才逐步切换成真实数据。
- 多线程在运算密集型的场景中是没有用武之处的,就算CPU是多核也没什么用,反而会造成顺序随机不易观察,线程不稳定容易出错,线程间切换内存消耗加大等弊端。
- 据说GPU可以用来挖矿、暴力破解等,对本场景这种高并发、简单逻辑的运算应该也非常适用,以后可能要用得上GPU编程(求推荐教程)。
转载于:https://www.cnblogs.com/batsing/p/5422081.html
玩蛇记--Python处理海量手机号码相关推荐
- python 儿童 游戏_防止孩子玩游戏的Python小程序
今天小编就带领大家来做一个防止孩子玩游戏的Python小程序.非常有趣,大家快来跟我一下看一下吧. 1查询电脑的所有进程 用Python循环检测电脑软件的运行情况,当发现游戏软件时弹出警告窗口,并截图 ...
- 用java玩的游戏平台_分享4个边玩边学Python的编程游戏网站
原标题:分享4个边玩边学Python的编程游戏网站 前言 大家好,欢迎来到 Crossin的编程教室 ! 学习编程虽然对有些人来说是件乐事,但是对大多数人来说仍然是一件比较枯燥困难的事情.当然,面临这 ...
- python美元汇率兑换程序代码_还可以这样玩?用Python完成一个在线汇率转换小程序...
原标题:还可以这样玩?用Python完成一个在线汇率转换小程序 大家好,小数在这里给大家拜个早年啦 今天给大家分享的是用Python完成一个在线汇率转换小程序,是基于一个持续更新的汇率网站实现的,让我 ...
- 【转载】【超级简单入门】用Python从海量文本抽取主题
转载自https://mp.weixin.qq.com/s/hMcJtB3Lss1NBalXRTGZlQ 加粗的部分和[]内的部分,均为自己后加上去的划重点标注~ ...... 用Python从海量文 ...
- AI玩俄罗斯方块(Python实现)
目录 1.环境 2.实现机制(Pierre Dellacherie算法) 3.代码实现 人工智能大火的今天,如果还是自己玩俄罗斯方块未免显得太LOW,为什么不对游戏升级,让机器自己去玩俄罗斯方块呢?有 ...
- 推荐一位玩自动化的 Python 爱好者
今天给大家推荐一位 Python 类的公众号「AirPython」,作者:星安果,果哥有 7 年的编程学习经验,热爱 Python 爬虫.Web.数据分析及自动化,平时喜欢分享一些实用.有趣的 Pyt ...
- AI视觉组仙人一步之高级玩法——从Python回归C语言
开心的程序猿@NXP 2021-02-04 Thursday 读过之前两篇的童鞋们,想来已经开始着手开发属于自己的AI视觉应用了,当然,手中还没有OpenART套件的朋友们,也不用着急,可以先参照 ...
- python easygui_极客养成记/Python一点也不难/第四节
关于本课程 选择Python作为第一个严谨编程语言学习,不仅仅因为它是最近几年最流行的编程语言,也不仅仅因为它是机器学习领域的核心语言.拿Python做入门语言学习,是因为它语法简单.功能强大.更接近 ...
- ExceptionLess新玩法 — 记日志
ExceptionLess 之前也有介绍过这个框架,其实网上也有很多的资料,无论是部署还是一些详细的高级玩法都讲的很清楚也很棒,博主也学习了一些他们的博文,因为很多的东西比如本地部署别人已经写了,我再 ...
最新文章
- 中国内地高校ESI排名出炉:342所大学上榜
- 关于Cocos2d-x很多奇怪的报错
- Python核心编程(第二版)第六章部分习题代码
- 是时候重新定义安全了,阿里云肖力解读安全责任共担模型
- caffe安装,编译(包括CUDA和cuDNN的安装),并训练,测试自己的数据(caffe使用教程)
- python 时间序列分析之ARIMA(不使用第三方库)
- stringstream的基本用法
- python中scale_Scale
- LINUX 常用命令集合
- LeetCode 431. 将 N 叉树编码为二叉树(递归/层序)
- 再介绍一篇Contrastive Self-supervised Learning综述论文
- 计算机组成原理r型指令logisim实现_第一章 计算机体系结构
- 浅谈API测试与UI Auomation一点心得
- 衡量视频序列特性的TI(时间信息)和SI(空间信息)
- 许晴×××汤的营养价值
- Linux基础_合并,归档,压缩,dump,编辑器
- mysql08---优化01
- vc_redist 各版本下载地址
- idea打包时控制台中文乱码
- 运筹学 $5约束极值
热门文章
- 6-5 实现daemonset和sidecar日志收集
- windows server 2008试题
- Android利用有道API播放英文发音
- 高效程序员工作法(六)
- paip 无线路由器的无线接入WAN方式WDS设置大法
- 多伦多创业板上市公司Fura Gems首席执行官Dev Shetty在纳斯达克电视中谈论公司如何以合乎道德的方式获取宝石、Fura Gems的未来等话题
- c语言中0,‘0’,‘\0’的区别
- LG KF350上网设置(看图说明)
- 字节测试开发工程师面经(二面已凉)
- 但可以在划定要求内选用原厂车灯提高亮度