python3中编解码、进制、字节、bytes及爬虫中经常遇到的编码问题的总结

摘要

  • 内容可能有些繁杂,我尽量排版清晰,描述通俗易懂,不说为了别人,后期我自己也要看的。
  • 内容是我查阅诸多资料汇总的,如有不对之处,欢迎告知。
  • 侵删

首先说一下:ASCII Unicode UTF-8:

  • 先说来历:

  ASCII 不支持中文及世界上很多的文字(所占内存1个字节),所以Unicode出现了,但是Unicode有点浪费内存(所占内存4个字节),所以UTF-8这种“弹性”存储的出现了(所占内存根据需要为1~4个字节)。(有的文章说两位的ASCII可以表示中文,这里不再深究。)

  • 搞清来历之后在说是在计算机中通用的字符编码工作模式:

  在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。举个例子:在用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件。


然后我们说一下有关python3中有关bytes类型和str类型:

我以有序列表的方式描述:

  1. python2中对编码处理的很乱,具体表现为你得在文件头加上#conding:utf8和在字符串前加上u,前者的意思是:此模块使用utf8编码进行保存,读取的时候也请按照utf8的标准读取,后者的意思是:该字符串是Unicode编码
  2. python3中字符串都是Unicode编码的,所以你不用(也没有所谓的Unicode解码方法)decode()方法;但是你可以将Unicode编码的中文转换为utf8编码,也就是encode()方法。
  3. 关于上点在做个补充:所谓decode():其实就是将别的编码的字符串转换为unicode编码的字符串,encode():其实就是将unicode编码的字符串转换为别的编码。
  4. 在将字符串存入磁盘和从磁盘读取字符串的过程中,Python自动地帮你完成了编码和解码(Unicode)的工作,你不需要关心它的过程。
  5. 那么,utf-8看上去这么好,为什么不是python和其它编程语言所使用的字符串编码?回答这个问题你可以参考:字符集与编码规则,然后理解下面这句话:因为UTF 是为unicode编码,设计的一种在存储和传输时节省空间的编码方案(规则)。
  6. bytes 就是二进制(就是0或者1)的字符串(专业点叫字节流),为了在ide环境中让我们相对直观的观察,中文被表现成\xe6\x96\x87这种形式,例如:“中文”二字表现为:b’\xe4\xb8\xad\xe6\x96\x87’
  7. 承接第6条,普遍认为:\xe4代表一个字节,这也就应正了在utf8中:这里一个汉字是占用3个字节的论述,但实际上是不一定的,也许还会是4个。一般来说:在国家标准GB2312, 一个汉字=2个字节,但在UTF-8中,一个汉字>=3个字节
  8. 如果你使用bytes类型,实质上是在告诉Python解释器,不需要它帮你自动地完成编码和解码的工作,你要手动进行,并指定编码格式,所以如果你用utf8编码为二进制存入的时候,那么你得用utf8解码它。
  9. Python已经严格区分了bytes和str两种数据类型,你不能在需要bytes类型参数的时候使用str参数,反之亦然。

再说一下进制问题:

我以有序列表的方式进行描述:

  1. 网络通信也就是有关socket的通信,更进而言之的是要建立tcp连接的通信,数据都是编码后传输的,也许是二进制也许是八进制或者十六进制。
  2. 这也是为什么我们在recv的时候要decode(),send()的时候需要encode()的原因,当然你也可以在字符串前面加上一个b,如果你真这样做了,我建议你在ipython中数据b'人生苦短',你看看会有什么小惊喜。
  3. 那么有了二进制为什么还要有16进制和8进制?因为在计算机的机器语言中,现在只能用二进制来实现。至于用八进制和十六进制,是传输的时候再用16进制或8进制也比10进制向二进制转换时更方便;也会使二进制串显得更加好看。
  4. 在补充一个小知识:url编码是一种专门用于url编码的方式,如果你觉得这样很难理解,那么你就认为它是“遗世独立”的即可,在urllib中有提供url编码解码的方法。

好了,再说一下爬虫中经常会遇到的一个错误提示。

我以无序列表进行阐述:

  • 先看一个我写的一个有关豆瓣爬虫的一个代码片:
with open("douban.json","w") as f:
    f.write(json.dumps(ret1))
  • 这个时候你的程序不会出错,但是你会发现,你的文件中没有中文,所有的中问都会变成:\u4eba\u751f\u82e6\u77ed这样的东西,然后呢得加上ensure_ascii=False,也就成了下面这样:
