标签:python

python

python文件操作之目录和路径

在前面的几节中我们讲述了python文件操作,包括基本普通文件和zip压缩包文件, python文件操作 python遍历某个文件夹下的全部文件 python文件操作之zip包操作 在对文件操作过程中往往会对文件路径进行获取,如文件路径所在的目录,文件路径所在的一级目录,通过文件路径截取文件名,文件扩展名等。这就涉及到python中的os模块以及和文件路径相关的os.path模块。 os模块 我们首先来看os模块,官方文档地址在这:https://docs.python.org/2/library/os.html?highlight=os#module-os ,我们来看下os模块中在实际项目中会经常用到的重要的API 首先来看下和文件以及目录操作相关的函数。 该函数用数字模式mode创建一个名为path的目录,mode默认值为0777.如果文件已经存在将会抛出OSError,在windows上会抛出WindowsError: [Error 183] 该系统错误表示当文件已存在时,无法创建该文件。注意当传入的是层级结构的目录时要求前面的目录树都存在,否则会抛出OSError,在windows上会抛出WindowsError: [Error 3],表示系统找不到指定的路径,这也是os.mkdir和os.makedirs的不同点,以如下代码为例: [crayon-5f03aa5b9cc2a516097054/] os.mkdir(path)会抛出异常,因为系统不存在C:\Users\hzhuqi\Deskto层级目录,os.mkdir(path2)可以正确执行,不会抛出异常,因为系统路径存在C:\Users\hzhuqi\Desktop层级目录 根据mode模式创建一个名为path的目录,如果path是一个层级目录树,则会创建每一级中间目录,即使该中间目录不存在(这是和mkdir不同的地方),如果层级目录树下的叶目录已经存在(注意是叶目录)则会抛出OSError。在windows上会抛出WindowsError: [Error 183] 该系统错误表示当文件已存在时,无法创建该文件。以如下代码为例: [crayon-5f03aa5b9cc31093353826/] os.makedirs(path)可以正确执行,即使中间层级目录Deskto不存在,也可以正确创建。os.makedirs(existed_path)会出现异常,如果existed_path在系统磁盘上已经存在的话。 删除path指向的文件,注意只能删除文件,如果path是目录,则会抛出异常,在windows上会出现WindowsError: [Error 5],表示拒绝访问 删除path所指向的目录,注意只能删除空的目录,如果path目录不为空,则会抛出异常,在windows上会出现WindowsError: [Error 2],系统找不到指定的文件 注意该函数只能删除目录,如果传入的path是文件则会抛出异常,在windows上会出现WindowsError: [Error 3],系统找不到指定的路径 删除path路径下的所有目录和子目录,传入的path参数需要是目录,不能是文件,否则会出现异常,传入的目录也要求是空目录,(os.removedirs(‘foo/bar/baz’) will first remove the directory ‘foo/bar/baz’, and then remove ‘foo/bar’ and ‘foo’ if they are empty),如果目录不为空,则会抛出异常,在windows上会出现WindowsError: [Error 145] ,表示目录不为空 和os.rmdir不同的是os.rmdir只会删除path指向的目录,但是removedirs会删除path路径上所有的层级目录,如果目录为空的话   os.path模块 os.path模块官方文档在这:https://docs.python.org/2/library/os.path.html?highlight=os.path#module-os.path,我们来看下os.path模块中在实际项目中会经常用到的重要的API 用来判断文件或者路径在系统平台上是否存在,存在返回True,否则返回False。函数可以说是路径操作中使用频率最高的函数了,因为为了程序的健壮性,在对文件或者目录操作之前都需要先判断该文件或者目录是否存在。 该函数返回的是path路径所在的目录的路径,注意返回的是目录的路径,而不是目录的名称,如以下代码: [crayon-5f03aa5b9cc36217265097/] 则输出为C:\Users\hzhuqi\Desktop,输出是目录所在的路径,而不是目录名Desktop。但是很多时候我们需要根据路径path获取到其所在的目录名称,这个时候我们可以使用os.path.basename(path)函数 该函数返回的是path路径的基本名称,如果path指向的是文件,则返回该文件的文件名(包括扩展名),如果是目录返回该目录名(在Unix平台上返回‘’空字符串,详见python官方文档)。如以下代码: [crayon-5f03aa5b9cc38261993822/] 那么前面说的根据文件名获取文件所在目录名就很容易解决了,我们可以连续调用两次该函数即可。 该函数根据path路径以文件后缀分隔符.为界限返回一个二元组(root,ext),其中root+ext=path。使用该函数我们可以获取去掉了后缀名的文件名,如果path本身是以.开始则返回(path,”),如splitext(‘.txt’) 返回 (‘.txt’, ”),或者path本身指向的是一个目录而不是文件,如splitext(‘C:\Users\hzhuqi\Desktop’)返回(‘C:\\Users\\hzhuqi\\Desktop’, ”),注意当path指向的是一个文件的时候返回的二元组(root,ext)的第一个元祖root不是文件名,而是文件名所在的路径名,如以下代码: [crayon-5f03aa5b9cc3a930289849/] 输出是C:\Users\hzhuqi\Desktop\test而不是test,也就是说输出是文件名所在的路径,而不是文件名test,所以如果我们要根据path获取文件名的时候我们需要先获取其basename,然后使用splitext函数获取文件名。代码如下: [crayon-5f03aa5b9cc3b727199703/] 输出为去掉了后缀.txt的文件名test 该函数也是使用频率非常高的一个函数,用来将多个path路径连接起来,在路径连接的时候建议使用该函数而不是使用字符串连接方式,如path+os.sep+’path2′     注:本文首次发表于huqi.tech,谢绝转载,如需转载,请注明出处:huqi.tech   扫描下方二维码实时接收最新技术干货推送 扫描二维码实时接收最新技术干货推送,而且会不定期的发布互联网名企内推机会哦!

