目录
3. 将多个网址URL或本地路径文件的Json内容进行嵌套合并
一、概念
Python:易读
Perl字符串模式匹配,python的正则表达式引擎基于Perl。但Perl符号语法晦涩。
Jython是java版实现的python,优点:可以直接调用Java类库。
Ruby完全面向对象,python借鉴了函数化编程结构。
二、基础
1. 入门
python使用内置函数help(obj)来获得参考帮助
主提示符>>> 等待新输入
次提示符... 等待某个未完的后续输入
py文件经过编译后,生成一种扩展名为pyc的二级制文件,是一种byte code;py文件变成pyc文件后,加载的速度有所提高,而且pyc是一种跨平台的字节码,是由python的虚拟机来执行的;但pyc的内容,是跟python的版本相关的,不同版本编译后的pyc文件是不通用的。
2. module,package
Python中的module,说白了,就是Python文件,而python文件一般后缀为py,所以就是你的xxx.py而已。
Python中的package,可以简单的理解为,一组的module,一堆(相关的)module组合而成的。
3. 文档结构:
1.起始行 #! /usr/bin/env python
编码行 #-*- coding: utf-8 -*- (该编码行只能在前2行,该格式是兼容emac编辑器的注释方式的,中间也可以用coding=utf-8的形式,直接使用#coding=utf-8也可以被python识别,编码方式支持gbk、cp939等参数,不加该编码行,python无法解析中文字符会报错)
2.模块文档(文档字符串)
3.模块导入
4.全局变量定义
5.类定义(若有)
6.函数定义(若有)
7.主程序
无论本文件(模块)是被别的模块导入还是作为脚本直接执行,都会执行主程序的代码。故一般主程序的代码如下:
if __name__ == '__main__':
main() #如果是直接执行,调用main函数;如果模块是被导入,__name__的值为模块名字
打印helloworld的文档结构示例文件:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""本处即为模块说明文档:
该文件是hello world的示例文件"""
#模块导入
import sys
#默认使用utf8编码
reload(sys)
sys.setdefaultencoding('utf8')
#当我们使用“import A.B”导入模块的时候, 在代码中使用B,仍然得使用“A.B”这种方式才能正确索引;而对于“from A import B”这样的语句,我们可以在代码中直接使用B就可以索引到B。
#但这种方法有可能使得当前程序的命名空间混乱,可以使用“from A import B as m_B”,这样在后面的程序就用m_B来代替B了,这样就不会出重名的问题啦
from <module> import <class/function>
#全局变量定义
global_var="hello"
#类定义
class PrintWho():
"""类内的文档字符串,说明类的作用"""
who=""
def __init__(self, who):
"""初始化函数"""
self.who=who
def printwho(self):
"""打印who变量"""
print self.who
def getwho(self):
"""返回who变量"""
return self.who
#函数定义
def main():
"""函数内的文档字符串,说明函数功能"""
# 全局变量需要在本地改变时,要先声明
# global global_var
# global_var = global_val + '11111'
local_var="world"
objwho=PrintWho(local_var)
print global_var + " " + objwho.getwho()
#主程序
if __name__ == '__main__':
main() #如果是直接执行,调用main函数;如果模块是被导入,__name__的值为模块名字
4. 变量
python是弱变量类型,不需要提前声明变量,也不需要声明变量的类型。
__xxx 不用‘from module import *’导入(该语句表示导入命名空间)
__xxx__ 系统定义名字
__xxx 类中的私有变量
使用None表示空/Null
布尔值使用True, False
5. 注释符
使用#作为注释符
文档字符串:写在模块、类、函数开始处的一个字符串,通过__doc__访问,例如:’print function_name.__doc__‘ 可以打印出该函数的文档字符串,而且这个语言可以就在函数function_name里使用。并且利用help命令看到的帮助信息也是取自文档字符串。
6. 代码块和语句分割
可以使用分号;分割同行的语句
通过缩进对齐表达代码块:建议缩进4个空格,避免制表符,因为各个平台或编辑器中解释不一致
7. 序列
对于序列(字符串、列表、元组)可用切片操作符:[],[:],[::]
元组是一种不可变类型,和列表相互转换使用list()、tuple()函数。
字典{key:value, key:value}
8. 函数定义
def funname(parm, parm=defaultvalue):
"""文档字符串""""
code
return 1
9. 类定义
class Child(Parent1,Parent2):
var=val
def __init__(self):
"""初始化函数""""
@staticmethod
def astaticmethod():
"""静态函数""""
def ageneralmethod(self, parm):
"""普通函数""""
10. 安装python包
easy_insall的作用和perl中的cpan,ruby中的gem类似,都提供了在线一键安装模块的傻瓜方便方式,而pip是easy_install的改进版,提供更好的提示信息,删除package等功能。
1)easy_install(低旧版本Python)
#安装一个包,增加版本参数时应该使用双引号括住
easy_install <package_name>
easy_install "<package_name>==<version>"
easy_install "<package_name>>=<version>"
#升级一个包
easy_install -U "<package_name>"
2)pip
# 安装一个包
pip install <package_name>
pip install <package_name>==<version>
pip install <package_name>>=<version>
# 升级一个包
pip install --upgrade <package_name>
# 删除一个包
pip uninstall <package_name>
3)为pip配置国内镜像源
执行`mkdir -p ~/.pip`创建目录,然后执行`vi ~/.pip/pip.conf`编辑pip.conf配置文件并填入以下内容:
[global]
# 阿里镜像源
index-url = http://mirrors.aliyun.com/pypi/simple/
# 清华镜像源(设置为了备份源)
extra-index-url = https://pypi.tuna.tsinghua.edu.cn/simple/
[install]
trusted-host=mirrors.aliyun.com
更多国内镜像源可以参考 国内开源软件镜像站点参考 自行配置,配置过多的备份源固然可以起到平衡负载的作用,但当包不可用时会依次查找所有源,时间会变长不少。
11. Python可执行文件
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: '<模块名>==<版本号>','console_scripts','<entry_point项>'
__requires__ = '<模块名>==<版本号>'
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.exit(
# <entry_point项>:在模块同级的"<模块名>-<版本号>-py2.7.egg-info"文件夹的下面的"entry_points.txt"文件中有相应程序的入口点
load_entry_point('<模块名>==<版本号>', 'console_scripts', '<entry_point项>')()
)
entry_points.txt文件示例:
表明需要执行的entry_point:MyEntryPointName,对应的是MyModule模块下的myPyFile.py文件中的myFunction()函数。
[console_scripts]
MyEntryPointName = MyModule.myPyFile:myFunction
三、常用操作
1. 判断和循环操作
if a<1 and b<1:
pass #pass表示不做任何操作
elif a<1 or (not b<1):
pass
else:
pass
while a<10
a+=1
for i in range(1, 10, 1)
pass
2. json操作
从json格式的内容的文件中读取到变量:
f = file("json.txt");
s = json.load(f)
f.close()
把字符串、数组等类型转换为json格式并打印或者输出到文件:
list={"a":"aa","b":"bb"}
print json.dumps(list)
f = open('json_out.txt', mode='w+')
json.dump(list, f)
3. 给python文件传递参数
import sys
print sys.argv[0] ##脚本名
print len(sys.argv) ##参数个数
print sys.argv[1] ## 第一个参数
print sys.argv ###参数数组
4. 文件操作
import os
filename="/home/dir/file"
if(os.path.isfile(filename)):
#或者os.path.exists(filename)
print filename + "exist!!!"
else:
print "File not exist!!!!"
5. web框架django和flask
1)Django
官方首页:https://www.djangoproject.com/
2)Flask
安装:
pip install Flask
使用实例,test.py文件:
#! env python
# -*- coding: utf-8 -*-
from flask import Flask, request, session
app = Flask(__name__)
@app.route('/')
def hello_world():
return '你好,flask!'
@app.route('/post/hello.do', methods=['POST'])
def hello_post():
return '{"post": true, "text": "hello"}'
@app.route('/user/<username>')
def show_user_profile(username):
return 'User %s' % username
@app.route('/test.do', methods=['GET', 'POST'])
def test():
# 访问路径,/test.do
print request.path
# 方法,GET或POST
print request.method
# get参数
print request.args
# post参数
print request.form
# json类型参数
print request.json
# file内容,"the_file"对应前端表单name属性
if 'the_file' in request.files:
f = request.files['the_file']
print f.filename
# cookies
print request.cookies
# 返回html模板
# return render_template('hello.html', name=name)
# 返回时设定header等
# resp = make_response(render_template('hello.html'), name=name)
# resp.set_cookie('username', 'the username')
# resp.headers['X-Something'] = 'A value'
# return resp
# 使用session
session['login'] = 'admin'
print session['login']
session.pop('login', None)
return 'test'
if __name__ == '__main__':
app.secret_key = 'session使用的密钥'
app.debug = True
app.run()
使用gunicorn来部署Flask服务:
安装:pip install gunicorn
部署:gunicorn --workers=4 --bind=0.0.0.0:8080 test:app
6. dict操作
#初始化一个dict类型,然后增加和删除元素,然后删除整个dict
my_dict = {}
my_dict["myKey1"] = "xxx"
del my_dict["myKey1"]
del my_dict
#判断dict是否存在某个key
if key in my_dict:
xxx #存在的处理逻辑
#使用key进行遍历
for key in my_dict:
print key, my_dict[key]
#使用key和value进行遍历
for k,v in my_dict:
print k, v
#使用key来遍历,因为有删除操作,所以,需要先得到一个keys数组
for key in my_dict.keys():
if xxx:
del my_dict[key]
7. 函数
函数可以作为函数变量,函数入参,函数返回值来使用。
举例:
def add(a, b):
return a + b
my_fun = add
my_fun(3, 4)
1)函数装饰器 @符号
函数装饰器 @符号 是一种函数调用函数的简单写法,例如:
@my_decorator
def add(a, b):
return a + b;
# 等价于:
add=my_decorator(add)
# 故,可以在my_decorator(func)函数中对函数进行预处理和后处理。举例:
def my_decorator(fun):
def inner_fun(a, b):
# 预处理和后处理相关事情
return -1 * fun(a, b);
return inner_fun
# 进阶1:多个装饰器修饰一个函数时,嵌套顺序为最下面的装饰器在最里面
# 进阶2:当装饰器带有多个参数的时候, 装饰器函数就需要多加一层嵌套:
def my_decorator2(d1, d2):
def my_decorator(fun):
def inner_fun(a, b):
# 预处理和后处理相关事情
return d1 * d2 * fun(a, b);
return inner_fun
#对应调用形式为:
@my_decorator2(d1=1, d2=2)
def add(a, b):
return a + b;
2)Lambda表达式
Python 使用 lambda 来创建匿名函数,语法如下:
lambda [arg1 [,arg2,.....argn]]:expression
其中冒号左侧为匿名函数的输入参数,冒号右侧为匿名函数的返回值。
8. 多线程
t1=threading.Thread(target=run,args=("arg1",)) 创建一个线程实例
t1.setDaemon(True) 把当前子线程设置为守护线程(一定要在start前设置),主线程结束,这些守护线程会强制结束。
t1.start() 启动这个线程实例
t1.join() 不加join的话,主线程和子线程完全是并行的;加了join,主线程得等这个子线程执行完毕,才能继续往下执行。
如果想获取多线程的返回结果,需要使用自定义继承了Thread类的自定义类的方式,举例:
class MyThread(threading.Thread):
def __init__(self, arg1, arg2):
super(MyThread, self).__init__()
self.arg1 = arg1
self.arg2 = arg2
def run(self):
self.result = self.arg1 + self.arg2
def get_result(self):
try:
return self.result
except:
return None
四、常用函数
help(obj) 帮助
exit(int)退出,输入为0表示正常退出,其他值表示非正常退出
dir(obj) 列出模块定义的标识符
raw_input(str) 获取输入
int(obj) 转换为整型
str(obj) 转换为字符串
open(filename, mode) 打开文件
len(obj) 获取长度
type(obj) 显示类型
range(start=0, stop, step=1) 获取范围的数值x序列,start<=x<stop,步长为step
id(obj) 显示id,近似于指针的值
randint(int, int) 获取2个参数之间的随机数
locals()返回所有局部变量
五、琐碎细节(不重要):
打印使用print关键字,支持类似c的%来替换变量
三引号(三个单引号或双引号连写)表示该字符串可以包含转义等特殊字符。
不支持前置或后置的单操作符:++ --
不支持case语句
不支持类型:char或byte,指针
赋值语句不是合法表达式,比如y=(x=x+1)不合法
python没有三元操作符(:?),但是可以类似如下形式来替代:min = x if x < y else y
3<4<5 合法的判断
支持增量赋值+=,多重赋值x=y=1
多元赋值,例如x,y=y,x
;同一行书写多条语句
位操作符:~ << >> & ^ |
常见操作符:+ - * / // % **,其中//为地板除(python中/一定是实际的除),**为幂运算
r/R原始字符串操作符,使用方法:置于引号前,如r'',则引号内的转义字符失效。
函数支持前向引用,假设A函数中调用B函数,那么B函数可以放在A函数之后实现。
对象赋值实际上是简单的对象引用(浅拷贝);深拷贝要使用deepcopy()函数。
局部变量可以替代全局变量,但是这样有危险:1.从局部变量所在块的开始和局部变量定义之前,全局变量就已经不可见,故此时试图访问全局变量会报错2.没有直接的指示符可以方便的访问同名的全局变量
python是解释性语言,不像c在编译时会自动优化一些常见的问题,需要自己注意优化程序。比如,不要把求长度函数放在循环里。
python解释器承担了内存管理的任务。垃圾收集器负责释放内存:寻找计数为0的对象和循环引用。
六、末节
\换行符,多行书写
支持函数内部内嵌函数
可以传递函数做参数
支持复数类型
支持异常模式:try-except-else-finally,raise
七、实例
1. 录制和回放数据
recorder.py文件:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import ConfigParser
import socket, select
import time
def record(listen,datafile,backendIp,backendPort):
print listen,datafile,backendIp,backendPort
#create socket to accept original request.
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen = int(listen)
#bind local port;
serverSocket.bind(('0.0.0.0', listen))
#start listen, max connection number: 20
serverSocket.listen(20)
#wait until a client connect, then return a New socket object and client's address. You can use the New socket object to communicate with client.
#call accept(), recv(), send() will blocked, so one time can ONLY process one connection.
#usually, out of accept() there is 'while True:' to circularly process the connections, and after accept(), should create a new thread to process connection ( such as recv() & send() ), so can process multi-connection at one time. It's classic Blocked Model.
serverConnection,address = serverSocket.accept()
try:
reqData = ''
while True:
try:
serverConnection.settimeout(0.1)
#recive the request, once max len: 8024
req = serverConnection.recv(8024)
reqData += req
if len(req)==0 : break
except:
break
backendPort = int(backendPort)
#create socket to resend original request to real server.
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientSocket.connect((backendIp, backendPort))
#resend the original request
clientSocket.send(reqData)
resData = ''
while True:
#receive the real response, once max len: 1024
res = clientSocket.recv(1024)
if not res: break
resData += res
#send the response back to client, and close connection.
serverConnection.send(resData)
time.sleep(0.01)
serverConnection.close()
#open file to wirte the real response, use as stub response. 'w' means create or open & clear a file, then write. so always keep the LAST response.
datafileHandle=open(datafile,'wb')
datafileHandle.write(resData);
datafileHandle.close()
except socket.timeout:
print 'record error'
return
def replay(listen,datafile):
print listen,datafile
datafileHandle=open(datafile,'rb')
#read the recorded response.
response = datafileHandle.read()
datafileHandle.close()
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#when socket is closed, its port can be use immediately.
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen = int(listen)
serverSocket.bind(('0.0.0.0', listen))
#max connection number: 200
serverSocket.listen(200)
#set to non-blocking
serverSocket.setblocking(0)
#Non-blocking Mode, using epoll method.
epoll = select.epoll()
#listen on data-arriving event.
epoll.register(serverSocket.fileno(), select.EPOLLIN)
try:
connections = {}; responses = {}
arrivedTimes = {};
while True:
#inquire happened events and return (fd, event-code). max wait time: 1s.
events = epoll.poll(1)
for fileno, event in events:
#serverSocket's read event, create a new socket to process.
if fileno == serverSocket.fileno():
try:
connection, address = serverSocket.accept()
#Non-blocking Mode, register to the same epoll object.
connection.setblocking(0)
epoll.register(connection.fileno(), select.EPOLLIN)
#index the connection, and set response to stub one.
connections[connection.fileno()] = connection
responses[connection.fileno()] = response
except:
print "epoll accept error"
#it's read event, so it's client's request arriving.
elif event & select.EPOLLIN:
try:
#receive data, max len: 18024
connections[fileno].recv(18024)
arrivedtime = int(time.time()*1000)
arrivedTimes[fileno] = arrivedtime
#change to listen to write-event.
epoll.modify(fileno, select.EPOLLOUT)
except:
print "epoll read error"
#it's wirte event, so now can send client a response.
elif event & select.EPOLLOUT:
try:
#time.time() return a float type time-stamp. so, here means: send time must after receive time 50ms and more.
#epoll is default level-trigged mode. so, 50ms is simulating the Network Delay.
currenttime = int(time.time()*1000)
if(currenttime - arrivedTimes[fileno] > 50):
#write response, and when all is sended, then unregister write-event and close write & read, wait for client close the connection.
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:]
if len(responses[fileno]) == 0:
epoll.modify(fileno, 0)
connections[fileno].shutdown(socket.SHUT_WR)
except:
print "epoll write error"
#HUP event means client close the connection. so unregister wirte-event and close connection socket.
elif event & select.EPOLLHUP:
try:
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]
except:
print "epoll close error"
finally:
print 'error'
epoll.unregister(serverSocket.fileno())
epoll.close()
serverSocket.close()
if __name__=='__main__':
configParser = ConfigParser.ConfigParser()
configParser.read('stub.conf');
if(not configParser.has_option("stub", "mode")):
print 'no mode option in config file.'
exit
mode = configParser.get("stub", "mode")
if(not configParser.has_option("stub", "listen")):
print 'no listen option in config file.'
exit
listen = configParser.get("stub", "listen")
if(not configParser.has_option("stub", "datafile")):
print 'no datafile option in config file.'
exit
datafile = configParser.get("stub", "datafile")
if(mode == '1'):
if( not configParser.has_option("backend", "ip")):
print 'no backend ip option in config file.'
exit
backendIp = configParser.get("backend", "ip")
if(not configParser.has_option("backend", "port")):
print 'no backend port option in config file.'
exit
backendPort = configParser.get("backend", "port")
if(listen and datafile and backendIp and backendPort):
record(listen,datafile,backendIp,backendPort)
else:
print 'config option lost,please check listen,datafile,backend ip,backend port.'
exit
else:
if(listen and datafile):
replay(listen,datafile)
else:
print 'config option lost,please check listen,datafile.'
exit
stub.conf文件:
[stub]
mode : 0
listen : 3000
datafile : mydata
[backend]
ip : 127.0.0.1
port : 3000
2. json diff脚本
#! /bin/env python
# -*- coding: utf-8 -*-
import json
import commands
import re
import traceback
import datetime
KEY_PREFIX_SEPARATOR = "."
VALUES_SEPARATOR = u"@@@"
DEFAULT_ENCODING = "UTF-8"
NOT_EXIST_FLAG = "__Not_Exist__"
def __merge_values(value1, value2):
if isinstance(value1, (unicode, str)):
value1 = "\"" + value1 + "\""
if isinstance(value2, (unicode, str)):
value2 = "\"" + value2 + "\""
if value1 is None:
value1 = u"Null"
if value2 is None:
value2 = u"Null"
if not isinstance(value1, unicode):
value1 = unicode(str(value1), DEFAULT_ENCODING)
if not isinstance(value2, unicode):
value2 = unicode(str(value2), DEFAULT_ENCODING)
return value1 + VALUES_SEPARATOR + value2
def json_diff(json1, json2):
"""
将2个json进行比较,找出差异,并存储为ignore文件
:param json1: 第1遍运行时的request的json字符串
:param json2: 第2遍运行时的request的json字符串
:return: 有差异的键的绝对路径,以及该键在2个json中对应的值(这些信息是用于提供xts系统对使用者展示)
"""
# todo: 入参校验
# f1=open(file1_path)
# f2=open(file2_path)
# json1=f1.read()
# json2=f2.read()
# f1.close()
# f2.close()
# print type(json1), json1
# print json2
# print "\n+++++++++++++++\n"
# print json1
# print "\n+++++++++++++++\n"
# print json2
# print "\n+++++++++++++++\n"
json_obj1 = json.loads(json1, encoding=DEFAULT_ENCODING)
json_obj2 = json.loads(json2, encoding=DEFAULT_ENCODING)
# print type(json_obj1), json_obj1
# print type(json_obj2), json_obj2
result = {} # 差异结果dict
result1 = {}
result2 = {}
__get_key_prefix_dict(None, result1, json_obj1)
__get_key_prefix_dict(None, result2, json_obj2)
# print "\n+++++++++++++++\n"
# print result1
# print "\n+++++++++++++++\n"
# print result2
# print "\n+++++++++++++++\n"
# print type(result1["key1"].encode("utf-8"))
# print result1["key1"].encode("utf-8") == "中文1"
# 遍历得到的前缀key数组并进行比较,将不同记入结果dict
for k in result1:
if k in result2:
# 均存在,比较值是否相同
if not isinstance(result1[k], type(result2[k])) or result1[k] != result2[k]:
result[k] = __merge_values(result1[k], result2[k])
else:
# 只在result1存在,记入差异结果dict
result[k] = __merge_values(result1[k], NOT_EXIST_FLAG)
for k in result2:
if k not in result1:
result[k] = __merge_values(NOT_EXIST_FLAG, result2[k])
return result
def json_assert(expect_json, actual_json, absolute_ignore, relactive_ignore, value_ignore, is_disorder_array,
is_full_compare, analyze_reference):
"""
验证实际的json是否和期望的json相符
:param expect_json: 期望的json字符串
:param actual_json: 实际的json字符串
:param absolute_ignore: 绝对路径表示的ignore集合;对性能几乎无影响
:param relactive_ignore: 相对路径表示的ignore集合(正则形式);随着输入个数的增加,性能成线性下降
:param value_ignore: 要忽略的value的集合(正则形式);随着输入个数的增加,性能成线性下降
:param is_disorder_array: 是否需要无序比较数组;开启后性能下降,性能下降幅度取决于无序的结果个数和验证失败的个数,且随着数组的层级上升成n^2比例下降
:param is_full_compare: 为false时只验证期望是否是实际的子集;为true时验证期望和实际为相等集;对性能几乎无影响
:param analyze_reference: 是否对值为引用的情况进行解析并展开;开启后性能验证下降,建议仅对失败的结果使用该选项复查
:return: 有差异的键的绝对路径,以及该键在2个json中对应的值
"""
# todo: 入参校验
json_exp = json.loads(expect_json, encoding=DEFAULT_ENCODING)
json_act = json.loads(actual_json, encoding=DEFAULT_ENCODING)
result = {}
result_exp = {}
result_act = {}
__get_key_prefix_dict(None, result_exp, json_exp)
__get_key_prefix_dict(None, result_act, json_act)
# 处理需要忽略的绝对路径(可以不存在)
for abs_key in absolute_ignore:
if abs_key in result_exp:
# print "ERROR" # todo:
# return None
del result_exp[abs_key]
if abs_key in result_act:
del result_act[abs_key]
# 处理需要忽略的相对路径(使用正则匹配,可以无匹配结果)
for rel_key in relactive_ignore:
# 因为有删除操作,所以,使用dict.keys()来遍历
for exp_key in result_exp.keys():
if re.search(rel_key, exp_key):
del result_exp[exp_key]
for act_key in result_act.keys():
if re.search(rel_key, act_key):
del result_act[act_key]
# 处理需要忽略的值的集合(使用正则匹配,可以无匹配结果)
for ign_value in value_ignore:
# 因为有删除操作,所以,使用dict.keys()来遍历
for exp_key in result_exp.keys():
if re.search(ign_value, str(result_exp[exp_key])):
del result_exp[exp_key]
for act_key in result_act.keys():
if re.search(ign_value, str(result_act[act_key])):
del result_act[act_key]
# 递归地处理引用形式:xxx.xxx.$ref=$.xxx.xxx.xx[x].xxx
if analyze_reference:
REF_PARTERN = '\.\$ref$'
# 处理期望结果dict
has_recursive_reference = True
while has_recursive_reference:
has_recursive_reference = False
for exp_key in result_exp.keys():
# 匹配出引用形式,然后再循环匹配其值的前缀,然后替换前缀后,加入expand_rst数组
if re.search(REF_PARTERN, exp_key):
ref_value = result_exp[exp_key][2:].replace('[', '.[')
if not ref_value:
# 例如内容为..即表示循环引用,类似这样的情况不做引用解析
continue
ref_value_pattern = u'^' + ref_value.replace('.', '\.').replace('[', '\[').replace(']', '\]')
replace_sucessful = False
for exp_key_inner in result_exp.keys():
if re.search(ref_value_pattern, exp_key_inner):
if re.search(REF_PARTERN, exp_key_inner):
# 说明为递归引用,被引用的地方仍然引用了其他地方
has_recursive_reference = True
# 解析后引用放到result_exp中,但是需要下轮大循环才能使用
result_exp[exp_key_inner.replace(ref_value, exp_key[0:-5], 1)] = result_exp[exp_key_inner]
replace_sucessful = True
if replace_sucessful:
del result_exp[exp_key]
else:
print exp_key
# 处理实际结果dict
has_recursive_reference = True
while has_recursive_reference:
has_recursive_reference = False
for act_key in result_act.keys():
# 匹配出引用形式,然后再循环匹配其值的前缀,然后替换前缀后,加入expand_rst数组
if re.search(REF_PARTERN, act_key):
ref_value = result_act[act_key][2:].replace('[', '.[')
if not ref_value:
# 例如内容为..即表示循环引用,类似这样的情况不做引用解析
continue
ref_value_pattern = u'^' + ref_value.replace('.', '\.').replace('[', '\[').replace(']', '\]')
replace_sucessful = False
for act_key_inner in result_act.keys():
if re.search(ref_value_pattern, act_key_inner):
if re.search(REF_PARTERN, act_key_inner):
# 说明为递归引用,被引用的地方仍然引用了其他地方
has_recursive_reference = True
# 解析后引用放到result_act中,但是需要下轮大循环才能使用
result_act[act_key_inner.replace(ref_value, act_key[0:-5], 1)] = result_act[act_key_inner]
replace_sucessful = True
if replace_sucessful:
del result_act[act_key]
else:
print act_key
# 区分数组比较方式,2种方法进行比较
if is_disorder_array:
# 无序比较数组,验证期望数据在实际中是否存在
for exp_key in result_exp:
# 直接验证存在的值是否相等,提高数组有序时的比较效率
if exp_key in result_act:
if result_exp[exp_key] == result_act[exp_key]:
continue
# 进行数组的无序比较
if not __disorder_array_assert(None, exp_key, result_exp[exp_key], result_act):
# 无序比较存在不同,记录结果
if exp_key in result_act:
result[exp_key] = __merge_values(result_exp[exp_key], result_act[exp_key])
else:
result[exp_key] = __merge_values(result_exp[exp_key], NOT_EXIST_FLAG)
else:
# 有序比较数组,验证期望数据在实际中是否存在
for exp_key in result_exp:
if exp_key not in result_act:
result[exp_key] = __merge_values(result_exp[exp_key], NOT_EXIST_FLAG)
elif result_exp[exp_key] != result_act[exp_key]:
result[exp_key] = __merge_values(result_exp[exp_key], result_act[exp_key])
# 上面的比较是判断了期望包含的在实际结果的情况,相等判断则需要再添加不再期望中却在实际结果中的情况
if is_full_compare:
# 验证是否有实际数据在期望中不存在
# fixme: 此处不支持数组无序比较
for act_key in result_act:
if act_key not in result_exp:
result[act_key] = __merge_values(NOT_EXIST_FLAG, result_act[act_key])
return result
def __get_key_prefix_dict(key_prefix, result, json_obj):
if not isinstance(result, dict):
print "输入参数错误:存储结果用的变量result类型错误." # todo:
return None
if isinstance(json_obj, dict):
# json串的情况,循环调用该Json串的所有键值
if not json_obj:
# dict为空,类似:"key":{},出口
result[key_prefix] = "{}"
return
next_key_prefix = "" if (key_prefix is None) else (key_prefix + KEY_PREFIX_SEPARATOR)
for (k, v) in json_obj.items():
__get_key_prefix_dict(next_key_prefix + k, result, v)
elif isinstance(json_obj, list):
# json数组情况,循环调用该json数组的所有元素
if not json_obj:
# list为空,类似:"key":[],出口
result[key_prefix] = "[]"
return
next_key_prefix = "" if (key_prefix is None) else (key_prefix + KEY_PREFIX_SEPARATOR)
for i in range(0, len(json_obj)):
__get_key_prefix_dict(next_key_prefix + "[" + str(i) + "]", result, json_obj[i])
else:
# 基本元素,即递归函数出口,得到一个结果放入结果dict
result[key_prefix] = json_obj
def __disorder_array_assert(key_prefix, exp_key, exp_value, result_act):
array_begin = exp_key.find("[")
if array_begin < 0:
# 递归出口:不含有数组,直接进行比较即可
full_key = ("" if key_prefix is None else key_prefix) + exp_key
if full_key in result_act:
if exp_value == result_act[full_key]:
return True
else:
return False
else:
return False
else:
# 将最上层的数组循环进行处理,递归调用求解
array_end = exp_key.find("]")
if array_end < array_begin:
print "ERROR" # todo:
return None
array_prefix = ("" if key_prefix is None else key_prefix) + exp_key[0:array_begin + 1]
array_postfix = exp_key[array_end:]
i = 0
while True:
array_full = array_prefix + str(i) + array_postfix
if array_full in result_act:
# 期望中存在对应的数组原因,递归调用进行比较
if __disorder_array_assert(array_prefix + str(i) + "]", array_postfix[1:], exp_value, result_act):
return True
else:
# 数组越界
return False
i += 1
if __name__ == '__main__':
(status, output1) = commands.getstatusoutput('cat ./input1.txt')
(status, output2) = commands.getstatusoutput('cat ./input2.txt')
list_out1 = output1.split("\n")
list_out2 = output2.split("\n")
commands.getstatusoutput('echo "" > ./json_diff_result.txt; echo "" > ./json_diff_exception.txt; echo "" > ./json_diff_retry_data.txt')
f = file("./json_diff_result.txt", "a+")
f_retry1 = file("./json_diff_retry_input1.txt", "a+")
f_retry2 = file("./json_diff_retry_input2.txt", "a+")
f_e = file("./json_diff_exception.txt", "a+")
f_log = file("./json_diff_log.txt", "a+")
i = 0
lines_count = len(list_out1)
for str_line in list_out1:
try:
begin = datetime.datetime.now()
# 0-null, 1-input1, 2-input2
list_col = []
list_col.append("null")
list_col.append(str_line)
list_col.append(list_out2[i])
# merge input1 and input2
str_line = "null###" + str_line + "###" + list_out2[i]
i += 1
now_line = str(i) + "/" + str(lines_count) + ": "
if list_col[1] == list_col[2]:
end = datetime.datetime.now()
print now_line + "same (" + str(end - begin) + ")"
f_log.writelines("\n=========================\n")
f_log.writelines(list_col[1])
f_log.writelines("\n=====\n")
f_log.writelines(list_col[2])
continue
# rst = json_assert(list_col[1], list_col[2], ["priceInfo.specialMinPriceMap","updatedCombinedCodebase","hotelPrice.localTimeupdatedCombinedCodebase","hotelPrice.localTime"], ["salesInfos", "bookingTime"], [], True, True)
rst = json_assert(list_col[1], list_col[2], ["priceInfo.specialMinPriceMap", "updatedCombinedCodebase",
"hotelPrice.localTimeupdatedCombinedCodebase",
"hotelPrice.localTime"], [], [], True, True, False)
if rst:
# 记入文件
f.writelines("\n=========================\n")
f.writelines(json.dumps(rst))
f.writelines("\n=====\n")
f.writelines(list_col[1])
f.writelines("\n=====\n")
f.writelines(list_col[2])
f_retry1.writelines(list_col[1])
f_retry1.writelines("\n")
f_retry2.writelines(list_col[2])
f_retry2.writelines("\n")
end = datetime.datetime.now()
print now_line + "---->Find Diff! (" + str(end - begin) + ")"
else:
end = datetime.datetime.now()
print now_line + "same (" + str(end - begin) + ")"
f_log.writelines("\n=========================\n")
f_log.writelines(list_col[1])
f_log.writelines("\n=====\n")
f_log.writelines(list_col[2])
except Exception, e:
# 记入文件
f_e.writelines("\n=========================\n")
f_e.writelines(e)
f_e.writelines("\n=====\n")
f_e.writelines(traceback.format_exc())
f_e.writelines("\n=====\n")
f_e.writelines(list_col[1])
f_e.writelines("\n=====\n")
f_e.writelines(list_col[2])
print now_line + "====>Exception!"
3. 将多个网址URL或本地路径文件的Json内容进行嵌套合并
将多个网址URL或本地路径文件的Json内容进行嵌套合并。当List的元素为dict类型时,特别处理:如果dict元素的字段“key”相同,则认为其相同,从而进行替换而不是添加。
调用 ./<scriptName>.py [input.txt] [output.txt] 会处理 input.txt里的所有网站URL或本地路径,并输出到output.txt中。
#! /usr/bin/env python3
# pip install deepmerge
from deepmerge import Merger
import datetime
import json
import sys
import re
import requests
from pathlib import Path
# 定义常量
INPUT_FILE_PATH = "input.txt"
OUTPUT_FILE_PATH = "output.txt"
def remove_comments_from_string(input_string):
# 使用正则表达式替换所有以 // 或 # 等行注释符开头直到行末的内容
# r'//[^\n]*' 匹配以 // 开头直到行末的内容
# re.MULTILINE 使得 ^ 和 $ 分别匹配每一行的开始和结束
input_string = re.sub(r'^[ ]*//[^\n]*', '', input_string, flags=re.MULTILINE)
input_string = re.sub(r'^[ ]*#[^\n]*', '', input_string, flags=re.MULTILINE)
input_string = re.sub(r'^[ ]*/\*.*?\*/', '', input_string, flags=re.DOTALL)
return input_string
def is_json(content):
"""检查内容是否为有效的 JSON 字符串"""
try:
json.loads(content)
except ValueError:
return False
return True
def get_local_file_content(file_path):
"""读取本地文件内容"""
try:
with open(file_path, 'r') as file:
content = file.read()
print(f"Read local file: {file_path}")
return content
except Exception as e:
print(f"Error reading local file {file_path}: {e}")
return None
def get_url_content(url, timeout=10):
"""通过 URL 获取内容,并设置超时时间"""
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status() # 检查 HTTP 请求是否成功
content = response.text
print(f"Fetched URL: {url}")
return content
except requests.Timeout as e:
print(f"Request timed out for URL {url}: {e}")
return None
except requests.RequestException as e:
print(f"Error fetching URL {url}: {e}")
return None
def process_input_file(input_file_path=INPUT_FILE_PATH):
"""处理输入文件"""
results = []
try:
with open(input_file_path, 'r') as input_file:
for line in input_file:
trimmed_line = line.strip()
print(f"Processing line: {trimmed_line}")
if trimmed_line.startswith('/') or trimmed_line.startswith('.'):
content = get_local_file_content(trimmed_line)
elif trimmed_line.startswith('http'):
content = get_url_content(trimmed_line)
else:
print("Line does not start with '/' or 'http', skipping.")
continue
if content is not None:
# 去除注释
content = remove_comments_from_string(content)
# 去除换行符,以防有些字段值内部使用导致解析非法
content = content.replace("\n", "").replace("\r", "")
#print(content)
if content is not None and is_json(content):
parsed_dict = json.loads(content)
results.append(parsed_dict)
print("Parsed JSON to dict successfully.")
else:
print("Content is not valid JSON, skipping.")
return results
except FileNotFoundError:
print(f"The file {input_file_path} was not found.")
except Exception as e:
print(f"An error occurred while processing the file: {e}")
def custom_list_merge(merger, path, list1, list2):
# 检查列表中的元素是否为字典类型
if all(isinstance(item, dict) for item in list1 + list2):
# 创建一个字典来存储 key 或 id 对应的字典
key_id_dict = {}
# 字段数组,用于查找唯一的标识符
identifier_fields = ['key', 'id', 'name']
for item in list1:
# 查找第一个存在的标识符字段
identifier = next((item.get(field) for field in identifier_fields if item.get(field)), None)
if identifier is not None:
key_id_dict[identifier] = item
else:
# 如果没有找到标识符字段,则直接添加
key_id_dict[id(item)] = item
# 合并 list2 中的字典
for item in list2:
# 查找第一个存在的标识符字段
identifier = next((item.get(field) for field in identifier_fields if item.get(field)), None)
if identifier is not None:
if identifier in key_id_dict:
# 如果标识符已经存在,则更新字典
key_id_dict[identifier].update(item)
else:
# 如果标识符不存在,则添加字典
key_id_dict[identifier] = item
else:
# 如果没有找到标识符字段,则直接添加
key_id_dict[id(item)] = item
# 将字典转换回列表
merged_list = list(key_id_dict.values())
return merged_list
else:
# 如果列表中的元素不是字典类型,则简单地合并列表
return list1 + list2
# 创建一个自定义的合并策略
custom_merger = Merger(
[
# 列表合并策略
(list, custom_list_merge),
# 集合合并策略
(set, "union"),
# 元组合并策略
(tuple, "concat"),
# 字典合并策略
(dict, "merge"),
],
# 当遇到不可合并的类型时,使用覆盖策略
["override"],
# 当遇到不可合并的类型时,使用覆盖策略
["override"]
)
# 示例字典
dict1 = {
"name": "Alice",
"age": 30,
"details": {
"height": 165,
"weight": 60,
"hobbies": [{"hours": 2}, {"hours": 1}],
"hobbiesWithKey": [{"key": "reading", "hours": 2}, {"key": "painting", "hours": 1}],
"numbers": [1, 2, 3]
}
}
dict2 = {
"age": 35,
"details": {
"height": 170,
"skills": ["coding", "cooking"],
"hobbies": [{"hours": 3}, {"hours": 1}],
"hobbiesWithKey": [{"key": "reading", "hours": 3}, {"key": "swimming", "hours": 1}],
"numbers": [4, 5, 6]
}
}
# 使用自定义合并策略
#merged_dict = custom_merger.merge(dict1, dict2)
#print(merged_dict)
def merge_dicts(dicts_list):
"""合并列表中的所有字典"""
merged_dict = {}
for d in dicts_list:
merged_dict = custom_merger.merge(merged_dict, d)
return merged_dict
def write_json_to_file(data, file_path=OUTPUT_FILE_PATH):
"""将数据写入 JSON 文件"""
try:
with open(file_path, 'w') as output_file:
json.dump(data, output_file, indent=4)
print(f"Data written to JSON file: {file_path}")
except Exception as e:
print(f"Error writing data to JSON file {file_path}: {str(e)}")
# 主函数
if __name__ == "__main__":
# 从命令行参数获取输入和输出文件路径
input_file_path = INPUT_FILE_PATH
# 使用当前时间生成默认输出文件名
current_time = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
output_file_path = current_time + "-" + OUTPUT_FILE_PATH
# 如果提供了命令行参数,则使用它们
if len(sys.argv) > 1:
input_file_path = sys.argv[1]
if len(sys.argv) > 2:
output_file_path = sys.argv[2]
result_dicts = process_input_file(input_file_path)
#print("All parsed dictionaries:", result_dicts)
# 合并所有字典
final_merged_dict = merge_dicts(result_dicts)
#print("Merged dictionary:", final_merged_dict)
# 写入 JSON 文件
write_json_to_file(final_merged_dict, output_file_path)
4. 提取Json数据的结构
#! /usr/bin/env python3
import json
def extract_structure(data, n=1):
"""
获取字典的结构,对于字典类型的值保留所有的键值对,
对于数组类型的值保留数组类型,但只保留前n个元素。
:param data: 输入的数据(可以是字典或列表)
:param n: 保留的数组元素数量,默认为1
:return: 处理后的数据结构
"""
if isinstance(data, dict):
# 如果是字典类型,则递归处理每个键值对
return {key: extract_structure(value, n) for key, value in data.items()}
elif isinstance(data, list):
# 如果是列表类型,则保留前n个元素,并保持列表类型
trimmed_list = data[:n]
return [extract_structure(item, n) for item in trimmed_list]
else:
# 如果既不是字典也不是列表,则直接返回该值
return data
def read_and_process_json_file(file_path, n=1):
"""
从指定文件中读取 JSON 字符串内容,并使用 extract_structure 函数解析内容。
:param file_path: 文件路径
:param n: 保留的数组元素数量,默认为1
:return: 解析后的数据结构
"""
with open(file_path, 'r') as file:
json_data = json.load(file)
processed_data = extract_structure(json_data, n)
return processed_data
# 示例使用
file_path = 'input.json' # 请确保替换为实际文件路径
# n定义每个数组输出的示例元素数量
result = read_and_process_json_file(file_path, n=2)
# 将处理后的字典转换为美化格式的 JSON 字符串并输出
formatted_json = json.dumps(result, indent=4, ensure_ascii=False)
print(formatted_json)
5. 支持多种常见预处理的嵌套合并Json内容的脚本
#! /usr/bin/env python3
# pip install deepmerge
from deepmerge import Merger
import datetime
import json
import sys
import re
import requests
from pathlib import Path
# 定义常量
INPUT_FILE_PATH = "input.txt"
OUTPUT_FILE_PATH = "output.txt"
def remove_comments_from_string(input_string):
# 使用正则表达式替换所有以 // 或 # 等行注释符开头直到行末的内容
# r'//[^\n]*' 匹配以 // 开头直到行末的内容
# re.MULTILINE 使得 ^ 和 $ 分别匹配每一行的开始和结束
input_string = re.sub(r'^[ ]*//[^\n]*', '', input_string, flags=re.MULTILINE)
input_string = re.sub(r'^[ ]*#[^\n]*', '', input_string, flags=re.MULTILINE)
input_string = re.sub(r'^[ ]*/\*.*?\*/', '', input_string, flags=re.DOTALL)
return input_string
def is_json(content):
"""检查内容是否为有效的 JSON 字符串"""
try:
json.loads(content)
except ValueError:
return False
return True
def get_local_file_content(file_path):
"""读取本地文件内容"""
try:
with open(file_path, 'r') as file:
content = file.read()
print(f"Read local file: {file_path}")
return content
except Exception as e:
print(f"Error reading local file {file_path}: {e}")
return None
def get_url_content(url, timeout=10):
"""通过 URL 获取内容,并设置超时时间"""
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status() # 检查 HTTP 请求是否成功
# 获取 Content-Type 头中的 charset 参数
content_type = response.headers.get('Content-Type', '')
# 如果 Content-Type 头中不携带 charset 参数,默认使用 UTF-8 编码
if 'charset=' not in content_type:
content = response.content.decode('utf-8')
else:
# 如果 Content-Type 头中携带 charset 参数,使用 requests 自动处理
content = response.text
print(f"Fetched URL: {url}")
return content
except requests.Timeout as e:
print(f"Request timed out for URL {url}: {e}")
return None
except requests.RequestException as e:
print(f"Error fetching URL {url}: {e}")
return None
def detect_encoding(s):
# 尝试 UTF-8 编码
try:
content_bytes = s.encode('utf-8')
return 'utf-8'
except UnicodeEncodeError:
pass
# 尝试 GBK 编码
try:
content_bytes = s.encode('gbk')
return 'gbk'
except UnicodeEncodeError:
pass
# 如果以上都不行,返回 None
return None
def process_input_file(input_file_path=INPUT_FILE_PATH):
"""处理输入文件"""
results = {}
try:
with open(input_file_path, 'r') as input_file:
for line in input_file:
trimmed_line = line.strip()
print(f"Processing line: {trimmed_line}")
if trimmed_line.startswith('/') or trimmed_line.startswith('.'):
content = get_local_file_content(trimmed_line)
elif trimmed_line.startswith('http'):
content = get_url_content(trimmed_line)
else:
print("Line does not start with '/' or 'http', skipping.")
continue
if content is not None:
# 去除注释
content = remove_comments_from_string(content)
# 去除换行符,以防有些字段值内部使用导致解析非法
content = content.replace("\n", "").replace("\r", "")
#print(content)
if content is not None and is_json(content):
# 检测 content 的编码
encoding = detect_encoding(content)
# 根据检测到的编码解码 content (得到 Unicode 字符串)
if encoding == 'gbk':
content = content.encode('gbk').decode('gbk')
else:
content = content.encode('utf-8').decode('utf-8')
parsed_dict = json.loads(content)
results[trimmed_line] = parsed_dict
print("Parsed JSON to dict successfully.")
else:
print("Content is not valid JSON, skipping.")
return results
except FileNotFoundError:
print(f"The file {input_file_path} was not found.")
except Exception as e:
print(f"An error occurred while processing the file: {e}")
def custom_list_merge(merger, path, list1, list2):
# 检查列表中的元素是否为字典类型
if all(isinstance(item, dict) for item in list1 + list2):
# 创建一个字典来存储 key 或 id 对应的字典
key_id_dict = {}
# 字段数组,用于查找唯一的标识符
identifier_fields = ['key', 'id', 'name']
for item in list1:
# 查找第一个存在的标识符字段
identifier = next((item.get(field) for field in identifier_fields if item.get(field)), None)
if identifier is not None:
key_id_dict[identifier] = item
else:
# 如果没有找到标识符字段,则直接添加
key_id_dict[id(item)] = item
# 合并 list2 中的字典
for item in list2:
# 查找第一个存在的标识符字段
identifier = next((item.get(field) for field in identifier_fields if item.get(field)), None)
if identifier is not None:
if identifier in key_id_dict:
# 如果标识符已经存在,则更新字典
key_id_dict[identifier].update(item)
else:
# 如果标识符不存在,则添加字典
key_id_dict[identifier] = item
else:
# 如果没有找到标识符字段,则直接添加
key_id_dict[id(item)] = item
# 将字典转换回列表
merged_list = list(key_id_dict.values())
return merged_list
else:
# 如果列表中的元素不是字典类型,则union方式合并不重复项
unique_items = set(list1).union(set(list2))
return list(unique_items)
# 创建一个自定义的合并策略
custom_merger = Merger(
[
# 列表合并策略
(list, custom_list_merge),
# 集合合并策略
(set, "union"),
# 元组合并策略
(tuple, "concat"),
# 字典合并策略
(dict, "merge"),
],
# 当遇到不可合并的类型时,使用覆盖策略
["override"],
# 当遇到不可合并的类型时,使用覆盖策略
["override"]
)
# 示例字典
dict1 = {
"name": "Alice",
"age": 30,
"details": {
"height": 165,
"weight": 60,
"hobbies": [{"hours": 2}, {"hours": 1}],
"hobbiesWithKey": [{"key": "reading", "hours": 2}, {"key": "painting", "hours": 1}],
"numbers": [1, 2, 3]
}
}
dict2 = {
"age": 35,
"details": {
"height": 170,
"skills": ["coding", "cooking"],
"hobbies": [{"hours": 3}, {"hours": 1}],
"hobbiesWithKey": [{"key": "reading", "hours": 3}, {"key": "swimming", "hours": 1}],
"numbers": [4, 5, 6]
}
}
# 使用自定义合并策略
#merged_dict = custom_merger.merge(dict1, dict2)
#print(merged_dict)
def merge_dicts(dicts_list):
"""合并列表中的所有字典"""
merged_dict = {}
for d in dicts_list:
merged_dict = custom_merger.merge(merged_dict, d)
return merged_dict
def write_json_to_file(data, file_path=OUTPUT_FILE_PATH):
"""将数据写入 JSON 文件"""
try:
with open(file_path, 'w', encoding='utf-8') as output_file:
json.dump(data, output_file, indent=4, ensure_ascii=False)
print(f"Data written to JSON file: {file_path}")
except Exception as e:
print(f"Error writing data to JSON file {file_path}: {str(e)}")
def flatten_video_content(d):
"""
如果字典 d 包含 "video" 键,则将其内容移至顶层字典并删除 "video" 键。
:param d: 输入字典
"""
if "video" in d:
# 将 "video" 下的内容移动到顶层字典
for key, value in d["video"].items():
d[key] = value
# 删除 "video" 键
del d["video"]
def rename_keys(d, pairs):
"""
根据键值对字典 pairs 重命名字典 d 中的键。
:param d: 主字典
:param pairs: 键值对字典,用于重命名 d 中的键
"""
for old_key, new_key in pairs.items():
if old_key in d:
# 保存旧键的值
value = d.pop(old_key)
# 将值赋给新键
d[new_key] = value
def process_spider_value(url, spider_value):
# 如果url为本地文件则不做额外处理
if url.startswith((".", "/")):
return spider_value
# 检查 spider_value 是否需要拼接处理
if not spider_value.startswith((".", "/")):
return spider_value
# 分离 URL 的基地址和路径
base_url = url.split("/", 1)[0] # 获取协议和域名部分
# 根据 spider_value 的不同前缀进行处理
if spider_value.startswith("."):
# 保留除最后一个 '/' 以外的所有路径
path = url[len(base_url):].rstrip("/").rsplit("/", 1)[0]
# 将 '.' 替换为 '/'
processed_spider_value = spider_value.replace(".", "/", 1)
else: # spider_value.startswith("/")
# 保留 URL 的主机部分
path = ""
processed_spider_value = spider_value.lstrip("/")
# 拼接新的 URL
new_url = base_url + path + "/" + processed_spider_value.lstrip("/")
return new_url
def process_spider(url, d):
"""
处理输入字典 d,如果 d 有 "spider" 字段,则:
如果 d 有 "sites" 字段且为数组,则将它的数组中所有 dict 类型的元素添加一个 "jar" 字段且其值设置为 d["spider"]。
如果已经存在 "jar" 字段且不为空字符串,则跳过处理。
:param d: 输入的字典
"""
# 检查是否有 "spider" 字段
if "spider" in d:
spider_value = d["spider"]
# 对spider地址进行预处理
spider_value = process_spider_value(url, spider_value)
# 检查是否有 "sites" 字段且为列表
if "sites" in d and isinstance(d["sites"], list):
sites = d["sites"]
# 遍历 "sites" 字段中的每个元素
for site in sites:
# 检查元素是否为字典类型
if isinstance(site, dict):
# 检查是否已有 "jar" 字段且不为空字符串
if "jar" not in site or site["jar"] == "":
site["jar"] = spider_value
else:
site["jar"] = process_spider_value(url, site["jar"])
def add_original_url(url, d):
# 检查URL是否以点或斜线开头,如果不是则继续处理
#if not url.startswith('.') and not url.startswith('/'):
# 如果'originalUrl'不存在于字典d中,则创建一个新的空列表
if 'originalUrl' not in d:
d['originalUrl'] = []
# 检查'originalUrl'是否已经是字符串类型
if isinstance(d['originalUrl'], str):
# 将原来的字符串转换为列表
d['originalUrl'] = [d['originalUrl'], url]
else:
# 如果'originalUrl'已经是列表,则直接追加新的URL
d['originalUrl'].append(url)
def preprocess_result(result):
"""
对结果进行预处理函数。
:param result: dict内容为url和对应的字典数组
"""
rename_keys_dict = {
"iptv": "lives",
"channel": "lives",
"analyze": "parses"
}
for key, value in result.items():
flatten_video_content(value)
add_original_url(key, value)
if "originalUrl" in value and isinstance(value["originalUrl"], list) and \
value["originalUrl"] and value["originalUrl"][0] != "":
process_spider(value["originalUrl"][0], value)
else:
process_spider(key, value)
rename_keys(value, rename_keys_dict)
# 主函数
if __name__ == "__main__":
# 从命令行参数获取输入和输出文件路径
input_file_path = INPUT_FILE_PATH
# 使用当前时间生成默认输出文件名
current_time = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
output_file_path = current_time + "-" + OUTPUT_FILE_PATH
# 如果提供了命令行参数,则使用它们
if len(sys.argv) > 1:
input_file_path = sys.argv[1]
if len(sys.argv) > 2:
output_file_path = sys.argv[2]
result = process_input_file(input_file_path)
result_dicts = list(result.values())
#print("All parsed dictionaries:", result_dicts)
# 预处理所有数据
preprocess_result(result)
# 合并所有字典
final_merged_dict = merge_dicts(result_dicts)
#print("Merged dictionary:", final_merged_dict)
# 写入 JSON 文件
write_json_to_file(final_merged_dict, output_file_path)