每天进步一点点:requests 读取网络图片

帮别人写一个小脚本,其中需要读取网络上的一些图片资源。话说以前倒是用过Python 的requests 对指定网址进行过POST和GET操作,想必读取图片应该用GET操作就可以了吧。

为了进行测试,我首先选择了一个图,虽然只是读一个图片,但是我们的征途是星辰大海。:

这图的地址是:

https://cdn.pixabay.com/photo/2018/08/14/13/23/ocean-3605547_960_720.jpg

然而我尝试直接保存,却保存成为了ocean-3605547_960_720.webp,第一次见到这个webp格式,查了一下:

WebP是Google新推出的影像技术,它可让网页图档有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快。

直接保存

不管了,我就按JPEG格式来保存试试吧,于是写了如下代码:

1
2
3
4
5
6
7
url = "https://cdn.pixabay.com/photo/2018/08/14/13/23/ocean-3605547_960_720.jpg"
r = requests.get(url)

print(len(r.content))
with open('image.jpg', 'wb') as fd:
fd.write(r.content)
fd.close()

运行后,显示出的r.content的长度为:81621,看一下生成的图像:

image.png

打开后也正常显示,说明这样读取/保存还是没有没问题的。

流形式保存

尽管上述方法可以直接保存图像,不过我查网上好多资料都是建议用iter_content()方法循环获取文件流信息。

直接上代码,需要注意的是requests.get中设置stream=True

1
2
3
4
5
6
7
url = "https://cdn.pixabay.com/photo/2018/08/14/13/23/ocean-3605547_960_720.jpg"
r = requests.get(url, stream=True)

with open('image2.jpg', 'wb') as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
fd.close()

这样做的好处,我猜测有两点:一是节省内存开销;二可以获取进度(通过r.headers['content-length']获取总长度信息)

使用第二种方法获取的图像与第一种方法获取的并没有什么区别,所以就不贴截图了。

使用r.raw

另外一种方法就是使用r.raw,因为我并不打算用这种方法,所以只是简单测试一下。

需要注意的是:

  • 使用r.raw,必须设置stream=True
  • 使用r.raw之前不能使用r.content之类的调用,否则相当于把输出读光了。
  • 使用r.raw.read(10)读取数据后,数据剩余长度会相应的减少r.headers['content-length']

requests文档中还有这样一段:

An important note about using Response.iter_content versus Response.raw. Response.iter_content will automatically decode the gzip and deflate transfer-encodings. Response.raw is a raw stream of bytes – it does not transform the response content. If you really need access to the bytes as they were returned, use Response.raw.

也就是说Response.iter_content会自动解码gzip压缩等,而Response.raw就是原始的字节流。所以还是老实地用Response.iter_content 或者直接用Response.content吧。😀

总结

尽管读图片有很多方法,但是因为我要读取的都是一些小图片,不会有多大内存开销,所以第一种方法足够了。

一个需要注意的地方就是在处理之前,先判断一下HTTP 状态码(r.status_code),正常返回应该都是2XX。

够用就好,否则又变成孔乙己研究茴香豆几种写法的问题了,白白地浪费时间和精力。

相关链接


This page is synchronized from the post: ‘每天进步一点点:requests 读取网络图片’

扩列

今天早晨邻居群里一个朋友加了我的微信,虽然我们在邻居群里熟识了好些年,但是这一刻才变成了“好友”。


(图源 :pixabay)

加了这个朋友之后,我脑海里突然就冒出来一个词汇:扩列。

说到扩列这词,第一次看到是在QQ上,看到这词之后,我也是一头雾水的,这是啥?扩啥?列啥?扩列到底是干啥的?

后来还是一个还在读初中的小朋友解释给我:所谓扩列就是扩充好友列表。然后除了感慨自己老了,连这些网络流行词汇都不了解了之外, 也不禁感慨,自己好久没有扩列了。

说起来,扩列最快的阶段当属刚玩QQ的时候吧,那时候随便逮住一个人就可以加好友的,甚至网络上随便搜索到一个人也可以侃上大半天之后发展成好友。