python

超级易懂爬虫系列之使用多线程爬取妹纸图

在前面的超级易懂爬虫系列中我们将爬虫的理论知识与实际操作相结合,相信大家都学会了,还没看过的猛戳以下链接 超级易懂爬虫系列之爬虫入门爬取妹子图 超级易懂爬虫系列之爬取妹子图 超级易懂爬虫系列之爬虫简单的架构 为了让大家能够更加容易明白,这个系列采取了先易后难,逐渐扩充其功能的方式进行讲解,在前面也说到过,在爬取妹纸图的过程中会将妹纸图保存到本地,因为这涉及到I/O操作,相对而言是很费时的一种操作,在前面爬取到某一网页的妹纸图时采取的是一个一个保存的方式,也就是说当爬取到了一个网页的时候需要等到将该网页的所有的妹纸图都保存到本地后才可以爬取下一个网页,而将妹纸图保存到本地相对而言算是耗时操作,因此我们想要是能够在爬取到该网页的妹纸图后能够同时保存n个妹纸图该多好,而不是一个一个的保存,这就是多线程的操作,通过开启多个线程来同时执行n个保存妹纸图的操作,从而提高程序的运行效率。在python中多线程是通过thread/threading模块来完成的,其中threading模块功能比thread模块功能更加高级,使用起来也更容易,因此我们以threading模块为例进行讲解。下面我们先来了解下该模块,然后使用多线程将我们前面的爬取妹纸图的程序进行改写,最后来测试下,采用多线程和不采用多线程程序的执行效率之间的差距。 了解了基本理论之后下面我们就来将之前的程序改写为使用线程的方式 [crayon-5f03aa5b9cfe1993452570/] 程序代码注释很详细,相信大家都看得懂,主要就是把原来的保存妹纸图的I/O操作放到一个单独的函数中,然后将其传给Thread类作为参数,这样每一个保存妹纸图的操作就是一个单独的线程操作。 然后测试一下使用多线程和不使用多线程的运行效率各是多少。首先运行我们在超级易懂爬虫系列之爬取全部妹子图中讲解的程序,这个是没使用多线程的,测试结果如下: 然后运行上面的使用多线程的程序,测试结果如下: 可以看到使用多线程明显效率提高了不少,如果I/O操作越密集,这种优势会越明显。   注:本文首次发表于huqi.tech,谢绝转载,如需转载,请注明出处:huqi.tech  

python

超级易懂爬虫系列之爬虫简单的架构

