Python数据处理笔记(1)

第一次处理比较大的数据,本以为十个很简单的任务,结果花了两天半才算做完,学到不少经验,记一下吧。

任务是给出一个XML文件(gz格式压缩的是45M,我展开之后是450M,不得不说XML的冗余还是蛮多的),里面有240w条数据(每条数据两个字段),用这些数据去ElasticSearch里面查询两属性,最后将一共四个字段导出csv格式。

最后的代码都放在https://github.com/laixintao/test-yorg/tree/master/company200w这里了,数据文件被ignore了。由于是一次性的任务,代码也就执行一次,所以写的比较随意,还被同事吐槽了……

我的思路是,首先解析XML文件,把数据存放到mongodb里面,然后根据mongo里面的数据一条一条去ES查询,最后将数据导出。

现在看来,决定中间用mongo来存储数据而不是直接用从文件解析出来的数据是无比正确的。这么大的文件不能保证程序一次就跑通,而使用文件就不能按数据查询,不能索引,无结构化,带来不必要的麻烦。数据放到mongo之后,后面的每一部都可以看作是针对mongo中的结构化数据进行的,可以随意添加字段,可以针对性的只修改有问题的数据,可以使用多线程/多进程处理。

我以前一直不是很理解数据库的索引,可能因为接触的数据都不大,也没花心思去理解。这次解析XML插入到mongo的时候,等程序跑了一段时间之后就变得特别慢。同事提醒我针对update的字段建立一条索引。索引执行完之后,我去,速度立马提升了10倍(目测)。后来我看到了索引的好处,每次用到数据库之前都是先进行索引再用,后来导致插入的时候变得很慢。建索引也是个学问,太多太少都不好(这段话会不会暴露我跟SB一样)。另外我发现了一个特经典的视频,1分钟理解btree。看完就知道为什么索引能这么快的提升速度了。

在解析XML的时候遇到一个坑,我用的是pyhton标准库的xml.sax,因为etree好像是将文件一下子读到内存里的,可能不太适合大型XML文件,相比之下xml.sax是从一个标签的开始读到结束,处理一次,在处理下一个。具体的代码可以看这一段https://github.com/laixintao/test-yorg/blob/master/company200w/xml2mongo.py但是characters方法好像并不是只用一次,从处理的结果来看,遇到一些特殊字符(比如html的&)就给断成几段文字了,后来发现了我又将他们连起来的。

导出就没什么好说的,用csv的标准库非常简单。

另外,现在看来自己写程序的时候急急忙忙,生怕没时间运行,但其实要是多花一个小时去写,说不定执行能少十个小时。找找哪些操作占用的时间多,想办法优化一下。比如去ES查数据,有条查询是可以一次查多条的(比如100),这样就可以有效减少网络请求的次数。

总结

  • 处理数据库中的数据,而不是文件
  • 数据很大的话及时建立索引
  • 减少IO的次数
  • 打印处理的进度,不要太多(打印也占用资源的)
  • 尽量先测试,再运行
 

去千岛湖团建

上周公司组织出游,目标是千岛湖。一共三天,玩的挺开心的,记一下流水账。

周六到的时候已经是下午了,本来计划是绕湖骑行,由于路上大巴出了一次事故,到的时候已经是下午了,就决定去瑶琳仙境。其实去骑行我是有点慌的,毕竟已经太长时间在毕业和实习之间忙碌没有运动了(好吧我承认是借口)……所以这次去瑶琳仙境我是比较开心的。

瑶琳仙境其实就是一个溶洞,里面有很多钟乳石。之前在德国的时候也去过一个(是开学破冰的时候,好像团建都喜欢去洞穴?),但是这个比起德国那个好看太多,一是面积大太多了,二是形状也很多,加上旁边有个导游一直在暗示我们这个像什么那个像什么,蛮有意思的(德国的时候也有一个导游,可能是我听不懂吧),三是五颜六色的灯光,比德国那个只有照明灯高到不知道哪里去了。