后来工作了,加的好友大部分都是同事。不过说到同事,不一定都是一起工作的,而是一个集团范围,成千上万人都可以算是同事,当然了成为好友的不能那么多。

再后来,QQ上的新好友都是一些有业务往来的客户等,虽然也称作好友,但是由于有了利益关系,和读书或者工作时加的好友还是不一样的。

再后来大家都从QQ切到微信上,又有了各种微信群,信息越来越多越来越喧嚣,但是能聊到一起去的好友却越来越少,很多人都是聊三两句后就无话可说了。

所有知道扩列这个词汇之后,我很羡慕初中的小盆友,她们还都热衷于玩QQ,每天乐此不疲地扩列,扩列来的朋友大多数也是同龄人,有着各种共同的爱好和聊不尽的话题。

而我们呢?即便偶尔扩列,但是扩列之后大部分都是“躺列”(躺在列表里的好友,可能老死不相往来),只是偶尔彼此朋友圈点个赞,表示还没有拉黑/被拉黑。

而扩列之后更大的一种可能就是,你今天扩列的好友,明天就做起了微商,什么护肤品、保健品、美食小吃、服饰衣帽等等连番轰炸, 偶尔还会发来一个拼多多的助力链接。


(图源 :pixabay)

所以,不扩列——孤独;扩列之后——更加孤独。孤独的感觉不是你QQ/微信/通讯录里好友很少,而是有着成百上千的好友,却不知道找谁说话。哎,只好在这自言自语喽。


This page is synchronized from the post: ‘扩列’

耳听为虚,眼见不一定为实

最近朋友转发了一条视频过来,主要讲的是社会达尔文主义,不过在说具体的事情之前,这个视频中先播放了一段声音,然后让大家选择自己听到的是Laurel还是Yanny。


(图源 :pixabay)

我听完几遍之后,确信自己听到就是Laurel,不过后来视频中解释到,其实这段声音是合成了两段声音在一起,对低频声音比较敏感的听到的会是Laurel,而对高频声音比较敏感的听到的则是Yanny。

虽然觉得视频中的说法应该就是事实,但是我还是把视频分享到朋友和邻居群里,大家的答案有的是Yanny,有的是Laurel,相对而言年纪略大的人听到是Laurel的比较多,年轻人听到是Yanny的比较多。

我还把视频分享给爱人和孩子,爱人听到的就是Laurel,而孩子明确表示听到的就是Yanny,这让爱人惊讶不已,听到视频后边的解释后,才算明白咋回事。

微信群里掀起了是Yanny还是Laurel的讨论高潮,相反视频中要说明的社会达尔文主义反而没人关注了。😀

其实不同的人对不同频段的声音敏感程度不一样,这是很正常的事情。这和蝙蝠能发出和接收超声波而大象能发出和听到次声波是一个道理的。

但是这件事却让我不由自主地想到了耳听为虚、眼见为实,这个成语。当然了,这个成语的耳听为虚与我说的不同人的耳朵对不同频段声音敏感度不一样是两码事。

不过“耳听为虚”,眼见就一定为实吗?这让我不由地想起有很大比例的人患有一种眼疾——色盲/色弱:

临床调查显示,男性色盲占4.9%,女性色盲仅占0.18%

那么在这部分人看到的这个世界的颜色和所谓的正常人是不一样的。不过颜色不过是通过眼、脑和我们的生活经验所产生的一种对光的视觉效应。我们据此来分正常人和色弱患者是不是又有些武断呢?

当然了,我不是要强调听到某某声音的人或者看到/看不到某某颜色的人才是正常人,而是想说,各种不同各种差异在我们生活中是真实存在的。

我们要尊重这种差异客观存在,而不是非要把我们的结论强行的加在别人那里——你听不到Laurel,所以你的耳朵又毛病,你看不到某种颜色所以你的眼睛不正常。

我们尊重这种差异,这样这个世界才会又各种悠扬婉转的声音,才会有各种绚丽多彩的颜色,这才是多彩多姿的世界呢。


(图源 :pixabay)