在前面的几篇文章中,我带领大家学习了爬虫的基本理论知识,同时带领大家学会了如何爬取自己心仪的妹纸图,现在对前面的内容做个总结,把爬虫的一些知识点抽取出来做一个简单的爬虫架构的分析。之所以这么做是为了后面带领大家使用分析爬虫框架时更容易明白,在后面讲解scrapy爬虫框架时大家会发现scrapy的基本组件与原理与这个几乎一模一样,只不过我们这个很简单而已。 在超级易懂爬虫系列之爬虫入门爬取妹子图 中(如果你还没看的话建议先去看一下)我们谈到爬虫是从一个网页开始不断爬取与之关联的url,然后抓取我们感兴趣的数据,那么首先就需要抓取网页,然后解析网页的内容,这样才能找出我们感兴趣的内容,同时还需要知道哪些网页已经爬取过了,哪些网页未爬取,因为网页之间的url可能会互相指向,因此要保存那些已经爬取了的url,爬取过的就不在爬取,同时新爬取的网页上可能存在指向新的网页的url,因此需要把新的url保存起来用来下一轮进行爬取。因此我们可以总结出爬虫至少需要以下三个模块。 网页下载器:用来将互联网上的网页以html的形式抓取下来 网页解析器:从抓取到的html文件中解析出我们感兴趣的数据 url管理器:用来管理哪些url是已经爬取过的,哪些是新增加的未爬取的url 用图示表示如下: 从这个图中,我们可以看到网页下载器,网页解析器,Url管理器组成了一个闭环,即程序中肯定存在一个while循环,在这个循环中,网页下载器不断的从Url管理器中取出一个待爬取的url,然后将该url的内容抓取下来,供网页解析器解析,网页解析器一方面会解析出我们感兴趣的数据,如妹纸图的下载地址,然后我们可以将其处理输出,产生价值数据,如将妹纸图下载下来保存至本地欣赏,另一方面网页解析器也会从当前url对应的html文件中解析出新的符合我们规则的新的url,如该妹纸图页面还存在指向其它页面的链接,如上一页,下一页可以浏览其它妹纸图,我们需要将新产生的url添加到url管理器中,在这个添加的过程中,需要判断该新产生的url是否是已爬取的(说明该url已经被抓取过)或者是否已经在Url管理器中(说明之前的某个页面存在指向该页面的url,导致该url已将添加到了url管理器中,不过还未爬取而已),如果该url未爬取同时也不在url管理器集合中,那么说明该url是一个合格的新的url,需要将其添加到url管理器中,重复上述过程(即while循环)直至url管理器中的url全部被取出为止,说明相关联的网页已经全部爬取完成。 这就是爬虫一个简单的架构,那么我们就按照上述架构来改写我们在超级易懂爬虫系列之爬取全部页面的妹子图中写的代码。 首先我们创建一个spider_girl的python工程,然后依此创建html_downloader.py,html_parser.py,url_manager,spider_main分别代表上面说到的网页下载器,网页解析器和url管理器以及爬虫主程序。如图: 其中spider_main是整个爬虫的调度模块,主要负责构建上面三个模块的实例以及网页爬取循环的控制,代码如下: 然后运行spider_main模块,可以看到和之前的效果是一样的,在程序控制台输出了所有的相关的妹纸图的url,在磁盘的C:\Users\htq\Desktop\girl目录下自动保存了心仪的妹纸图。 可以看到核心代码和我们在超级易懂爬虫系列之爬取全部页面的妹子图中讲解的是一样的,只不过此处根据各个模块的功能将其单独抽象为了一个类,然后提供了相应的功能函数,但是核心思路还是我们在前面讲到的架构图,此处将其抽象出来主要是为了后续更容易扩展其功能,如我们还可以将spider_main模块中的数据处理的那块再抽象出来搞一个数据输出模块output_data.py,这样如果后续需要将文件读写放到线程中时很容易直接改写output_data.py的代码,而不需要过多的改写spider_main中的代码。另一方面是为后面带领大家玩scrapy框架时大家能够更容易明白,在后面讲解scrapy爬虫框架时大家会发现scrapy的基本组件与原理与这个几乎一模一样,只不过我们这个很简单而已。   更多关于爬虫的干货,请继续关注超级易懂爬虫系列!   注:本文首次发表于huqi.tech,请勿转载,如需转载,请注明出处:huqi.tech

python

超级易懂爬虫系列之爬取妹子图

