术→技巧, 研发

Python JSON/JSONP数据解析

钱魏Way · · 2,020 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

JSON简介

JSON即JavaScript Object Notation,它是一种轻量级的数据交换格式,非常适合于服务器与JavaScript的交互。

在普通的Web应用中,开发者经常为XML的解析伤透了脑筋,无论是服务器端生成或处理XML,还是客户端用JavaScript解析XML,都常常导致复杂的代码,极低的开发效率。实际上,对于大多数Web应用来说,许多AJAX应用甚至直接返回HTML片段来构建动态Web页面。和返回XML并解析它相比,返回HTML片段大大降低了系统的复杂性,但同时缺少了一定的灵活性。

JSON为Web应用开发者提供了另一种数据交换格式。和XML一样,JSON也是基于纯文本的数据格式。由于JSON天生是为JavaScript准备的,因此,JSON的数据格式非常简单,您可以用JSON传输一个简单的String,Number,Boolean,也可以传输一个数组,或者一个复杂的Object对象。

JSON的结构基于下面两点

  • 名称/值对的集合不同语言中,它被理解为对象(object),记录(record),结构(struct),字典(dictionary),哈希表(hashtable),键列表(keyedlist)等
  • 值的有序列表多数语言中被理解为数组(array)

JSON以一种特定的字符串形式来表示JavaScript对象。如果将具有这样一种形式的字符串赋给任意一个JavaScript变量,那么该变量会变成一个对象引用,而这个对象就是字符串所构建出来的。

这里假设我们需要创建一个User对象,并具有以下属性:

  • 用户ID
  • 用户名
  • 网站

您可以使用以下JSON形式来表示User对象:

{"UserID":100,"Name":"biaodianfu","website":"https://www.biaodianfu.com"};

然后如果把这一字符串赋予一个JavaScript变量,那么就可以直接使用对象的任一属性了。

<script>
var User = {"UserID":100,"Name":"biaodianfu","Website":"https://www.biaodianfu.com"};
alert(User.Name);
</script>

实际使用时可能更复杂一点,比如我们为Name定义更详细的结构,使它具有EnglishName和ChineseName:

{"UserID":100,"Name":{"EnglishName":"biaodianfu","ChineseName":"标点符"},"Website":"https://www.biaofianfu.com"};

完整代码:

<script>
var User = {
"UserID":100,
"Name":{"EnglishName":"biaodianfu","ChineseName":"标点符"},
"Website":"https://www.biaofianfu.com"
};
alert(User.Name.ChineseName);
</script>

如果某个页面需要一个用户列表,而不仅仅是一个单一的用户信息,那么就需要创建一个用户列表数组。下面代码演示了使用JSON形式定义这个用户列表:

<script>
var UserList = [
{
"UserID":100,
"Name":{"EnglishName":"biaodianfu","ChineseName":"标点符"},
"Website":"https://www.biaofianfu.com"
},
{
"UserID":101,
"Name":{"EnglishName":"biaodianfu","ChineseName":"标点符"},
"Website":"https://www.biaofianfu.com"
},
{
"UserID":102,
"Name":{"EnglishName":"biaodianfu","ChineseName":"标点符"},
"Website":"https://www.biaofianfu.com"
},
];
alert(UserList[0].Name.ChineseName);
</script>

除了使用”.”引用属性外,还可以使用下面语句:

alert(UserList[0]["Name"]["ChineseName"]);
alert(UserList[0].Name["ChineseName"]);

JSON的一些规则:

  • 对象是属性、值对的集合。一个对象的开始于“{”,结束于“}”。每一个属性名和值间用“:”提示,属性间用“,”分隔。
  • 数组是有顺序的值的集合。一个数组开始于”[“,结束于”]”,值之间用”,”分隔。
  • 值可以是引号里的字符串、数字、true、false、null,也可以是对象或数组。这些结构都能嵌套。
  • 字符串和数字的定义和C或Java基本一致。

JSON的一些优点:

  • JSON提供了一种优秀的面向对象的方法,以便将元数据缓存到客户机上。
  • JSON帮助分离了验证数据和逻辑。
  • JSON帮助为Web应用程序提供了Ajax的本质

JSON与Python字典的区别

JSON与Python中的dict非常类似,都是以key-value的形式存储数据,而且json、dict也可以非常方便的通过dumps、loads进行格式的相互转换。

