mdbq 3.9.9__py3-none-any.whl → 3.9.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mdbq/__version__.py +1 -1
- mdbq/mysql/mysql.py +1 -1669
- mdbq/mysql/uploader.py +121 -57
- {mdbq-3.9.9.dist-info → mdbq-3.9.11.dist-info}/METADATA +1 -1
- {mdbq-3.9.9.dist-info → mdbq-3.9.11.dist-info}/RECORD +7 -7
- {mdbq-3.9.9.dist-info → mdbq-3.9.11.dist-info}/WHEEL +0 -0
- {mdbq-3.9.9.dist-info → mdbq-3.9.11.dist-info}/top_level.txt +0 -0
mdbq/mysql/uploader.py
CHANGED
@@ -99,7 +99,6 @@ class MySQLUploader:
|
|
99
99
|
:param port: 数据库端口,默认为3306
|
100
100
|
:param charset: 字符集,默认为utf8mb4
|
101
101
|
:param collation: 排序规则,默认为utf8mb4_0900_ai_ci
|
102
|
-
|
103
102
|
:param max_retries: 最大重试次数,默认为10
|
104
103
|
:param retry_interval: 重试间隔(秒),默认为10
|
105
104
|
:param pool_size: 连接池大小,默认为5
|
@@ -122,7 +121,7 @@ class MySQLUploader:
|
|
122
121
|
self.write_timeout = write_timeout
|
123
122
|
self.ssl = ssl
|
124
123
|
self._prepared_statements = StatementCache(maxsize=100)
|
125
|
-
self._max_cached_statements = 100
|
124
|
+
self._max_cached_statements = 100 # 用于控制 StatementCache 类中缓存的 SQL 语句数量,最多缓存 100 条 SQL 语句
|
126
125
|
self._table_metadata_cache = {}
|
127
126
|
self.metadata_cache_ttl = 300 # 5分钟缓存时间
|
128
127
|
|
@@ -352,21 +351,29 @@ class MySQLUploader:
|
|
352
351
|
:raises ValueError: 如果日期格式无效或分表方式无效
|
353
352
|
"""
|
354
353
|
try:
|
355
|
-
# date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d
|
354
|
+
# date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d')
|
356
355
|
date_obj = self._validate_datetime(date_value, True)
|
357
356
|
except ValueError:
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
357
|
+
error_msg = f"`{table_name}` 无效的日期格式1: {date_value}"
|
358
|
+
logger.error(error_msg)
|
359
|
+
raise ValueError(error_msg)
|
360
|
+
|
361
|
+
# try:
|
362
|
+
# # date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d %H:%M:%S')
|
363
|
+
# date_obj = self._validate_datetime(date_value, True)
|
364
|
+
# except ValueError:
|
365
|
+
# try:
|
366
|
+
# # date_obj = datetime.datetime.strptime(date_value, '%Y-%m-%d')
|
367
|
+
# date_obj = self._validate_datetime(date_value, True)
|
368
|
+
# except ValueError:
|
369
|
+
# error_msg = f"无效的日期格式1: {date_value}"
|
370
|
+
# logger.error(error_msg)
|
371
|
+
# raise ValueError(error_msg)
|
365
372
|
|
366
373
|
if partition_by == 'year':
|
367
374
|
return f"{table_name}_{date_obj.year}"
|
368
375
|
elif partition_by == 'month':
|
369
|
-
return f"{table_name}_{date_obj.year}
|
376
|
+
return f"{table_name}_{date_obj.year}-{date_obj.month:02d}" # 这里和兼容旧版 `2025-05`,但可能是个隐患
|
370
377
|
else:
|
371
378
|
error_msg = "分表方式必须是 'year' 或 'month'"
|
372
379
|
logger.error(error_msg)
|
@@ -386,7 +393,11 @@ class MySQLUploader:
|
|
386
393
|
raise ValueError(error_msg)
|
387
394
|
|
388
395
|
# 移除非法字符,只保留字母、数字、下划线和美元符号
|
389
|
-
cleaned = re.sub(r'[^\w\u4e00-\u9fff$]', '', identifier)
|
396
|
+
cleaned = re.sub(r'[^\w\u4e00-\u9fff$]', '_', identifier)
|
397
|
+
|
398
|
+
# 将多个连续的下划线替换为单个下划线, 移除开头和结尾的下划线
|
399
|
+
cleaned = re.sub(r'_+', '_', cleaned).strip('_')
|
400
|
+
|
390
401
|
if not cleaned:
|
391
402
|
error_msg = f"无法清理异常标识符: {identifier}"
|
392
403
|
logger.error(error_msg)
|
@@ -597,7 +608,7 @@ class MySQLUploader:
|
|
597
608
|
column_type_lower = column_type.lower()
|
598
609
|
|
599
610
|
# 处理百分比值
|
600
|
-
if
|
611
|
+
if str(value).strip().endswith('%'):
|
601
612
|
try:
|
602
613
|
# 移除百分号并转换为小数
|
603
614
|
percent_value = float(value.strip().replace('%', ''))
|
@@ -606,16 +617,22 @@ class MySQLUploader:
|
|
606
617
|
except ValueError:
|
607
618
|
pass # 如果不是有效的百分比数字,继续正常处理
|
608
619
|
|
609
|
-
|
610
|
-
if isinstance(value,
|
611
|
-
|
612
|
-
|
620
|
+
elif 'int' in column_type_lower:
|
621
|
+
if isinstance(value, str):
|
622
|
+
# 移除可能的逗号和空格
|
623
|
+
value = value.replace(',', '').strip()
|
624
|
+
# 尝试转换为浮点数再转整数
|
625
|
+
try:
|
626
|
+
return int(float(value))
|
627
|
+
except ValueError:
|
628
|
+
raise ValueError(f"`{value}` 无法转为整数")
|
629
|
+
return int(value) if value is not None else None
|
613
630
|
elif any(t in column_type_lower for t in ['float', 'double', 'decimal']):
|
614
631
|
if isinstance(value, str):
|
615
632
|
# 处理可能包含逗号的数字字符串
|
616
633
|
value = value.replace(',', '')
|
617
634
|
return float(value) if value is not None else None
|
618
|
-
elif '
|
635
|
+
elif 'date' in column_type_lower or 'time' in column_type_lower:
|
619
636
|
if isinstance(value, (datetime.datetime, pd.Timestamp)):
|
620
637
|
return value.strftime('%Y-%m-%d %H:%M:%S')
|
621
638
|
elif isinstance(value, str):
|
@@ -630,12 +647,11 @@ class MySQLUploader:
|
|
630
647
|
return value.replace('\\', '\\\\').replace("'", "\\'")
|
631
648
|
return str(value)
|
632
649
|
elif 'json' in column_type_lower:
|
633
|
-
import json
|
634
650
|
return json.dumps(value) if value is not None else None
|
635
651
|
else:
|
636
652
|
return value
|
637
653
|
except (ValueError, TypeError) as e:
|
638
|
-
error_msg = f"
|
654
|
+
error_msg = f"转换异常 -> 无法将 `{value}` 的数据类型转为: `{column_type}` -> {str(e)}"
|
639
655
|
logger.error(error_msg)
|
640
656
|
raise ValueError(error_msg)
|
641
657
|
|
@@ -681,7 +697,8 @@ class MySQLUploader:
|
|
681
697
|
auto_create: bool,
|
682
698
|
date_column: Optional[str],
|
683
699
|
indexes: Optional[List[str]],
|
684
|
-
batch_id: Optional[str] = None
|
700
|
+
batch_id: Optional[str] = None,
|
701
|
+
update_on_duplicate: bool = False
|
685
702
|
):
|
686
703
|
"""实际执行表上传的方法"""
|
687
704
|
# 检查表是否存在
|
@@ -711,7 +728,9 @@ class MySQLUploader:
|
|
711
728
|
# 插入数据
|
712
729
|
self._insert_data(
|
713
730
|
db_name, table_name, data, set_typ,
|
714
|
-
check_duplicate, duplicate_columns
|
731
|
+
check_duplicate, duplicate_columns,
|
732
|
+
batch_id=batch_id,
|
733
|
+
update_on_duplicate=update_on_duplicate
|
715
734
|
)
|
716
735
|
|
717
736
|
def _infer_data_type(self, value: Any) -> str:
|
@@ -721,12 +740,13 @@ class MySQLUploader:
|
|
721
740
|
:param value: 要推断的值
|
722
741
|
:return: MySQL数据类型字符串
|
723
742
|
"""
|
724
|
-
if value is None:
|
743
|
+
if value is None or str(value).lower() in ['', 'none', 'nan']:
|
725
744
|
return 'VARCHAR(255)' # 默认字符串类型
|
726
745
|
|
727
746
|
# 检查是否是百分比字符串
|
728
|
-
if isinstance(value, str)
|
729
|
-
|
747
|
+
if isinstance(value, str):
|
748
|
+
if value.endswith('%'):
|
749
|
+
return 'DECIMAL(10,4)' # 百分比统一使用DECIMAL(10,4)
|
730
750
|
|
731
751
|
if isinstance(value, bool):
|
732
752
|
return 'TINYINT(1)'
|
@@ -773,6 +793,26 @@ class MySQLUploader:
|
|
773
793
|
else:
|
774
794
|
return 'VARCHAR(255)'
|
775
795
|
|
796
|
+
def normalize_column_names(self, data: Union[pd.DataFrame, List[Dict[str, Any]]]) -> Union[
|
797
|
+
pd.DataFrame, List[Dict[str, Any]]]:
|
798
|
+
"""
|
799
|
+
1. pandas:规范化列名
|
800
|
+
2. 字典列表:规范化每个字典的键
|
801
|
+
|
802
|
+
参数:
|
803
|
+
data: 输入数据,支持两种类型:
|
804
|
+
- pandas.DataFrame:将规范化其列名
|
805
|
+
- List[Dict[str, Any]]:将规范化列表中每个字典的键
|
806
|
+
"""
|
807
|
+
if isinstance(data, pd.DataFrame):
|
808
|
+
# 处理DataFrame
|
809
|
+
data.columns = [self._validate_identifier(col) for col in data.columns]
|
810
|
+
return data
|
811
|
+
elif isinstance(data, list):
|
812
|
+
# 处理字典列表
|
813
|
+
return [{self._validate_identifier(k): v for k, v in item.items()} for item in data]
|
814
|
+
return data
|
815
|
+
|
776
816
|
def _prepare_data(
|
777
817
|
self,
|
778
818
|
data: Union[Dict, List[Dict], pd.DataFrame],
|
@@ -807,6 +847,9 @@ class MySQLUploader:
|
|
807
847
|
logger.error(error_msg)
|
808
848
|
raise ValueError(error_msg)
|
809
849
|
|
850
|
+
# 统一处理原始数据中列名的特殊字符
|
851
|
+
data = self.normalize_column_names(data)
|
852
|
+
|
810
853
|
# 将set_typ的键转为小写
|
811
854
|
set_typ = {k.lower(): v for k, v in set_typ.items()}
|
812
855
|
|
@@ -826,11 +869,11 @@ class MySQLUploader:
|
|
826
869
|
if sample_values:
|
827
870
|
inferred_type = self._infer_data_type(sample_values[0])
|
828
871
|
filtered_set_typ[col] = inferred_type
|
829
|
-
logger.debug(f"自动推断列
|
872
|
+
logger.debug(f"自动推断列 `{col}` 的数据类型为: {inferred_type}")
|
830
873
|
else:
|
831
874
|
# 没有样本值,使用默认类型
|
832
875
|
filtered_set_typ[col] = 'VARCHAR(255)'
|
833
|
-
logger.debug(f"
|
876
|
+
logger.debug(f"列 `{col}` 使用默认数据类型: VARCHAR(255)")
|
834
877
|
|
835
878
|
prepared_data = []
|
836
879
|
for row_idx, row in enumerate(data, 1):
|
@@ -842,7 +885,7 @@ class MySQLUploader:
|
|
842
885
|
|
843
886
|
if col_name not in row:
|
844
887
|
if not allow_null:
|
845
|
-
error_msg = f"
|
888
|
+
error_msg = f"行号:{row_idx} -> 缺失列: `{col_name}`"
|
846
889
|
logger.error(error_msg)
|
847
890
|
raise ValueError(error_msg)
|
848
891
|
prepared_row[col_name] = None
|
@@ -850,7 +893,7 @@ class MySQLUploader:
|
|
850
893
|
try:
|
851
894
|
prepared_row[col_name] = self._validate_value(row[col_name], filtered_set_typ[col_name], allow_null)
|
852
895
|
except ValueError as e:
|
853
|
-
error_msg = f"
|
896
|
+
error_msg = f"行号:{row_idx}, 列名:`{col_name}`-> 报错: {str(e)}"
|
854
897
|
logger.error(error_msg)
|
855
898
|
raise ValueError(error_msg)
|
856
899
|
prepared_data.append(prepared_row)
|
@@ -871,7 +914,8 @@ class MySQLUploader:
|
|
871
914
|
partition_by: Optional[str] = None,
|
872
915
|
partition_date_column: str = '日期',
|
873
916
|
auto_create: bool = True,
|
874
|
-
indexes: Optional[List[str]] = None
|
917
|
+
indexes: Optional[List[str]] = None,
|
918
|
+
update_on_duplicate: bool = False
|
875
919
|
):
|
876
920
|
"""
|
877
921
|
上传数据到数据库的主入口方法
|
@@ -888,6 +932,7 @@ class MySQLUploader:
|
|
888
932
|
:param partition_date_column: 用于分表的日期列名,默认为'日期'
|
889
933
|
:param auto_create: 表不存在时是否自动创建,默认为True
|
890
934
|
:param indexes: 需要创建索引的列列表,可选
|
935
|
+
:param update_on_duplicate: 遇到重复数据时是否更新旧数据(默认为False)
|
891
936
|
:raises: 可能抛出各种验证和数据库相关异常
|
892
937
|
"""
|
893
938
|
upload_start = time.time()
|
@@ -898,25 +943,25 @@ class MySQLUploader:
|
|
898
943
|
|
899
944
|
logger.info("开始上传数据", {
|
900
945
|
'批次号': batch_id,
|
901
|
-
'
|
902
|
-
'
|
946
|
+
'库': db_name,
|
947
|
+
'表': table_name,
|
903
948
|
'分表方式': partition_by,
|
904
|
-
'
|
949
|
+
'排重': check_duplicate,
|
905
950
|
'总计行数': len(data) if hasattr(data, '__len__') else 1,
|
906
951
|
'自动建表': auto_create
|
907
952
|
})
|
908
953
|
|
909
954
|
try:
|
910
|
-
#
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
955
|
+
# 验证参数
|
956
|
+
if not set_typ:
|
957
|
+
logger.debug(f'set_typ 参数缺失,建表不指定数据类型字典,后续存储数据容易引发异常')
|
958
|
+
|
959
|
+
if partition_by:
|
960
|
+
partition_by = str(partition_by).lower()
|
961
|
+
if partition_by not in ['year', 'month']:
|
962
|
+
error_msg = "分表方式必须是 'year' 或 'month'"
|
963
|
+
logger.error(error_msg)
|
964
|
+
raise ValueError(error_msg)
|
920
965
|
|
921
966
|
# 准备数据
|
922
967
|
prepared_data, filtered_set_typ = self._prepare_data(data, set_typ, allow_null)
|
@@ -962,7 +1007,7 @@ class MySQLUploader:
|
|
962
1007
|
db_name, part_table, part_data, filtered_set_typ,
|
963
1008
|
primary_keys, check_duplicate, duplicate_columns,
|
964
1009
|
allow_null, auto_create, partition_date_column,
|
965
|
-
indexes, batch_id
|
1010
|
+
indexes, batch_id, update_on_duplicate
|
966
1011
|
)
|
967
1012
|
except Exception as e:
|
968
1013
|
logger.error("分表上传失败", {
|
@@ -976,7 +1021,7 @@ class MySQLUploader:
|
|
976
1021
|
db_name, table_name, prepared_data, filtered_set_typ,
|
977
1022
|
primary_keys, check_duplicate, duplicate_columns,
|
978
1023
|
allow_null, auto_create, partition_date_column,
|
979
|
-
indexes, batch_id
|
1024
|
+
indexes, batch_id, update_on_duplicate
|
980
1025
|
)
|
981
1026
|
|
982
1027
|
success_flag = True
|
@@ -1004,7 +1049,8 @@ class MySQLUploader:
|
|
1004
1049
|
check_duplicate: bool = False,
|
1005
1050
|
duplicate_columns: Optional[List[str]] = None,
|
1006
1051
|
batch_size: int = 1000,
|
1007
|
-
batch_id: Optional[str] = None
|
1052
|
+
batch_id: Optional[str] = None,
|
1053
|
+
update_on_duplicate: bool = False
|
1008
1054
|
):
|
1009
1055
|
"""
|
1010
1056
|
实际执行数据插入的方法
|
@@ -1016,6 +1062,7 @@ class MySQLUploader:
|
|
1016
1062
|
:param check_duplicate: 是否检查重复数据,默认为False
|
1017
1063
|
:param duplicate_columns: 用于检查重复的列,可选
|
1018
1064
|
:param batch_size: 批量插入大小,默认为1000
|
1065
|
+
:param update_on_duplicate: 遇到重复数据时是否更新旧数据(默认为False)
|
1019
1066
|
:param batch_id: 批次ID用于日志追踪,可选
|
1020
1067
|
"""
|
1021
1068
|
if not data:
|
@@ -1048,8 +1095,21 @@ class MySQLUploader:
|
|
1048
1095
|
|
1049
1096
|
where_clause = " AND ".join(conditions)
|
1050
1097
|
|
1051
|
-
|
1098
|
+
if update_on_duplicate:
|
1099
|
+
# 更新模式 - 使用ON DUPLICATE KEY UPDATE语法
|
1100
|
+
update_clause = ", ".join([f"`{col}` = VALUES(`{col}`)" for col in all_columns])
|
1101
|
+
sql = f"""
|
1052
1102
|
INSERT INTO `{db_name}`.`{table_name}`
|
1103
|
+
(`{'`,`'.join(safe_columns)}`)
|
1104
|
+
VALUES ({placeholders})
|
1105
|
+
ON DUPLICATE KEY UPDATE {update_clause}
|
1106
|
+
"""
|
1107
|
+
|
1108
|
+
# 注意:在update_on_duplicate模式下,row_values只需要插入数据,不需要排重列值
|
1109
|
+
def prepare_values(row):
|
1110
|
+
return [row.get(col) for col in all_columns]
|
1111
|
+
else:
|
1112
|
+
sql = f"""INSERT INTO `{db_name}`.`{table_name}`
|
1053
1113
|
(`{'`,`'.join(safe_columns)}`)
|
1054
1114
|
SELECT {placeholders}
|
1055
1115
|
FROM DUAL
|
@@ -1058,6 +1118,10 @@ class MySQLUploader:
|
|
1058
1118
|
WHERE {where_clause}
|
1059
1119
|
)
|
1060
1120
|
"""
|
1121
|
+
|
1122
|
+
# 在check_duplicate模式下,row_values需要插入数据+排重列值
|
1123
|
+
def prepare_values(row):
|
1124
|
+
return [row.get(col) for col in all_columns] + [row.get(col) for col in duplicate_columns]
|
1061
1125
|
else:
|
1062
1126
|
sql = f"""
|
1063
1127
|
INSERT INTO `{db_name}`.`{table_name}`
|
@@ -1065,6 +1129,10 @@ class MySQLUploader:
|
|
1065
1129
|
VALUES ({placeholders})
|
1066
1130
|
"""
|
1067
1131
|
|
1132
|
+
# 普通模式下,row_values只需要插入数据
|
1133
|
+
def prepare_values(row):
|
1134
|
+
return [row.get(col) for col in all_columns]
|
1135
|
+
|
1068
1136
|
total_inserted = 0
|
1069
1137
|
total_skipped = 0
|
1070
1138
|
total_failed = 0 # 失败计数器
|
@@ -1080,11 +1148,7 @@ class MySQLUploader:
|
|
1080
1148
|
for row in batch:
|
1081
1149
|
try:
|
1082
1150
|
# 准备参数
|
1083
|
-
row_values =
|
1084
|
-
# 如果是排重检查,添加排重列值
|
1085
|
-
if check_duplicate:
|
1086
|
-
row_values += [row.get(col) for col in duplicate_columns]
|
1087
|
-
|
1151
|
+
row_values = prepare_values(row)
|
1088
1152
|
cursor.execute(sql, row_values)
|
1089
1153
|
successful_rows += 1
|
1090
1154
|
conn.commit() # 每次成功插入后提交
|
@@ -1096,13 +1160,13 @@ class MySQLUploader:
|
|
1096
1160
|
# 记录失败行详细信息
|
1097
1161
|
error_details = {
|
1098
1162
|
'批次号': batch_id,
|
1099
|
-
'
|
1100
|
-
'
|
1163
|
+
'库': db_name,
|
1164
|
+
'表': table_name,
|
1101
1165
|
'error_type': type(e).__name__,
|
1102
1166
|
'error_message': str(e),
|
1103
|
-
'
|
1167
|
+
'数据类型': set_typ,
|
1104
1168
|
'是否排重': check_duplicate,
|
1105
|
-
'
|
1169
|
+
'排重列': duplicate_columns
|
1106
1170
|
}
|
1107
1171
|
logger.error(f"单行插入失败: {error_details}")
|
1108
1172
|
continue # 跳过当前行,继续处理下一行
|
@@ -1227,7 +1291,7 @@ def main():
|
|
1227
1291
|
# 准备数据
|
1228
1292
|
data = [
|
1229
1293
|
{'日期': '2023-01-8', 'name': 'JACk', 'AGE': '24', 'salary': 555.1545},
|
1230
|
-
{'日期': '2023-01-15', 'name': 'Alice', 'AGE': 35, 'salary': 100},
|
1294
|
+
{'日期': '2023-01-15', 'name': 'Alice', 'AGE': 35, 'salary': '100'},
|
1231
1295
|
{'日期': '2023-01-15', 'name': 'Alice', 'AGE': 30, 'salary': 0.0},
|
1232
1296
|
{'日期': '2023-02-20', 'name': 'Bob', 'AGE': 25, 'salary': 45000.75}
|
1233
1297
|
]
|
@@ -1,5 +1,5 @@
|
|
1
1
|
mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
|
2
|
-
mdbq/__version__.py,sha256=
|
2
|
+
mdbq/__version__.py,sha256=zVFC_9Hi22FnOaPTZtjaH6okUJz6Vi_VZ5smgBB5eZQ,18
|
3
3
|
mdbq/aggregation/__init__.py,sha256=EeDqX2Aml6SPx8363J-v1lz0EcZtgwIBYyCJV6CcEDU,40
|
4
4
|
mdbq/aggregation/optimize.py,sha256=2oalzD9weZhDclUC22OLxYa8Zj7KnmsGUoUau_Jlyc4,19796
|
5
5
|
mdbq/aggregation/query_data.py,sha256=5_OzjGR5Sq00q-EgAYmSE5V9i4Solw9y4hkldl4mvt8,179808
|
@@ -10,9 +10,9 @@ mdbq/log/mylogger.py,sha256=jHCVO7KPQrg2kcCaIrakHivZmFBJyy-24sIn2rsbK4Y,24440
|
|
10
10
|
mdbq/log/spider_logging.py,sha256=-ozWWEGm3HVv604ozs_OOvVwumjokmUPwbaodesUrPY,1664
|
11
11
|
mdbq/mysql/__init__.py,sha256=A_DPJyAoEvTSFojiI2e94zP0FKtCkkwKP1kYUCSyQzo,11
|
12
12
|
mdbq/mysql/deduplicator.py,sha256=brhX3eyE8-kn3nAYweKfBbAkXiNcyw_pL4CTyPqmPBg,21983
|
13
|
-
mdbq/mysql/mysql.py,sha256=
|
13
|
+
mdbq/mysql/mysql.py,sha256=0Yaaw178wk_iRxI2PpVGeec_tDal4Fq5jyCyB0scgvQ,55339
|
14
14
|
mdbq/mysql/s_query.py,sha256=X055aLRAgxVvueXx4NbfNjp6MyBI02_XBb1pTKw09L0,8660
|
15
|
-
mdbq/mysql/uploader.py,sha256=
|
15
|
+
mdbq/mysql/uploader.py,sha256=_Z0m7nEXzmxY6josxBeQR7nG-bsL8FQHR_RDLjj7IzE,52480
|
16
16
|
mdbq/other/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
|
17
17
|
mdbq/other/download_sku_picture.py,sha256=YU8DxKMXbdeE1OOKEA848WVp62jYHw5O4tXTjUdq9H0,44832
|
18
18
|
mdbq/other/otk.py,sha256=iclBIFbQbhlqzUbcMMoePXBpcP1eZ06ZtjnhcA_EbmE,7241
|
@@ -25,7 +25,7 @@ mdbq/redis/__init__.py,sha256=YtgBlVSMDphtpwYX248wGge1x-Ex_mMufz4-8W0XRmA,12
|
|
25
25
|
mdbq/redis/getredis.py,sha256=Uk8-cOWT0JU1qRyIVqdbYokSLvkDIAfcokmYj1ebw8k,24104
|
26
26
|
mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
|
27
27
|
mdbq/spider/aikucun.py,sha256=OhyEv1VyAKTOHjLDM37iNDQeRg5OnrNoKODoG2VxHes,19806
|
28
|
-
mdbq-3.9.
|
29
|
-
mdbq-3.9.
|
30
|
-
mdbq-3.9.
|
31
|
-
mdbq-3.9.
|
28
|
+
mdbq-3.9.11.dist-info/METADATA,sha256=NuMxnqS57k-Z2opqI-yPWqkFPdTJqPJUaqZSwUDb1uY,364
|
29
|
+
mdbq-3.9.11.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
30
|
+
mdbq-3.9.11.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
|
31
|
+
mdbq-3.9.11.dist-info/RECORD,,
|
File without changes
|
File without changes
|