with open("douban.json","w") as f:
    f.write(json.dumps(ret1,ensure_ascii=False))
  • 这个时候再跑程序的时候就好了?其实不然,会报这样一个错误:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-11: ordinal not in range(128)(“ascii”编解码器无法对位置中的字符进行编码)
  • 这是为什么?那么我们就了解一下添加 ensure_ascii=False这个参数的时候究竟是什么意思呢?
  • 在源码描述中:Then the strings written to fp can contain non-ASCII characters if they appear in strings contained in obj. Otherwise, all such characters are escaped in JSON strings。
  • 个人认为通俗理解:如果ensure_ascii=true(默认值),则确保输出中所有传入的非ASCII字符均已转义(通过别的方式编码)。如果ensure_ascii=false,则这些字符(只能通过别的方式编码的字符)将按原样输出。具体参考:
In [7]: import json

In [8]: a={"我":"你"}

In [9]: json.dumps(a)
Out[9]: '{"\\u6211": "\\u4f60"}'
In [10]: json.dumps(a,ensure_ascii=False)
Out[10]: '{"我": "你"}'
  • 所以当ensure_ascii=false的时候,我们在打开文件的时候最好以能兼容所有字符的utf-8的方式打开才行,也就是加上encoding="utf-8"
with open("douban.json","w",encoding="utf-8") as f:
    f.write(json.dumps(ret1,ensure_ascii=False))
  • 这样就可以将json字符串正常写入文件中了。

  • 有的朋友肯定会好奇,难道默认的encoding不是utf-8吗?然后我就去看了一下源码,默认的是None,进而我就猜测,应该是以要写入数据类型来确定以什么样的方式打开,而对于json.dumps()所返回的数据类型默认的就不是以encoding="utf-8"方式打开的。

    • 比如你在f.write(json.dumps(ret1))的时候,是在写入ascii编码的数据;
    • 在f.write(json.dumps(ret1,ensure_ascii=False))的时候,是在写入部分ascii编码的数据;
  • 所以,如果你不指定,就会以ascii进行编码写入了,而ascii是无法表示中文的,也就会报出: “ascii”编解码器无法对位置中的字符进行编码的错误了。

  • 如果你还不相信,那么请你阅读open()函数中,源码里的这段英文: encoding is the name of the encoding used to decode or encode the file. This should only be used in text mode. The default encoding is platform dependent, but any encoding supported by Python can be passed. See the codecs module for the list of supported encodings.(请看红色部分)

  • 另外,如果有的朋友看了代码片之后,可能会有这样一个疑问:"\u6211": "\u4f60"明明是Unicode编码啊,你怎么可以说是ascii码呢?

  • 我们回想一下关于ensure_ascii=true的相关知识:ensure_ascii=true确保输出中所有传入的非ASCII字符均已转义(通过别的方式编码),仔细阅读这句话,是不是就理解了?

  • 而且,我在一些转码平台中发现,ascii和Unicode是可以互转的,比如:"\u6211": "\u4f60"转成ascii的时候是:\u6211\u4f60,同时,我还发现,当我在markdown编辑的时候,我如果不把转码后的这一坨用代码片的形式包裹起来的话,它们会自动变成:\u6211 \u4f60,所以,也就应正了刚才的说法:确保输出中所有传入的非ASCII字符均已转义(通过别的方式编码)

  • 最终我们可以得出以下几个结论:
      1、如果你在使用json.dumps()的时候,不指定ensure_ascii=False的话:一部分以ASCII编码,一部分不能以ASCII编码,会被Unicode编码再转义;如果你指定了的话,那么不能被ASCII编码的,会保持原样。
      2、打开文件的编码方式,你可以指定,如果你不指定,那么就根据你要写入文件的内容来确定。
      3、另外我们还需要知道,在前端和后端通信的过程使用的tcp链接,所以数据传输必须是编码之后的,因此,原滋原味的json字符串,一定是编码之后的,无论你是什么样的编码。

最后,我们补充一下有关字节的知识:

:我们常说的bit,位就是传说中提到的计算机中的最小数据单位:说白了就是0或者1;计算机内存中的存储都是01这两个东西。
字节:英文单词:(byte),byte是存储空间的基本计量单位。1byte 存1个英文字母,2个byte存一个汉字。规定上是1个字节等于8个比特(1Byte = 8bit)。
字与字长:字就是由一些字符组成的,是据算计处理数据时一次存取,加工和传送的数据长度。
字由若干字节构成,字的位数叫字长,一台8位机子:一个字等于1个字节,字长为8位,如果是16位的机子,一个字等于2个字节,字长为16,字是计算机处理数据和运算的单位。

由此可见,计算机的字长决定了其CPU一次操作处理实际位数的多少,即:计算机的字长越大,其性能越好。

位、字,字节与KB的关系:

KB 1KB=1024B

MB 1MB=1024KB

GB 1GB=1024MB

TB 1TB=1024GB

最最后,如果你想了解with的话。

  • 把握两个点:
    1、第一点:__enter__和__exit__方法;
    2、contextmanager装饰器与yield
    • 如果你想了解yield的话:请把握住__iter__和__next__方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值