在前面一节中,我们首先讲解了关于爬虫的基本理论知识,然后带领大家动手写了一个入门级的爬虫程序–爬取心仪的妹子图,什么?你还不知道?赶快去看:写给小白看的爬虫系列之爬虫入门爬取妹子图 不知道大家消化的怎么样了,哈哈大家看到了自己心仪的妹纸吗?不过在前面一节中为了让大家更好的理解爬虫,采取了先易后难的策略,只爬取了一个网页的妹纸图,然后有的小伙伴觉得这不足以体现出对自己女神的热爱啊,要把我家女神全部的美图都抓取下来欣赏,好吧,现在就满足你对女神的全部幻想,本节带领大家在原来基础上爬取所有的相关网页的美图,让你一次看个够!想想还有点小激动呢? 在前面一节中我们讲过互联网上的网页存在很多的url链接,这些url可能相互指向,因此如果要爬取全部的url,我们需要知道哪些url已经被爬取过,哪些是新增加的url,在新增加的url所在的html中也可能存在新的指向其它网页的url,因此首先我们需要知道这些url的共同特点,即这些url的格式是怎样的,然后通过一个正则表达式来匹配这些url的格式,从而在html中搜索出相关的url。如图,我们打开http://iyangzi.com/?p=21,然后会看到当我们浏览完了该页面的全部美图之后,在页面的底部会存在2个指向其它页面的链接用来浏览上一页和下一页,类似于博客文章底部会搞一个上一篇,下一篇这样的跳转链接按钮一样,同样的,我们按F12,点击带箭头的光标,然后让右侧的源码区域自动定位到相应位置,我们可以看到该跳转链接为http://iyangzi.com/?p=10 ,如图 同样的我们可以多试几个页面,当然也可以直接点击这些链接,然后在浏览器的地址栏可以看到这些链接,最终可以发现跳转url的格式都是类似于http://iyangzi.com/?p=数字的形式,而且数字后面无其它内容,即是以数字结尾的,因此我们可以使用正则表达式来匹配这些具备类似格式的url,即 [crayon-5f03aa5b9d4fe720846812/] 这样我们就可以从html中过滤掉不相关的网页的url,找出我们需要的url,然后将这些url添加到带爬取的集合中,余下的内容就和爬取一个网页是相同的啦。不过这里需要注意的是,我们还需要一个记录已经爬取了的url的集合,这样当我们爬取一个网页的时候就从保存新增加的url的集合中取出该url,然后将其添加到已经爬取的url的集合,当我们在一个页面中发现新增加的url时就要考虑该url是否已将在待爬取的url集合中(表示该url和之前的某个url存在相互链接,导致该url已经添加到待爬取url集合中,因此不需要重复添加)或者在已经爬取的url集合中(表示该url已经爬取过)即: [crayon-5f03aa5b9d503445629511/] 其中的new_urls是新增加的待爬取的url集合,crawed_urls是已经爬取的url的集合,如果既不在新增加的url集合中也不在已经爬取的url集合中才说明该url需要爬取。这一块就是爬取全部相关网页的思路,我们先小结下重点: 分析多个url的格式,抽象出这些url的特点,一般会用正则表达式匹配 需要两个集合,一个用来保存每爬取一个页面后新增加的url,一个用来保存已经爬取了的url 新增加的url既不在保存新增加的url集合new_urls中也不在已经爬取了的url集合crawed_urls中才表示该url需要被爬取,因此我们将其添加到保存新增加的url集合new_urls中,供下一轮爬取使用 重复上述过程,直至新增加的url集合new_urls中的url被全部爬取为止,此时意味着全部的相关网页爬取完成。 现在我们来开始爬取和心仪妹子相关的全部网页的美图吧,是不是很激动,哈哈!代码如下: [crayon-5f03aa5b9d505241442816/] 程序代码注释很详细,相信大家都看的懂,主要是在上一节内容的基础上添加了爬取全部网页的内容,结合上面的小结和代码估计大家一会就看明白了,然后运行程序,在磁盘的C:\Users\htq\Desktop\girl_目录下就会自动保存心仪的妹纸图哦,如图:   图片太多,只截了一页的图,哈哈,心仪的妹纸已经到碗里了,赶快去看看吧!   这样就完成了爬取全部相关网页的功能啦,在前一节中说过爬虫远不止这么简单,如某些网站会进行反爬虫,因此面对这种情况我们需要应对反爬虫,如使用多线程或者多进程提高效率,如本实例中会通过文件操作的方式将从网页上爬取的妹纸图片保存到本地,这相对而言是很耗时的,因此我们可以放到一个线程中进行处理,再如使用爬虫框架等等,这些内容将会以爬虫系列的形式进行讲解,希望大家后期继续关注,多多支持!   注:本文首次发表于huqi.tech,请勿转载,如需转载,请注明出处:huqi.tech

python

超级易懂爬虫系列之爬虫入门爬取妹子图