傍晚的时候才到酒店,之前说过是一个五星级的酒店,但是最后到目的地之后还是被惊艳了,我之前只住过快捷酒店,第一次住这种感觉好豪华。酒店有落地的漂亮,有阳台,卫生间干事分离,浴缸漂亮又干净(可惜没用过),毛巾特别多,有水果。尤其是床,太舒服了。唯一感觉不足的地方是,插座设计的不合理,不够多,用起来不方便,灯的开关也不好找。

除了房间,酒店有很棒的游泳池(消毒水很少,水很舒服),有健身房和乒乓球,瑜伽室,棋牌室等等,非常爽。

第二天上午没啥集体活动,于是跟同事一起去爬了有很多奇奇怪怪名字的山。

这么帅不是我啦,是我同事

下午在千岛湖里玩皮划艇。说实话看见这湖,越看越眼熟,最后终于想起来,原来我来过这地方!上次去新安江,最后坐船到的千岛湖。不敢确认是因为上次明明失去安徽,原来千岛湖是三省交界处,那么一切都对起来了,这确实是我第二次来这个地方。

第一次玩皮划艇这种运动,上手还蛮快的。刚进湖的时候有点不稳,一划就很害怕,十分钟适应了之后,简直像疯狗一样想去哪就去哪了。

晚上,是酒店准备的湖边烧烤,非常好吃,一口气吃了好多。

好大的虾啊

各种大餐随便吃

第一次体验到这种度假的酒店,相比之前那种到处观光的旅行,这种算是比较休闲的,玩回来也不是很累,很适合放松。以后希望能和欣一起去这种地方玩。

 

Python lxml教程

lxml是Python处理xml文档的一个库,速度快,易编程,可以“make life easier”。这篇文章是lxml的快速上手教程。

XML在lxml中的表示

在DOM中,文档是以节点(node)的形式组织的。某节点又有子节点,表示Elements,Attributes,Text等。

例如,下面这个DOM可以用如图所示的节点组织。

在lxml中,只有Element,Element有子Element,构成一棵树。Element有一下属性:

  • .tag – element的名字,比如“p”或“em”等
  • .text – 元素的文本内容,从开头到第一个子节点。如果从开头到一个子节点没有内容,那么就是None。比如p的text是”To find out”
  • .tail – 元素后面的内容,到下一个元素为止。比如em的tail是”, see the”
  • .attrib – 元素的属性。“<h2 class="arch" id="N15">”的.attrib就是 “{"class": "arch", "id": "N15"}
  • (子元素列表) – Element的很多行为都和list类似,可以用来索引。比如Element[0]就是表示Element的第1个子元素。可以使用len()查看这个Element一共有多少个子元素

上面的DOM使用lxml的Element表示:

注意.tail,比如,”,see the \n”本来在DOM中是p的节点,但是在lxml里成为了em的.tail属性。

操作Element

在lxml中,一个Element实体的表现和Python的list很相似,可以使用len()获得这个Element的子元素的数量,可以使用下标操作子元素,可以使用replace(), delete()等方法。假设E是一个Element实体,那么可以进行以下操作。

  • 通过E[i]获得第i+1个元素
  • 通过E[start:end]获得从start到end之间的元素
  • 可以通过下标替换一个元素:E[i] = c
  • 删除一个元素:del E[i]
  • 通过循环迭代所有元素:
  • 通过append()添加子元素
  • 使用clear()将子元素清空,此外:
    • .attrib字典将清空
    • tail和text将设置为None

此外,Element还有一些其他的方法。

Element.find()

找到和path匹配的元素,如果有多个,返回第一个。可以查找子元素的子元素,”tag1/tag2/…/tagn“。

Element.findall()

找到所有匹配的元素,以列表的形式返回。

Element.findtext()

找出所有和path(path是Element的后代即可)匹配的元素中的文本。如果有多个,返回第一个。如果匹配path但是元素没有文本,返回”(default在这种情况下不会使用)。