既然都是key-value格式,为啥还需要进行格式转换呢?

  • JSON(JavaScript Object Notation):json是一种数据格式,是纯字符串。可以被解析成Python的dict或者其他形式。
  • dict(dictionary):字典是一个完整的数据结构,是对HashTable这一数据结构的一种实现,是一套从存储到提取都封装好了的方案。它使用内置的哈希函数来规划key对应value的存储位置,从而获得较快的数据读取速度。

JSON和dict的区别:

  • json的key只能是字符串,python的dict可以是任何可hash对象。
  • json的key可以是有序、重复的,python的dict的key不可以重复。
  • json 的 key 存在默认值 undefined,dict 没有默认值。
  • json 的 value 只能是字符串、浮点数、布尔值或者 null,或者它们构成的数组或者对象。
  • json 访问方式可以是 [], 也可以是 .,遍历方式分 in、of,dict 的 value 仅可以通过下标 [] 访问。
  • json 的字符串强制双引号,dict 字符串可以单引号、双引号。
  • json 里只有数组,dict 可以嵌套 tuple。
  • json 中的中文必须是 unicode 编码,如“你好”在 json 中应为 “\u4f60\u597d”。
  • json 的数据类型是字符串(str),字典的数据类型是字典(dict)。
  • json 定义布尔值和空值:true、false、null。
  • Python 定义布尔值和空值:True、False、None。

Python 标准库 JSON 模块

Python 中可以使用 json 模块来对 JSON 数据进行编解码,它包含了两个函数:

JSON 字符串转 Python 字典

# Import the module
import json

# String with JSON format
data_JSON = """
{
"size": "Medium",
"price": 15.67,
"toppings": ["Mushrooms", "ExtraCheese", "Pepperoni", "Basil"],
"client": {
"name": "Jane Doe",
"phone": "455-344-234",
"email": "janedoe@email.com"
}
}
"""

# Convert JSON string to dictionary
data_dict = json.loads(data_JSON)
print(type(data_dict))
print(data_dict["size"])
print(data_dict["price"])
print(data_dict["toppings"])
print(data_dict["client"])

输出内容:

<class 'dict'>
Medium
15.67
['Mushrooms', 'ExtraCheese', 'Pepperoni', 'Basil']
{'name': 'Jane Doe', 'phone': '455-344-234', 'email': 'janedoe@email.com'}

JSON 转 Python 字典过程中的数据类型转换:

Python 字典转 JSON 字符串

# Import the module
import json

# Python Dictionary
client = {
"name": "Nora",
"age": 56,
"id": "45355",
"eye_color": "green",
"wears_glasses": False
}

# Get a JSON formatted string
client_JSON = json.dumps(client)
print(type(client_JSON))
print(client_JSON)

输出内容:

<class 'str'>
{"name": "Nora", "age": 56, "id": "45355", "eye_color": "green", "wears_glasses": false}

Python 字典转 JSON 时的数据类型转换:

Python JSON 数据美化

client_JSON = json.dumps(client, indent=4)
print(client_JSON)

输出内容:

{
"name": "Nora",
"age": 56,
"id": "45355",
"eye_color": "green",
"wears_glasses": false
}

JSON 文件的读取与写入

# Open the orders.json file
with open("orders.json") as file:
# Load its content and make a new dictionary
data = json.load(file)

# Delete the "client" key-value pair from each order
for order in data["orders"]:
del order["client"]

# Open (or create) an orders_new.json file
# and store the new version of the data.
with open("orders_new.json", 'w') as file:
json.dump(data, file)

json 与 simplejson 的区别

我们看到许多项目使用了 simplejson 模块而不是标准库中的 json 模块。为什么要使用这些替代方法而不是标准库中的替代方法?

Python 标准库中的 json 模块其实就是 simplejson,只不过更新频率相比独立的 simplejson 要慢一些。两者功能相似,性能上 simplejson 要更好一些。

建议这样使用:

try:
import simplejson as json
except ImportError:
import json

其他 JSON 解析工具

  • python-rapidjson:RapidJSON 是腾讯开源的一个高效的 C++ JSON 解析器及生成器。目前被包装为 Python 库。
  • ultrajson:用 C 写的 json 解析库
  • pysimdjson:simdjson 项目的 Python 绑定,这是 SIMD 加速的 JSON 解析器.如果没有 SIMD 指令,则使用后备解析器,从而使 pysimdjson 在任何地方都可以安全使用。
  • orjson:orjson 是 Rust 语言写的,本地没有 Rust 环境他会提示你需要 Rust 环境,安装起来比较麻烦,据说性能非常的快。
  • marshmallow:marshmallow is an ORM/ODM/framework-agnostic library for converting complex data types, such as objects, to and from native Python data types.