首先解释下什么是爬虫以及为何需要爬虫。爬虫定义: 一段自动抓取互联网信息的程序 这里为了让小白能够明白,没引用百度百科的定义。那么为何需要爬虫呢?我们知道互联网是由海量的网页组成,每一个网页都对应着一个url,每一个网页上也可能链接着其它的网页的url,每一个html网页上都展示着一些信息,如文本,图片等,当我们在浏览这些信息时,可能会发现很多我们感兴趣的数据,如心仪的妹子图,觉得这个妹子好漂亮,想把这张图片保存下来,当然如果只是几张妹子图,我们可以使用浏览器图片保存功能保存到磁盘,但是如果是几千张妹子图,我们手动的一个一个保存岂不….,因此我们会想要是能够有一个自动帮我们抓取网页妹子的程序帮我们来完成这个过程,我们只管欣赏妹子多好。对这就是爬虫,爬虫能够从一个页面的url开始,不断的爬取与之相关联的所有的url,且能够抓取我们感兴趣的有价值的数据,如妹子图。当然这只是一个简单的例子而已,事实上爬虫的功能远远不止爬取妹子图这么简单,爬虫在搜索引擎中也能够大显身手,总之爬虫的价值在于让互联网上的数据为我们所用。 好了说明了什么是爬虫以及为何需要爬虫,现在就来实现一个简单的给小白看的入门的爬取妹子图的爬虫程序,那么一个简单的爬虫程序需要哪些部分组成呢?还是按照上面的例子来看,既然爬虫是从一个网页开始不断爬取与之关联的url,然后抓取我们感兴趣的数据,那么首先就需要抓取网页,然后解析网页的内容,这样才能找出我们感兴趣的内容,同时还需要知道哪些网页已经爬取过了,哪些网页未爬取,因为网页之间的url可能会互相指向,因此要保存那些已经爬取了的url,爬取过的就不在爬取,同时新爬取的网页上可能存在指向新的网页的url,因此需要把新的url保存起来用来下一轮进行爬取。因此至少要包括网页下载器,网页解析器和Url管理器,一个用来抓取网页,将互联网上的网页以html的形式抓取下来,一个用来解析网页数据,从抓取到的html文件中寻找我们感兴趣的数据,一个用来管理已爬取的url和新增加的url。既然是写给小白看的,那么初次就不搞那么复杂,我们首先选择一个网页爬取,然后逐渐扩充其功能。这样能够让小白更加容易明白,当你看完了本教程,觉得原来爬虫也没那么难,那就达到我们的目的了。下面就进入正题,开始抓取我们心仪的妹子图之旅吧。 python中抓取网页使用urllib2,该模块是官方内置的,网页的解析使用BeautifulSoup,该模块是一个第三方库,使用时需要先安装该库,我们一个一个来看: urllib2/requests urllib2是python官方的url模块,因为是官方内置的,所以使用时只需要要import urllib2即可 requests是第三方的lib,相对urllib2功能更加强大 因此是给小白看的,所以本文章以urllib2为例进行讲解,首先我们来看一下urllib2中一些重要的函数 BeautifulSoup 是用Python写的一个HTML/XML的解析器,该解析器会将html/xml文件看作一个DOM(Document Object Model)树,每一个html中的节点标签类似一棵树的节点。因此这是一种结构化的解析方式。下面我们来了解下如何使用。 这是文档官方网址:https://www.crummy.com/software/BeautifulSoup/bs4/doc/。 BeautifulSoup的使用一般遵循如下几个步骤 构造一个BeautifulSoup对象 搜索节点,其中搜索节点可以按照节点名称如html中的<a>,<div>标签,节点属性如href或者class属性,节点的内容三个方面来搜索,主要涉及的函数是find和find_all 访问节点的属性,内容,当搜索到了对应的节点之后就可以访问节点的内容。 下面一一介绍: 好了基本原理都讲完了,赶快来抓取我们心仪的妹子哦,只需简单的几个步骤,心仪的妹子图就到碗里来了哦! 首先找一个心仪的妹子,这个就看个人喜好了,看谁是你心中女神了,看谁长的漂亮啦,比如刘诗诗啦,这里少年就以少年心中的女神90后演员杨紫(因为喜欢她在青云志中饰演的陆雪琪,被仙哭,被美哭有木有)为例。首先我们在浏览器中打开心仪妹子图网址http://iyangzi.com/?p=173,然后在心仪的图片上右键选择检查元素,或者直接按F12,就可以看到网页的源代码了,然后点击那个带箭头的选项,如图: 然后你会发现当鼠标在左侧区域移动时右侧的源代码区也会随之移动定位到对应位置,这样我们就可以找出心仪的妹子图片位于那个tag下,如图通过多次移动我们发现妹子图位于<div class=”post-conten”>的<p>tag下,如图: 找到了我们感兴趣的数据之后,我们就可以写一个爬虫程序自动为我们抓取心仪的妹子图哦,然后将抓取的妹子图保存到我们的磁盘上就可以了哦,代码如下: [crayon-5f03aa5b9d85d457158262/] 代码是不是很简单,基本上就是前面介绍的那些知识点,然后运行程序可以看到,控制台输出了妹子图的网络地址 在磁盘的C:\Users\htq\Desktop\girl目录下已经自动帮我们保存好了心仪的妹子图片哦,还不赶快去欣赏欣赏。 好了,一个简单的入门级的抓取妹子图的爬虫就写好了,嗯,不仅学会了写爬虫,还收获了妹子图,简直不要太激动,小伙伴们赶快去试试抓取自己心仪的女神的图片欣赏吧。 当然这只是简单的讲解了爬虫的原理,爬虫远不止这么简单,如前面说的爬取互联网上海量的url的数据,对已爬取和新增加的url进行管理等,再如某些网站会进行反爬虫,因此面对这种情况我们需要应对反爬虫,再如使用多线程或者多进程提高效率,再如使用爬虫框架等等,这些内容将会以爬虫系列的形式进行讲解,希望大家后期继续关注,多多支持!   注:本文首次发表于huqi.tech,请勿转载,如需转载,请注明出处:huqi.tech   扫描二维码实时接收最新技术干货推送,而且会不定期的发布互联网名企内推机会哦!

python

python格式化字符串与输出对齐