Element.get()

获得一个attribute的值,如果没有,使用default。

Element.getchildren()

获得所有子元素(感觉这方法和元素本身一样啊……)

Element.getiterator()

得到元素的迭代器。如果tag省略,元素本身会作为第一个元素。

比如遍历下面这个树。

Element.insert()

插入一个新的子元素。

Element.items()

就和字典的items()一样,会返回一个tuple的list。

Element.iterancestors()

和Element.getiterator()类似,不过是从当前节点开始,往上遍历祖先,直到遍历到根目录。

Element.keys()

返回所有attributes的key。

Element.xpath()

非常常用的一个方法,关于xpath有太多要说的了,以后再写吧……

参考资料

  1. Python XML processing with lxml
 

VimScript学习笔记(12):快速开关(Toggle)

Toggle

在前面的章节中,我们介绍过设置Vim的一种方式。使用叹号可以快速将布尔变量设置为相反的值(set someoption!)。把常用的设置映射成快捷键会非常实用。

但是这只对布尔类型的设置有用。对于需要提供参数来设置的项目,就不能用这种方法进行快速设置了。

Toggle选项

在这种情况下,我们可以定义一个函数,然后将某个函数map到这个快捷键。这里用foldcolumn举例,这个设置是在左边打开/关闭代码折叠的层级的。

toggle=0 关闭

foldcolumn=4 显示4个层级

下面的代码可以快速设置foldcolumn为0或4切换。将代码复制到~/.vimrc中然后source一下。

再举一个例子,下面的代码可以快速开关quickfix窗口。

但问题是,关闭窗口的时候会跳到最后一个窗口,我们关闭的时候跳到之前打开的窗口。可以在打开quickfix的时候记录一下是哪个窗口。

 

突如其来的一千块

前段时间一直在忙毕业设计的事情,实习的公司请了很多假,到手的工资很少。自己业余时间还有些额外收入,前几天老板打电话,问最近忙不,进度有些慢。我也发现自己很少顾及这边,就说这个月少要1000块钱吧。

到下个月就没什么收入了,都没钱交下个月的房租了,就差这1000块钱了,说是少要了,又不能厚着脸皮反悔。

正发愁呢,结果今天玩手机,随便打开中国银行的app(真的是随便打开的,因为之前CSDN的稿费不定时打,所以平时没事就会打开app看看),发现竟然多了七百多块钱。这卡已经不使用很久了,只有CSDN会给我打稿费,还有就是学校的钱都进这个卡。我已经半年多没写稿子了,只可能是学校。最大的可能是之前的实习补贴吧,可是实习在12月份就结束了,我以为早就已经都结算清了……

然后晚上(就是刚刚),在超市买东西的时候往支付宝里充钱,发现一直充不进去。原来选的是早已经不用的一张卡,可是昨天也是这么用的啊…… 我又试着用这张“废卡”充了10块,发现充进去了。查了查账单,原来最近用的一直是这张卡里的钱,我以为用的是一张常用卡,而常用卡的钱压根就没少!“废卡里”大约有将近三百。思前想后,真想不起来什么时候还在这张卡里剩了300块钱。我的Mac电脑又上不了网银查明细。

开头说的那个老板,之前还有一个事情。

3年前我在那里兼职,十月一的时候,我在俱乐部给老板值班,也没说好给多少钱。假期结束之后,老板给我四百,我说“哇,这么多”。老板说,嫌多啊,那我拿回来一张。最后就给了我三百。这件事后来我跟我爸说了,我爸说,从这件事就看出来你还年轻。

今年回家的时候,妈妈又提起来这件事,说每次想起来就觉得心疼,你还是个学生,那个老板怎么这么狠心,给你的钱又要回去呢。这件事我都忘记了,妈妈一直还记得。其实这件事我也没怎么放在心上,也就难受了一小会,所以后还和他合作。

这一千块钱,真是意外的惊喜。可能,是妈妈在帮我吧。