常见的编码 (ASCII, Unicode, UTF-8, GBK, base64, urlencode)

编码在爬虫中经常涉及,常见的编码有常规编码(ASCII、Unicode、UTF-8、GBK), base64, urlencode。

下面逐一介绍:

1. 常规编码

常规编码约定了字符集中字符与一定长度二进制的映射关系,字符集是指各国家的文字、标点符号、图形符号和数字等字符的集合,计算机要准确地处理不同字符集,就需对字符进行编码。这种编码包括ASCII、Unicode、UTF-8、GBK。

'lx'.encode() # 不指定编码格式时,会自动调用计算机的默认编码格式进行编码
'中国'.encode('utf-8').decode('GBK') # 编码与解码的格式不同,会导致报错

1.1 ASCII

ASCII是基于拉丁字母表的一套计算机编码系统,主要用于显示现代英语和其他西欧语言。ASCII编码实际上约定了字符和二进制的映射关系,如小写字母“a”对应的8位二进制数为01100001。因此,我们也可以将它看作二进制与拉丁字符的映射表。

ASCII的RFC文档编号为20(详见https://tools.ietf.org/html/rfc20),其中约定了ASCII的使用范围、标准码、字符表示和代码识别等内容。

ASCII码默认使用7位二进制数来表示所有的大写字母、小写字母、数字(0~9)、标点符号和特殊的控制符。

1.2 Unicode

unicode被称为统一码、万国码,以\u、&#开头,python实现如下:

# str转为 Unicode 编码:
chinese_str = "四川省"
print(chinese_str.encode("unicode_escape")) # b'\\u56db\\u5ddd\\u7701'

# Unicode 编码转为str:
a = b'\\u56db\\u5ddd\\u7701'
print(a.decode('unicode_escape')) # 四川省

1.3 UTF-8

UTF-8(8位元,Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,由Ken Thompson和Rob Pike在1992年提出,是互联网上使用最广泛的Unicode实现方式。

UTF-8使用1到4个字节来表示Unicode字符,根据字符在Unicode中的位置决定所需的字节数量。具体来说:

  • ASCII字符(U+0000至U+007F)只需1字节编码。
  • 带有变音符号的拉丁文、希腊文、西里尔字母等字符(U+0080至U+07FF)需要2字节编码。
  • 其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)通常使用3字节编码。
  • 极少使用的语言字符使用4字节编码。

由于UTF-8编码可以根据字符的不同长度进行压缩,因此在存储和传输文本数据时可以节省存储空间和带宽。

1.4 GBK

GBK编码主要用于中文字符的编码,支持简体中文、繁体中文和一些其他东亚语言的字符。由于GBK编码是针对汉字的编码标准,所以在处理中文文本时非常有效。然而,需要注意的是,GBK编码并不是全球通用的字符编码标准,它主要适用于中文语境下的字符编码和文本处理。

GBK编码采用双字节编码方案。
GBK编码包含了GB2312编码中的全部汉字,并对一些生僻汉字和少数民族文字进行了扩充,共收录了21003个中日韩汉字和符号,以及883个图形符号,总计21886个字符。
在GBK编码中,英文字母、数字、标点等非汉字字符仍然只占用一个字节,其编码值与ASCII码相同。

2. Base64

Base64在基于常规编码的基础上,将字符转为字节,然后将字节切分为6位的长度,并约定了6位字节与64个字符的对应关系。

Base64编码的出现主要是为了解决在不同系统之间传输二进制数据时可能遇到的问题。Base64编码使用64个可打印字符(包括大写字母A-Z、小写字母a-z、数字0-9、加号“+”和斜杠“/”,以及一个用作填充的等号“=”)来表示二进制数据。这使得Base64编码后的数据可以在文本协议中安全传输,而不会被忽略或错误解释。

将字符进行Base64编码时,首先要将字符转换成对应的ASCII码,然后得出8位二进制数,接着连接3个8位输入,形成字节数为24的输入组,再将24位输入组拆分成4组6位的二进制数,然后将6位二进制数转换为十进制数,最后找到十进制数在Base64编码表中对应的字符,并将这些字符组合成新的字符串,这个字符串就是编码结果。编码过程中用到的Base64编码表如图所示。

在这里插入图片描述

要注意的是,在编码过程中,如果字符位数少于24位,那么就需要进行特殊处理,也就是在编码结果的末尾用“=”符号填充。

我们可以通过一个例子来加深对Base64编码过程的理解。首先,我们将字符async转换成ASCII码,并找到对应的8位二进制数。字符、ASCII码和8位二进制数的对应值如图:

在这里插入图片描述

接着将3组8位二进制数连接成24位的输入组,再将24位输入组拆分成4组6位的二进制数。要注意的是,如果输入组的元素不足24位,那么就用0进行填充。24位输入组转换成6位二进制数的过程如图:

在这里插入图片描述

得到6位二进制数之后,我们还需要计算出对应的十进制。二进制转十进制其实是按权相加,将二进制数写成加权系数展开式,并按十进制加法规则求和。字符“a”对应的6位二进制数为011000,将其转换成十进制时,计算过程如图:

在这里插入图片描述

按照这个计算方法,计算其他的6位二进制数,最后得到字符“async”对应的十进制值:

24 23 13 57 27 38 12 65

补位字符“=”没有对应的值,本书约定其值为65。在得到所有的十进制值之后,就可以将其与RFC4648中的Base64编码表进行映射,从而得出编码后的字符串。映射过程如图:

在这里插入图片描述

最终得出字符“async”的Base64编码结果为“YXN5bmM=”,完整的编码过程如图:

在这里插入图片描述

Base64编码时所用的对照表是固定的,也就是说它的编码过程是可逆的。这意味着我们只需要将编码的流程倒置,就能够得解码的方法。Base64编码表中的 “+” 和 “/” 会影响文件编码和URI编码,我们在实际使用时,需要考虑到应用场景中是否包含文件编码或URI 。

如果在URI场景下使用Base64,就会引起错误,RFC4648文档中给出了一个解决办法:使用“-”和“_”替代“+”和“/”。

base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%base64加密后的密文长度视原字符而定

base64加密后的密文大概率最后1位或2位是=,这就是它的特点;此外其他编码方法如果用16进制表示,字母要么是大写要么是小写,而bash64会有大小写同时出现的情况和+,/,且字母不仅仅是a-f

浏览器原生提供了base64的编码、解码方法:btoa atob

window.btoa('lx')
window.atob('bHg=')

Js实现如下:

<html>
    <script type="text/javascript">
		// 创建Base64对象
		var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
		 
		// 定义字符串
		var string = 'i am bobo!';
		 
		// 加密
		var encodedString = Base64.encode(string);
		alert(encodedString); 
		 
		// 解密
		var decodedString = Base64.decode(encodedString);
		alert(decodedString); 
	</script>
		
</html>

nodejs实现如下:

var str1 = 'lx';
var str2 = 'bHg=';
var strToBase64 = new Buffer(str1).toString('base64');
var base64ToStr = new Buffer(str2, 'base64').toString();

python实现如下:

import base64

res = base64.b64encode('lx'.encode('utf-8'))
print(res) # b'bHg='

data = base64.b64decode('bHg='.encode())
origin = data.decode('utf-8')
print(origin) # 'lx'

补充:魔改Base64
爬虫工程师只需要按照Base64解码规则进行倒推,就能得到原字符。这显然不是开发者想要见到的结果。其实,开发者还可以通过自定义编码规则的方式保护数据。只需要稍微改动一下Base64编码过程中用到的对照表,或者改动输入组的划分规则,就可以创造一个新的编码规则。

Base64编码和解码时都是将原本8位的二进制数转换成6位的二进制数。如果我们改动位数,将其设置为5位或者4位,那么就可以实现新的编码规则。

此时如果爬虫工程师使用Base64对该编码结果进行解码,那么他将无法得到正确的原字符。这不仅达到了保护数据的目的,还能够迷惑爬虫工程师,使其将时间花费在“Base64解码不成功”的问题上。

3. urlencode

urlencode 称为url编码、百分号编码;就是将字符串以URL编码,一种编码方式,主要为了解决url中中文乱码问题。

python实现如下:

  • 传入参数类型:字典
  • 功能:将存入的字典参数编码为URL查询字符串,即转换成以key1=value1&key2=value2的形式

例子1:url标准符号,数字字母

from urllib.parse import urlencode

base_url = "https://m.weibo.cn/api/container/getIndex?"
params1 = {"value": "english", "page": 1}
url1 = base_url + urlencode(params1)
print(urlencode(params1))  # value=english&page=1
print(url1) # https://m.weibo.cn/api/container/getIndex?value=english&page=1

例子2:汉字, /, &, =, URL编码转化为%xx的形式

from urllib.parse import urlencode

params2 = {
    'name': "王二",
    'extra': "/",
    'special': '&',
    'equal': '='}
base_url = "https://m.weibo.cn/api/container/getIndex?"
url2 = base_url + urlencode(params2)
print(urlencode(params2)) # name=%E7%8E%8B%E4%BA%8C&extra=%2F&special=%26&equal=%3D
print(url2) # https://m.weibo.cn/api/container/getIndex?name=name=%E7%8E%8B%E4%BA%8C&extra=%2F&special=%26&equal=%3D

例子3:以上两例子默认utf8编码,如果用gb2312编码,则需指定

from urllib.parse import urlencode

params2 = {
    'name': "王二",
    'extra': "/",
    'special': '&',
    'equal': '='}
base_url = "https://m.weibo.cn/api/container/getIndex?"
url2 = base_url + urlencode(params2, encoding='gb2312')
print(urlencode(params2, encoding='gb2312')) 
print(url2)
# name=%CD%F5%B6%FE&extra=%2F&special=%26&equal=%3D
# https://m.weibo.cn/api/container/getIndex?name=%CD%F5%B6%FE&extra=%2F&special=%26&equal=%3D
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值