当我们在对数据进行分析的时候通常会在excel中创建一个表格,表格的开始是一行属性,然后下面每一行的每一列都是和属性相关的内容,如图所示, 但是用excel需要手动一个一个输入,作为工程师,当然是要偷懒的,虽然可以使用第三方库操作excel的方式,但是太麻烦,能不能直接在文本文件中写入同样达到上述效果呢?如果不用excel直接在文本中进行写入虽然也能保存数据,但是会存在一个对齐的问题,每一行的数据虽然是对的,但是如果每一行的每一列没对齐,一方面不够美观,另一方面也不利于分析数据。本节就是为了解决这个问题,实现在文本文件中达到excel中每一列自动对齐效果。其中涉及到的知识点就是字符串的格式化与固定宽度的对齐。

python

python遍历某个文件夹下的全部文件

在前面的一小节中,我们谈到了对文件的读写操作,这一节我们谈谈使用python遍历某个文件夹下的全部文件,因为在实际的项目场景中这种情况非常常见,如在apk的逆向工程中可能需要在把dex文件反编译后产生的smali文件中查找某一特定的字符串,然后进行替换操作。这个场景很显然就涉及到了对某一文件夹下所有文件进行遍历的过程。这个过程很显然是对文件,目录,路径进行操作,而这些很显然是属于操作系统管理的范畴,因此这些功能被定义在了python中的os模块和os.path模块,使用前需要import相应的模块。这一小节就讲讲python中这两个模块常用的和文件操作相关的重要函数。 应用场景:将某一文件夹及其子文件夹下的全部的txt文件中的某个字符串替换为指定的字符串,这个实例综合了上一节和这一节的内容,在实际项目的文件操作中很常见。 [crayon-5f03aa5b9de4a873623692/]  

python

python文件操作

在任何一门高级语言中,文件I/O操作是非常重要的一个模块,python中的文件操作和C语言中非常类似,也很简单,虽然很简单,但是文件操作中也有很多需要主要的地方,尤其是读写效率的问题。 和c语言一样python使用open函数打开一个文件,该函数定义如下: [crayon-5f03aa5b9e16e039857788/] 当然对于我们而言通常不需要关心那么多参数,通常我们只关心最前面的两个参数,file和mode,file表示的是要打开的文件,通常是一个包含绝对路径的字符串,第二个参数mode表示打开的访问方式,默认值为r,表示只读。第二个参数的常用的取值和对应含义如下: r 只读模式,也是默认的打开模式,如果文件不存在将抛出异常 w只写模式,不能读取文件内容,如果文件存在将清空文件内容,如果不存在则创建 a追加模式,可读,文件存在则在文件末尾追加内容,不存在则创建 +更新模式,可读可写,通常与r,w,a连用,打开一个磁盘文件用于更新(进行读写操作) r+读写模式,即r与+模式的组合,如果文件不存在将抛出异常 w+读写模式,与r+不同的是该模式会清空文件已存在的内容,且如果文件不存在会创建该文件 可以看到这和c语言中是一模一样的,通常我们在实际项目中使用的较多的模式是‘r+’,’w+’或者’a+’。open函数的使用很简单,和C语言一样: [crayon-5f03aa5b9e174983869732/] 但是正如前面说到的,以r或者r+模式打开问的时候如果文件不存在程序会抛出异常No such file or directory,因此上面的写法显然是不对的,我们必须对这种可能出现的情况进行处理,因此通常我们会使用try/except语句,像下面这样,个人觉得java语言在这方面设计的很好,对于可能会出现IO异常的操作,java会要求必须try/catch否则IDE会自动提示错误。而python如果不处理不会提示错误,但运行时可能出错。 [crayon-5f03aa5b9e176288391501/] 在上面这段代码中我们对可能出现的异常进行捕获,然后处理(该代码中是使用print打印出出错原因),即使出现异常程序仍可以顺序往下执行,如果不这么做那么当打开文件失败(如文件不存在)时程序会终止退出,因此文件操作时必须要对可能出现的异常进行捕获处理。 那么上述代码是否就是合理的代码呢?我们知道,当我们打开一个文件进行操作后,最好是在不需要读写的时候使用close()函数关闭该操作。因此一个规范的打开文件的操作应该是这样的: [crayon-5f03aa5b9e177406455347/] 注意在finally语句块中,调用close()关闭读写操作之前,要对该文件对象进行判空操作,因为在open函数打开文件的时候可能失败,即出现异常,此时fileObject为空,但finally块不管是否出现异常是一定会执行的,因此需要先判空再操作,这基本上才是打开一个文件的规范代码,但是每次这么写太麻烦,因此python为我们提供了with as语句。 [crayon-5f03aa5b9e179638114032/] 使用with as语句在该语句块结束的时候文件会自动关闭,即使出现异常也会close() [crayon-5f03aa5b9e17a400526109/] 但是注意with as 语句打开文件,只是帮我们自动处理了close()的调用而已,异常的捕获还是需要我们自己处理,因此对文件进行打开操作时使用with as且捕获异常是一个好的习惯。 文件的读 文件的读主要涉及到三个重要的函数read(),readline()和readlines() read(size):如果不指定size的大小,此时size默认为-1,事实上此时read()会调用readall()函数,此时会一次性读取文件的全部内容,因此如果文件比较大,会占用非常大的内存,程序将会运行的十分缓慢,效率较低,此时通常不会直接使用read()而是会指定一个size参数,每次读取size个字节的内容。 readline():默认读取一行,包括行结束符,因此如果需要得到不包含行结束符的数据的话需要程序员自己处理。 readlines():该函数会读取文件所有的行然后把结果作为一个字符串列表返回 通过这三个函数的描述我们知道,如果读取的文件不是很大的话,我们通常使用read()或者readLines()函数一次性读取文件的全部内容,而不是使用readLine()一次读取一行,因此这样效率较慢,如果文件比较大,我们可以使用readLine()或者read(size)的方式一次读取一行或者size个字节进行处理,否则会很占内存。另外这三个函数不会处理行结束符,因此如果我们希望得到的数据不包含行结束符(windows下为’\n’,mac下为’\r’)的时候,需要我们手动处理,以readLines()为例,代码如下: data=[line.strip() for line in fileObject.readlines()] 其中strip()如果不指定参数的话,默认去除首尾空白符包括(‘\n’, ‘\r’, ‘\t’, ‘ ‘) 文件的写 文件的写和读是一一对应的关系,但是不存在writeline()函数,只存在write()和writelines(),含义和文件的读是一一对立的,因此也很容易理解。 write(size):向文件中写入size个字节 writelines():向文件中一次写入一个字符串列表 需要注意的是write()和writelines()不会写入行结束符,因此我们向文件中写入内容是需要自己处理行结束符。 在对文件操作时一个常用的场景就是先读取文件的全部内容到一个字符串列表中,然后对该字符串列表中的数据进行处理后写回原文件,把之前文件的内容替换掉,这种模式只能使用’r+’模式打开,那么这就存在一个问题就是之前因为读取了文件的内容,因此此时文件指针已经指向了文件的末尾,因此如果我们直接调用write()或者writelines()的话,此时的效果事实上相当于追加模式,会写到原文件的末尾,因此此时我们需要移动文件指针到文件开始位置,然后写入数据。文件指针的移动使用seek()函数。 seek(offset[,whence]) 该函数的第一个参数表示相对某个位置的偏移量,这里所说的相对某个位置就是第二个参数的含义,第二个参数可以取0,1,2三个值,0表示从文件开头开始算起,1表示从当前位置开始算起,2表示从文件末尾算起。默认值为0,从文件开始位置算起。因此代码如下: [crayon-5f03aa5b9e17e872401241/] 使用场景:在很多场合下我们需要读取一个文件中的内容,然后对该内容中的某些字符串进行特定处理,如替换某些关键词,然后将处理后的结果写回到原文件。本实例展示的是从一个记录了baidu,alibaba,tencent,jd,netease,360等知名互联网企业的文本文件中找出带有alibaba的行,然后替换为tencent.最后将结果写回原文件。 [crayon-5f03aa5b9e180038734394/] 这个实例是项目中进行文件操作是经常会遇到的,而且也把前面所总结的知识点全部用到了,包括前面说的对文件操作使用with as,使用try/except对可能出现的异常进行处理等一些好的习惯。 对文件操作尽量使用with as,确保文件操作会调用close() 文件操作一定要使用try/except对可能出现的异常进行处理 如果操作的文件不大的话使用readlines()一次性读取全部文件效率较高,此时不要使用readline(),如果文件较大使用read(size)或者readline()进行处理,减少对内存的消耗 在向原操作文件写回处理的内容的时候注意文件指针的位置,某些场景下需要调用seek(0)回到文件开始位置再写入。

python

python中的字符编码问题