PythonJSONP

JSONP(JSON with Padding)是JSON的一种”使用模式”,可以让网页从别的网域要数据。

由于同源策略,一般来说位于server1.example.com的网页无法与不是server1.example.com的服务器沟通,而HTML的<script>元素是一个例外。利用<script>元素的这个开放策略,网页可以得到从其他来源动态产生的JSON资料,而这种使用模式就是所谓的JSONP。用JSONP抓到的资料并不是JSON,而是任意的JavaScript,用JavaScript直译器执行而不是用JSON解析器解析。

为了理解这种模式的原理,先想像有一个回传JSON文件的URL,而Javascript程式可以用XMLHttpRequest跟这个URL要资料。假设我们的URL是http://server2.example.com/RetrieveUser?UserId=xxx。假设小明的UserId是1234,且当浏览器透过URL传小明的UserId,也就是抓取http://server2.example.com/RetrieveUser?UserId=1234,得到:

{"Name":"小明","Id":1823,"Rank":7}

这个JSON资料可能是依据传过去URL的查询参数动态产生的。

这个时候,把<script>元素的src属性设成一个回传JSON的URL是可以想像的,这也代表从HTML页面透过script元素抓取JSON是可能的。

然而,一份JSON文件并不是一个JavaScript程序。为了让浏览器可以在<script>元素执行,从src里URL回传的必须是可执行的JavaScript。在JSONP的使用模式里,该URL回传的是由函数呼叫包起来的动态生成JSON,这就是JSONP的”填充(padding)”或是”前辍(prefix)”的由来。

惯例上浏览器提供回调函数的名称当作送至服务器的请求中命名查询参数的一部份,例如:

<script type="text/javascript" src="http://server2.example.com/RetrieveUser?UserId=1234&jsonp=parseResponse"></script>

服务器会在传给浏览器前将JSON数据填充到回调函数(parseResponse)中。浏览器得到的回应已不是单纯的资料叙述而是一个脚本。在本例中,浏览器得到的是:

parseResponse({"Name":"Cheeso","Id":1823,"Rank":7})

虽然这个填充(前辍)”通常”是浏览器执行背景中定义的某个回调函数,它也可以是变量赋值、if叙述或者是其他JavaScript叙述。JSONP要求(也就是使用JSONP模式的请求)的回应不是JSON也不被当作JSON解析——回传内容可以是任意的运算式,甚至不需要有任何的JSON,不过惯例上填充部份还是会触发函数调用的一小段JavaScript片段,而这个函数呼叫是作用在JSON格式的资料上的。

另一种说法,典型的JSONP就是把既有的JSON API用函数请求包起来以达到跨域存取的解法。为了要启动一个JSONP请求(或者说,使用这个模式),你需要一个script元素。因此,浏览器必须为每一个JSONP要求加(或是重用)一个新的、有所需src值的<script>元素到HTML DOM里—或者说是”注入”这个元素。浏览器执行该元素,抓取src里的URL,并执行回传的JSON。也因为这样,JSON被称作是一种”让使用者利用script元素注入的方式绕开同源策略”的方法。

使用远端网站的script标签会让远端网站得以注入任何的内容至网站里。如果远端的网站有JavaScript注入漏洞,原来的网站也会受到影响.现在有一个正在进行计划在定义所谓的JSON-P严格安全子集,使浏览器可以对MIME类别是”application/json-p”请求做强制处理。如果回应不能被解析为严格的JSON-P,浏览器可以丢出一个错误或忽略整个回应。

粗略的JSONP部署很容易受到跨网站的伪造要求(CSRF/XSRF)的攻击。因为HTML <script>标签在浏览器里不遵守同源策略,恶意网页可以要求并取得属于其他网站的JSON资料。当使用者正登入那个其他网站时,上述状况使得该恶意网站得以在恶意网站的环境下操作该JSON资料,可能泄漏使用者的密码或是其他敏感资料。

只有在该JSON资料含有不该泄漏给第三方的隐密资料,且服务器仅靠浏览器的同源策略阻挡不正常要求的时候这才会是问题。若服务器自己决定要求的专有性,并只在要求正常的情况下输出资料则没有问题。只靠Cookie并不够决定要求是合法的,这很容易受到跨网站的伪造要求攻击。

JSONP的客户端实现

我们知道,哪怕跨域js文件中的代码(当然指符合web脚本安全策略的),web页面也是可以无条件执行的。

远程服务器remoteserver.com根目录下有个remote.js文件代码如下:

alert('我是远程文件');

本地服务器localserver.com下有个jsonp.html页面代码如下:

<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>

毫无疑问,页面将会弹出一个提示窗体,显示跨域调用成功。

现在我们在jsonp.html页面定义一个函数,然后在远程remote.js中传入数据进行调用。

jsonp.html页面代码如下:

<script type="text/javascript">
var localHandler = function(data){
alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
};
</script>
<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>

remote.js文件代码如下:

localHandler({"result":"我是远程js带来的数据"});

运行之后查看结果,页面成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,并且还接收到了远程js带来的数据。很欣喜,跨域远程获取数据的目的基本实现了,但是又一个问题出现了,我怎么让远程js知道它应该调用的本地函数叫什么名字呢?毕竟是jsonp的服务者都要面对很多服务对象,而这些服务对象各自的本地函数都不相同啊?我们接着往下看。

聪明的开发者很容易想到,只要服务端提供的js脚本是动态生成的就行了呗,这样调用者可以传一个参数过去告诉服务端”我想要一段调用XXX函数的js代码,请你返回给我”,于是服务器就可以按照客户端的需求来生成js脚本并响应了。

看jsonp.html页面的代码:

<script type="text/javascript">
// 得到航班信息查询结果后的回调函数
var flightHandler = function(data){
    alert('你查询的航班结果是:票价' + data.price + '元,' + '余票' + data.tickets + '张。');
};
// 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
// 创建script标签,设置其属性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
</script>

这次的代码变化比较大,不再直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,本例中的重点也就在于如何完成jsonp调用的全过程。

我们看到调用的url中传递了一个code参数,告诉服务器我要查的是CA1998次航班的信息,而callback参数则告诉服务器,我的本地回调函数叫做flightHandler,所以请把查询结果传入这个函数中进行调用。

OK,服务器很聪明,这个叫做flightResult.aspx的页面生成了一段这样的代码提供给jsonp.html(服务端的实现这里就不演示了,与你选用的语言无关,说到底就是拼接字符串):

flightHandler({
    "code": "CA1998",
    "price": 1780,
    "tickets": 5
});

我们看到,传递给flightHandler函数的是一个json,它描述了航班的基本信息。运行一下页面,成功弹出提示窗口,jsonp的执行全过程顺利完成!

到这里为止的话,相信你已经能够理解jsonp的客户端实现原理了吧?剩下的就是如何把代码封装一下,以便于与用户界面交互,从而实现多次和重复调用。

jQuery对JSONP的实现

我们依然沿用上面那个航班信息查询的例子,假定返回jsonp结果不变:

<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript">
jQuery(document).ready(function(){

    $.ajax({
        type: "get",
        async: false,
        url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
        dataType: "jsonp",
        jsonp: "callback", //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
        jsonpCallback: "flightHandler", //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
        success: function(json){
            alert('您查询到航班信息:票价:' + json.price + '元,余票:' + json.tickets + '张。');
        },
        error: function(){
            alert('fail');
        }
    });
});
</script>

为什么我这次没有写flightHandler这个函数呢?而且竟然也运行成功了!这就是jQuery的功劳了,jquery在处理jsonp类型的ajax时自动帮你生成回调函数并把数据取出来供success属性方法来调用。

ajax与jsonp的异同

ajax和jsonp这两种技术在调用方式上”看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;

但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。

所以说,其实ajax与jsonp的区别不在于是否跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取。

还有就是,jsonp是一种方式或者说非强制性协议,如同ajax一样,它也不一定非要用json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用jsonp提供公开服务。

总而言之,jsonp不是ajax的一个特例,哪怕jquery等巨头把jsonp封装进了ajax,也不能改变这一点!

Python与JSONP

Python生成JSONP数据:

def dumps_jsonp(request):
    funcname = request.GET.get('callback')
    #print(funcname)
    content = '%s(%s)' % (funcname, data())
    #print(content)
    return HttpResponse(content)

Python读取JSONP数据:

import re
import json

def loads_jsonp(_jsonp):
    try:
        return json.loads(re.match(".*?({.*}).*", _jsonp, re.S).group(1))
    except:
        raise ValueError('Invalid Input')

参考链接:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注