否则只容许一种声音存在,只能涂抹一种颜色,那么无论这种声音多动听,无论这种颜色多好看,这个世界也只能是单调乏味的。


This page is synchronized from the post: ‘耳听为虚,眼见不一定为实’

每天进步一点点:Python项目的打包与发布

想将自己写的python3 代码部署到另外一台VPS上,自己最常用的方式就是用scp复制了,但是总是感觉这样很繁琐也不优雅。你看别的项目用pip install安装多好呀。


(图源 :pixabay)

于是去学了一下怎么打包,怎么弄pip,发现网上的文章太多,并且很多都已经过时了,最后参考Packaging Python Projects,大致搞明白了怎么做,在这记录一下。

打包操作

打包操作就是把python项目放到一个安装包/分发包里,这样无论是传输还是安装使用时,都只需和一个文件打交道就好了,不必面对一大堆目录。

安装支持工具

在进行打包之前,首先需要安装相应的支持工具,主要是setuptoolswheel

python3 -m pip install --user --upgrade setuptools wheel

我的系统中这两个已经存在了。

setup.py

之后就是弄好项目的目录结构以及创建setup.py。

项目的目录结构大致这样:

image.png

setup.py的内容以及各项内容的意义请参考这里:https://packaging.python.org/tutorials/packaging-projects/#creating-setup-py

除了setup.py外,弄个README.md以及LICENSE会让项目看起来更加正规与友好。当然了,像我这种自己用的小项目,其实没有这俩文件也没啥。

生成分发包

做好上述工作以后,就可以生成分发包了。

生成分发包的命令如下:

python3 setup.py sdist bdist_wheel

可以用如下命令查看setup.py后边具体命令的解释:

python3 setup.py --help-commands

返回信息如下:

image.png

所以我们知道sdist是创建源码分发包,而bdist_wheel是创建wheel格式分发包。

执行完成后,我们就会在dist目录下发现与sdistbdist_wheel相对应的两个文件,这两个文件就可以用于安装/分发等操作啦。

发布项目

其实项目打包好,并且能安装,对我而言已经足够了,不过还是探索了一下发布项目的流程。

首先强调一点,仅仅是测试的话,应该使用Test PyPI。以下内容以pypi为例,大部分同样适用于TestPyPI。

创建API token

在上传项目之前,先创建API token。

访问如下链接:

https://pypi.org/manage/account/

点击Add API token

image.png

起个名字:

image.png

查看生成的token,主要保存好:

image.png

使用Token

pypi 上给出如下提示:

To use this API token:

  • Set your username to token
  • Set your password to the token value, including the pypi- prefix

所以我们编辑$HOME/.pypirc文件,加入如下内容:

1
2
3
[pypi]
username = __token__
password = pypi-xxxxxxxx

上传项目

在上传项目之前,需要安装twine

python3 -m pip install --user --upgrade twine

关于twine的介绍(主要解决加密上传的问题):

Twine is the primary tool developers use to upload packages to the Python Package Index or other Python package indexes. It is a command-line program that passes program files and metadata to a web API. Developers use it because it’s the official PyPI upload tool, it’s fast and secure, it’s maintained, and it reliably works.

然后使用如下指令就可以上传/发布项目啦:

