添加目录和文件
This commit is contained in:
parent
82c1e3cb2a
commit
0ed998d086
|
|
@ -0,0 +1,207 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Awesome-pyecharts</title>
|
||||||
|
<script type="text/javascript" src="https://assets.pyecharts.org/assets/v5/echarts.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
<div id="4754d7e5d0ec43a598727be636d4512d" class="chart-container" style="width:500px; height:350px; "></div>
|
||||||
|
<script>
|
||||||
|
var chart_4754d7e5d0ec43a598727be636d4512d = echarts.init(
|
||||||
|
document.getElementById('4754d7e5d0ec43a598727be636d4512d'), 'white', {renderer: 'canvas'});
|
||||||
|
var option_4754d7e5d0ec43a598727be636d4512d = {
|
||||||
|
"animation": true,
|
||||||
|
"animationThreshold": 2000,
|
||||||
|
"animationDuration": 1000,
|
||||||
|
"animationEasing": "cubicOut",
|
||||||
|
"animationDelay": 0,
|
||||||
|
"animationDurationUpdate": 300,
|
||||||
|
"animationEasingUpdate": "cubicOut",
|
||||||
|
"animationDelayUpdate": 0,
|
||||||
|
"aria": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"color": [
|
||||||
|
"#94BFFF",
|
||||||
|
"#6AA1FF",
|
||||||
|
"#4080FF",
|
||||||
|
"#165DFF",
|
||||||
|
"#0E42D2",
|
||||||
|
"#072CA6"
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"type": "pie",
|
||||||
|
"name": "age",
|
||||||
|
"colorBy": "data",
|
||||||
|
"legendHoverLink": true,
|
||||||
|
"selectedMode": false,
|
||||||
|
"selectedOffset": 10,
|
||||||
|
"clockwise": false,
|
||||||
|
"startAngle": 90,
|
||||||
|
"minAngle": 0,
|
||||||
|
"minShowLabelAngle": 0,
|
||||||
|
"avoidLabelOverlap": true,
|
||||||
|
"stillShowZeroSum": true,
|
||||||
|
"percentPrecision": 2,
|
||||||
|
"showEmptyCircle": true,
|
||||||
|
"emptyCircleStyle": {
|
||||||
|
"color": "lightgray",
|
||||||
|
"borderColor": "#000",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderType": "solid",
|
||||||
|
"borderDashOffset": 0,
|
||||||
|
"borderCap": "butt",
|
||||||
|
"borderJoin": "bevel",
|
||||||
|
"borderMiterLimit": 10,
|
||||||
|
"opacity": 1
|
||||||
|
},
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "25~34",
|
||||||
|
"value": "2096"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "35~44",
|
||||||
|
"value": "1193"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "18~24",
|
||||||
|
"value": "1103"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "45~54",
|
||||||
|
"value": "1046"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "above54",
|
||||||
|
"value": "380"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "under18",
|
||||||
|
"value": "222"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"radius": [
|
||||||
|
"50%",
|
||||||
|
"75%"
|
||||||
|
],
|
||||||
|
"center": [
|
||||||
|
"50%",
|
||||||
|
"60%"
|
||||||
|
],
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"position": "outside",
|
||||||
|
"color": "#86909C",
|
||||||
|
"margin": 8,
|
||||||
|
"fontSize": "12px",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"formatter": "{b|{b}}\n{c|{c}}",
|
||||||
|
"rich": {
|
||||||
|
"b": {
|
||||||
|
"fontWeight": "bold"
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"lineHeight": 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labelLine": {
|
||||||
|
"show": true,
|
||||||
|
"showAbove": false,
|
||||||
|
"length": 15,
|
||||||
|
"length2": 15,
|
||||||
|
"smooth": false,
|
||||||
|
"minTurnAngle": 90,
|
||||||
|
"maxSurfaceAngle": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"legend": [
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
"25~34",
|
||||||
|
"35~44",
|
||||||
|
"18~24",
|
||||||
|
"45~54",
|
||||||
|
"above54",
|
||||||
|
"under18"
|
||||||
|
],
|
||||||
|
"selected": {},
|
||||||
|
"show": false,
|
||||||
|
"left": "center",
|
||||||
|
"top": "top",
|
||||||
|
"orient": "horizontal",
|
||||||
|
"align": "auto",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"itemWidth": 10,
|
||||||
|
"itemHeight": 10,
|
||||||
|
"inactiveColor": "#86909C",
|
||||||
|
"textStyle": {
|
||||||
|
"color": "#86909C",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"fontSize": "12px"
|
||||||
|
},
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"borderColor": "#ccc",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"pageButtonItemGap": 5,
|
||||||
|
"pageButtonPosition": "end",
|
||||||
|
"pageFormatter": "{current}/{total}",
|
||||||
|
"pageIconColor": "#2f4554",
|
||||||
|
"pageIconInactiveColor": "#aaa",
|
||||||
|
"pageIconSize": 15,
|
||||||
|
"animationDurationUpdate": 800,
|
||||||
|
"selector": false,
|
||||||
|
"selectorPosition": "auto",
|
||||||
|
"selectorItemGap": 7,
|
||||||
|
"selectorButtonGap": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tooltip": {
|
||||||
|
"show": false,
|
||||||
|
"trigger": "item",
|
||||||
|
"triggerOn": "mousemove|click",
|
||||||
|
"axisPointer": {
|
||||||
|
"type": "line"
|
||||||
|
},
|
||||||
|
"showContent": true,
|
||||||
|
"alwaysShowContent": false,
|
||||||
|
"showDelay": 0,
|
||||||
|
"hideDelay": 100,
|
||||||
|
"enterable": false,
|
||||||
|
"confine": false,
|
||||||
|
"appendToBody": false,
|
||||||
|
"transitionDuration": 0.4,
|
||||||
|
"textStyle": {
|
||||||
|
"fontSize": 14
|
||||||
|
},
|
||||||
|
"borderWidth": 0,
|
||||||
|
"padding": 5,
|
||||||
|
"order": "seriesAsc"
|
||||||
|
},
|
||||||
|
"title": [
|
||||||
|
{
|
||||||
|
"show": true,
|
||||||
|
"target": "blank",
|
||||||
|
"subtarget": "blank",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"textAlign": "auto",
|
||||||
|
"textVerticalAlign": "auto",
|
||||||
|
"triggerEvent": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
chart_4754d7e5d0ec43a598727be636d4512d.setOption(option_4754d7e5d0ec43a598727be636d4512d);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Awesome-pyecharts</title>
|
||||||
|
<script type="text/javascript" src="https://assets.pyecharts.org/assets/v5/echarts.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
<div id="7b7b30294d504414a9c9d2309c2caee7" class="chart-container" style="width:500px; height:350px; "></div>
|
||||||
|
<script>
|
||||||
|
var chart_7b7b30294d504414a9c9d2309c2caee7 = echarts.init(
|
||||||
|
document.getElementById('7b7b30294d504414a9c9d2309c2caee7'), 'white', {renderer: 'canvas'});
|
||||||
|
var option_7b7b30294d504414a9c9d2309c2caee7 = {
|
||||||
|
"animation": true,
|
||||||
|
"animationThreshold": 2000,
|
||||||
|
"animationDuration": 1000,
|
||||||
|
"animationEasing": "cubicOut",
|
||||||
|
"animationDelay": 0,
|
||||||
|
"animationDurationUpdate": 300,
|
||||||
|
"animationEasingUpdate": "cubicOut",
|
||||||
|
"animationDelayUpdate": 0,
|
||||||
|
"aria": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"color": [
|
||||||
|
"#4080FF",
|
||||||
|
"#165DFF"
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"type": "pie",
|
||||||
|
"name": "gender",
|
||||||
|
"colorBy": "data",
|
||||||
|
"legendHoverLink": true,
|
||||||
|
"selectedMode": false,
|
||||||
|
"selectedOffset": 10,
|
||||||
|
"clockwise": false,
|
||||||
|
"startAngle": 90,
|
||||||
|
"minAngle": 0,
|
||||||
|
"minShowLabelAngle": 0,
|
||||||
|
"avoidLabelOverlap": true,
|
||||||
|
"stillShowZeroSum": true,
|
||||||
|
"percentPrecision": 2,
|
||||||
|
"showEmptyCircle": true,
|
||||||
|
"emptyCircleStyle": {
|
||||||
|
"color": "lightgray",
|
||||||
|
"borderColor": "#000",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderType": "solid",
|
||||||
|
"borderDashOffset": 0,
|
||||||
|
"borderCap": "butt",
|
||||||
|
"borderJoin": "bevel",
|
||||||
|
"borderMiterLimit": 10,
|
||||||
|
"opacity": 1
|
||||||
|
},
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "male",
|
||||||
|
"value": "4331"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "female",
|
||||||
|
"value": "1709"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"radius": [
|
||||||
|
"50%",
|
||||||
|
"75%"
|
||||||
|
],
|
||||||
|
"center": [
|
||||||
|
"50%",
|
||||||
|
"60%"
|
||||||
|
],
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"position": "outside",
|
||||||
|
"color": "#86909C",
|
||||||
|
"margin": 8,
|
||||||
|
"fontSize": "12px",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"formatter": "{b|{b}}\n{c|{c}}",
|
||||||
|
"rich": {
|
||||||
|
"b": {
|
||||||
|
"fontWeight": "bold"
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"lineHeight": 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labelLine": {
|
||||||
|
"show": true,
|
||||||
|
"showAbove": false,
|
||||||
|
"length": 15,
|
||||||
|
"length2": 15,
|
||||||
|
"smooth": false,
|
||||||
|
"minTurnAngle": 90,
|
||||||
|
"maxSurfaceAngle": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"legend": [
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
"male",
|
||||||
|
"female"
|
||||||
|
],
|
||||||
|
"selected": {},
|
||||||
|
"show": false,
|
||||||
|
"left": "center",
|
||||||
|
"top": "top",
|
||||||
|
"orient": "horizontal",
|
||||||
|
"align": "auto",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"itemWidth": 10,
|
||||||
|
"itemHeight": 10,
|
||||||
|
"inactiveColor": "#86909C",
|
||||||
|
"textStyle": {
|
||||||
|
"color": "#86909C",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"fontSize": "12px"
|
||||||
|
},
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"borderColor": "#ccc",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"pageButtonItemGap": 5,
|
||||||
|
"pageButtonPosition": "end",
|
||||||
|
"pageFormatter": "{current}/{total}",
|
||||||
|
"pageIconColor": "#2f4554",
|
||||||
|
"pageIconInactiveColor": "#aaa",
|
||||||
|
"pageIconSize": 15,
|
||||||
|
"animationDurationUpdate": 800,
|
||||||
|
"selector": false,
|
||||||
|
"selectorPosition": "auto",
|
||||||
|
"selectorItemGap": 7,
|
||||||
|
"selectorButtonGap": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tooltip": {
|
||||||
|
"show": false,
|
||||||
|
"trigger": "item",
|
||||||
|
"triggerOn": "mousemove|click",
|
||||||
|
"axisPointer": {
|
||||||
|
"type": "line"
|
||||||
|
},
|
||||||
|
"showContent": true,
|
||||||
|
"alwaysShowContent": false,
|
||||||
|
"showDelay": 0,
|
||||||
|
"hideDelay": 100,
|
||||||
|
"enterable": false,
|
||||||
|
"confine": false,
|
||||||
|
"appendToBody": false,
|
||||||
|
"transitionDuration": 0.4,
|
||||||
|
"textStyle": {
|
||||||
|
"fontSize": 14
|
||||||
|
},
|
||||||
|
"borderWidth": 0,
|
||||||
|
"padding": 5,
|
||||||
|
"order": "seriesAsc"
|
||||||
|
},
|
||||||
|
"title": [
|
||||||
|
{
|
||||||
|
"show": true,
|
||||||
|
"target": "blank",
|
||||||
|
"subtarget": "blank",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"textAlign": "auto",
|
||||||
|
"textVerticalAlign": "auto",
|
||||||
|
"triggerEvent": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
chart_7b7b30294d504414a9c9d2309c2caee7.setOption(option_7b7b30294d504414a9c9d2309c2caee7);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Awesome-pyecharts</title>
|
||||||
|
<script type="text/javascript" src="https://assets.pyecharts.org/assets/v5/echarts.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
<div id="2e0448f1fb5e40528b7b67c41f22b854" class="chart-container" style="width:500px; height:350px; "></div>
|
||||||
|
<script>
|
||||||
|
var chart_2e0448f1fb5e40528b7b67c41f22b854 = echarts.init(
|
||||||
|
document.getElementById('2e0448f1fb5e40528b7b67c41f22b854'), 'white', {renderer: 'canvas'});
|
||||||
|
var option_2e0448f1fb5e40528b7b67c41f22b854 = {
|
||||||
|
"animation": true,
|
||||||
|
"animationThreshold": 2000,
|
||||||
|
"animationDuration": 1000,
|
||||||
|
"animationEasing": "cubicOut",
|
||||||
|
"animationDelay": 0,
|
||||||
|
"animationDurationUpdate": 300,
|
||||||
|
"animationEasingUpdate": "cubicOut",
|
||||||
|
"animationDelayUpdate": 0,
|
||||||
|
"aria": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"color": [
|
||||||
|
"#94BFFF",
|
||||||
|
"#6AA1FF",
|
||||||
|
"#4080FF",
|
||||||
|
"#165DFF",
|
||||||
|
"#0E42D2",
|
||||||
|
"#072CA6"
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"type": "pie",
|
||||||
|
"name": "genres",
|
||||||
|
"colorBy": "data",
|
||||||
|
"legendHoverLink": true,
|
||||||
|
"selectedMode": false,
|
||||||
|
"selectedOffset": 10,
|
||||||
|
"clockwise": false,
|
||||||
|
"startAngle": 90,
|
||||||
|
"minAngle": 0,
|
||||||
|
"minShowLabelAngle": 0,
|
||||||
|
"avoidLabelOverlap": true,
|
||||||
|
"stillShowZeroSum": true,
|
||||||
|
"percentPrecision": 2,
|
||||||
|
"showEmptyCircle": true,
|
||||||
|
"emptyCircleStyle": {
|
||||||
|
"color": "lightgray",
|
||||||
|
"borderColor": "#000",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderType": "solid",
|
||||||
|
"borderDashOffset": 0,
|
||||||
|
"borderCap": "butt",
|
||||||
|
"borderJoin": "bevel",
|
||||||
|
"borderMiterLimit": 10,
|
||||||
|
"opacity": 1
|
||||||
|
},
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "drama",
|
||||||
|
"value": "2260"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "comedy",
|
||||||
|
"value": "1854"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "action",
|
||||||
|
"value": "902"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sci-fi",
|
||||||
|
"value": "347"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "thriller",
|
||||||
|
"value": "162"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "others",
|
||||||
|
"value": "515"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"radius": [
|
||||||
|
"50%",
|
||||||
|
"75%"
|
||||||
|
],
|
||||||
|
"center": [
|
||||||
|
"50%",
|
||||||
|
"60%"
|
||||||
|
],
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"position": "outside",
|
||||||
|
"color": "#86909C",
|
||||||
|
"margin": 8,
|
||||||
|
"fontSize": "12px",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"formatter": "{b|{b}}\n{c|{c}}",
|
||||||
|
"rich": {
|
||||||
|
"b": {
|
||||||
|
"fontWeight": "bold"
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"lineHeight": 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labelLine": {
|
||||||
|
"show": true,
|
||||||
|
"showAbove": false,
|
||||||
|
"length": 15,
|
||||||
|
"length2": 15,
|
||||||
|
"smooth": false,
|
||||||
|
"minTurnAngle": 90,
|
||||||
|
"maxSurfaceAngle": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"legend": [
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
"drama",
|
||||||
|
"comedy",
|
||||||
|
"action",
|
||||||
|
"sci-fi",
|
||||||
|
"thriller",
|
||||||
|
"others"
|
||||||
|
],
|
||||||
|
"selected": {},
|
||||||
|
"show": false,
|
||||||
|
"left": "center",
|
||||||
|
"top": "top",
|
||||||
|
"orient": "horizontal",
|
||||||
|
"align": "auto",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"itemWidth": 10,
|
||||||
|
"itemHeight": 10,
|
||||||
|
"inactiveColor": "#86909C",
|
||||||
|
"textStyle": {
|
||||||
|
"color": "#86909C",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"fontSize": "12px"
|
||||||
|
},
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"borderColor": "#ccc",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"pageButtonItemGap": 5,
|
||||||
|
"pageButtonPosition": "end",
|
||||||
|
"pageFormatter": "{current}/{total}",
|
||||||
|
"pageIconColor": "#2f4554",
|
||||||
|
"pageIconInactiveColor": "#aaa",
|
||||||
|
"pageIconSize": 15,
|
||||||
|
"animationDurationUpdate": 800,
|
||||||
|
"selector": false,
|
||||||
|
"selectorPosition": "auto",
|
||||||
|
"selectorItemGap": 7,
|
||||||
|
"selectorButtonGap": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tooltip": {
|
||||||
|
"show": false,
|
||||||
|
"trigger": "item",
|
||||||
|
"triggerOn": "mousemove|click",
|
||||||
|
"axisPointer": {
|
||||||
|
"type": "line"
|
||||||
|
},
|
||||||
|
"showContent": true,
|
||||||
|
"alwaysShowContent": false,
|
||||||
|
"showDelay": 0,
|
||||||
|
"hideDelay": 100,
|
||||||
|
"enterable": false,
|
||||||
|
"confine": false,
|
||||||
|
"appendToBody": false,
|
||||||
|
"transitionDuration": 0.4,
|
||||||
|
"textStyle": {
|
||||||
|
"fontSize": 14
|
||||||
|
},
|
||||||
|
"borderWidth": 0,
|
||||||
|
"padding": 5,
|
||||||
|
"order": "seriesAsc"
|
||||||
|
},
|
||||||
|
"title": [
|
||||||
|
{
|
||||||
|
"show": true,
|
||||||
|
"target": "blank",
|
||||||
|
"subtarget": "blank",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"textAlign": "auto",
|
||||||
|
"textVerticalAlign": "auto",
|
||||||
|
"triggerEvent": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
chart_2e0448f1fb5e40528b7b67c41f22b854.setOption(option_2e0448f1fb5e40528b7b67c41f22b854);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Awesome-pyecharts</title>
|
||||||
|
<script type="text/javascript" src="https://assets.pyecharts.org/assets/v5/echarts.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
<div id="868ad9e67d3b4dc7b4a6749ac3409728" class="chart-container" style="width:500px; height:350px; "></div>
|
||||||
|
<script>
|
||||||
|
var chart_868ad9e67d3b4dc7b4a6749ac3409728 = echarts.init(
|
||||||
|
document.getElementById('868ad9e67d3b4dc7b4a6749ac3409728'), 'white', {renderer: 'canvas'});
|
||||||
|
var option_868ad9e67d3b4dc7b4a6749ac3409728 = {
|
||||||
|
"animation": true,
|
||||||
|
"animationThreshold": 2000,
|
||||||
|
"animationDuration": 1000,
|
||||||
|
"animationEasing": "cubicOut",
|
||||||
|
"animationDelay": 0,
|
||||||
|
"animationDurationUpdate": 300,
|
||||||
|
"animationEasingUpdate": "cubicOut",
|
||||||
|
"animationDelayUpdate": 0,
|
||||||
|
"aria": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"color": [
|
||||||
|
"#94BFFF",
|
||||||
|
"#6AA1FF",
|
||||||
|
"#4080FF",
|
||||||
|
"#165DFF",
|
||||||
|
"#0E42D2",
|
||||||
|
"#072CA6"
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"type": "pie",
|
||||||
|
"name": "occupation",
|
||||||
|
"colorBy": "data",
|
||||||
|
"legendHoverLink": true,
|
||||||
|
"selectedMode": false,
|
||||||
|
"selectedOffset": 10,
|
||||||
|
"clockwise": false,
|
||||||
|
"startAngle": 90,
|
||||||
|
"minAngle": 0,
|
||||||
|
"minShowLabelAngle": 0,
|
||||||
|
"avoidLabelOverlap": true,
|
||||||
|
"stillShowZeroSum": true,
|
||||||
|
"percentPrecision": 2,
|
||||||
|
"showEmptyCircle": true,
|
||||||
|
"emptyCircleStyle": {
|
||||||
|
"color": "lightgray",
|
||||||
|
"borderColor": "#000",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderType": "solid",
|
||||||
|
"borderDashOffset": 0,
|
||||||
|
"borderCap": "butt",
|
||||||
|
"borderJoin": "bevel",
|
||||||
|
"borderMiterLimit": 10,
|
||||||
|
"opacity": 1
|
||||||
|
},
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "college/grad student",
|
||||||
|
"value": "759"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "other",
|
||||||
|
"value": "711"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "executive/managerial",
|
||||||
|
"value": "679"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "academic/educator",
|
||||||
|
"value": "528"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "technician/engineer",
|
||||||
|
"value": "502"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "others",
|
||||||
|
"value": "2861"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"radius": [
|
||||||
|
"50%",
|
||||||
|
"75%"
|
||||||
|
],
|
||||||
|
"center": [
|
||||||
|
"50%",
|
||||||
|
"60%"
|
||||||
|
],
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"position": "outside",
|
||||||
|
"color": "#86909C",
|
||||||
|
"margin": 8,
|
||||||
|
"fontSize": "12px",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"formatter": "{b|{b}}\n{c|{c}}",
|
||||||
|
"rich": {
|
||||||
|
"b": {
|
||||||
|
"fontWeight": "bold"
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"lineHeight": 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labelLine": {
|
||||||
|
"show": true,
|
||||||
|
"showAbove": false,
|
||||||
|
"length": 15,
|
||||||
|
"length2": 15,
|
||||||
|
"smooth": false,
|
||||||
|
"minTurnAngle": 90,
|
||||||
|
"maxSurfaceAngle": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"legend": [
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
"college/grad student",
|
||||||
|
"other",
|
||||||
|
"executive/managerial",
|
||||||
|
"academic/educator",
|
||||||
|
"technician/engineer",
|
||||||
|
"others"
|
||||||
|
],
|
||||||
|
"selected": {},
|
||||||
|
"show": false,
|
||||||
|
"left": "center",
|
||||||
|
"top": "top",
|
||||||
|
"orient": "horizontal",
|
||||||
|
"align": "auto",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"itemWidth": 10,
|
||||||
|
"itemHeight": 10,
|
||||||
|
"inactiveColor": "#86909C",
|
||||||
|
"textStyle": {
|
||||||
|
"color": "#86909C",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"fontSize": "12px"
|
||||||
|
},
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"borderColor": "#ccc",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"pageButtonItemGap": 5,
|
||||||
|
"pageButtonPosition": "end",
|
||||||
|
"pageFormatter": "{current}/{total}",
|
||||||
|
"pageIconColor": "#2f4554",
|
||||||
|
"pageIconInactiveColor": "#aaa",
|
||||||
|
"pageIconSize": 15,
|
||||||
|
"animationDurationUpdate": 800,
|
||||||
|
"selector": false,
|
||||||
|
"selectorPosition": "auto",
|
||||||
|
"selectorItemGap": 7,
|
||||||
|
"selectorButtonGap": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tooltip": {
|
||||||
|
"show": false,
|
||||||
|
"trigger": "item",
|
||||||
|
"triggerOn": "mousemove|click",
|
||||||
|
"axisPointer": {
|
||||||
|
"type": "line"
|
||||||
|
},
|
||||||
|
"showContent": true,
|
||||||
|
"alwaysShowContent": false,
|
||||||
|
"showDelay": 0,
|
||||||
|
"hideDelay": 100,
|
||||||
|
"enterable": false,
|
||||||
|
"confine": false,
|
||||||
|
"appendToBody": false,
|
||||||
|
"transitionDuration": 0.4,
|
||||||
|
"textStyle": {
|
||||||
|
"fontSize": 14
|
||||||
|
},
|
||||||
|
"borderWidth": 0,
|
||||||
|
"padding": 5,
|
||||||
|
"order": "seriesAsc"
|
||||||
|
},
|
||||||
|
"title": [
|
||||||
|
{
|
||||||
|
"show": true,
|
||||||
|
"target": "blank",
|
||||||
|
"subtarget": "blank",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"textAlign": "auto",
|
||||||
|
"textVerticalAlign": "auto",
|
||||||
|
"triggerEvent": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
chart_868ad9e67d3b4dc7b4a6749ac3409728.setOption(option_868ad9e67d3b4dc7b4a6749ac3409728);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Awesome-pyecharts</title>
|
||||||
|
<script type="text/javascript" src="https://assets.pyecharts.org/assets/v5/echarts.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body >
|
||||||
|
<div id="048f27062959471185621056032705b8" class="chart-container" style="width:500px; height:350px; "></div>
|
||||||
|
<script>
|
||||||
|
var chart_048f27062959471185621056032705b8 = echarts.init(
|
||||||
|
document.getElementById('048f27062959471185621056032705b8'), 'white', {renderer: 'canvas'});
|
||||||
|
var option_048f27062959471185621056032705b8 = {
|
||||||
|
"animation": true,
|
||||||
|
"animationThreshold": 2000,
|
||||||
|
"animationDuration": 1000,
|
||||||
|
"animationEasing": "cubicOut",
|
||||||
|
"animationDelay": 0,
|
||||||
|
"animationDurationUpdate": 300,
|
||||||
|
"animationEasingUpdate": "cubicOut",
|
||||||
|
"animationDelayUpdate": 0,
|
||||||
|
"aria": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"color": [
|
||||||
|
"#94BFFF",
|
||||||
|
"#6AA1FF",
|
||||||
|
"#4080FF",
|
||||||
|
"#165DFF",
|
||||||
|
"#0E42D2",
|
||||||
|
"#072CA6"
|
||||||
|
],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"type": "pie",
|
||||||
|
"name": "zip",
|
||||||
|
"colorBy": "data",
|
||||||
|
"legendHoverLink": true,
|
||||||
|
"selectedMode": false,
|
||||||
|
"selectedOffset": 10,
|
||||||
|
"clockwise": false,
|
||||||
|
"startAngle": 90,
|
||||||
|
"minAngle": 0,
|
||||||
|
"minShowLabelAngle": 0,
|
||||||
|
"avoidLabelOverlap": true,
|
||||||
|
"stillShowZeroSum": true,
|
||||||
|
"percentPrecision": 2,
|
||||||
|
"showEmptyCircle": true,
|
||||||
|
"emptyCircleStyle": {
|
||||||
|
"color": "lightgray",
|
||||||
|
"borderColor": "#000",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderType": "solid",
|
||||||
|
"borderDashOffset": 0,
|
||||||
|
"borderCap": "butt",
|
||||||
|
"borderJoin": "bevel",
|
||||||
|
"borderMiterLimit": 10,
|
||||||
|
"opacity": 1
|
||||||
|
},
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "CA",
|
||||||
|
"value": "1100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NY",
|
||||||
|
"value": "464"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MN",
|
||||||
|
"value": "432"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TX",
|
||||||
|
"value": "311"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MA",
|
||||||
|
"value": "298"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "others",
|
||||||
|
"value": "3368"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"radius": [
|
||||||
|
"50%",
|
||||||
|
"75%"
|
||||||
|
],
|
||||||
|
"center": [
|
||||||
|
"50%",
|
||||||
|
"60%"
|
||||||
|
],
|
||||||
|
"label": {
|
||||||
|
"show": true,
|
||||||
|
"position": "outside",
|
||||||
|
"color": "#86909C",
|
||||||
|
"margin": 8,
|
||||||
|
"fontSize": "12px",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"formatter": "{b|{b}}\n{c|{c}}",
|
||||||
|
"rich": {
|
||||||
|
"b": {
|
||||||
|
"fontWeight": "bold"
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"lineHeight": 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labelLine": {
|
||||||
|
"show": true,
|
||||||
|
"showAbove": false,
|
||||||
|
"length": 15,
|
||||||
|
"length2": 15,
|
||||||
|
"smooth": false,
|
||||||
|
"minTurnAngle": 90,
|
||||||
|
"maxSurfaceAngle": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"legend": [
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
"CA",
|
||||||
|
"NY",
|
||||||
|
"MN",
|
||||||
|
"TX",
|
||||||
|
"MA",
|
||||||
|
"others"
|
||||||
|
],
|
||||||
|
"selected": {},
|
||||||
|
"show": false,
|
||||||
|
"left": "center",
|
||||||
|
"top": "top",
|
||||||
|
"orient": "horizontal",
|
||||||
|
"align": "auto",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"itemWidth": 10,
|
||||||
|
"itemHeight": 10,
|
||||||
|
"inactiveColor": "#86909C",
|
||||||
|
"textStyle": {
|
||||||
|
"color": "#86909C",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"fontFamily": "PingFang SC",
|
||||||
|
"fontSize": "12px"
|
||||||
|
},
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"borderColor": "#ccc",
|
||||||
|
"borderWidth": 0,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"pageButtonItemGap": 5,
|
||||||
|
"pageButtonPosition": "end",
|
||||||
|
"pageFormatter": "{current}/{total}",
|
||||||
|
"pageIconColor": "#2f4554",
|
||||||
|
"pageIconInactiveColor": "#aaa",
|
||||||
|
"pageIconSize": 15,
|
||||||
|
"animationDurationUpdate": 800,
|
||||||
|
"selector": false,
|
||||||
|
"selectorPosition": "auto",
|
||||||
|
"selectorItemGap": 7,
|
||||||
|
"selectorButtonGap": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tooltip": {
|
||||||
|
"show": false,
|
||||||
|
"trigger": "item",
|
||||||
|
"triggerOn": "mousemove|click",
|
||||||
|
"axisPointer": {
|
||||||
|
"type": "line"
|
||||||
|
},
|
||||||
|
"showContent": true,
|
||||||
|
"alwaysShowContent": false,
|
||||||
|
"showDelay": 0,
|
||||||
|
"hideDelay": 100,
|
||||||
|
"enterable": false,
|
||||||
|
"confine": false,
|
||||||
|
"appendToBody": false,
|
||||||
|
"transitionDuration": 0.4,
|
||||||
|
"textStyle": {
|
||||||
|
"fontSize": 14
|
||||||
|
},
|
||||||
|
"borderWidth": 0,
|
||||||
|
"padding": 5,
|
||||||
|
"order": "seriesAsc"
|
||||||
|
},
|
||||||
|
"title": [
|
||||||
|
{
|
||||||
|
"show": true,
|
||||||
|
"target": "blank",
|
||||||
|
"subtarget": "blank",
|
||||||
|
"padding": 5,
|
||||||
|
"itemGap": 10,
|
||||||
|
"textAlign": "auto",
|
||||||
|
"textVerticalAlign": "auto",
|
||||||
|
"triggerEvent": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
chart_048f27062959471185621056032705b8.setOption(option_048f27062959471185621056032705b8);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset=“UTF-8”>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="../stylesheet.css">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table style="width: 100%">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>分箱</th>
|
||||||
|
|
||||||
|
<th>拒绝规则</th>
|
||||||
|
|
||||||
|
<th>分箱逾期率</th>
|
||||||
|
|
||||||
|
<th>拒绝率</th>
|
||||||
|
|
||||||
|
<th>拒绝逾期率</th>
|
||||||
|
|
||||||
|
<th>累计逾期率</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[0, 350)</td>
|
||||||
|
|
||||||
|
<td><350</td>
|
||||||
|
|
||||||
|
<td>48.96</td>
|
||||||
|
|
||||||
|
<td>4.21</td>
|
||||||
|
|
||||||
|
<td>48.96</td>
|
||||||
|
|
||||||
|
<td>30.72</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[350, 400)</td>
|
||||||
|
|
||||||
|
<td><400</td>
|
||||||
|
|
||||||
|
<td>31.50</td>
|
||||||
|
|
||||||
|
<td>7.09</td>
|
||||||
|
|
||||||
|
<td>41.86</td>
|
||||||
|
|
||||||
|
<td>44.26</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[400, 450)</td>
|
||||||
|
|
||||||
|
<td><450</td>
|
||||||
|
|
||||||
|
<td>20.04</td>
|
||||||
|
|
||||||
|
<td>11.09</td>
|
||||||
|
|
||||||
|
<td>33.99</td>
|
||||||
|
|
||||||
|
<td>56.22</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[450, 500)</td>
|
||||||
|
|
||||||
|
<td><500</td>
|
||||||
|
|
||||||
|
<td>10.47</td>
|
||||||
|
|
||||||
|
<td>22.29</td>
|
||||||
|
|
||||||
|
<td>22.17</td>
|
||||||
|
|
||||||
|
<td>73.68</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[500, 550)</td>
|
||||||
|
|
||||||
|
<td><550</td>
|
||||||
|
|
||||||
|
<td>5.61</td>
|
||||||
|
|
||||||
|
<td>36.53</td>
|
||||||
|
|
||||||
|
<td>15.72</td>
|
||||||
|
|
||||||
|
<td>85.59</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[550, 600)</td>
|
||||||
|
|
||||||
|
<td><600</td>
|
||||||
|
|
||||||
|
<td>2.69</td>
|
||||||
|
|
||||||
|
<td>56.74</td>
|
||||||
|
|
||||||
|
<td>11.08</td>
|
||||||
|
|
||||||
|
<td>93.71</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[600, 650)</td>
|
||||||
|
|
||||||
|
<td><650</td>
|
||||||
|
|
||||||
|
<td>1.16</td>
|
||||||
|
|
||||||
|
<td>86.09</td>
|
||||||
|
|
||||||
|
<td>7.69</td>
|
||||||
|
|
||||||
|
<td>98.76</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[650, 1000)</td>
|
||||||
|
|
||||||
|
<td><1000</td>
|
||||||
|
|
||||||
|
<td>0.60</td>
|
||||||
|
|
||||||
|
<td>100.00</td>
|
||||||
|
|
||||||
|
<td>6.71</td>
|
||||||
|
|
||||||
|
<td>100.00</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
|
||||||
|
if (event.data.action === 'requestHeight') {
|
||||||
|
|
||||||
|
const iframeId = event.data.iframeId;
|
||||||
|
|
||||||
|
adjustIframeHeight(iframeId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
function adjustIframeHeight(iframeId) {
|
||||||
|
|
||||||
|
const height = document.documentElement.scrollHeight + 16;
|
||||||
|
|
||||||
|
console.log(height)
|
||||||
|
|
||||||
|
window.parent.postMessage({action: 'responseHeight', iframeId: iframeId, height: height}, '*');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset=“UTF-8”>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="../stylesheet.css">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table style="width: 100%">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>分箱</th>
|
||||||
|
|
||||||
|
<th>逾期样本数</th>
|
||||||
|
|
||||||
|
<th>还款样本数</th>
|
||||||
|
|
||||||
|
<th>WOE</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[0.0, 0.1)</td>
|
||||||
|
|
||||||
|
<td>1230</td>
|
||||||
|
|
||||||
|
<td>66078</td>
|
||||||
|
|
||||||
|
<td>-1.36</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[0.1, 0.2)</td>
|
||||||
|
|
||||||
|
<td>474</td>
|
||||||
|
|
||||||
|
<td>15366</td>
|
||||||
|
|
||||||
|
<td>-0.78</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[0.2, 0.5)</td>
|
||||||
|
|
||||||
|
<td>1253</td>
|
||||||
|
|
||||||
|
<td>23479</td>
|
||||||
|
|
||||||
|
<td>-0.27</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[0.5, 0.8)</td>
|
||||||
|
|
||||||
|
<td>1585</td>
|
||||||
|
|
||||||
|
<td>13759</td>
|
||||||
|
|
||||||
|
<td>0.47</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td>[0.8, inf)</td>
|
||||||
|
|
||||||
|
<td>5466</td>
|
||||||
|
|
||||||
|
<td>20520</td>
|
||||||
|
|
||||||
|
<td>1.29</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
|
||||||
|
if (event.data.action === 'requestHeight') {
|
||||||
|
|
||||||
|
const iframeId = event.data.iframeId;
|
||||||
|
|
||||||
|
adjustIframeHeight(iframeId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
function adjustIframeHeight(iframeId) {
|
||||||
|
|
||||||
|
const height = document.documentElement.scrollHeight + 16;
|
||||||
|
|
||||||
|
console.log(height)
|
||||||
|
|
||||||
|
window.parent.postMessage({action: 'responseHeight', iframeId: iframeId, height: height}, '*');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"szkt": {"token": "a62c56ace614a6546191d5af8ca8b1513cfaeaea7ce67d0a37de994ab6c2aa4e2a0b058e0da575ff376dd51dc19c5ad353ab2761cb6d9db4d521b83adeee2979b78f7ae70765b26985165b6266d084b75f2f918008966e72a116d8bca5ec4c7cecc5223f78fa47b4d40aa9cf5277a11b0b967ad06e84ef7c4acbc53ccdef936c062b2d037ae0dad8c29d50426b668ec349cc8c0099a0270e16f97d31e4f058bc086334468f88d934c7fd1464ed3800833d2f486dc06f0689b99abbb78a8ebf4a3877bd82d0dd765dc09b7a1594fa8849d51f59282a81048c52e82e8320d1ad042a6c307ca831647cba4356564704780f", "expired_timestamp": 1759201579.393386}}
|
||||||
|
|
@ -0,0 +1,992 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 导入模块
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sqlite3
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from email.parser import BytesParser
|
||||||
|
from email.policy import default
|
||||||
|
from email.utils import parsedate_to_datetime
|
||||||
|
from functools import wraps
|
||||||
|
from imaplib import IMAP4_SSL
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, Generator, Literal, Optional, Tuple, Union
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
import pandas
|
||||||
|
from pydantic import BaseModel, Field, HttpUrl, model_validator
|
||||||
|
from requests import Response, Session
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
from urllib3.util.retry import Retry
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
封装sqlalchemy,实现按照SQL查询
|
||||||
|
类和函数的区别
|
||||||
|
类作为对象的模板,定义了对象的结构和行为;而函数则作为实现特定功能或操作的代码块,提高了代码的可读性和可维护性。
|
||||||
|
使用方法:
|
||||||
|
with MySQLClient(database='data_analysis') as client:
|
||||||
|
dataframe = client.execute_query(sql='select * from {{}}')')
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLClient:
|
||||||
|
"""MySQL客户端"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
database,
|
||||||
|
host: str = "cdb-7z9lzx4y.cd.tencentcdb.com",
|
||||||
|
port: int = 10039,
|
||||||
|
username: str = "root",
|
||||||
|
password: str = "Te198752",
|
||||||
|
): # 默认为刘弼仁的腾讯云MySQL数据库
|
||||||
|
|
||||||
|
# 数据库登录密码安全编码
|
||||||
|
password = quote_plus(password)
|
||||||
|
|
||||||
|
# 构建数据库连接字符
|
||||||
|
connect_config = f"mysql+pymysql://{username}:{password}@{host}:{port}/{database}?charset=utf8"
|
||||||
|
|
||||||
|
# 创建MySQL引擎并连接数据库
|
||||||
|
self.engine = create_engine(
|
||||||
|
connect_config,
|
||||||
|
pool_size=5,
|
||||||
|
max_overflow=10,
|
||||||
|
pool_recycle=3600,
|
||||||
|
pool_pre_ping=True,
|
||||||
|
) # 连接池中保持打开连接的数量为5,额外连接数为10,连接1小时后重新回收重连,检查连接有效性
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""析构时自动关闭连接"""
|
||||||
|
|
||||||
|
if hasattr(self, "engine") and self.engine:
|
||||||
|
self.engine.dispose()
|
||||||
|
|
||||||
|
def execute_query(self, sql: str) -> pandas.DataFrame:
|
||||||
|
"""执行SQL查询并返回DATAFRAME"""
|
||||||
|
|
||||||
|
if not hasattr(self, "engine") or not self.engine:
|
||||||
|
raise ConnectionError("未创建数据库连接")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self.engine.connect() as connection:
|
||||||
|
dataframe = pandas.read_sql_query(
|
||||||
|
text(sql), connection, coerce_float=False
|
||||||
|
) # 不尝试将非整数数值转为浮点(维持DECIMAL)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
except:
|
||||||
|
connection.rollback()
|
||||||
|
raise RuntimeError("执行SQL查询并返回DATAFRAME发生其它异常")
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
封装urllib.request的相关操作,实现常用HTTPREQUEST
|
||||||
|
使用方法:
|
||||||
|
client = HTTPClient()
|
||||||
|
response = clinet.post(url)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TokenBucket:
|
||||||
|
|
||||||
|
def __init__(self, refill_rate, max_tokens):
|
||||||
|
"""令牌桶,基于令牌桶算法限制请求频率"""
|
||||||
|
|
||||||
|
# 填充令牌速率(个/秒)
|
||||||
|
self.refill_rate = refill_rate
|
||||||
|
# 令牌桶最大令牌数
|
||||||
|
self.max_tokens = max_tokens
|
||||||
|
# 令牌桶当前令牌数
|
||||||
|
self.tokens = max_tokens
|
||||||
|
# 上一次填充令牌时间戳(使用单调递增时间,单位为秒)
|
||||||
|
self.refill_timestamp = time.monotonic()
|
||||||
|
|
||||||
|
# 获取令牌
|
||||||
|
# noinspection PyMissingReturnStatement
|
||||||
|
def acquire(self) -> tuple[bool, float]:
|
||||||
|
|
||||||
|
with threading.Lock():
|
||||||
|
# 本次填充令牌时间戳
|
||||||
|
refill_timestamp = time.monotonic()
|
||||||
|
|
||||||
|
# 重新计算令牌桶中令牌数
|
||||||
|
self.tokens = min(
|
||||||
|
self.max_tokens,
|
||||||
|
self.tokens
|
||||||
|
+ self.refill_rate * (refill_timestamp - self.refill_timestamp),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.refill_timestamp = refill_timestamp
|
||||||
|
|
||||||
|
# 若令牌桶当前令牌数大于等于1则减少令牌
|
||||||
|
if self.tokens >= 1:
|
||||||
|
self.tokens -= 1
|
||||||
|
return True, 0.0
|
||||||
|
|
||||||
|
# 同时返回等待时间
|
||||||
|
return False, 0.2
|
||||||
|
|
||||||
|
|
||||||
|
# 将令牌桶以装饰函数封装为请求频率限制方法
|
||||||
|
def restrict(refill_rate=5, max_tokens=5):
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
|
||||||
|
# 初始化令牌桶
|
||||||
|
token_bucket = TokenBucket(refill_rate=refill_rate, max_tokens=max_tokens)
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
|
# 重试次数
|
||||||
|
retries = 0
|
||||||
|
|
||||||
|
# 若重试数小于等于最大重试次数,则循环检查是否允许请求
|
||||||
|
while retries <= 10:
|
||||||
|
|
||||||
|
success, wait_time = token_bucket.acquire()
|
||||||
|
|
||||||
|
# 若允许请求则返回嵌套函数,若不允许请求则等待
|
||||||
|
if success:
|
||||||
|
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
time.sleep(wait_time * 1.5**retries)
|
||||||
|
|
||||||
|
retries += 1
|
||||||
|
|
||||||
|
raise Exception("request too frequently")
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class RequestException(Exception):
|
||||||
|
"""请求异常"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, status: int = 400, code: int = 0, message: str = "request failed"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
:param status: 状态编码,默认为0
|
||||||
|
:param message: 错误信息,默认为RequestException
|
||||||
|
"""
|
||||||
|
self.status = status
|
||||||
|
self.code = code
|
||||||
|
self.message = message
|
||||||
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"请求发生异常({self.status}, {self.message})"
|
||||||
|
|
||||||
|
|
||||||
|
# 请求参数数据模型
|
||||||
|
class Arguments(BaseModel):
|
||||||
|
"""
|
||||||
|
:param url: 统一资源定位符,基于统一资源定位符校验器进行校验
|
||||||
|
:param params: 查询参数
|
||||||
|
:param headers: 请求头
|
||||||
|
:param data: 表单数据
|
||||||
|
:param json_data: JSON # 入参时使用别名,出参时根据BY_ALIAS=TRUE确定是否使用别名
|
||||||
|
:param files: 上传文件
|
||||||
|
:param stream: 是否启用流式传输
|
||||||
|
:param guid: 全局唯一标识
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 统一资源定位符
|
||||||
|
url: HttpUrl = Field(default=...)
|
||||||
|
# 查询参数
|
||||||
|
params: Optional[Dict] = Field(default=None)
|
||||||
|
# 请求头
|
||||||
|
headers: Optional[Dict] = Field(default=None)
|
||||||
|
# 表单数据
|
||||||
|
data: Optional[Dict] = Field(default=None)
|
||||||
|
# JSON
|
||||||
|
json_data: Optional[Dict] = Field(default=None, alias="json")
|
||||||
|
# 上传文件
|
||||||
|
files: Optional[
|
||||||
|
Dict[
|
||||||
|
str,
|
||||||
|
Union[
|
||||||
|
Tuple[str, bytes], Tuple[str, bytes, str], Tuple[str, bytes, str, dict]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
] = Field(default=None)
|
||||||
|
# 是否启用流式传输
|
||||||
|
stream: Optional[bool] = Field(default=None)
|
||||||
|
# 全局唯一标识
|
||||||
|
guid: Optional[str] = Field(default=None)
|
||||||
|
|
||||||
|
# 表单数据和JSON数据互斥
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_data(self):
|
||||||
|
if self.data and self.json_data:
|
||||||
|
raise ValueError("cannot use both data and json parameters simultaneously")
|
||||||
|
return self
|
||||||
|
|
||||||
|
# 上传文件和启用流式传输互斥
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_files(self):
|
||||||
|
if self.files and self.stream:
|
||||||
|
raise ValueError(
|
||||||
|
"cannot use both files and stream parameters simultaneously"
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
# HTTP客户端
|
||||||
|
class HTTPClient:
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
timeout: int = 60,
|
||||||
|
default_headers: Optional[Dict[str, str]] = None,
|
||||||
|
total: int = 3,
|
||||||
|
backoff_factor: float = 0.5,
|
||||||
|
cache_enabled: bool = False,
|
||||||
|
cache_ttl: int = 90,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
:param timeout: 超时时间,单位为秒
|
||||||
|
:param default_headers: 默认请求头
|
||||||
|
:param total: 最大重试次数
|
||||||
|
:param backoff_factor: 重试间隔退避因子
|
||||||
|
:param cache_enabled: 是否使用缓存
|
||||||
|
:param cache_ttl: 缓存生存时间,单位为天
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 超时时间
|
||||||
|
self.timeout = timeout
|
||||||
|
# 创建HTTP会话并挂载适配器
|
||||||
|
self.session = self._create_session(
|
||||||
|
default_headers=default_headers, total=total, backoff_factor=backoff_factor
|
||||||
|
)
|
||||||
|
|
||||||
|
# 是否使用缓存
|
||||||
|
self.cache_enabled = cache_enabled
|
||||||
|
# 缓存生存时间
|
||||||
|
self.cache_ttl = cache_ttl
|
||||||
|
# 若使用缓存,则初始化缓存数据库
|
||||||
|
if self.cache_enabled:
|
||||||
|
self._initialize_cache_database()
|
||||||
|
|
||||||
|
# 创建HTTP会话并挂载适配器
|
||||||
|
@staticmethod
|
||||||
|
def _create_session(
|
||||||
|
total: int,
|
||||||
|
backoff_factor: float,
|
||||||
|
default_headers: Optional[Dict[str, str]] = None,
|
||||||
|
) -> Session:
|
||||||
|
"""
|
||||||
|
:param default_headers 默认请求头
|
||||||
|
:param total 最大重试次数
|
||||||
|
:param backoff_factor 重试间隔退避因子
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 创建会话对象
|
||||||
|
session = Session()
|
||||||
|
|
||||||
|
# 设置请求头
|
||||||
|
if default_headers:
|
||||||
|
session.headers.update(default_headers)
|
||||||
|
|
||||||
|
# 设置重试策略(优先按照服响应等待时长,若未返回则默认按照退避算法等待)
|
||||||
|
strategy_retries = Retry(
|
||||||
|
allowed_methods=["HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||||
|
status_forcelist=[
|
||||||
|
408,
|
||||||
|
502,
|
||||||
|
503,
|
||||||
|
504,
|
||||||
|
], # 408:请求超时,502:网关错误,503:服务不可用,504:网关超时
|
||||||
|
total=total,
|
||||||
|
respect_retry_after_header=True,
|
||||||
|
backoff_factor=backoff_factor,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建适配器并绑定重试策略
|
||||||
|
adapter = HTTPAdapter(max_retries=strategy_retries)
|
||||||
|
# 就HTTP请求生效
|
||||||
|
session.mount("http://", adapter)
|
||||||
|
# 就HTTPS请求生效
|
||||||
|
session.mount("https://", adapter)
|
||||||
|
|
||||||
|
return session
|
||||||
|
|
||||||
|
def _initialize_cache_database(self):
|
||||||
|
"""初始化缓存数据库"""
|
||||||
|
|
||||||
|
# 创建缓存数据库连接(使用SQLite)
|
||||||
|
self.cache_connection = sqlite3.connect(
|
||||||
|
database="SQLite.db", check_same_thread=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cache_connection.execute(
|
||||||
|
"""CREATE TABLE IF NOT EXISTS caches (guid TEXT PRIMARY KEY, response TEXT, timestamp REAL)"""
|
||||||
|
)
|
||||||
|
# 创建时间戳索引
|
||||||
|
self.cache_connection.execute(
|
||||||
|
"""CREATE INDEX IF NOT EXISTS index_timestamp ON caches(timestamp)"""
|
||||||
|
)
|
||||||
|
# 删除过期缓存
|
||||||
|
self.cache_connection.execute(
|
||||||
|
"DELETE FROM caches WHERE timestamp < ?",
|
||||||
|
(time.time() - self.cache_ttl * 86400,), # 缓存生存时间单位转为秒
|
||||||
|
)
|
||||||
|
# 提交事物
|
||||||
|
self.cache_connection.commit()
|
||||||
|
|
||||||
|
# 在缓存数据库查询响应
|
||||||
|
def _query_response(self, guid: str) -> Optional[Dict]:
|
||||||
|
|
||||||
|
with threading.Lock():
|
||||||
|
cursor = None
|
||||||
|
try:
|
||||||
|
# 创建游标
|
||||||
|
cursor = self.cache_connection.cursor()
|
||||||
|
# 根据请求唯一标识查询响应
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT response FROM caches WHERE guid = ? AND timestamp >= ?",
|
||||||
|
(guid, time.time() - self.cache_ttl * 86400),
|
||||||
|
)
|
||||||
|
if result := cursor.fetchone():
|
||||||
|
return json.loads(result[0])
|
||||||
|
return None
|
||||||
|
# 若发生异常则返回NONE
|
||||||
|
except:
|
||||||
|
self.cache_connection.rollback()
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
# 将响应保存至缓存数据库
|
||||||
|
def _save_response(self, guid: str, response: Dict):
|
||||||
|
|
||||||
|
with threading.Lock():
|
||||||
|
cursor = None
|
||||||
|
try:
|
||||||
|
# 创建游标
|
||||||
|
cursor = self.cache_connection.cursor()
|
||||||
|
# 新增或覆盖响应
|
||||||
|
cursor.execute(
|
||||||
|
"INSERT OR REPLACE INTO caches (guid, response, timestamp) VALUES (?, ?, ?)",
|
||||||
|
(guid, json.dumps(response, ensure_ascii=False), time.time()),
|
||||||
|
)
|
||||||
|
# 提交事物
|
||||||
|
self.cache_connection.commit()
|
||||||
|
# 若发生异常则返回NONE
|
||||||
|
except:
|
||||||
|
self.cache_connection.rollback()
|
||||||
|
finally:
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
# GET请求
|
||||||
|
def get(self, **kwargs) -> Union[Dict, str]:
|
||||||
|
|
||||||
|
return self._request(method="GET", arguments=Arguments(**kwargs))
|
||||||
|
|
||||||
|
# POST请求
|
||||||
|
def post(self, **kwargs) -> Union[Dict, str]:
|
||||||
|
|
||||||
|
return self._request(method="POST", arguments=Arguments(**kwargs))
|
||||||
|
|
||||||
|
# 文件下载
|
||||||
|
def download(
|
||||||
|
self, stream=False, chunk_size=1024, **kwargs
|
||||||
|
) -> Union[Dict, str, Generator[bytes, None, None]]:
|
||||||
|
|
||||||
|
response = self._request(
|
||||||
|
method="GET", arguments=Arguments(**{"stream": stream, **kwargs})
|
||||||
|
)
|
||||||
|
|
||||||
|
# 若禁用流式传输,则返回响应
|
||||||
|
if not stream:
|
||||||
|
return response
|
||||||
|
# 若启用流式传输,则处理流式传输响应并返回
|
||||||
|
return self._process_stream_response(response=response, chunk_size=chunk_size)
|
||||||
|
|
||||||
|
def _request(self, method: Literal["GET", "POST"], arguments: Arguments) -> Any:
|
||||||
|
"""发送请求"""
|
||||||
|
|
||||||
|
# 请求参数模型
|
||||||
|
arguments = arguments.model_dump(exclude_none=True, by_alias=True)
|
||||||
|
|
||||||
|
# URL由HTTPURL对象转为字符串
|
||||||
|
arguments["url"] = str(arguments["url"])
|
||||||
|
|
||||||
|
# 重构表单数据
|
||||||
|
if arguments.get("data") is not None:
|
||||||
|
arguments["data"] = {
|
||||||
|
key: value
|
||||||
|
for key, value in arguments["data"].items()
|
||||||
|
if value is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
# 重构JSON格式数据
|
||||||
|
if arguments.get("json_data") is not None:
|
||||||
|
arguments["json_data"] = {
|
||||||
|
key: value
|
||||||
|
for key, value in arguments["json_data"].items()
|
||||||
|
if value is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
# 重构文件数据
|
||||||
|
if arguments.get("files") is not None:
|
||||||
|
files_valid = {}
|
||||||
|
# 遍历文件数据键值对
|
||||||
|
for key, value in arguments["files"].items():
|
||||||
|
if isinstance(value, (tuple, list)):
|
||||||
|
match len(value):
|
||||||
|
# 若文件数据包括文件名称和文件内容
|
||||||
|
case 2:
|
||||||
|
files_valid[key] = (value[0], value[1], None, None)
|
||||||
|
# 若文件数据包含文件名称、文件内容和内容类型
|
||||||
|
case 3:
|
||||||
|
files_valid[key] = (value[0], value[1], value[2], None)
|
||||||
|
# 若文件数据包含文件名称、文件内容、内容类型和请求头
|
||||||
|
case 4:
|
||||||
|
files_valid[key] = (value[0], value[1], value[2], value[3])
|
||||||
|
arguments.update({"files": files_valid})
|
||||||
|
|
||||||
|
# 全局唯一标识
|
||||||
|
guid = arguments.pop("guid", None)
|
||||||
|
|
||||||
|
# 若使用缓存且本次请求参数包含全局唯一标识,则优先返回缓存数据库中响应
|
||||||
|
if self.cache_enabled and guid is not None:
|
||||||
|
# 在缓存数据库查询响应
|
||||||
|
response = self._query_response(guid=guid)
|
||||||
|
# 若缓存响应非空则返回
|
||||||
|
if response is not None:
|
||||||
|
return response
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送请求
|
||||||
|
response = self.session.request(
|
||||||
|
method=method, timeout=self.timeout, **arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
# 若返回错误状态码则抛出异常
|
||||||
|
response.raise_for_status()
|
||||||
|
# 处理响应
|
||||||
|
response = self._process_response(response=response)
|
||||||
|
|
||||||
|
# 若请求全局唯一标识非NONE则响应保存至缓存数据库
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
if guid is not None:
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
self._save_response(guid=guid, response=response)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
except Exception as exception:
|
||||||
|
# 尝试根据响应解析响应状态码和错误信息,否则进行构造
|
||||||
|
try:
|
||||||
|
# JOSN反序列化
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
response_decoded = response.json()
|
||||||
|
# 响应状态码
|
||||||
|
status = response_decoded["status"]
|
||||||
|
# 错误信息
|
||||||
|
message = response_decoded["message"]
|
||||||
|
except:
|
||||||
|
status = getattr(getattr(exception, "response", None), "status", None)
|
||||||
|
url = arguments["url"]
|
||||||
|
message = str(exception).split("\n")[0]
|
||||||
|
# 重新构建错误信息
|
||||||
|
message = f"{method} {url} failed: {message}"
|
||||||
|
raise RequestException(status=status, message=message)
|
||||||
|
|
||||||
|
# 处理响应
|
||||||
|
@staticmethod
|
||||||
|
def _process_response(response: Response) -> Any:
|
||||||
|
|
||||||
|
# 响应内容
|
||||||
|
content = response.content
|
||||||
|
# 若响应内容为空则返回NONE
|
||||||
|
if not content:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 标准化内容类型
|
||||||
|
content_type = (
|
||||||
|
response.headers.get("Content-Type", "").split(";")[0].strip().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 根据内容类型匹配解析返回内容方法
|
||||||
|
# noinspection PyUnreachableCode
|
||||||
|
match content_type:
|
||||||
|
case "application/json" | "text/json":
|
||||||
|
# JSON反序列化
|
||||||
|
return response.json()
|
||||||
|
case "application/xml" | "text/xml":
|
||||||
|
# 解析为XML(ELEMENT对象)
|
||||||
|
return ElementTree.fromstring(text=content)
|
||||||
|
case _:
|
||||||
|
# 若内容类型以IMAGE/开头则返回图片格式和图片数据
|
||||||
|
if content_type.startswith("image/"):
|
||||||
|
# 图片格式
|
||||||
|
image_format = content_type.split(sep="/", maxsplit=1)[1]
|
||||||
|
return f"{image_format}", content
|
||||||
|
else:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# 处理流式传输响应
|
||||||
|
@staticmethod
|
||||||
|
def _process_stream_response(
|
||||||
|
response: Response, chunk_size: int
|
||||||
|
) -> Generator[bytes, None, None]: # 生成器不接受发SEND发送至、结束时返回NONE
|
||||||
|
|
||||||
|
# 检查数据分块
|
||||||
|
if not isinstance(chunk_size, int) and isinstance(chunk_size, bool):
|
||||||
|
raise ValueError("chunk_size must type=int")
|
||||||
|
|
||||||
|
if chunk_size <= 0:
|
||||||
|
raise ValueError("chunk_size must >0")
|
||||||
|
|
||||||
|
try:
|
||||||
|
for chunk in response.iter_content(chunk_size=chunk_size):
|
||||||
|
if chunk:
|
||||||
|
yield chunk
|
||||||
|
finally:
|
||||||
|
response.close()
|
||||||
|
|
||||||
|
|
||||||
|
class Authenticator:
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
"""认证器(用于获取访问令牌)"""
|
||||||
|
|
||||||
|
# 初始化
|
||||||
|
self._initialize()
|
||||||
|
|
||||||
|
def _initialize(self):
|
||||||
|
"""初始化访问凭证"""
|
||||||
|
|
||||||
|
# 创建访问凭证地址对象
|
||||||
|
self.certifications_path = (
|
||||||
|
Path(__file__).parent.resolve() / "certifications.json"
|
||||||
|
)
|
||||||
|
# 若访问凭证地址对象不存在则创建
|
||||||
|
if not self.certifications_path.exists():
|
||||||
|
with open(self.certifications_path, "w", encoding="utf-8") as file:
|
||||||
|
json.dump(
|
||||||
|
{},
|
||||||
|
file,
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 初始化HTTP客户端
|
||||||
|
self.http_client = HTTPClient()
|
||||||
|
|
||||||
|
def _szkt_get_certification(self) -> tuple[str, float]:
|
||||||
|
"""获取深圳快瞳访问凭证"""
|
||||||
|
|
||||||
|
# 请求深圳快瞳访问凭证获取接口
|
||||||
|
response = self.http_client.get(
|
||||||
|
url="https://ai.inspirvision.cn/s/api/getAccessToken?accessKey=APPID_6Gf78H59D3O2Q81u&accessSecret=947b8829d4d5d55890b304d322ac2d0d"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 若响应非成功则抛出异常
|
||||||
|
if not (response["status"] == 200 and response["code"] == 0):
|
||||||
|
raise RuntimeError("获取深圳快瞳访问凭证发生异常")
|
||||||
|
|
||||||
|
# 返回令牌、失效时间戳
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return (
|
||||||
|
response["data"]["access_token"],
|
||||||
|
time.time() + response["data"]["expires_in"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _hlyj_get_certification(self) -> tuple[str, float]:
|
||||||
|
"""获取合力亿捷访问凭证"""
|
||||||
|
|
||||||
|
# 企业访问标识
|
||||||
|
access_key_id = "25938f1c190448829dbdb5d344231e42"
|
||||||
|
|
||||||
|
# 签名秘钥
|
||||||
|
secret_access_key = "44dc0299aff84d68ae27712f8784f173"
|
||||||
|
|
||||||
|
# 时间戳(秒级)
|
||||||
|
timestamp = int(time.time())
|
||||||
|
|
||||||
|
# 签名,企业访问标识、签名秘钥和时间戳拼接后计算的十六进制的HMAC-SHA256
|
||||||
|
signature = hmac.new(
|
||||||
|
secret_access_key.encode("utf-8"),
|
||||||
|
f"{access_key_id}{secret_access_key}{timestamp}".encode("utf-8"),
|
||||||
|
hashlib.sha256,
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
|
# 请求合力亿捷访问凭证获取接口
|
||||||
|
response = self.http_client.get(
|
||||||
|
url=f"https://kms.7x24cc.com/api/v1/corp/auth/token?access_key_id={access_key_id}×tamp={timestamp}&signature={signature}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 若响应非成功则抛出异常
|
||||||
|
if not response["success"]:
|
||||||
|
raise RuntimeError("获取合力亿捷访问凭证发生异常")
|
||||||
|
|
||||||
|
# 返回令牌、失效时间戳
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return (
|
||||||
|
response["data"],
|
||||||
|
time.time() + 3600, # 访问令牌有效期为1小时
|
||||||
|
)
|
||||||
|
|
||||||
|
def _feishu_get_certification(self) -> tuple[str, float]:
|
||||||
|
"""获取飞书访问凭证"""
|
||||||
|
|
||||||
|
# 请求飞书访问凭证获取接口
|
||||||
|
response = self.http_client.post(
|
||||||
|
url="https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
|
||||||
|
data={
|
||||||
|
"app_id": "cli_a1587980be78500c",
|
||||||
|
"app_secret": "vZXGZomwfmyaHXoG8s810d1YYGLsIqCA",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 若响应非成功则抛出异常
|
||||||
|
if not response["code"] == 0:
|
||||||
|
raise RuntimeError("获取飞书访问凭证发生异常")
|
||||||
|
|
||||||
|
# 返回令牌、失效时间戳
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return (
|
||||||
|
response["tenant_access_token"],
|
||||||
|
time.time() + response["expire"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_token(self, servicer: str) -> str | None:
|
||||||
|
"""获取访问令牌"""
|
||||||
|
"""
|
||||||
|
:param servicer: 服务商,数据类型为字符串
|
||||||
|
"""
|
||||||
|
|
||||||
|
with threading.Lock():
|
||||||
|
# 初始化令牌和失效时间戳
|
||||||
|
token, expired_timestamp = None, 0
|
||||||
|
try:
|
||||||
|
with open(self.certifications_path, "r", encoding="utf-8") as file:
|
||||||
|
# 读取所有服务商访问凭证
|
||||||
|
certifications = json.load(file)
|
||||||
|
# 获取服务商访问凭证
|
||||||
|
certification = certifications.get(servicer, None)
|
||||||
|
# 若服务商访问凭证非NONE则解析令牌和失效时间戳
|
||||||
|
if certification is not None:
|
||||||
|
# 解析服务商访问令牌
|
||||||
|
token = certification["token"]
|
||||||
|
# 解析服务商访问令牌失效时间戳
|
||||||
|
expired_timestamp = certification["expired_timestamp"]
|
||||||
|
|
||||||
|
# 若JSON反序列化时发生异常则重置访问凭证
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
with open(self.certifications_path, "w", encoding="utf-8") as file:
|
||||||
|
json.dump(
|
||||||
|
{},
|
||||||
|
file,
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
raise RuntimeError("获取访问令牌发生异常")
|
||||||
|
|
||||||
|
# 若当前时间戳大于失效时间戳,则请求服务商获取访问凭证接口
|
||||||
|
if time.time() > expired_timestamp:
|
||||||
|
# noinspection PyUnreachableCode
|
||||||
|
match servicer:
|
||||||
|
case "szkt":
|
||||||
|
# 获取深圳快瞳访问凭证
|
||||||
|
token, expired_timestamp = self._szkt_get_certification()
|
||||||
|
case "feishu":
|
||||||
|
token, expired_timestamp = self._feishu_get_certification()
|
||||||
|
case "hlyj":
|
||||||
|
token, expired_timestamp = self._hlyj_get_certification()
|
||||||
|
case _:
|
||||||
|
raise RuntimeError(f"服务商({servicer})未设置获取访问令牌方法")
|
||||||
|
|
||||||
|
# 更新服务商访问凭证
|
||||||
|
certifications[servicer] = {
|
||||||
|
"token": token,
|
||||||
|
"expired_timestamp": expired_timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 将所有服务商访问凭证保存至本地文件
|
||||||
|
with open(self.certifications_path, "w", encoding="utf-8") as file:
|
||||||
|
json.dump(
|
||||||
|
certifications,
|
||||||
|
file,
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
封装飞书客户端,实现获取验证码、操作多维表格等
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class FeishuClinet:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.authenticator = Authenticator()
|
||||||
|
|
||||||
|
self.http_client = HTTPClient()
|
||||||
|
|
||||||
|
def _headers(self):
|
||||||
|
"""请求头"""
|
||||||
|
|
||||||
|
# 装配飞书访问凭证
|
||||||
|
return {
|
||||||
|
"Authorization": f"Bearer {self.authenticator.get_token(servicer='feishu')}",
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_verification_code():
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# 执行时间戳
|
||||||
|
execute_timestamp = time.time()
|
||||||
|
|
||||||
|
# 超时时间戳
|
||||||
|
timeout_timestamp = execute_timestamp + 65
|
||||||
|
|
||||||
|
# 建立加密IMAP连接
|
||||||
|
server = IMAP4_SSL("imap.feishu.cn", 993)
|
||||||
|
|
||||||
|
# 登录
|
||||||
|
server.login("mars@liubiren.cloud", "a2SfPUgbKDmrjPV2")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
# 若当前时间戳大于超时时间戳则返回NONE
|
||||||
|
if time.time() <= timeout_timestamp:
|
||||||
|
|
||||||
|
# 等待10秒
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
# 选择文件夹(邮箱验证码)
|
||||||
|
server.select("&kK57sZqMi8F4AQ-")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# 获取最后一封邮件索引,server.search()返回数据类型为元组,第一个元素为查询状态,第二个元素为查询结果(邮件索引字节串的列表);然后,从列表获取字节串并分割取最后一个,作为最后一封邮件索引
|
||||||
|
index = server.search(None, "ALL")[1][0].split()[-1]
|
||||||
|
|
||||||
|
# 获取最后一封邮件内容并解析,server.fetch()返回数据类型为元组,第一个元素为查询状态,第二个元素为查询结果(邮件内容字节串的列表);然后,从列表获取字节串并解析正文
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
contents = BytesParser(policy=default).parsebytes(
|
||||||
|
server.fetch(index, "(RFC822)")[1][0][1]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 遍历邮件内容,若正文内容类型为纯文本或HTML则解析发送时间和验证码
|
||||||
|
for content in contents.walk():
|
||||||
|
|
||||||
|
if (
|
||||||
|
content.get_content_type() == "text/plain"
|
||||||
|
or content.get_content_type() == "text/html"
|
||||||
|
):
|
||||||
|
|
||||||
|
# 邮件发送时间戳
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
send_timestamp = parsedate_to_datetime(
|
||||||
|
content["Date"]
|
||||||
|
).timestamp()
|
||||||
|
|
||||||
|
# 若邮件发送时间戳大于执行时间戳则解析验证码并返回
|
||||||
|
if (
|
||||||
|
execute_timestamp
|
||||||
|
> send_timestamp
|
||||||
|
>= execute_timestamp - 35
|
||||||
|
):
|
||||||
|
|
||||||
|
# 登出
|
||||||
|
server.logout()
|
||||||
|
|
||||||
|
# 解析验证码
|
||||||
|
return re.search(
|
||||||
|
r"【普康健康】您的验证码是:(\d+)",
|
||||||
|
content.get_payload(decode=True).decode(),
|
||||||
|
).group(1)
|
||||||
|
|
||||||
|
# 若文件夹无邮件则继续
|
||||||
|
except:
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 若超时则登出
|
||||||
|
else:
|
||||||
|
|
||||||
|
server.logout()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
|
||||||
|
raise RuntimeError("获取邮箱验证码发生其它异常")
|
||||||
|
|
||||||
|
# 查询多维表格记录,单次最多查询500条记录
|
||||||
|
@restrict(refill_rate=5, max_tokens=5)
|
||||||
|
def query_bitable_records(
|
||||||
|
self,
|
||||||
|
bitable: str,
|
||||||
|
table_id: str,
|
||||||
|
field_names: Optional[list[str]] = None,
|
||||||
|
filter_conditions: Optional[dict] = None,
|
||||||
|
) -> pandas.DataFrame:
|
||||||
|
|
||||||
|
# 先查询多维表格记录,在根据字段解析记录
|
||||||
|
|
||||||
|
# 装配多维表格查询记录地址
|
||||||
|
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{bitable}/tables/{table_id}/records/search?page_size=20"
|
||||||
|
|
||||||
|
response = self.http_client.post(
|
||||||
|
url=url,
|
||||||
|
headers=self._headers(),
|
||||||
|
json={"field_names": field_names, "filter": filter_conditions},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 响应业务码为0则定义为响应成功
|
||||||
|
assert response.get("code") == 0, "查询多维表格记录发生异常"
|
||||||
|
|
||||||
|
# 多维表格记录
|
||||||
|
records = response.get("data").get("items")
|
||||||
|
|
||||||
|
# 检查响应中是否包含还有下一页标识,若有则继续请求下一页
|
||||||
|
while response.get("data").get("has_more"):
|
||||||
|
|
||||||
|
url_next = url + "&page_token={}".format(
|
||||||
|
response.get("data").get("page_token")
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.http_client.post(
|
||||||
|
url=url_next,
|
||||||
|
headers=self._headers(),
|
||||||
|
json={"field_names": field_names, "filter": filter_conditions},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.get("code") == 0, "查询多维表格记录发生异常"
|
||||||
|
|
||||||
|
# 合并记录
|
||||||
|
records.append(response.get("data").get("items"))
|
||||||
|
|
||||||
|
# 装配多维表格列出字段地址
|
||||||
|
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{bitable}/tables/{table_id}/fields?page_size=20"
|
||||||
|
|
||||||
|
response = self.http_client.get(
|
||||||
|
url=url,
|
||||||
|
headers=self._headers(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.get("code") == 0, "列出多维表格字段发生异常"
|
||||||
|
|
||||||
|
# 多维表格字段
|
||||||
|
fields = response.get("data").get("items")
|
||||||
|
|
||||||
|
while response.get("data").get("has_more"):
|
||||||
|
|
||||||
|
url_next = url + "&page_token={}".format(
|
||||||
|
response.get("data").get("page_token")
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.http_client.get(
|
||||||
|
url=url_next,
|
||||||
|
headers=self._headers(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.get("code") == 0, "列出多维表格字段发生异常"
|
||||||
|
|
||||||
|
fields.append(response.get("data").get("items"))
|
||||||
|
|
||||||
|
# 字段映射
|
||||||
|
field_mappings = {}
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
|
||||||
|
# 字段名
|
||||||
|
field_name = field["field_name"]
|
||||||
|
|
||||||
|
# 根据字段类型匹配
|
||||||
|
match field["type"]:
|
||||||
|
|
||||||
|
case 1005:
|
||||||
|
|
||||||
|
field_type = "主键"
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
|
||||||
|
field_type = "文本"
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
|
||||||
|
field_type = "单选"
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
|
||||||
|
# 数字、公式字段的显示格式
|
||||||
|
match field["property"]["formatter"]:
|
||||||
|
|
||||||
|
case "0":
|
||||||
|
|
||||||
|
field_type = "整数"
|
||||||
|
|
||||||
|
case _:
|
||||||
|
|
||||||
|
raise ValueError("未设置数字、公式字段的显示格式")
|
||||||
|
|
||||||
|
case _:
|
||||||
|
|
||||||
|
raise ValueError("未设置字段类型")
|
||||||
|
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
field_mappings.update({field_name: field_type})
|
||||||
|
|
||||||
|
# 记录数据体
|
||||||
|
records_data = []
|
||||||
|
|
||||||
|
# 解析记录
|
||||||
|
for record in records:
|
||||||
|
|
||||||
|
# 单条记录数据体
|
||||||
|
record_data = {}
|
||||||
|
|
||||||
|
for field_name, content in record["fields"].items():
|
||||||
|
|
||||||
|
match field_mappings[field_name]:
|
||||||
|
|
||||||
|
case "主键" | "单选" | "整数":
|
||||||
|
|
||||||
|
record_data.update({field_name: content})
|
||||||
|
|
||||||
|
case "文本":
|
||||||
|
|
||||||
|
# 若存在多行文本则拼接
|
||||||
|
fragments_content = ""
|
||||||
|
|
||||||
|
for fragment_content in content:
|
||||||
|
|
||||||
|
fragments_content += fragment_content["text"]
|
||||||
|
|
||||||
|
record_data.update({field_name: fragments_content})
|
||||||
|
|
||||||
|
case _:
|
||||||
|
|
||||||
|
raise ValueError("未设置字段解析方法")
|
||||||
|
|
||||||
|
records_data.append(record_data)
|
||||||
|
|
||||||
|
return pandas.DataFrame(records_data)
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
storage:
|
||||||
|
sqlite:
|
||||||
|
base_dir:
|
||||||
|
env: SQLITE_STORAGE_BASE_DIR
|
||||||
|
|
||||||
|
compute_logs:
|
||||||
|
module: dagster.core.storage.local_compute_log_manager
|
||||||
|
class: LocalComputeLogManager
|
||||||
|
config:
|
||||||
|
env: LOCAL_COMPUTE_LOG_MANAGER_DIRECTORY
|
||||||
|
|
||||||
|
local_artifact_storage:
|
||||||
|
module: dagster.core.storage.root
|
||||||
|
class: LocalArtifactStorage
|
||||||
|
config:
|
||||||
|
env: DAGSTER_LOCAL_ARTIFACT_STORAGE_DIR
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue