solomonxie / blog-in-the-issues

A personalised tech-blog, notebook, diary, presentation and introduction.
https://solomonxie.github.io
67 stars 12 forks source link

Python学习笔记 #24

Open solomonxie opened 6 years ago

solomonxie commented 6 years ago

这种方式记笔记可能会很轻松,开篇试试吧

solomonxie commented 6 years ago

Get System Arguments 获取系统参数

import sys

# 输出文件名
print sys.argv[0]

# 输出第一个参数
sys.argv[1]
solomonxie commented 6 years ago

Define a class 定义类

class Person:
    self.name = ''
    self.id = 0
    self.father = 1
    self.mother = 2 

    def __init__(self, name):
        self.name = name
    self.born()

    def born(self):
        self.id = self.father + self.mother

me = Person('Solomon')
print(me.id)
solomonxie commented 6 years ago

❖ Python path路径问题

看似是个小问题,但是在python里实际上是个非常容易被混淆的东西。

路径解析

路径解析就是你拿出一个包含路径文字的str字符串,然后把它的每一部分都拆分解析出来,包括文件名,扩展名,文件夹名和文件夹路径等。

文件名

>>> s = '/Users/me/movie/abc.mp4'
>>> os.path.basename(s)
'abc.mp4'

文件名(不含扩展名)

>>> s = '/Users/me/movie/abc.mp4'
>>> os.path.basename(os.path.splitext(s)[0])
'abc'

目录名

>>> s = '/Users/me/movie/'
>>> os.path.basename(os.path.realpath(s))
'movie'

为什么要这么写?看看下面实验就知道: image

获取系统相关路径

获取当前系统用户文件夹(Home Directory):

# 也就是解析命令行里`~`指向的地址
path = os.path.expanduser("~")

# 延伸:
path = os.path.expanduser('~/.tmux')

获取当前脚本相关路径

获取当前脚本的所有相关位置。

参考文章

需要import osimport sys

solomonxie commented 6 years ago

python调用命令行

参考这篇文章

import os

#只返回结果
os.system(command)

#或者,返回结果与终端显示信息
with os.popen(command, mode) as f: 
    print(f.read())
solomonxie commented 6 years ago

Python代码之美

有时特别想摘抄一些别人漂亮的代码书写。在这里贴上吧。

gh-issues-import.py

代码整齐和常量名全大写 image 分隔有序,不用注释也可以清晰表面之间分别 image 简单函数和复杂函数的断行方式 image

solomonxie commented 6 years ago

❖ Virtualenv虚拟环境的正确使用方法

之前当真以为是每个小项目都配置一个virtualenv环境,就每个文件夹都分别用virtualenv命令生成一个环境,还出现了一大堆文件夹,而且每次都要pip install重新安装一系列东西。 再加上很多小项目都有git配置,两者冲突,所以必须在.gitignore文件里屏蔽virtualenv都一切文件夹。

后来发现: virtualenv虚拟环境不是只在这一个文件夹里生效的,它是那种只要开启了,你就带着这个虚拟都帽子到哪里哪个文件夹哪个项目都生效。 所以我就想,直接配置一个单独的文件夹专门放置virtualenv环境,然后每次都开启着它然后cd到项目文件夹去操作就行了。 这样既不会搞乱系统python环境,又不会每次都创建环境那么麻烦,毕竟不是什么大项目嘛。 如果是大型的项目,再单独在项目其中创建一个虚拟环境就好了。绝大多数时候,我只需要一个虚拟环境代替系统的python环境就足够了。

步骤是这样的:

  1. 随便建一个位置,比如cd ~/,然后virtualenv venv,创建了一个叫venv的文件夹并且在里面配置了虚拟python环境。
  2. 配置命令行别名:alias venv="source ~/venv/bin/activate",这样就能一键开启虚拟空间,带上小帽子。
  3. 安装自己所有需要的包,代替系统环境:pip install PACKAGE-1 PACKAGE-2...,或者pip install -r PATH/requirements.txt从之前的备份列表中一键恢复安装。
solomonxie commented 6 years ago

~Python2中文简易解决方案~ (暂废除)

翻了翻几年前研究Python中文编码的问题,原来如此复杂。。。。一时间全忘了。 为了避开这个理论上的难题,我直接开启了实验出真知的模式,试验出一个简单的方法。 简单来说如下:

  1. 首先页头要有# -*- coding: utf-8 -*-的声明
  2. 整个py文件中,只要任何一处设计字符串,都要认真处理
  3. 所有获得的字符串,都要先进行"字符串".decode('utf-8)解码为某种原始编码。
  4. 所有要发出的或写入文件的,都必须要进行"字符串".encode('utf-8')编码为统一码。
solomonxie commented 6 years ago

❖ 对Python 2.x字符编码的通宵抱怨

首作时间:Dec 8, 2015

总之对于这个我唯一认定的事就是:Python里要是弄不明白编码,那这个语言就放弃吧!

下面是目前收获到的一些内容,没写完,再议吧。。。

首先要在文件第一行写上编码声明

#coding:utf-8

编码声明的格式其实很随意的,coding=utf-8, -- Coding:Utf-8 -- 等等都行,
Python只识别关键的字。
如果不写编码声明,那么文件中出现的任何中文都会报错:
SyntaxError: Non-ASCII character '\xe5' in file xx.py on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

Python 2.x 系列对中文真是弱爆了,查了巨多的文献,还是不那么明了。
本来想要练习开发个什么小程序,结果全都被扼杀在编码的坑里了。
随便搜一搜"Python 编码"就知道,所有作者们都遇到了一毛一样的问题。。。
据说,

除非把整个编码系统从古至今的历史熟透,各编码的原理通透,
相互之间的转换了熟于胸,Python的这个坑是跨不过去的。小看了这小玩意儿!  

下面开始正式研究下吧,争取能做到practical。

这是原始汉字数据:

chinese = '你好' # 普通中文字符串
uni = u'你好' # 字符串前加u代表转换为unicode变量类型

研究一个原始的中文字符串

在IDLE命令行中执行一下试试

>>> chinese
'\xc4\xe3\xba\xc3'
>>> print chinese
你好
>>> type(chinese)
<type 'str'>

这是什么鬼!为什么直接输变量名和打印变量名会不一样呢?! 静悄悄的理解一下,应该是: chinese变量在内存中存储的就是\xc4这种编码,是给机器看的 而print打印出来的是给人看的,所以python要处理一下让人能看懂。 再来说,\xc4这是个什么码?而且你好是两个字,为什么编码里有4个\x$$这种? 所以推理到,python对每个汉字是用2个编码来存储的。 首先能确定的是,\x$$这种格式是ASCII编码,因为百科到如下:

ASCII(American Standard Code forInformation Interchange),是一种单字节的编码。计算机世界里一开始只有英文,而单字节可以表示256个不同的字符,可以表示所有的英文字符和许多的控制符号。不过ASCII只用到了其中的一半(\x80以下)

也就是说,英文字母用\x01\x80之间的数表示,数字不用编码, 那么汉字这种国外字就全部都是从\x80往后排了。因为字太多,肯定不够用,所以:

  1. 格式变成了'\x字母数字'这种东西
  2. 而且还用两个字节码来表示一个中国汉字。

    用chardet检测字符串编码

那么来检验一下我的推理吧?用个第三方模块chardet

import chardet
print chardet.detect('你好')
# [结果]: {'confidence': 0.3598212120361634, 'encoding': 'TIS-620'}

这什么鬼!!为什么检测出来有35%的TIS-620编码? 不能放弃,我再来试一试:

import chardet
print chardet.detect(str('你好'))
# [结果]: {'confidence': 0.3598212120361634, 'encoding': 'TIS-620'}
print chardet.detect(repr('你好'))
# [结果]: {'confidence': 1.0, 'encoding': 'ascii'}

真是受够了。。。 为什么检测str()出来的就是TIS-620,而检测repr()出来的就是ASCII呢? Python世界中,str和repr到底谁才更接近本源、谁才是出来捣乱的呢? 唉算了吧,不用这个了。

str 和 repr 在存储和print打印上的对比

我又想起了一个实验,我们再来一下:

>>> repr('你好')
"'\\xc4\\xe3\\xba\\xc3'"
>>> print repr('你好')
'\xc4\xe3\xba\xc3'
>>> str('你好')
'\xc4\xe3\xba\xc3'
>>> print str('你好')
你好

什么毛病!据说repr()str()是一样的啊!怎么会这样呢? 实验里,repr()str()的内存数据的确是一样的,但是一print就不一样了: 只有print str()才会显示出中文! 那可能是print的问题了。那是不是说如果我不print,而是别的操作如存到文件中,那么就不会有中文出现了?试试吧:

f = open('test.txt', 'w')
f.write(str('你好'))
f.write(repr('你好'))
# [结果]:你好 '\xe4\xbd\xa0\xe5\xa5\xbd'

也就是说不是print的问题了,str()repr()在本质上就是有区别的。 只有str()会把中文显示出来,而repr()不但会把中文显示成ASCII码, 还会异常搞笑的把在print里显示4位字节码变成6位字节码!!!这是什么毛病?! 好吧,这个先留着,来看看转成了unicode的中文吧。

研究已变成unicode类型的中文字符串

好吧,再来试一试uni:

>>> uni
u'\u4f60\u597d'
>>> print uni
你好
>>> type(uni)
<type 'unicode'>

这又是什么鬼!我原先的想象是, print一个unicode对象,肯定出来的是unicode码,但两种方法的结果完全反了。。。 再静悄悄的理解下,uni肯定也是在内存中存储的是unicode编码。 在python中unicode的格式是:u'\u$$$$',其中$$$$代表4位的字符编码。 然后又试了一下这个:

str(unicode类型) 和 repr(unicode类型) 结果的对比

>>> str(uni)
.......
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1 ......
>>> repr(uni)
"u'\\u4f60\\u597d'"

str()和repr()的对比

这个好理解,用help(str)命令可以看到: str(basestring)是将某个object变成一个漂亮的易观看的字符串。 并且,它需要的是basestring类型变量作为参数。 而help(repr)只是简单说,repr(object)会返回这个object的规范化字符串。 它对参数并没有什么要求。 这样对比来看的话,str和repr在inout上都不一样。 str 对谁进来有限定,出去要漂亮要好看; repr对谁进来无所谓,出去也够标准就行。

原始中文字符串和unicode格式的中文字符串对比

暂时写不下去了,再议吧。。。。。。。。

这些事我目前为止收集到的Python 编码相关文章,还没解决问题。

都是PDF的~~~ 最近养成了“好习惯”,看过的网页都一键转成PDF,一边做笔记,一边存档。这样以后也容易翻了。不像以前看过了,解决了,就丢到脑后了,然后过一阵子不用,又重新查一遍。

【整理】Python中实际上已经得到了正确的Unicode或某种编码的字符,但是看起来或打印出来却是乱码 _ 在路上.pdf 【整理】Python中用encoding声明的文件编码和文件的实际编码之间的关系 _ 在路上.pdf 【整理】关于Python脚本开头两行的:#!_usr_binpython和# -- coding utf-8 --的作用 – 指定文件编码类型 _ 在路上.pdf 【总结】Python 2.x中常见字符编码和解码方面的错误及.pdf ITArticles_Python的中文显示方法.pdf Python - 编码转换 - 生命不息,学习不止! - 博客频道 - CSDN.pdf Python repr() 或str() 函数 - mingaixin - 博客园.pdf python 编码转换 [Python俱乐部].pdf Python2.x的编码问题.pdf Python编码和Unicode - 博客 - 伯乐在线.pdf Python的中文编码问题 - WuXianglong - SegmentFault.pdf python读写文件,和设置文件的字符编码比如utf-8 - 为程序员服务.pdf Python语言十分钟快速入门.pdf python中文编码详解 - Cody的笔记本 - 博客频道 - CSDN.pdf 初探python编码 - LX - 博客频道 - CSDN.pdf 中文字符编码标准+Unicode+Code Page _ 在路上.pdf 字符串,那些你不知道的事 _ Keep Writing Codes.pdf

solomonxie commented 6 years ago

Python2 requests库抓取网页出现乱码

练习抓取网页时遇到的,如果是简书等这些标准网站,正常抓取是没问题的。但是很多网页竟然怎么抓取都是所有中文都乱码。弄的我还以为是python代码本身的encoding问题。最后才追溯到原来是出现在源头requests库里面。

参考这两篇文章,requests官方文档, 和,代码分析Python requests库中文编码问题,非常有参考性。

第二篇文章中看到,很多网页实际上并不都是utf-8的编码格式,还有很多是ISO-8859-1格式,如下图: image 但是,其实不是网页本身的问题!我们查看网页本身的headers发现,他们的charset值是utf-8,但是为什么用r.encoding()得到的却是ISO-8859-1呢?文章中指出原来是requests的bug,而且常年不解决。所以就需要我们自己来想办法。 我们不能手动去检查每一个网页的编码啊,那样太麻烦了。 官方文档中出现了这么一小句话,非常重要,亲测有效: image 虽然这句话不是为了处理网页的,但是二进制!沿着这个思路,又在官网看怎么将网页获取为二进制模式的: image 就是使用r.content获取。

solomonxie commented 6 years ago

❖ 关于解决Python2乱码问题的终极解决方案 (TL;DR)

image

有个特别好玩的现象,当我们为了python编码头疼的时候,几乎搜索到所有的文章都会先发一通牢骚。然后在无可奈何地写解决思路(是解决思路不是方案)。这个问题真不是新手问题,即使是十几年python老手也经常头疼。中国外国都一样。看看这个python专家在PyCon大会上用半个多小时讲解乱码的视频就了解了,他自己都给自己的来回encoding, decoding, encoding, decoding说晕了,台下举手他都拒绝回答,可想而知这个问题复杂性。

我认为,几乎每个pythoner,都会有一段人生浪费在了编码上。可以说这个问题,是如果你不彻彻底底解决,就永远会崩溃的地步。翻看我曾经写的数篇文章就知道了:

牢骚结束,下面是我又一次用了两个整天才测试整理书写完成的ipython notebook笔记。ipynb格式的笔记源文件在这里,当然有可能会链接失效,有喜欢ipython的live coding笔记的且想要用这个笔记测试编码的,请联系我。

首先,需要先要了解python的print大法

如果python的print的特性都没有了解的话,希望你不要贸然尝试用print去调试测试乱码编码的问题。 这里的print厉害到让你不高兴的地步——它不管你塞过来的是什么格式什么编码,字符串数组对象什么的的都一口气全打印出来。 感觉好像很好,但其实是我们仔细研究编码问题的最大阻碍。 因为你塞给print一个unicode它能打出中文,塞一个utf-8或iso8895给它,也一样给你打印出原文。这样以来,你看着它出现原文后,就欣喜若狂产生了一种胜利的错觉。 所以我想在这里最先说清楚它:

不要轻易在研究乱码的时候用print测试目标!

也不是说这种时候一点都不能用,而是说你可以print别的什么东西,但是如果想看清某个变量本质的话,千万不要用。 这个时候要用print repr(字符串) ,或者最好是在命令行或ipython里面测试,像这样: image

看出区别了吗?明确了这点,再来继续研究编码问题。

简单来说,先要记住,在Python2里字符串只有两大阵营:

unicodestr

如果type(字符串)显示结果是str,其实指的是bytes字节码。 而其它各种我们所说的utf-8gb2312等等也都是Unicode的不同实现方式。 这里不要去考虑那么复杂,只要先记住这两大阵营就行。

encodingdecoding

绝对要记住的: 从unicode转换到str,这个叫encoding,编码。 从str转换到unicode,这个叫decoding,解码。 image (图片引用自知乎相关某答案。)

来回记住这个问题,才能进入下一步!

然后来看个案例。 image

通过上面两种格式的对比我们看到,str和unicode的各种区别。

那么,既然变量里面会出现两种不同的格式,如果我们把两种格式的字符串连在一起操作会发生什么呢? 如下: image

看!著名的编码错误UnicodeDecodeError: 'ascii' codec can't decode就这样出现了!

以上是我们用显性字符串来比较两种格式字符串的区别。

但是,我们经常性处理python编码问题,都不是在这种显性的字符串上出现的,不是从网上爬取的就是从本地文件读取的,意思就是文件内容庞大,编码格式很难猜到是什么。 所以这里我们将问题再拆分为两部分讨论:本地文件和网络资源。

本地文件编码测试

首先在本地建立一个有中文的以utf-8格式保存的文本文件(实际上无论.txt还是.md等都无所谓,内容是一样的)。 内容只有'你好'。

然后我们来读取一下:

image

上面看到,从文件读取出来的,就是str格式的字符串。 那么如果要把str转化为unicode,就要解码,也就是decoding.

image

这种时候实际上是最迷糊也最容易造成之后错误的,就是分不清该编码还是该解码。

所以上面提到,必须要记住这两个区别。 那么如果现在我搞反了怎么办?就会再次出现下面错误:

image

话说回来,我们该怎么统一他们呢?

为了避免两种格式的字符串在一起乱搞,统一他们是必须的。但是以哪一种为统一的呢,unicode还是bytes?

网上各种文章统一口径,要求代码中出现所有的变量都统一为unicode。 可是我在实践和测试中都越来越发现:这种做法真的不那么可靠,甚至我怀疑有可能我们碰到那么多的问题,都是由它搅乱引起的。

下面我们来看看做常用的环境下字符串都是什么格式

image

这样就明白了:除了r.text返回的内容外,其它几乎都是使用str格式,也就是bytes字节码码。所以我们只要转化requests相关的内容就行!

实际上,requests返回的response中, 除了用.text获取内容,我们还可以用.content获取同样的内容,只不过是bytes格式。

那就正和我们意,不用再去转化每一个地方的字符串,而只要盯紧这一个地方就足够了。

为什么我们不能把所有字符串变量统一为unicode呢?

先提醒下,变成unicode的过程,叫decoding。不要记错。 像.text经常把ISO8859等猜不到也检测不到编码(机率很低)的字符串扔过来,如果遇到的话,是很麻烦的。 decoding有两种方法:

unicode(b'你好‘)
b'你好'.decode('utf-8')

这里因为不知道来源的编码,所以必须用unicode()来解码,而不能用.decode('utf-8'),因为显然你不能乱写解码名称,如果来源果真是(很大几率是)ISO8859等方式,那么错误的解码肯定会产生乱码,或者直接程序报错。切记!

所以这里只能用unicode()解码。如下例:

image

结论:一定记住,全文都统一用str格式字符串

只要盯紧requests、json等这种经常处理外来资源的库就好了。

只要控制好外来源的字符串,统一为str,其它一切都好说!

实际上,我发现遇到的绝大多数编码问题,实际上不是python原生方法导致的,而是这些外来库所引起的!因为每个模块都会有自己的一套处理编码的方式,你还真不知道它是采用哪个。就像JSON的dumps()一样埋着大坑等着我们。所以真正应该盯紧的就是这些库了。

下面是一个从获取网络资源(含中文且被requests认为编码是ISO8850的网页)到本地操作且存储到本地文件的完整测试。

import requests

r = requests.get('http://pycoders-weekly-chinese.readthedocs.io/en/latest/issue5/unipain.html')

# write a webpage to local file
with open('test.html', 'w') as f:
    f.write( r.content )

# read from a local html file
with open('test.html', 'r') as f:
    ss = f.read()

大功告成!效果如下:

image

再也不用纠结、检查每一个变量、写一大堆嵌套转化方法了!注意,只要盯紧各种外来模块和库的文字处理就够了。

另外,关于JSON的乱码问题,又是一个新的较长篇章。我会单分一篇,请到我的专栏里找。

solomonxie commented 6 years ago

❖ Python2操作JSON出现乱码的解决方案

其实刚刚写过一整篇Python编码问题的解决方案,由于JSON又是一种特殊案例(与库相关,与语言本身无关)所以就单独提出来说。

我们来看一个从网上获取json并又存到本地文件的例子

import requests,json

r = requests.get('https://api.github.com/repos/solomonxie/\
solomonxie.github.io/issues/25/comments')

# 获取到我的github中某条issue的所有评论,形式为<JSON格式的字符串>
comments = json.loads( r.content )

# 取某一条评论查看内容(中文)
cc = comments[0]['body'][0:10] # 取出的内容是'## 配置:先从配置'

然后来测试下变量cc: image

好,到这里先停一下!

JSON的读取到目前为止,都是正常的:JSON Object对象给出的值都是unicode,没有被莫名转义,也没有报错误。

但是,unicode格式,意味着它和str格式不兼容! 这时,害羞的大姑娘Unicode刚出炉,你不能在这个时候让它和Str操作在一起! 报错也往往就在这种疏于防备的时候!

比如你看:

image

上面打印了三条Unicode和Str的结合, 前两条分别是以Str格式的结合,以Unicode格式的结合。 但是第三条,把两个不同格式的字符串结合,就出错了。

对不起,这里不是Javascript,变量不可以任意交合。Python对变量和编码都是极其谨慎的。

所以明白了这点,我们再继续。

上面获得了JSON Object对象,那么再来试试将JSON对象整体存到文本文件中。

如果要存到本地文件,那么就必须把Object对象转换为Str格式的字符串。 json库自带.dumps()函数可以进行转化。 但是这里问题出现了!我们来小试一下: image

竟然连print大法都不能把json.dumps()返回的内容正确打印出来。经过各种测试和查看官网对于此函数的文档,发现:

原来json.dumps()是默认所有非ascii码强制转化为代号(而非汉字)的,于repr()效果等同!

官方文档里有说明,json.dumps()里面有个ensure_ascii参数,默认为True。 意思就是默认把所有非ascii字码用\强制转化。所以,为了关闭这个功能,我们必须把它设为False. 下面是个小测试: image

这样一来JSON在Python里的编码问题就解决了:须用json.dumps(obj, ensure_ascii=False)来转化为字符串

下面是完整的代码测试:

# @网络资源到本地存储真实测试
import requests,json

r = requests.get('https://api.github.com/repos/solomonxie/solomonxie.github.io/issues/25/comments')

# 获取到我的github中某条issue的所有评论,形式为<JSON格式的字符串>
comments = json.loads( r.content )

outgoing = json.dumps( comments, ensure_ascii=False )

with open('test.txt', 'w') as f:
    f.write(outgoing.encode('utf-8'))
with open('test.txt', 'r') as f:
    read = f.read()

print read[0:20], type(read)

来看结果: image

大功告成!

solomonxie commented 6 years ago

更多Python2编码的小细节

数组.join()合并

数组中必须所有的元素都是字符串,且都是统一的编码才能合并,否则报错。统一后,如果全是unicode,那么返回的字符串就是unicode;如果元素全是str,那么返回的就是str。 image

solomonxie commented 6 years ago

python将某个目录打包为zip文件

比较古老的方法是用zipfile库创建zip包,但是要写各种循环迭代需要很多行代码。 还有另一种python自带库shutil,可以一句话打包为zip文件。

import shutil
shutil.make_archive(base_name, format, root_dir, base_dir)

很快就打包好了! 唯一注意的是,怎样把它安装自己想象的结构打包。

solomonxie commented 6 years ago

Python 日期和时间

获取当前日期:

from datetime import date
d = str(date.today())
print(d)

字符串转日期: 转换YYYY-MM-DDTHH:mm:sZ格式日期,如2015-12-07T22:31:28Z

from datetime import datetime

# 根据给定日期,制作一个相应的格式
date_format = "%Y-%m-%dT%H:%M:%SZ" 

# 用给定日期格式来解码转换
d = datetime.strptime('2015-12-07T22:31:28Z', date_format)

print(d)
#[out]: 2015-12-07 22:31:28

日期转字符串(给定格式):

from datetime import datetime

format  = '%Y-%m-%d'
today  = datetime.today()

print( today.strftime(format) )
#[out]: '2018-07-31'

日期加减:

import datetime

today = datetime.date.today()

oneday = datetime.timedelta(days=1)

yesterday = today - oneday

tommorrow = today + oneday

print(yesterday, today, tommorrow)
#[out]: 2011-06-02      2011-06-03      2011-06-04
solomonxie commented 6 years ago

Python调试工具pdb —— Python Debuger

参考文章1参考文章2

常用命令

# 退出循环 (指针要指在for语句上才行)
until

# 下一步
next 或 n

# 深入性下一步(进入每次调用的函数里面)
step 或 s

# 移动到上一层/下一层函数
up 或 u
down 或 d

# 执行直至当前函数结束
return 或 r
solomonxie commented 6 years ago

用requests报错requests.exceptions.SSLError

image 明明没有改代码,突然就报这种错误。 调查发现,原来是被服务器拒了。可能是今天来回调试,多次访问同一个地址,就被屏蔽了。 但是,同样是没有设置请求Headers的客户端postman和insomnia就还能正常访问,不知道为什么。 后来知道了,原来是服务器拒绝给我传送数据,因为访问量太大了! Github的API是比较好的,它会在response中返回一个当前访问剩余量和下次能再次开始访问的时间。所以搞明白这个就知道,不是自己代码的事,而是访问量的事了。解决方法就是request访问时加上auth认证,这样就会从默认的每小时60次访问增加到每小时5000次。基本上够用了。

solomonxie commented 6 years ago

Python操作Git库 GitPython

参考文章 参考文章 复杂点的参考

试了一圈发现,git库的用法设置非常符合原生git命令,只不过之间加了个.而已。 比如原本命令行里是git add .,这里就是repo.git.add('.'), 原本是git commit -m "信息",这里就是repo.git.commit(m='信息') 可以说减少了很多学习时间,基本上我很多都是没参考文档自己猜出来的也能用。

sudo pip install gitpython

库安装好后可以直接在python中用了。

创建、识别、克隆仓库

文件夹地址可以是全路径,也可以是.当前文件夹、../上级文件夹等用法。

# 在文件夹里新建一个仓库,如果已存在git仓库也不报错不覆盖没问题
repo = git.Repo.init(path='文件夹地址')

# 选择已有仓库
repo = git.Repo( '仓库地址' )

# 克隆仓库
repo = git.Repo.clone_from(url='git@github.com:USER/REPO.git', to_path='../new')

常用语句:

# 查看repo状态
print repo.git.status()   # 返回通常的status几句信息
print repo.is_dirty()    # 返回是否有改动(包括未add和未commit的)

# 添加文件 可以是单个文件名,也可以是`[ ]`数组,还可以是`.`代表全部
print repo.git.add( '文件名' )

# commit提交
print repo.git.commit( m='提交信息' )

远程交互操作

# 创建remote:
remote = repo.create_remote(name='gitlab', url='git@gitlab.com:USER/REPO.git')

# 远程交互:
remote = repo.remote()
remote.fetch()
remote.pull()
remote.push()

实验效果

 # 原意是返回工作区是否改变的状态
# 但是测试发现,工作区有变动它返回False,没变动却返回True
print repo.is_dirty()

生成tar压缩包

# 压缩到 tar 文件
with open('repo.tar', 'wb') as fp:
    repo.archive(fp)
solomonxie commented 6 years ago

pip 常用操作

注意:

## 安装pip
$ curl https://bootstrap.pypa.io/get-pip.py >> get-pip.py
$ python get-pip.py

## 安装包
pip install <package name>

## 删除包
pip uninstall <package name>

## 升级某个包
pip install --upgrade <package name>

## 安装某个版本的包
pip install django==1.9

## 升级自己
pip install --upgrade pip

## 显示模块包的安装路径
pip show <package name>

## 查看已经过期的软件(不是最新版)
pip list --outdated

## 列出已安装的包 (二者皆可)
pip list
pip freeze

## 导出已安装包到requirements.txt
pip freeze > requirements.txt

## 批量安装包
pip install -r requirements.txt

## 搜索包
pip search

## 查询可升级的包
pip list -o
solomonxie commented 6 years ago

Python 删除某文件夹

参考文章

# 删除某个目录及里面所有内容,第二个参数为True时忽略所有错误中断
shutil.rmtree('<path>', True)
solomonxie commented 6 years ago

Python 异常捕获 [DRAFT]

Python的异常,会直接导致进程的死亡。

常用配置是这样的:

try:
    do_something()
except BaseException as e:
    print 'Failed to do something: ' + str(e)
solomonxie commented 6 years ago

Python 睡眠 [DRAFT]

参考文章

import time

# Wait for 5 seconds
time.sleep(5)

# Wait for 300 milliseconds
# .3 can also be used
time.sleep(.300)
solomonxie commented 6 years ago

Python 文件读取的练习

原作于:Dec 6, 2015

为了了解txt的读取速度,每一段都加上了时间计算

先练习了下基本的txt文件读写

start = time.clock()
f = open('hello.txt', 'r+')
f2 = open('hello2.txt', 'w')
f2.write(f.read())
f2.close
f.close()
end = time.clock()
print str(end - start)

然后是将14M的txt文件逐行复制到新文件的速度

start = time.clock()
f = open('hello.txt', 'r+') # 14M的txt文件
f2 = open('hello2.txt', 'w') # 逐行复制到新文件里
n = 0
while True:
    line = f.readline()
    n += 1
    if line:
        f2.write(line)
        #print '现在正在写入第 %s 行数据。' % n
    else:
        break
f2.close
f.close()
end = time.clock()
print str(end - start)

接着是生成1.4G的txt文件

start = time.clock()
f = open('hello.txt', 'r') # 原文件14M
f2 = open('hello-1g.txt','a')
content = f.read()
f.close()
# 想要生成1.4G的txt文件
# 用来测试各种方式读写大文件的速度
for i in range(1000):
    f2.write(content)
f2.close()
end = time.clock()
print str(end-start) # 用了446秒,无所谓了,主要是弄出一个1G的文件

用readline()来逐行读取1G文件的速度

start = time.clock()
f = open('hello-1g.txt', 'r')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
f.close()
end = time.clock()
print str(end-start) #用了0.08秒

时间是一瞬间吧,这样就看出来,其实不管txt文件多大, 只要用readline()一行一行读,就不占内存,也就是不影响速度。

用read()和来读取1G文件的速度

start = time.clock()
f = open('hello-1g.txt', 'r')
content = f.read() # print出1G是不可能的,直接赋予变量吧
f.close()
end = time.clock()
print str(end-start) 

呃,把1G的内容赋予变量——也就是一个变量在内存中占据1G以上的地盘,直接报错,如下图: baidushurufa_2015-12-6_4-55-26

哈哈 ,这样一来,大概就明白了。 几十M的小文件的话,read()和readline()哪个都无所谓了,方便就行。 大文件的话,绝对不能read()直接读取。

源代码

# coding:utf-8
import time
##################################
"""
# 用read()和来读取1G文件的速度
start = time.clock()
f = open('hello-1g.txt', 'r')
#因为内容太长了,print是看不出处理速度的
#所以直接赋予变量了
content = f.read()
f.close()
end = time.clock()
print str(end-start)
"""
##################################
#"""
# 用readline()和来读取1G文件的速度
start = time.clock()
f = open('hello-1g.txt', 'r')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
print f.readline().decode('gb2312')
f.close()
end = time.clock()
print str(end-start) #用了0.08秒
#"""
##################################
"""
start = time.clock()
f = open('hello.txt', 'r') # 原文件14M
f2 = open('hello-1g.txt','a')
content = f.read()
f.close()
# 想要生成1.4G的txt文件
# 用来测试各种方式读写大文件的速度
for i in range(1000):
    f2.write(content)
f2.close()
end = time.clock()
print str(end-start) # 用了446秒
"""
##################################
"""
start = time.clock()
f = open('hello.txt', 'r+')
f2 = open('hello2.txt', 'w')
n = 0
while True:
    line = f.readline()
    n += 1
    if line:
        f2.write(line)
        #print '现在正在写入第 %s 行数据。' % n
    else:
        break
f2.close
f.close()
end = time.clock()
print str(end - start)
"""
##################################
"""
start = time.clock()
f = open('hello.txt', 'r+')
f2 = open('hello2.txt', 'w')
f2.write(f.read())
f2.close
f.close()
end = time.clock()
print str(end - start)
"""
solomonxie commented 6 years ago

Python集合的操作

有时候在对比两个数组,如果运用上集合的话就会相当精妙。

基本操作

参考文章

s = set([3,5,9,10])
t = set([1,2,3,4,5,6,7,8,9,10])

# 基本运算
a = t | s          # t 和 s的并集  
b = t & s          # t 和 s的交集  
c = t – s          # 求差集(项在t中,但不在s中)  
d = t ^ s          # 对称差集(项在t或s中,但不会同时出现在二者中) 

# 基本操作:  
t.add('x')            # 添加一项  
s.update([10,37,42])  # 添加多项  
t.remove('H')     #删除一项

以下来自官方参考

image

image

solomonxie commented 6 years ago

❖ Python Logging 日志记录入门

Python Logging原来真的远比我想象的要复杂很多很多,学习路线堪比git。但是又绕不过去,alternatives又少,所以必须要予以重视,踏踏实实认认真真的来好好学学才行。

学习Logging的目的: 简单脚本还好,print足够。 但是稍微复杂点,哪怕是三四个文件加起来两三百行代码,调试也开始变复杂起来了。 再加上如果是后台长期运行的那种脚本,运行信息的调查更是复杂起来。 一开始我还在各种查crontab的日志查看,或者是python后台运行查看,或者是python stdout的获取等等,全都找错了方向。 真正的解决方案在于正确的logging。 记录好了的话,我不需要去找python的控制台输出stdout,也不需要找crontab的日志,只需要查看log文件即可。 下面是python的logging学习记录。

最简单的日志输出(无文件记录)

import logging

logging.error("出现了错误")
logging.info("打印信息")
logging.warning("警告信息")

首先,忘掉logging.info()! 忘掉logging.basicConfig()!

网上各种关于python logging的文章实在是太不体谅新手了,logging这么复杂的东西竟然想表现得很简单,还用各种简单的东西做假象。 实际上我们真正要用起来的日志,绝对是不会直接用logging.info()logging.basicConfig()这样的,这是此模块的官方推出来迷惑人的——看似让你一键上手,快速看到结果,但是跟实际真的不搭! 所以为了后面解释起来轻松,必须先警告这点:忘记它们俩! 记住,唯一要用到logging.什么的,就只有logging.getLogger()这一次。

了解logging的工作流

不想上流程图一类的东西,那样反而更迷糊。 简单说吧: logging模块是会自动将你自定制的logger对象全局化的, 也就是说, 你在自己的模块里只要定义了一次某个logger,比如叫log,那么只要是在同一个模块中运行的其他文件都能读取到它。 比如说,你在主文件main.py中自定义了一个logger,可能设置了什么输出文件、输出格式什么的,然后你在main.py中会引用一些别的文件或模块,比如sub.py,那么在这个sub.py中你什么都不用设置,只要用一句logger = logging.getLogger('之前在main.py定义的日志名')即可获得之前的一切自定义设置。

当然,被调用的文件(先称为子模块)中,用logging.getLogger('日志名')时,最好在日志名后加一个.子名称这样的,比如main.sub。这样输出的时候就会显示出来某条日志记录是来自于这个文件里了。当然,.前面的父级logger必须名字一致,是会被识别出来的! 然后,子日志还可以再子日志,甚至一个子模块可以再让所有函数各又一个子子日志,比如main.sub.func1这样的。logging都会根据.识别出来上下级关系的。

这样一说,实际上也就是class类继承的那种机制了。你按照父级名称继承,然后还可以改写自己的新设置等。

了解了这些概念以后,才能来谈代码。实际上也就好理解多了。

设置logger的方法

看来看去,这篇文章说得比较全面也最清楚,以下很多都参考到它的内容:Python 101: An Intro to logging

一般想要自定义一个logger,比如让它输出信息时按照什么格式显示,输出到哪个文件,要不要输出到屏幕一类,有三种方法可以达到设置:

三种达到的目的都是一样的,字典用的人很少也不方便,配置文件比较好用只是.ini的语法不是很方便读,且不容易做到变量的动态设置,所以一般直接在python代码里写就好。

常用设置语句

以下是程序主入口文件的通用写法,注意,一定要在主入口定义好logger,这样其他所有的子模块才能够继承到。

#   main.py
import logging
import otherMod2   # 等下会调用到的子模块

def main():
    """
    这个文件是程序的主入口
    """

    define_logger()

    log = logging.getLogger('exampleApp')

    # 输出信息测试
    logger.info("Program started")
    result = otherMod2.add(7, 8)     # 这个是来自别的模块的方法
    logger.info("Done!")

def define_logger():
    logger = logging.getLogger("exampleApp")
    logger.setLevel(logging.INFO)

    # 设置输出格式
    formatter = logging.Formatter('\n%(asctime)s - %(name)s - %(levelname)s :\n\n\t %(message)s')

    # 设置日志文件处理器
    fh = logging.FileHandler("new_snake.log")
    fh.setFormatter(formatter)    # 为这个处理器添加格式

    # 设置屏幕stdout输出处理器
    sh  = logging.StreamHandler(stream=None)
    sh.setFormatter(formatter)

    # 把处理器加到logger上
    logger.addHandler(fh)
    logger.addHandler(sh)

if __name__ == "__main__":
    main()

下面是子模块中的调用方法(很简单):

# otherMod2.py
import logging

module_logger = logging.getLogger("exampleApp.otherMod2")

def add(x, y):
    # 这里一句`getLogger`就继承到父级的logger了
    logger = logging.getLogger("exampleApp.otherMod2.add")

    # 输出测试
    logger.info("added %s and %s to get %s" % (x, y, x+y))
    return x+y

注意,主文件中,在什么地方定义logger都可以,可以在main()里也可以在任何单独的函数或类里,无所谓。只要在调用子模块之前定义好了就可以了。一旦定义过,日志名就会被记下来,然后子模块就可以轻松继承到。

solomonxie commented 6 years ago

定期执行Python脚本

目前知道的有两种方法:python自带的time.sleep()定时器循环执行某段代码 和 linux系统的crontab命令定期执行某个脚本

solomonxie commented 6 years ago

❖ Python执行脚本将输出重定向时编码错误

原本以为python内部的编码问题解决了,但是用linux命令将标准输出重定向时没想到又遇到了亲切的编码问题。

image

根据文章文章的解释,是因为linux的重定向命令并不知道python文件的输出编码而默认使用了ascii,所以当输出有超出128的字都会报错。 解决方法很简单:

在执行python的命令前加上env PYTHONIOENCODING=utf-8,如:

env PYTHONIOENCODING=utf-8 python ~/hello.py >> log.txt

还可以分开写:

$ export PYTHONIOENCODING=utf8
$ python hello.py  > hello.txt

这里还有一些相关的stackoverflow回答

这还是不能输出所有内容

因为linux输出重定向的道理(在刚刚写的Linux学习的篇章里有专门说明),光是编码还不行,会发现还有很多内容并没有转向到文件里,而还是显示在屏幕上了。 其实我们上面写的转向语句,只是把显示在屏幕上的stdout标准输出转向了日志文件,可是还有stderr标准错误没有转向到日志文件,所以才显示到了显示屏里。 虽然看上去很多内容看起来并不是错误,比如git push的正常返回,好像和stderr标准错误没什么关系,可是它们本质上是通过stderr输出到屏幕的,只是我们不知道而已。 所以这时候, 应该把标准错误合流到标准输出里,一起转向。 在命令的结尾加上2>&1,让2转向1,意思就是让标准错误转向至标准输出。其中>代表Redirect to&没意义只是用来告诉系统后面的1是代表输出设置,而不是文件名。

用上面的例子,这里应该这样写:

env PYTHONIOENCODING=utf-8 python ~/hello.py >> log.txt 2>&1

然后,哒哒!屏幕上不会显示任何内容了!也就是说所有的东西都转向了log.txt文件里保存。

solomonxie commented 6 years ago

❖ Python clipboard/pasteboard image 处理剪切板图像问题

目前python对剪切板的原生支持几乎是没有,必须下载第三发模块。 目前比较流行的是跨平台最好的pyperclip和比较强大的gtk。 但是pyperclip虽然简单易用,但是只支持文字,不支持图片等。 gtk支持剪切板中的图像,但是代码较复杂,因为它原本是为了做桌面程序的库。

再深入调查,python专门处理的图像的PIL库以及其升华版Pillow库,也并不能完好支持剪切板图像的读取。Pillow只能支持windows平台上的剪切板图像读取。

看到这么麻烦,我试图转换思路: 看看能不能用外部程序如命令行等先把剪切板图片保存为本地图片然后再让python来处理。 但是,linux和mac都原生对剪切板图像的支持也不是很好,即使是第三方应用xclip、xsel等建立在x window基础上的应用也很难做到这个简单的东西。 目前在Mac平台上比较好用的相关命令行应用,只有pngpaste。方便好用,只是不支持gif和在文件上直接复制来的数据。

还是不够满意,于是再深度搜索和阅读大量的文章、大量的尝试,最后得到Mac上处理剪切板图像的python方案:PyObjC库。 Pyobjc库是用python实现与Mac电脑基层api连接操作的库,在操作Mac OS底层问题上十分强大。 pyobjc中有一个AppKit模块,而Appkit模块中有一个NSPasteboard类,有非常全面的支持剪切板操作的方法支持。 我的Mac系统是Sierra,10.12,在以前安装过xcode的基础上,直接用pip install pyobjc即可完成整个库的安装,非常简单。注意:为了怕库太复杂影响系统其他程序操作,所以我打开virtualenv环境,安装在项目里而不是系统里。 安装好后就可以直接在python里面编码了。以下代码改编自这篇简书文章

# pasteboard.py

import os
import time

# 从PyObjC库的AppKit模块引用NSPasteboard主类,和PNG、TIFF的格式类
from AppKit import NSPasteboard, NSPasteboardTypePNG, NSPasteboardTypeTIFF

def get_paste_img_file():
    """
    将剪切板数据保存到本地文件并返回文件路径
    """
    pb = NSPasteboard.generalPasteboard()  # 获取当前系统剪切板数据
    data_type = pb.types()  # 获取剪切板数据的格式类型

    # 根据剪切板数据类型进行处理
    if NSPasteboardTypePNG in data_type:          # PNG处理
        data = pb.dataForType_(NSPasteboardTypePNG)
        filename = 'HELLO_PNG.png'
        filepath = '/tmp/%s' % filename            # 保存文件的路径
        ret = data.writeToFile_atomically_(filepath, False)    # 将剪切板数据保存为文件
        if ret:   # 判断文件写入是否成功
            return filepath
    elif NSPasteboardTypeTIFF in data_type:         #TIFF处理: 一般剪切板里都是这种
        # tiff
        data = pb.dataForType_(NSPasteboardTypeTIFF)
        filename = 'HELLO_TIFF.tiff'
        filepath = '/tmp/%s' % filename
        ret = data.writeToFile_atomically_(filepath, False)
        if ret:
            return filepath
    elif NSPasteboardTypeString in data_type:
        # string todo, recognise url of png & jpg
        pass

if __name__ == '__main__':
    print get_paste_img_file()
solomonxie commented 6 years ago

Python图片转化为Base64编码

常用github api上传文件或图片,必须要将文件转化为base64编码才能上传。所以这里总结了下:

import base64

with open('PATH-TO-IMAGE', 'rb') as f:
    pic = f.read()

print base64.b64encode(pic)
solomonxie commented 6 years ago

pip freeze命令显示当前环境所有安装的package

对于我们经常使用virtualenv虚拟环境来说,经常需要指明需要哪些python package倚赖包。另外由于virtualenv如果和git仓库共存的状况下,我们必须要屏蔽文件夹里所有virtualenv的内容,所以这种情况更需要有一个显示的方法指明项目需要哪些倚赖包。 很简单的一句话就搞定:

pip freeze > requirements.txt

这样的话,pip就会自动显示出当前环境下已安装的所有package包,并且利用>重定向,输出到一个txt文档里。 以后的话,还可以用这个txt文件达到一键安装所有倚赖:

pip install -r requirements.txt
solomonxie commented 6 years ago

Python运行matplotlib时报错:Python is not installed as a framework.

参考这篇回答。 即使我的matplotlib是在virtualenv虚拟环境里安装的,它还是会在用户目录下生成一个~/.matplotlib目录。 然后我们在创建一个文件并填入一句话:

touch vim ~/.matplotlib/matplotlibrc
echo "backend: TkAgg" > ~/.matplotlib/matplotlibrc

image

solomonxie commented 6 years ago

Python requests返回Max retries exceeded错误

经常在脚本访问API时接受到这个反馈,这个可以理解因为一般一个ip太频繁访问某个网址就会被服务器拒绝。 但是比如我访问Github的API,明明已经通过认证且每小时5000次访问量了,怎么会没消费掉访问量就被返回Max retires呢。 查了很多文章,大家只是说让requests去sleep一会儿再访问,但是这不是正确的解决方案。 最后通过这个回答,真的一键解决了: snip20180225_61

也就是,安装这个包就好了:pip install pyopensslpip install -U pyopenssl。也就是当时报错里提示的关于SSL的什么东西,这样就解决了。

solomonxie commented 6 years ago

crontabvirtualenv搭配使用

crontab是linux系统下定时执行任务的命令,virtualenv是python虚拟环境。 那么,怎么让crontab定时执行某个python脚本时是保证在virtualenv的虚拟环境中运行的呢? 答案是: ~在crontab执行shell命令时,不是用系统默认的python如python PATH-TO-SCRIPT.py这样的,而是指定运行虚拟环境中的python,如~/venv/bin/python PATH-TO-SCRIPT.py这样的。~ 参考这篇文章

solomonxie commented 6 years ago

pip uninstall卸载包或pip install时发生Operation not permitted错误

在Mac上,无论是pip uninstall还是sudo pip uninstall,都会发生这个错误。 实际上,是Mac装机自带python的固有问题,包括如果不sudo pip就不能用的问题,都是这里的原因。 解决方法很简单: 只要brew reinstall python就全解决了! 注意,重装python意味着很多之前装的packages包都会丢失,请用pip freeze requirements.txt备份。

当然,我目的就是为了清楚所有系统python环境下的包才重装的,结果发现重装python之后还顺便解决了之前的各种权限问题。现在不用sudo也能pip installpip uninstall了。 删除了系统python中所有的packages后,感觉轻松了很多。 之后就可以无忧无干扰的在virtualenv下安心编程了。

solomonxie commented 6 years ago

研究Python某些库附带安装的一些packages包

卸载系统python的安装包时,由于需要手输几十个包的名称,发现了一些有意思的。后来知道这些包是安装Jupyter Notebook时附带安装的,看起来可以作为以后自己使用的包,以下列一些参考。

solomonxie commented 6 years ago

Python 批量卸载packages包

不像pip install -r requirements.txt可以批量安装包,卸载就没有原生方法了,需要用巧劲。 目前Stackoverflow有这么一种用法: pip freeze | grep pyobjc-framework | xargs pip uninstall -y 其中pyobjc-framework 是搜索关键字,搜索包含这些字的包然后批量卸载。 如果不是指定某些关键字,直接pip freeze | xargs pip uninstall -y,那么就是卸载所有的包了。 不出所料的话,应该是执行不了的,总有哪个包的卸载会出错,然后中断进程。

如果想达到恢复出厂设置的感觉,那么直接类似brew reinstall python这样的重装python就可以了罢。不过我再重装后,虽然很多删掉了,但还是会有些遗留,需要手动清除。

solomonxie commented 6 years ago

Python List Comprehension

指的Python单行循环:

a = [n for n in alist if n>0]

进阶

如果在单行循环中,想获得某个item的序号,那么就需要用到python自带的enumerate()函数

# Python "List Comprehension" method
old = ['zero', 'one', 'two', 'three', 'four', 'five', 'six']
new = [item for index, item in enumerate(list1) if index < 4]
print new

# Output:
['one', 'two', 'three']

注意:使用enumerate()时,必须用2个变量承接它的返回值,第一个是index序号,第二个是item本身。

solomonxie commented 6 years ago

Python去除空白字符

Python里面不是trim,而是strip.

# 去除两边所有空白字符
string.strip()

# 去除两边指定的字符 (不分顺序)
string.strip('\r\t\n')

# 只去除左边、右边,用法同上
string.lstrip(s)
string.rstrip(s)
solomonxie commented 6 years ago

升级pip报错:ImportError: module 'pip' has no attribute 'main'

参考:解决 ImportError: module 'pip' has no attribute 'main'

正在用pip(没有在虚拟环境中,直接在系统里操作的),提示我可以将9.9升级到10.0,然后就sudo pip install --upgrade pip了,结果就报这个错误,导致所有的pip操作都无法进行。找到解决方案如下:

image

## 如果报权限错误 则加上sudo
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py;python get-pip.py

完成。

solomonxie commented 6 years ago

❖ BeautifulSoup 入门 (Python)

BeautifulSoup是Python包里最有名的HTML parser分解工具之一。简单易用

安装:

pip install beautifulsoup4

注意大小写,而且不要安装BeautifulSoup,因为BeautifulSoup代表3.0,已经停止更新。

常用语法

参考我之前的文章:BeautifulSoup :一些常用功能的使用和测试

# 创建实例
soup = BeautifulSoup(html, 'html5lib')

选择器

根据不同的网页,选择器的使用会很不同:

# 最佳选择器: CSS选择器(返回tag list)
results = soup.select('div[class*=hello_world] ~ div')

for tag in results:
    print(tag.string)       #print the tag's html string
    # print(tag.get_text())     #print its inner text

#单TAG精确选择器:返回单个tag. 
tag = soup.find('div', attrs={'class': 'detail-block'})
print(tag.get_text())

# 多Tag精确选择器: 返回的是text,不是tag
results = soup.find_all('div', attrs={'class': 'detail-block'})

# 多class选择器(标签含有多个Class),重点是"class*="
results = soup.select('div[class*=hello_world] ~ div')

获取值

tag = soup.find('a')

# 只获取标签的文本内容
text = tag.get_text()

# 获取标签的全部内容(如<a href='sdfj'> asdfa</a>)
s = tag.string

# 获取标签的属性
link = tag['href']

修改值

参考:Beautiful Soup(四)--修改文档树

tag = soup.find('a', attrs={'class': 'detail-block'})

#修改属性
tag['href'] = 'https://google.com'

# 修改内容 <tag>..</tag>中间的内容
tag.string = 'New Content'

# 删除属性
del tag['class']

对象类型

在我们使用选择器搜索各类tag标签时,BeautifulSoup会根据使用的函数而返回不同类型的变量。而不同的变量的使用方法也需要注意。

增删改标签

参考:使用BeautifulSoup改变网页内容

# 修改标签内容
tag = soup.find('title')
tag.string = 'New Title'
solomonxie commented 6 years ago

Python 对url的操作

Python原生带有url的切割、组合、识别等包,可以很轻松引用。 参考官方文档: urlparse

Url parse (解析)

image

Url join (组合)

image

solomonxie commented 6 years ago

❖ Virtualenv创建Python3环境

Mac安装Python3

注意,一般没什么人会完全删除Python2.7而只用Python3, 所以几乎都是同时安装Python2.7和Python3.

参考官方文档:在Mac OS X上安装Python 3

一般Ubuntu或Mac默认是Python2.7, 所以需要先安装系统的Python3环境,Virtualenv才能够通过它来创建虚拟环境。需要注意的是,不同系统和不同包管理器的安装方式不一样,注意你选择的安装方法一定不能和Python2.7冲突。 Mac的话,需要麻烦一点, brew install python3是行不通的,因为会提示Error: python 2.7.14_3 is already installed. To upgrade to 3.6.5, runbrew upgrade python.` 具体操作如下:

$ brew upgrade python

#==> Upgrading 1 outdated package, with result:
#python 2.7.14_3 -> 3.6.5

然后很快就安装成果,并显示如下: image

这个时候在命令行里输入python,就会直接跳入python3的编程环境了。 image

然后我们通过which命令,得知两种版本python的位置,便于之后virtualenv的设置: image

安装Python3环境的Virtualenv

这个时候已经保证了本机同时存在Python2.7和Python3,那么安装虚拟环境就简单多了:

# 创建虚拟环境
$ virtualenv -p python3 ~/FOLDER-PATH/venv3
# 或更具体的指定路径(同样适用于Python2的安装)
$ virtualenv -p /usr/local/opt/python/libexec/bin/python ~/FOLDER-PATH/venv3

# 进入虚拟环境
$ source ~/FOLDER-PATH/venv3/bin/activate

# 退出环境
deactivate

然后就在你自己定义路径下添加了一个venv3文件夹,这就是你的虚拟环境啦。 每次只需要source ~/FOLDER-PATH/venv3/bin/activate就可以进入Python3的虚拟环境了。 当然,为了简单,我把这么长的一句话设置成为alias,一句venv3就可以简单进入环境。

Python3虚拟环境下安装包

Python3的环境下,是需要用pip3来安装各种包的。

升级至Python3后Python2的异常

Mac中升级到Python3后,原本的python命令行关键字被直接指定为python3,而原有的python2需要通过python2.7来进入python2.

原来Virtualvenv的Python2环境下无法用pip安装包

image 错误原文如下:

$ pip install requests
Collecting requests
  Could not fetch URL https://pypi.python.org/simple/requests/: There was a problem confirming the ssl certificate: [SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:590) - skipping
  Could not find a version that satisfies the requirement requests (from versions: )
No matching distribution found for requests

解决方案:在这个虚拟环境下,重新安装一下pip就好了:

$ curl https://bootstrap.pypa.io/get-pip.py >> get-pip.py
$ python get-pip.py

# 必要的时候升级下pip
$ pip install --upgrade pip

完成,顺利安装好后就能顺利install包了。

solomonxie commented 6 years ago

Python2 乱码解决方案(Anti-TLDR)

# 首先把所有相关字符串变量检查一遍
print type(s1)
print type(s2)
print type(s3)

# 然后把所有"unicode"格式的字符串转换成"str"格式
s1 = str(s1)

完成!

solomonxie commented 6 years ago

❖ Python os.walk() 文件遍历

学习os.walk文件遍历,最最最重要的是了解这种遍历方法的逻辑,它的路径!!不了解的话,永远也学不会。

os.walk()是Python原生的遍历文件夹方法,但是表现出来的逻辑真的不是很好记忆。

用法:

os.walk(顶级目录地址, topdown=True, onerror=None, followlinks=False) 

函数返回一个三元Tupple: (目录路径(字符串), 子目录名(列表), 文件名(列表))

image

遍历路径

os.walk()逐层扫描的,一层一层来。 比如上图中,从Root根目录下开始扫,

记住,它不会无穷尽深入每一个目录一直到底,而是逐层扫。 扫完一层后,再跳出来需要遍历的目录深入下一层。

指定深度

os.walk默认是深入到底的,遍历所有的位置。有时候我们只需要一层或两层。 抱歉,os.walk()没这个功能,只能自己写。 方法就是:声明一个depth变量记录当前深度,循环到一定深度后,用break语句退出循环。 以下为示例代码:

depth = 1
for root, subdir, filenames in os.walk():
    if depth is 2:
        break
    depth += 1

示例:只看文件

search_dir = '/home/me/`
for root, subdir, filenames in os.walk(search_dir):
    print(root, subdir)
    for fn in filenames:
        print(fn)
solomonxie commented 6 years ago

Python 文件基本I/O操作

操作模式 Mode

solomonxie commented 6 years ago

Python3 编码乱码问题

没想到从Python2升级到3,还是有编码问题-_-!

小技巧

在txt等文本文件读取时,如果遇到打开文件乱码,那么最简单的方法是把文件拖到Chrome浏览器里,然后在Chrome开发工具的Console里检查文件的编码格式:

>>> document.charset
'UTF-16LE'

检查好了,然后再到python里针对其进行解码。

solomonxie commented 6 years ago

Python Scrapy的准备工作

原作于:Nov 27, 2015

昨天稍微练习了下Python自带爬虫模块urllib2,感觉还行。 不过要是各种re正则表达式来匹配海量网页,效率也太低了。 所以今天试着安装了声誉不错的BeautifulSoup,感受了下便捷的节点搜索。 然后顺带着研究了下适合并行等复杂爬虫的大名鼎鼎的Scrapy。 好像安装之前需要先安装不少的东西。 官方文档里面是这样说的: baidushurufa_2015-11-27_1-47-56 然后就开始安装Python必备的pip 了,在linux系统中这个都是自带的,想要Python什么模块的话,直接pip一句命令就安装好了,但是在windows里面还要各种去模块的官网上下载exe文件再安装。我也体会到了。 这次索性借这个把pip装上。 然后到了pip官网,阅读了下官方文档,发现其实巨简单——Python 2.7.9以上版本自带就安装了。 如下图: pip pip2 也就是说,在windows的cmd命令行中输入pip,回车,就可以看到它已经安装好了。 不过,不知道为什么,突然360卫士就蹦出来一个木马病毒,杀掉后,pip就再也不能用了…… 然后还是按照官网指示,安装get-pip.py来。然而还是一样。 但是换种方法,输入:python -m pip --version才可以运行。。。鬼才要在pip前输入这么长啊! 然后又用easy_install来安装pip, cmd中输入 easy_install pip
然后成功,可以随处运行pip了~~~ 然后安装setuptools,用pip很简单: pip install setuptools
成功! 再回到Scrapy的documentation的安装列表: pip install lxml
pip install pywin32
再按照这篇教程安装了几个相应的模块: pip install twisted
pip install pyOpenSSL
最后pip install Scrapy
齐活! 然后在cmd中输入Scrapy回车,有反应的话,就完工了!

scra

solomonxie commented 6 years ago

❖ Python 安装Mysql模块及安装中错误的解决 (Windows)

初试爬虫之后,各种快感。然后进入到Python练习的下一阶段了——把抓取到的数据存到数据库中。 再三考虑,还是决定从MySQL开始入手。虽然评论区很多倾向于SQLite及MongoDB等新潮玩意,但是MySQL还是占有决定性的市场。为了适应以后生存,这方面必须得会,就拿它先练手吧。

我的开发环境是中文win7系统32位, Python 2.7, MySQL 14.4。(Linux在虚拟机里呢,熟练之前先不挑战开发环境了-_-!) 注意:这里是安装python的mysql模块,而不是mysql, 到了这一步它应该是已经安装好了的(包括MySQL ServerMySQL python connector)。

先检查自己是不是已经安装了这个模块 极其简单:在Python的命令行中输入import MySQLdb,如果没有报错,那就已经安装了。

最简单的安装方法

其实就是随便找个地方按下win+R,输入cmd回车——打开windows命令行,进行著名的pip安装大法

pip install mysql-python

按理来说,这一步足够了。但是我这出现了据说在windows环境下python安装模块的痛:命令行里返回了错误:

error: Unable to find vcvarsall.bat

然后我想到,是不是在windows用pip不太合适?所以还是循规蹈矩地到Python官网下载了MySQLdb的源文件,即MySQL-python-1.2.5.zip (md5)这个压缩包。 随便找个地方解压缩,然后以最快的速度在cmd命令行中进入这个目录,输入:

python setup.py build python setup.py install

按理来说,到这一步就完全成功了。不过,返回的结果是一毛一样的。。。

error: Unable to find vcvarsall.bat

然后我就知道了:其实pip安装,和我自己下载源码用python setup.py buildpython setup.py install是一样的效果。 问题源头还是在vcvarsall.bat这个东西上。一看文件名就知道是和vc相关。 查询相关资料,说是凡是安装和操作系统底层密切相关的Python扩展,几乎都会遇到这个错误。 经过搜索,绝大多数的回答都是:需要安装Microsoft Visual Studio2008或者2010版本,才能满足Python在windows系统上安装各种底层扩展的需要。 正在下载2G的VS中。。。 不过趁着下载等待时间,我在评论区发现了更easy的方法。。。。 image 打开页面,http://www.lfd.uci.edu/~gohlke/pythonlibs/ 是这个模样: image

满屏幕毫无美感的英文,连排版都没有,真有点不太好接受。不过趁着VS还没下载完,就简单读了读,发现了第二行关键词:University of California, Irvine.,原来是加大的作品啊,一看就是科学家制作,比较大气,耐着心读了读说明段落——好像是专门针对windows对python支持性差做的工作——把python扩展都制作成了二进制文件,即.whl文件。

安装二进制的Python扩展包

看起来好像是个好东西,就ctrl+f查找mysql,还真找到了!

MySQL-python, a Python database API 2.0 interface for the MySQL database Mysqlclient is a Python 3 compatible fork of MySQL-python. MySQL_python-1.2.5-cp27-none-win32.whl MySQL_python-1.2.5-cp27-none-win_amd64.whl

选择win32.whl这个文件下载,才772k。 但是这个whl文件格式怎么安装呢?回到网页上面,发现说了是用pip安装,于是我在这个目录打开cmd命令行,输入:

哈哈,献丑了!whl文件的安装方法,在pip的官方文档里说明的很清楚(看这里) 所以再来了一遍: 输入:

pip install MySQL_python-1.2.5-cp27-none-win32.whl 返回: Processing c:\tdownload\mysql\mysql_python-1.2.5-cp27-none-win32.whl Installing collected packages: MySQL-python Successfully installed MySQL-python-1.2.5

安装成功!

到Python里面试了一下import MySQLdb,也正常! 于是乎,我觉得写文章的这个功夫,已经下载好的Microsoft Visual Studio也没必要了。。。。

solomonxie commented 6 years ago

❖ Python 发送邮件

程序人员对于邮件自动化的日常需求还是很高的。但是入过了Linux的命令行邮件客户端如Sendmail, Mutt, Alpine等坑之后,发现现代其实很少人真的在用它们实现邮件自动化,根据搜索引擎里相关文章的数量就可知一二。取而代之的是,现代都在用Python或PHP等编程语言直接实现。Python更是自带一套模块实现邮件发送。

先上示例代码,之后再详解。

注:全部代码在Python3环境下测试通过,正常使用,正常显示,无需任何外置模块。

参考:菜鸟教程 - Python SMTP发送邮件 参考:简单三步,用 Python 发邮件

发送HTML格式的漂亮邮件

import smtplib
from email.mime.text import MIMEText

# Settings of sender's server
host = 'smtp.aliyun.com'
sender = 'Jason@aliyun.com'
user = 'Jason@aliyun.com'
password = input('Please type your password: ')
to = ['Jason@outlook.com']

# Content of email
subject = 'Python send html email test'
with open('./test.html', 'r') as f:
    content = f.read()

# Settings of the email string
email = MIMEText(content,'html','utf-8')
email['Subject'] = subject
email['From'] = sender
email['To'] = to[0]
msg = email.as_string()

# Login the sender's server
print('Logging with server...')
smtpObj = smtplib.SMTP() 
smtpObj.connect(host, 25)
smtpObj.login(user, password)
print('Login successful.')

# Send email
smtpObj.sendmail(sender, to, msg) 
smtpObj.quit() 
print('Email has been sent')

发送带附件的邮件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage

# Settings of sender's server
host = 'smtp.aliyun.com'
sender = 'Jason@aliyun.com'
user = 'Jason@aliyun.com'
password = input('Please type your password: ')
to = ['Jason@outlook.com']

# Make content of email
subject = 'Python send email with attachments'
with open('./test.html', 'r') as f:
    content = MIMEText(f.read(),'html','utf-8')
    content['Content-Type'] = 'text/html'
    print('Loaded content.')

# Make txt attachment
with open('./txt.md', 'r') as f:
    txt = MIMEText(f.read(),'plain','utf-8')
    txt['Content-Type'] = 'application/octet-stream'
    txt['Content-Disposition'] = 'attachment;filename="txt.md"'
    print('Loaded txt attachment file.')

# Make image attachment
with open('./pic.png', 'rb') as f:
    img = MIMEImage(f.read())
    img['Content-Type'] = 'application/octet-stream'
    img['Content-Disposition'] = 'attachment;filename="pic.png"'
    print('Loaded image attachment file.')

# Attach content & attachments to email
email = MIMEMultipart()
email.attach(content)
email.attach(txt)
email.attach(img)

# Settings of the email string
email['Subject'] = subject
email['From'] = sender
email['To'] = to[0]
msg = email.as_string()

# Login the sender's server
print('Logging with server...')
smtpObj = smtplib.SMTP() 
smtpObj.connect(host, 25)
smtpObj.login(user, password)
print('Login successful.')

# Send email
smtpObj.sendmail(sender, to, msg) 
smtpObj.quit() 
print('Email has been sent')

发送邮件大杀器:Yagmail

之所以放在最后,是相衬托出传统的发送邮件是多繁琐多麻烦,实际上我们需要的只是超级简单的东西。Yagmail正是为了实现这个而生的,一句话就可以完成所有的登录、发送文字、HTML、附件等功能。

参考Github:yagmail -- Yet Another GMAIL/SMTP client

一句话发送邮件:

yagmail.SMTP('username').send('to@a.com', 'Subject', 'This is the body')

正常一点的发送邮件:

import yagmail

yag = yagmail.SMTP('user', 'password', host='server.com', port='123')
contents = [
    'This is the body, and here is just text http://somedomain/image.png',
    'You can find a file attached.', 
    './dataset/pic.jpg'
]
yag.send('solomonxie@outlook.com', 'yagmail tst', contents)
solomonxie commented 6 years ago

❖ pipenv虚拟环境全方位入门

2018的PyCon把最新型最先进的Python虚拟环境pipenv吵得火热。看了下介绍感觉真的很好用,它在virtualenv的基础上包装了一些更便捷的功能,解决了很多很多virtualenv欠缺的事情。

参考pipenv的前世今生:PyCon 2018 之 Python 未来的依赖管理工具 pipenv 参考:pipenv 更优雅的管理你的python开发环境 直接参考创造者Kenneth的官方说明

简单说,pipenv就是把pipvirtualenv包装起来的一个便携工具。

它不会在你的项目文件夹里生成一大堆东西,只有两个文本文件:

安装

Mac安装很简单,只要用Homebrew:

$ brew install pipenv

Linux的话,是用pip安装:

$ pip install --user pipenv

安装好后,终端里还调取不了命令,因为它现在只是个包。 需要先找到它的真是路径,然后为了方便把它加到bash或zsh等shell里面:

# 先获取python包的位置
$ python -m site --user-base

比如我的显示在/home/pi/.local,那么pipenv就藏在/home/pi/.local/bin里。 所以需要打开shell的设置文件,比如bash的话就编辑~/.bash_profile, zsh的话就编辑~/.zshrc,在里面把刚才查到的包路径存进去:

alias pipenv="home/pi/.local/bin/pipenv"

注意:我没有像其他人一样整个export进去,因为不知道为什么树莓派里面的zsh使用不来这个。

创建虚拟环境

在某个文件夹创建一个Python3环境:

# 泛指python的版本
$ pipenv --three

# 或者,特指某个python版本
$ pipenv --python 3.5

# 或者,特指某个位置的python
$ pipenv --python <path/to/python>

然后就会显示如下动态,可以看出来,pipenv调用了virtualenv,从本机把Python3环境拷贝一份到某个本机位置,然后在你的项目文件夹里只创建了两个文件PipfilePipfile.lock,记录了所有你这个项目需要的环境配置,内容极其简单易懂:

image

显示当前虚拟环境的储存位置

$ pipenv --venv

运行环境

运行虚拟环境(无需进入特定shell即可按照该环境运行脚本):

$ pipenv run python xxx.py

进入环境

进入虚拟环境:

# 进入虚拟环境
$ pipenv shell

# 退出虚拟环境
$ exit

其实进入pipenv虚拟环境,本质上就是virtualenvsource ./bin/activate动作,只是使用不一样。进入后,你会发现用deactivate也是能生效的。但是:

注意:进入pipenv环境后千万不要用deactivate退出,而应该用exit退出。否则你再进去这个环境就会产生错误:

Shell for UNKNOWN_VIRTUAL_ENVIRONMENT already activated. 
No action taken to avoid nested environments.

安装packages包

$ pipenv install <包名>

你需要知道的是,进入pipenv虚拟环境后,你还是可以用pip install来安装包的,也能正常使用,因为virtualenv就是这样做的。 但是,这样你就不算使用了pipenv策略了,如果你要在项目文件夹里的Pipfile记录所有项目需要的依赖环境,就应该放弃使用pip install而使用pipenv install,这样你的Pipfile就会精确记录所有需要的依赖。

重新安装所有packages: 有时候需要冲github上clone项目,下载好后,只需要一句话就可以完成创建环境:

# 根据Pipfile中的描述安装所有依赖
$ pipenv install

# 或者,根据Pipfile.lock中的描述安装所有依赖
$ pipenv install --ignore-pipfile

# 或者,只安装dev组的依赖
$ pipenv install --dev

# 或者,根据曾经在pip上导出requirements.txt安装依赖
$ pipenv install -r <path-to-requirements.txt>

按照树形结构显示当前环境的依赖关系:

$ pipenv graph

然后就会显示出如下效果: image

删除虚拟环境:

# 删除某个包
pipenv uninstall <包名>

# 删除整个环境
$ pipenv --rm

pipenv lock时遇到的SSL Error

错误反馈如下:

Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
usr/local/Cellar/pipenv/2018.5.18/libexec/lib/python3.6/site-packages/pipenv/vendor/requests/sessions.py", line 508, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/Cellar/pipenv/2018.5.18/libexec/lib/python3.6/site-packages/pipenv/vendor/requests/sessions.py", line 618, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/Cellar/pipenv/2018.5.18/libexec/lib/python3.6/site-packages/pipenv/vendor/requests/adapters.py", line 506, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /pypi/pyobjc-framework-netfs/json (Caused by SSLError(SSLError(1, u'[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:590)'),))

参考pipenv的issue解答。

最佳解决方案是:

$ pip install pyopenssl

因为这种SSL Error在其他地方也常见,一般都是没有在环境里安装pyopenssl的问题。所以不管你在哪个环境,如果出现这个SSL问题,就先装pyopenssl解决。 注意:不要用pipenv install pyopenssl,因为你真的不想在每个环境里都重新装一遍这个,干脆把它撞到本机:$ pip install pyopenssl.

常见错误操作

不要在”pipenv shell“里面运行“pipenv install”

尽量不要这样做,因为没有必要。 不过也有一些特殊情况,是需要这样做的:比如我在安装Pillow包的时候,只有在pipenv shell里面才能够安装成功。

不要在”pipenv shell“里面运行”deactivate“

solomonxie commented 6 years ago

Python正则表达式Regular Expression

import re
s = '<h3>下一个你需要输入的数字是83105. </h3>'

pattern = re.compile(r'<h3>\D+(\d{5})\D*</h3>')
result = pattern.findall(s)

print(result)
#out>>>  ['83105']