python3 -m twine upload --repository pypi dist/*

其它补充

上传项目可以上传源码版或者wheel版(或者其它版本),都可以用pip安装的,推荐wheel版。

setup.py文件还是挺复杂的,我现在还有点晕头转向,弄好还真不容易呢。关于setup.py更详细内容,可以参考:Writing the Setup Script

参考链接


This page is synchronized from the post: ‘每天进步一点点:Python项目的打包与发布’

密码此前已被破解😱

今天在一个网站(PyPI )注册账户,这个网站在我的判断中属于中等重要的网站,所以我选取了自己一个经常在中等重要网站上使用的密码。


(图源 :pixabay)

按说在很多网站使用同一个密码不是一个好行为,不过要注册的网站那么多,都用不同的密码,哪里记得过来嘛,又不想用什么密码管理软件,所以就把网站分三六九等,重要的网站一站一密码,其它的视情况分配。

然而今天的密码输入后,竟然出现了如下提示:

image.png

文字内容如下:

This password appears in a security breach or has been compromised and cannot be used. Please refer to the FAQ for more information.

翻译过来就是:

此密码出现在安全漏洞中或已被破解,无法使用。更多信息请参考FAQ。

看了一下FAQ信息:

During each of these processes, PyPI generates a SHA-1 hash of the supplied password and uses the first five (5) characters of the hash to check the Have I Been Pwned API and determine if the password has been previously compromised. The plaintext password is never stored by PyPI or submitted to the Have I Been Pwned API.

大意就是说我注册的时候,它们用密码SHA-1 哈希的前5位去一个API那检查,然后判断密码是不是在安全漏洞中或者已经被破解过。

虽然我这个密码是用在中等重要的网站,但是也不是诸如123456,abc123这样超级简单的密码,之前图片中的密码强度也证实了这一点,毕竟是我精心设置的。

但是,既然说是被破解了,那肯定是被破解了,大概率的情况是我用这个密码的某个网站被拖库了,所以这个密码真的就不安全了。

既然如此,就重新选取个密码吧,哎,看来有时间要把用到这个密码的所有网站密码都换一下了,尽管其实被盗了也没啥。

不过,经过这件事,发现Have I Been Pwned 这个API,研究了一下,还是非常好玩的,这也算是有失有得吧。

相关链接


This page is synchronized from the post: ‘密码此前已被破解😱’

每天进步一点点:Python读写文件的模式 & r+

帮朋友写一段短小的Python代码,其每次运行时会产生一个字符串,需要与上次运行时生成的结果字符串对比一下,来判断是否需要继续。


(图源 :pixabay)

要想做到这点有很多办法,比如说读写数据库啊、读写配置文件啊,但是无论那样都觉得太麻烦了,毕竟我们没有太多的数据要存储,不过是一个字符串。

想来想去,最简单的办法莫过于直接写个文本文件了,因为既要读取又要写入,于是我直接写了如下打开文件的代码:

f = open(fname, 'rw')

其中fname保存着路径+文件名,看着觉得挺完美的,然而执行时直接报错了:

ValueError: must have exactly one of create/read/write/append mode

擦,如果只是这些模式,我怎么一边愉快地读,一边愉快地写了?网上找了一些内容,有建议读模式打开文件同时写模式再打开,还有的建议打开A文件读,再打开B文件写。

如果打开A读,再打开B写,那么我需要关闭A后,在用B覆盖A,那样好像还不如我打开并读A,关闭A,再打开写A。尽管这个思路应该完全可行,但是Python真的不能用读写方式打开吗?

于是找了一下Python官网上的关于open的内容,并看到这部分:

image.png

尤其是其中'+' | open for updating (reading and writing)简直就是我想要的啊,虽然我按我的经验+是要和其它标记一起用的。但是还是不死心试了一下:

f = open(fname, '+')

果不其然出错了:

ValueError: Must have exactly one of create/read/write/append mode and at most one plus

还是老老实实用r+吧,亦即f = open(fname, 'r+')果然打开没有问题。但是新问题又来了,我读完字符串并写入后,新新写入的内容直接追加到原来的内容后边了。

不过想想也是,因为读完文件内容的时候,文件指针已经指到文件末尾了,在这个位置写入,当然就是追加喽。

或许我们可以用如下指令将指针移动到文件最前端:

f.seek(0)

不过想想,这样或许也不安全,假设当前文件内的字符串为1234567890,我要新写入的字符串为12345,那么你写完以后,字符串的内容变不变呢?

我没去测试字符串的内容变不变,但是我觉得从逻辑上就不优雅。为了优雅的处理,我还是用如下代码吧:

f.seek(0)
f.truncate()
f.write(str_a)

也就是说先定位到文件头,再清空内容,再写入,这样看起来优雅多了。把程序跑起来,果然好用,这样我就放心啦。

相关链接


This page is synchronized from the post: ‘每天进步一点点:Python读写文件的模式 & r+’

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×