今天在使用python处理字符串时出现了一个如下错误 [crayon-5f03aa5b9e351179957666/] 从字面意思来看很显然是字符编码问题,然后百度了好多解决方案,根本不靠谱,最终折腾了半天还是解决了,记录一下 还是按照国际惯例,先描述一下应用场景:在对Apk的so加密保护中,有这么一个过程,将dex通过baksmali.jar反编译后得到的smali文件中,判断用户输入的待保护的so列表soList中 的so是否被调用,我们知道用户输入的so一般形如libxxx.so的格式,而java调用语句则是System.loadLibrary(“xxx”);的形式,即去除了前面的lib和.so后缀。反映到smali代码中就是如下的形式: [crayon-5f03aa5b9e356412080720/] 其中的xxx就是so列表中去除了前面的lib和.so后缀的so名称。那么这里就存在一个场景就是遍历修正用户输入后的so列表,然后在smali文件中判断是否存在调用该so的代码,即是否存在形如上述两条语句的代码,那么我们需要做的某一步就是构造出上述两个语句来,即讲so列表中的so与字符串const-string v0,相连接,刚开始以为很简单,不就是如下代码就可以吗? [crayon-5f03aa5b9e359767015352/] 但是运行时出现开头提到的错误,很显然错误说的是编码问题,然后百度了一下,都说要在python文件开头加上utf-8注释之类的语句,或者说是使用如下语句: import sys reload(sys) sys.setdefaultencoding(‘utf-8’) 事实上没任何作用,仍然报开头提到的错误。那么我们仔细思考下为何会报那个错误,难道不应该是字符串的连接那么简单吗?事实证明不是那么简单,因为python中的list列表中存储的虽然是字符串,但是在python内部是使用unicode表示的,因此不能直接将一个list列表中的字符串与”的字符串相连接,因为这两种字符串可能属于不同的编码格式,是不能够直接相连接的,str与unicode之间的关系如下所示: [crayon-5f03aa5b9e35b608547966/] 也就是说str是字节串,是unicode经过编码(encode)后的字节组成的,这两者之间通过 decode/encode相互转化,那么知道了具体原因自然就知道该如何解决了,代码如下 [crayon-5f03aa5b9e35c342175054/] 将list列表中的unicode先encode为字符串,然后再与‘xxx’字符串相连接,所以以后大家遇到这个问题,就不要相信网上的谣言说在python文件开头加上utf-8的注释,一定要弄清楚处理的字符串是str类型还是unicode类型,判断方式是使用type [crayon-5f03aa5b9e35e793749388/] 看似都是字符串,一个是str类型一个是unicode类型,总之要搞明白要处理的是str还是unicode, 使用对的处理方法(str.decode/unicode.encode) 一个和本内容相关的传送门:python进阶编码处理小结

python

python遍历删除同时修改List中的元素

         之前没怎么接触过python,因为项目要用的python,所以开始学习python,说实话和java,c++比起来,python简单多了,但是由于是初次学习,在使用python遍历删除元素的时候遇到了一些坑,简单的记录下。        先说下应用场景:在进行安卓Apk的so加密保护中,需要从用户输入的一个so的list列表中,首先要判断用户输入的so是否在待加密Apk中存在,当然允许用户输入大小写有误,如果不存在则将该so从用户输入的list列表中删除,如果仅仅是大小写不匹配则认为该so是存在的,即需要修正so列表,且结果要反应到用户传入的so的list中。        因为结果要反应到用户传入的so的list中,那么很显然,我们应该对传入的list进行操作,而不能重新定一个list操作,然后返回。soLists为传入的用户输入的so列表,因为工作原因,只能贴出部分代码,代码如下: [crayon-5f03aa5b9e529280805094/]   在上述代码中遍历方式使用的是for in range,而不是for in形式。 因为虽然使用for in可以做到安全的遍历删除,遍历拷贝的list,操作原始的list:代码如下: [crayon-5f03aa5b9e52f206308269/] 但是事实上,so = existedSo这句代码起不到期待的作用,因为为了保证遍历时的安全删除采用的是遍历拷贝的list,操作原始的list(如果不这么做则不能在遍历时删除元素,不然可能导致数组下标越界的错误),所以so = existedSo这个语句不会反应到原始的传入的so的list中,也即起不到修正大小写的作用。所以采用的是for in range形式倒序遍历删除同时可以修改原list中的元素的值。 因为是直接对原list进行操作,所以为了保证遍历的时候安全删除采用了倒序遍历的形式,即通过指定range函数的参数,range函数的定义如下: [crayon-5f03aa5b9e531226475245/] 我们知道在c++/java中我们通常这么使用for循环 [crayon-5f03aa5b9e533660784979/] 而在python中的range函数的start参数就是起始访问下标,stop就是结束访问下标,而step则是访问的步长,该值可以为正(正序访问元素),也可以为负(逆序访问元素),但是不能为0.如: [crayon-5f03aa5b9e534504662254/] 等同于c++/java中的: [crayon-5f03aa5b9e535356805471/] 总结:如果仅仅只是在遍历的时候安全删除元素,可以采用 1遍历拷贝的list,操作原始的list: [crayon-5f03aa5b9e536513773073/] 2采用for in range 的形式倒序遍历。 [crayon-5f03aa5b9e538139751275/] 如果仅仅只是在遍历的时候安全删除元素,推荐第一种方式,如果不仅要保证遍历时安全删除,同时要修改原list的元素的值,那个采用第二种方式。