mdbq 4.0.5__py3-none-any.whl → 4.0.7__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 CHANGED
@@ -1 +1 @@
1
- VERSION = '4.0.5'
1
+ VERSION = '4.0.7'
@@ -180,21 +180,18 @@ class MysqlDatasQuery:
180
180
  从数据库中下载数据
181
181
  """
182
182
  def __init__(self, download_manager):
183
- # target_service 从哪个服务器下载数据
184
183
  self.months = 0 # 下载几个月数据, 0 表示当月, 1 是上月 1 号至今
185
184
  self.download_manager = download_manager
186
- self.update_service = True # 调试时加,true: 将数据写入 mysql 服务器
187
185
  self.pf_datas = []
188
- self.pf_datas_jd = [] # 京东聚合销售表
189
186
 
190
187
  @staticmethod
191
- def try_except(func): # 在类内部定义一个异常处理方法
188
+ def try_except(func):
192
189
  @wraps(func)
193
190
  def wrapper(*args, **kwargs):
194
191
  try:
195
192
  return func(*args, **kwargs)
196
193
  except Exception as e:
197
- logger.info('函数执行错误', {'函数': func.__name__, '错误': str(e)}) # 将异常信息返回
194
+ logger.info('函数执行错误', {'函数': func.__name__, '错误': str(e), 'args': args, 'kwargs': kwargs})
198
195
 
199
196
  return wrapper
200
197
 
@@ -227,7 +224,6 @@ class MysqlDatasQuery:
227
224
  )
228
225
  __res.append(df)
229
226
  df = pd.concat(__res, ignore_index=True)
230
-
231
227
  df.rename(columns={
232
228
  '场景名字': '营销场景',
233
229
  '主体id': '商品id',
@@ -235,7 +231,6 @@ class MysqlDatasQuery:
235
231
  '总成交笔数': '成交笔数',
236
232
  '总成交金额': '成交金额'
237
233
  }, inplace=True)
238
-
239
234
  df = df.astype({
240
235
  '商品id': str,
241
236
  '花费': 'float64',
@@ -273,7 +268,7 @@ class MysqlDatasQuery:
273
268
  '直接成交金额': ('直接成交金额', np.max)
274
269
  }
275
270
  )
276
- df.insert(loc=1, column='推广渠道', value='万相台无界版') # df中插入新列
271
+ df.insert(loc=1, column='推广渠道', value='万相台无界版')
277
272
  set_typ = {
278
273
  '日期': 'date',
279
274
  '推广渠道': 'varchar(100)',
@@ -290,7 +285,7 @@ class MysqlDatasQuery:
290
285
  '直接成交笔数': 'int',
291
286
  '直接成交金额': 'decimal(12,2)',
292
287
  }
293
-
288
+ # 制作其他聚合表
294
289
  self.pf_datas.append(
295
290
  {
296
291
  '集合名称': '天猫汇总表调用',
@@ -298,7 +293,7 @@ class MysqlDatasQuery:
298
293
  ['日期', '店铺名称', '推广渠道', '营销场景', '商品id', '花费', '展现量', '点击量', '加购量',
299
294
  '成交笔数', '成交金额', '直接成交笔数', '直接成交金额', '自然流量曝光量']]
300
295
  }
301
- ) # 制作其他聚合表
296
+ )
302
297
  logger.info('正在更新数据库', {'主机': f'{host}:{port}', '库': db_name, '表': table_name})
303
298
  uld.upload_data(
304
299
  db_name=db_name,
@@ -720,23 +715,11 @@ class MysqlDatasQuery:
720
715
  __res.append(df)
721
716
  df = pd.concat(__res, ignore_index=True)
722
717
  df['宝贝id'] = df['宝贝id'].astype(str)
723
- # df = df.groupby(['日期', '店铺名称', '宝贝id', '行业类目'], as_index=False).agg(
724
- # **{
725
- # '销售额': ('销售额', np.min),
726
- # '销售量': ('销售量', np.min),
727
- # '订单数': ('订单数', np.min),
728
- # '退货量': ('退货量', np.max),
729
- # '退款额': ('退款额', np.max),
730
- # '退款额_发货后': ('退款额_发货后', np.max),
731
- # '退货量_发货后': ('退货量_发货后', np.max),
732
- # }
733
- # )
734
718
  # 仅保留最新日期的数据
735
719
  idx = df.groupby(['日期', '店铺名称', '宝贝id'])['更新时间'].idxmax()
736
720
  df = df.loc[idx]
737
721
  df = df[['日期', '店铺名称', '宝贝id', '行业类目', '销售额', '销售量', '订单数', '退货量', '退款额', '退款额_发货后', '退货量_发货后']]
738
- df['件均价'] = df.apply(lambda x: x['销售额'] / x['销售量'] if x['销售量'] > 0 else 0, axis=1).round(
739
- 0) # 两列运算, 避免除以0
722
+ df['件均价'] = np.where(df['销售量'] > 0, df['销售额'] / df['销售量'], 0).round(0)
740
723
  df['价格带'] = df['件均价'].apply(
741
724
  lambda x: '2000+' if x >= 2000
742
725
  else '1000+' if x >= 1000
@@ -850,8 +833,7 @@ class MysqlDatasQuery:
850
833
  '直接成交金额': ('直接成交金额', np.max)
851
834
  }
852
835
  )
853
- df.insert(loc=1, column='推广渠道', value='万相台无界版') # df中插入新列
854
-
836
+ df.insert(loc=1, column='推广渠道', value='万相台无界版')
855
837
  # 开始处理用户特征
856
838
  df_sx = self.download_manager.data_to_df(
857
839
  db_name='达摩盘3',
@@ -860,7 +842,7 @@ class MysqlDatasQuery:
860
842
  end_date=end_date,
861
843
  projection={'人群名称': 1, '消费能力等级': 1, '用户年龄': 1},
862
844
  )
863
- df_sx['人群名称'] = df_sx['人群名称'].apply(lambda x: f'达摩盘:{x}')
845
+ df_sx['人群名称'] = '达摩盘:' + df_sx['人群名称']
864
846
  df_sx.rename(columns={'消费能力等级': '消费力层级'}, inplace=True)
865
847
  df = pd.merge(df, df_sx, left_on=['人群名字'], right_on=['人群名称'], how='left')
866
848
  df.pop('人群名称')
@@ -869,10 +851,8 @@ class MysqlDatasQuery:
869
851
  df['用户年龄'] = df['用户年龄'].apply(
870
852
  lambda x: "~".join(re.findall(r'(\d{2})\D.*(\d{2})岁', str(x))[0])
871
853
  if str(x) != 'nan' and re.findall(r'(\d{2})\D.*(\d{2})岁', str(x)) else x)
872
-
873
854
  # 1. 匹配 L后面接 2 个或以上数字,不区分大小写,示例:L345
874
855
  # 2. 其余情况,L 后面接多个数字的都会被第一条 if 命中,不区分大小写
875
-
876
856
  df['消费力层级'] = df.apply(
877
857
  lambda x:
878
858
  ''.join(re.findall(r'(l\d+)', x['人群名字'].upper(), re.IGNORECASE))
@@ -883,12 +863,10 @@ class MysqlDatasQuery:
883
863
  else 'L2' if re.findall(r'(l\d*2)', x['人群名字'], re.IGNORECASE) and str(x['消费力层级']) == 'nan'
884
864
  else 'L1' if re.findall(r'(l\d*1)', x['人群名字'], re.IGNORECASE) and str(x['消费力层级']) == 'nan'
885
865
  else x['消费力层级'], axis=1)
886
-
887
866
  # 1. 匹配连续的 4 个数字且后面不能接数字或"元"或汉字,筛掉的人群示例:月均消费6000元|受众20240729175213|xxx2024真皮公文包
888
867
  # 2. 匹配 2数字_2数字且前面不能是数字,合法匹配:人群_30_50_促; 非法示例:L345_3040 避免识别出 35~20 岁用户的情况
889
868
  # pattern = r'(\d{4})(?!\d|[\u4e00-\u9fa5])' # 匹配 4 个数字,后面不能接数字或汉字
890
869
  # pattern = r'(?<![\d\u4e00-\u9fa5])(\d{4})' # 匹配前面不是数字或汉字的 4 个连续数字
891
-
892
870
  # 匹配 4 个数字,前面和后面都不能是数字或汉字
893
871
  pattern1 = r'(?<![\d\u4e00-\u9fa5])(\d{4})(?!\d|[\u4e00-\u9fa5])'
894
872
  # 匹配指定字符,前面不能是数字或 l 或 L 开头
@@ -909,8 +887,7 @@ class MysqlDatasQuery:
909
887
  else x
910
888
  )
911
889
  # 年龄层不能是 0 开头
912
- df['用户年龄'] = df['用户年龄'].apply(
913
- lambda x: '' if str(x).startswith('0') else x)
890
+ df['用户年龄'] = np.where(df['用户年龄'].astype(str).str.startswith('0'), '', df['用户年龄'])
914
891
  df['用户年龄'] = df['用户年龄'].apply(
915
892
  lambda x:
916
893
  re.sub(f'~50', '~49' ,str(x)) if '~50' in str(x) else
@@ -919,10 +896,6 @@ class MysqlDatasQuery:
919
896
  re.sub(r'\d{4}~', '', str(x)) if str(x) != 'nan' else
920
897
  x
921
898
  )
922
- # df = df.head(1000)
923
- # df.to_csv('/Users/xigua/Downloads/test.csv', index=False, header=True, encoding='utf-8_sig')
924
- # breakpoint()
925
-
926
899
  # 下面是添加人群 AIPL 分类
927
900
  dir_file = f'\\\\192.168.1.198\\时尚事业部\\01.运营部\\0-电商周报-每周五更新\\分类配置文件.xlsx'
928
901
  dir_file2 = '/Volumes/时尚事业部/01.运营部/0-电商周报-每周五更新/分类配置文件.xlsx'
@@ -1080,7 +1053,6 @@ class MysqlDatasQuery:
1080
1053
  dir_file = dir_file2
1081
1054
  if os.path.isfile(dir_file):
1082
1055
  df_fl = pd.read_excel(dir_file, sheet_name='关键词分类', header=0)
1083
- # df_fl.rename(columns={'分类1': '词分类'}, inplace=True)
1084
1056
  df_fl = df_fl[['关键词', '词分类']]
1085
1057
  # 合并并获取词分类信息
1086
1058
  df = pd.merge(df, df_fl, left_on=['词名字_词包名字'], right_on=['关键词'], how='left')
@@ -1159,7 +1131,6 @@ class MysqlDatasQuery:
1159
1131
  return pd.DataFrame()
1160
1132
  return df
1161
1133
 
1162
-
1163
1134
  @try_except
1164
1135
  @upload_data_decorator()
1165
1136
  def tg_cjzb(self, db_name='聚合数据', table_name='天猫_超级直播', is_maximize=True):
@@ -1207,7 +1178,6 @@ class MysqlDatasQuery:
1207
1178
  if col not in cjzb_qzt.columns.tolist():
1208
1179
  cjzb_qzt[col] = 0
1209
1180
  df = pd.concat([df, cjzb_qzt], ignore_index=True)
1210
-
1211
1181
  df.rename(columns={
1212
1182
  '观看次数': '观看次数',
1213
1183
  '总购物车数': '加购量',
@@ -1283,7 +1253,6 @@ class MysqlDatasQuery:
1283
1253
  '直接成交金额': 'decimal(12,2)',
1284
1254
  }
1285
1255
  logger.info('正在更新数据库', {'主机': f'{host}:{port}', '库': db_name, '表': table_name})
1286
-
1287
1256
  return df, {
1288
1257
  'db_name': db_name,
1289
1258
  'table_name': table_name,
@@ -1406,57 +1375,7 @@ class MysqlDatasQuery:
1406
1375
  'partition_date_column': '日期', # 用于分表的日期列名,默认为'日期'
1407
1376
  'indexes': [], # 普通索引列
1408
1377
  'transaction_mode': 'batch', # 事务模式
1409
- 'unique_keys': [['日期', '推广渠道', '店铺名称', '营销场景', '报表类型']], # 唯一约束列表
1410
- }
1411
-
1412
- @try_except
1413
- @upload_data_decorator()
1414
- def idbm_bak(self, db_name='聚合数据', table_name='商品id编码表'):
1415
- """ 用生意经日数据制作商品 id 和编码对照表 """
1416
- year = datetime.datetime.today().year
1417
- data_values = []
1418
- for year in range(2022, year+1):
1419
- data_values += self.download_manager.columns_to_list(
1420
- db_name='生意经3',
1421
- table_name=f'宝贝指标_{year}',
1422
- columns_name=['宝贝id', '商家编码', '行业类目'],
1423
- )
1424
- df = pd.DataFrame(data=data_values)
1425
- df['宝贝id'] = df['宝贝id'].astype(str)
1426
- df.drop_duplicates(subset='宝贝id', keep='last', inplace=True, ignore_index=True)
1427
- # df['行业类目'] = df['行业类目'].apply(lambda x: re.sub(' ', '', x))
1428
- try:
1429
- df[['一级类目', '二级类目', '三级类目']] = df['行业类目'].str.split(' -> ', expand=True).loc[:, 0:2]
1430
- except:
1431
- try:
1432
- df[['一级类目', '二级类目']] = df['行业类目'].str.split(' -> ', expand=True).loc[:, 0:1]
1433
- except:
1434
- df['一级类目'] = df['行业类目']
1435
- df.drop('行业类目', axis=1, inplace=True)
1436
- df.sort_values('宝贝id', ascending=False, inplace=True)
1437
- df = df[(df['宝贝id'] != '973') & (df['宝贝id'] != 973) & (df['宝贝id'] != '0')]
1438
- set_typ = {
1439
- '宝贝id': 'bigint',
1440
- '商家编码': 'varchar(100)',
1441
- '一级类目': 'varchar(100)',
1442
- '二级类目': 'varchar(100)',
1443
- '三级类目': 'varchar(100)',
1444
- }
1445
- logger.info('正在更新数据库', {'主机': f'{host}:{port}', '库': db_name, '表': table_name})
1446
- return df, {
1447
- 'db_name': db_name,
1448
- 'table_name': table_name,
1449
- 'set_typ': set_typ,
1450
- 'primary_keys': [], # 创建唯一主键
1451
- 'check_duplicate': False, # 检查重复数据
1452
- 'duplicate_columns': [], # 指定排重的组合键
1453
- 'update_on_duplicate': True, # 更新旧数据
1454
- 'allow_null': False, # 允许插入空值
1455
- 'partition_by': None, # 分表方式
1456
- 'partition_date_column': '日期', # 用于分表的日期列名,默认为'日期'
1457
- 'indexes': [], # 普通索引列
1458
- 'transaction_mode': 'batch', # 事务模式
1459
- 'unique_keys': [['宝贝id']], # 唯一约束列表
1378
+ 'unique_keys': [['日期', '推广渠道', '店铺名称', '营销场景', '报表类型', '花费', '展现量']], # 唯一约束列表
1460
1379
  }
1461
1380
 
1462
1381
  @try_except
@@ -1650,9 +1569,6 @@ class MysqlDatasQuery:
1650
1569
  end_date=end_date,
1651
1570
  projection=projection,
1652
1571
  )
1653
- # df.drop_duplicates(
1654
- # subset=['日期', '店铺名称', '商品id', '商品访客数'], keep='last',
1655
- # inplace=True, ignore_index=True)
1656
1572
  # 保留最新日期的数据
1657
1573
  idx = df.groupby(['日期', '店铺名称', '商品id'])['更新时间'].idxmax()
1658
1574
  df = df.loc[idx]
@@ -1691,7 +1607,6 @@ class MysqlDatasQuery:
1691
1607
  df['上市季节'] = df['上市年月'].apply(lambda x: check_jijie(x))
1692
1608
  p = df.pop('上市季节')
1693
1609
  df.insert(loc=9, column='上市季节', value=p)
1694
-
1695
1610
  set_typ = {
1696
1611
  '商品id': 'BIGINT',
1697
1612
  '店铺名称': 'varchar(100)',
@@ -1784,7 +1699,6 @@ class MysqlDatasQuery:
1784
1699
  )
1785
1700
  __res.append(df)
1786
1701
  df = pd.concat(__res, ignore_index=True)
1787
- # df['日期'] = pd.to_datetime(df['日期'], format='%Y-%m-%d', errors='ignore') # 转换日期列
1788
1702
  df = df.astype({'访客数': 'int64'}, errors='ignore')
1789
1703
  df = df[df['访客数'] > 0]
1790
1704
  df.drop_duplicates(subset=['日期', '店铺名称', '类别', '来源构成', '一级来源', '二级来源', '三级来源', '访客数'], keep='last', inplace=True, ignore_index=True)
@@ -1800,7 +1714,6 @@ class MysqlDatasQuery:
1800
1714
  df_visitor3['index'] = df_visitor3['index'] + 100
1801
1715
  df_visitor3.rename(columns={'index': '三级来源索引'}, inplace=True)
1802
1716
  df_visitor3 = df_visitor3[['三级来源', '三级来源索引']]
1803
-
1804
1717
  # 包含二级来源名称和预设索引值列
1805
1718
  df_visitor2 = df[df['日期'] >= pd.to_datetime(last_month)]
1806
1719
  df_visitor2 = df_visitor2[(df_visitor2['二级来源'] != '汇总') & (df_visitor2['二级来源'] != '0')]
@@ -1810,7 +1723,6 @@ class MysqlDatasQuery:
1810
1723
  df_visitor2['index'] = df_visitor2['index'] + 100
1811
1724
  df_visitor2.rename(columns={'index': '二级来源索引'}, inplace=True)
1812
1725
  df_visitor2 = df_visitor2[['二级来源', '二级来源索引']]
1813
-
1814
1726
  # 包含一级来源名称和预设索引值列
1815
1727
  df_visitor1 = df[df['日期'] >= pd.to_datetime(last_month)]
1816
1728
  df_visitor1 = df_visitor1[(df_visitor1['一级来源'] != '汇总') & (df_visitor1['一级来源'] != '0')]
@@ -1939,7 +1851,6 @@ class MysqlDatasQuery:
1939
1851
  projection=projection,
1940
1852
  )
1941
1853
  __res.append(df)
1942
-
1943
1854
  df = pd.concat(__res, ignore_index=True)
1944
1855
  df = df.groupby(
1945
1856
  ['日期', '店铺名称', '产品线', '触发sku_id', '跟单sku_id', 'spu_id', '花费', '展现数', '点击数'],
@@ -2201,13 +2112,6 @@ class MysqlDatasQuery:
2201
2112
  __res.append(df)
2202
2113
  df = pd.concat(__res, ignore_index=True)
2203
2114
  df = df[df['商品id'] != '合计']
2204
- # df = df.groupby(['日期', '店铺名称', '商品id', '货号', '访客数', '成交客户数', '加购商品件数', '加购人数'],
2205
- # as_index=False).agg(
2206
- # **{
2207
- # '成交单量': ('成交单量', np.max),
2208
- # '成交金额': ('成交金额', np.max),
2209
- # }
2210
- # )
2211
2115
  # 仅保留最新日期的数据
2212
2116
  idx = df.groupby(['日期', '店铺名称', '商品id', '货号', '访客数', '成交客户数', '加购商品件数', '加购人数'])['更新时间'].idxmax()
2213
2117
  df = df.loc[idx]
@@ -2271,13 +2175,6 @@ class MysqlDatasQuery:
2271
2175
  __res.append(df)
2272
2176
  df = pd.concat(__res, ignore_index=True)
2273
2177
  df = df[df['商品id'] != '合计']
2274
- # df = df.groupby(['日期', '店铺名称', '商品id', '货号', '访客数', '成交客户数', '加购商品件数', '加购人数'],
2275
- # as_index=False).agg(
2276
- # **{
2277
- # '成交单量': ('成交单量', np.max),
2278
- # '成交金额': ('成交金额', np.max),
2279
- # }
2280
- # )
2281
2178
  # 仅保留最新日期的数据
2282
2179
  idx = df.groupby(['日期', '店铺名称', '商品id', '货号', '访客数', '成交客户数', '加购商品件数', '加购人数'])['更新时间'].idxmax()
2283
2180
  df = df.loc[idx]
@@ -2544,7 +2441,6 @@ class MysqlDatasQuery:
2544
2441
  df_jd = pd.DataFrame() # 京东推广
2545
2442
  df_jd_qzyx = pd.DataFrame() # 京东全站推广
2546
2443
  df_jd_ziying = pd.DataFrame() # 京东推广
2547
-
2548
2444
  start_date, end_date = self.months_data(num=self.months)
2549
2445
  projection = {
2550
2446
  '日期': 1,
@@ -2659,7 +2555,6 @@ class MysqlDatasQuery:
2659
2555
  '成交金额': ('总成交金额', np.max)
2660
2556
  }
2661
2557
  )
2662
-
2663
2558
  # 天猫的全站推广包含在营销场景报表中,淘宝店不包含
2664
2559
  df_tb_qzt = pd.DataFrame()
2665
2560
  if '全站推广' not in df_tb['营销场景'].tolist():
@@ -2711,7 +2606,6 @@ class MysqlDatasQuery:
2711
2606
  }
2712
2607
  )
2713
2608
  df_tb_qzt['营销场景'] = '全站推广'
2714
-
2715
2609
  # 品销宝报表
2716
2610
  projection = {
2717
2611
  '日期': 1,
@@ -2750,7 +2644,6 @@ class MysqlDatasQuery:
2750
2644
  )
2751
2645
  df_tm_pxb.rename(columns={'报表类型': '营销场景', '消耗': '花费'}, inplace=True)
2752
2646
  df_tm_pxb['营销场景'] = '品销宝'
2753
-
2754
2647
  # 因为 2024.04.16及之前的营销场景报表不含超级直播,所以在此添加
2755
2648
  if start_date < pd.to_datetime('2024-04-17'):
2756
2649
  projection = {
@@ -2788,7 +2681,7 @@ class MysqlDatasQuery:
2788
2681
  '成交金额': ('总成交金额', np.max)
2789
2682
  }
2790
2683
  )
2791
-
2684
+ # 京东数据
2792
2685
  projection = {
2793
2686
  '日期': 1,
2794
2687
  '产品线': 1,
@@ -2832,7 +2725,6 @@ class MysqlDatasQuery:
2832
2725
  df_jd = df_jd[['日期', '店铺名称', '产品线', '花费', '展现数', '点击数', '加购量', '成交笔数', '成交金额']]
2833
2726
  df_jd.rename(columns={'产品线': '营销场景', '展现数': '展现量', '点击数': '点击量'}, inplace=True)
2834
2727
  df_jd = df_jd[df_jd['花费'] > 0]
2835
-
2836
2728
  projection = {
2837
2729
  '日期': 1,
2838
2730
  '产品线': 1,
@@ -2867,7 +2759,7 @@ class MysqlDatasQuery:
2867
2759
  df_jd_qzyx.rename(columns={'产品线': '营销场景'}, inplace=True)
2868
2760
  df_jd_qzyx = df_jd_qzyx[['日期', '店铺名称', '营销场景', '花费', '展现量', '点击量', '成交笔数', '成交金额']]
2869
2761
  df_jd_qzyx = df_jd_qzyx[df_jd_qzyx['花费'] > 0]
2870
-
2762
+ # 京东自营店数据
2871
2763
  projection = {
2872
2764
  '日期': 1,
2873
2765
  '产品线': 1,
@@ -3045,7 +2937,6 @@ class MysqlDatasQuery:
3045
2937
  )
3046
2938
  idx = df.groupby(['日期', '店铺名称', 'spuid'])['更新时间'].idxmax()
3047
2939
  df = df.loc[idx]
3048
-
3049
2940
  # 调整列顺序, 定义需要前置的列
3050
2941
  cols_to_move = ['日期','平台','店铺名称','品牌名','商品名称', '商品款号','spuid', '一级类目名称', '二级类目名称', '三级类目名称']
3051
2942
  # 生成新的列顺序:前置列 + 剩余列(保持原顺序)
@@ -3146,7 +3037,6 @@ class MysqlDatasQuery:
3146
3037
  '消费能力等级': 1,
3147
3038
  '用户性别': 1,
3148
3039
  }
3149
- # projection = {}
3150
3040
  df_crowd = self.download_manager.data_to_df(
3151
3041
  db_name='达摩盘3',
3152
3042
  table_name='我的人群属性',
@@ -3158,7 +3048,6 @@ class MysqlDatasQuery:
3158
3048
  df_crowd.drop_duplicates(subset=['人群id',], keep='last', inplace=True, ignore_index=True)
3159
3049
  df_crowd.pop('日期')
3160
3050
  df_crowd = df_crowd.astype({'人群id': 'int64'}, errors='ignore')
3161
-
3162
3051
  projection = {}
3163
3052
  __res = []
3164
3053
  for year in range(2024, datetime.datetime.today().year + 1):
@@ -3178,8 +3067,6 @@ class MysqlDatasQuery:
3178
3067
  # 清除一些不必要的字符
3179
3068
  df['用户年龄'] = df['用户年龄'].apply(lambda x: '~'.join(re.findall(r'^(\d+).*-(\d+)岁$', str(x))[0]) if '岁' in str(x) else x)
3180
3069
  df['消费能力等级'] = df['消费能力等级'].apply(lambda x: f'L{''.join(re.findall(r'(\d)', str(x)))}' if '购买力' in str(x) else x)
3181
- # df.to_csv('/Users/xigua/Downloads/test3.csv', index=False, header=True, encoding='utf-8_sig')
3182
- # breakpoint()
3183
3070
  df.rename(columns={'消耗_元': '消耗'}, inplace=True)
3184
3071
  set_typ = {
3185
3072
  '日期': 'date',
@@ -3416,7 +3303,6 @@ class MysqlDatasQuery:
3416
3303
  result_i = re.findall('_i$|_i_|^i_', str(keyword), re.IGNORECASE)
3417
3304
  result_p = re.findall('_p$|_p_|_pl|^p_||^pl_', str(keyword), re.IGNORECASE)
3418
3305
  result_l = re.findall('_l$|_l_|^l_', str(keyword), re.IGNORECASE)
3419
-
3420
3306
  datas = [
3421
3307
  {
3422
3308
  '类别': 'A',
@@ -3435,7 +3321,6 @@ class MysqlDatasQuery:
3435
3321
  '值': result_l,
3436
3322
  }
3437
3323
  ]
3438
-
3439
3324
  is_res = False
3440
3325
  for data in datas:
3441
3326
  if data['值']:
@@ -3568,7 +3453,6 @@ class MysqlDatasQuery:
3568
3453
  '成交金额': ('成交金额', np.sum)
3569
3454
  }
3570
3455
  )
3571
-
3572
3456
  zb.rename(columns={
3573
3457
  '观看次数': '点击量',
3574
3458
  }, inplace=True)
@@ -3596,16 +3480,13 @@ class MysqlDatasQuery:
3596
3480
  '直接成交金额': 'float64',
3597
3481
  '自然流量曝光量': 'int64',
3598
3482
  }, errors='raise')
3599
-
3600
3483
  df = pd.concat([tg, zb, pxb], axis=0, ignore_index=True)
3601
3484
  df.fillna(0, inplace=True) # concat 之后要填充空值
3602
- df = df.astype(
3603
- {
3604
- '商品id': str,
3605
- '自然流量曝光量': 'int64',
3606
- }
3607
- )
3608
- [df[col].apply(lambda x: '0' if str(x) == '' else x) for col in df.columns.tolist()]
3485
+ df = df.astype({
3486
+ '商品id': str,
3487
+ '自然流量曝光量': 'int64',
3488
+ })
3489
+ df.replace(to_replace='', value=0, inplace=True)
3609
3490
  set_typ = {
3610
3491
  '日期': 'date',
3611
3492
  '店铺名称': 'varchar(100)',
@@ -3650,7 +3531,6 @@ def get_day_of_month(num):
3650
3531
  _, _lastDay = calendar.monthrange(months_ago.year, months_ago.month) # 返回月的第一天的星期和当月总天数
3651
3532
  _firstDay = datetime.date(months_ago.year, months_ago.month, day=1).strftime('%Y-%m-%d')
3652
3533
  _lastDay = datetime.date(months_ago.year, months_ago.month, day=_lastDay).strftime('%Y-%m-%d')
3653
-
3654
3534
  return _firstDay, _lastDay
3655
3535
 
3656
3536
 
@@ -3690,9 +3570,7 @@ def date_table():
3690
3570
  group['第n周_new'] = f'第{num}周'
3691
3571
  num += 1
3692
3572
  __res.append(group.copy())
3693
- # break
3694
3573
  df = pd.concat(__res, ignore_index=True)
3695
- # df['日期'] = df['日期'].apply(lambda x: pd.to_datetime(x))
3696
3574
  df['weekname'] = df['日期'].dt.day_name()
3697
3575
  dict_dt = {
3698
3576
  'Monday': '星期一',
@@ -3746,7 +3624,6 @@ def date_table():
3746
3624
  def query1(months=1, download_manager=None):
3747
3625
  sdq = MysqlDatasQuery(download_manager=download_manager) # 实例化数据处理类
3748
3626
  sdq.months = months # 设置数据周期, 1 表示近 2 个月
3749
-
3750
3627
  # 依赖表 -- >>
3751
3628
  sdq.tg_wxt(db_name='聚合数据', table_name='天猫_主体报表')
3752
3629
  sdq.tg_cjzb(db_name='聚合数据', table_name='天猫_超级直播')
@@ -3792,7 +3669,6 @@ def query3(months=1, download_manager=None):
3792
3669
  def main(months=3):
3793
3670
  # 1. 更新日期表 更新货品年份基准表, 属性设置 3 - 货品年份基准
3794
3671
  date_table()
3795
-
3796
3672
  # 2. 数据聚合
3797
3673
  download_manager = s_query.QueryDatas(
3798
3674
  username=username,
@@ -3808,5 +3684,4 @@ def main(months=3):
3808
3684
 
3809
3685
  if __name__ == '__main__':
3810
3686
  # main(months=3)
3811
-
3812
3687
  pass
mdbq/mysql/unique_.py CHANGED
@@ -321,14 +321,14 @@ def main():
321
321
  # "sku榜单": [['日期', '平台', '店铺名称', '条码']],
322
322
  # "spu榜单": [['日期', '平台', '店铺名称', '商品款号', '访客量']],
323
323
  # },
324
- # "生意参谋3": {
325
- # "crm成交客户": [['客户id']],
326
- # "商品排行": [['日期', '店铺名称', '商品id']],
327
- # "流量来源构成": [['日期', '店铺名称', '来源构成', '类别', '一级来源', '二级来源', '三级来源']],
328
- # "手淘搜索": [['日期', '店铺名称', '搜索词', '词类型', '访客数']],
329
- # "新品追踪": [['日期', '店铺名称', '商品id']],
330
- # "直播分场次效果": [['场次id']],
331
- # },
324
+ "生意参谋3": {
325
+ # "crm成交客户": [['客户id']],
326
+ # "商品排行": [['日期', '店铺名称', '商品id']],
327
+ "流量来源构成": [['日期', '店铺名称', '来源构成', '类别', '一级来源', '二级来源', '三级来源']],
328
+ # "手淘搜索": [['日期', '店铺名称', '搜索词', '词类型', '访客数']],
329
+ # "新品追踪": [['日期', '店铺名称', '商品id']],
330
+ # "直播分场次效果": [['场次id']],
331
+ },
332
332
  # "生意经3": {
333
333
  # "sku销量_按名称": [['日期', '店铺名称', '宝贝id', 'sku名称', '销售额']],
334
334
  # "sku销量_按商家编码": [['日期', '店铺名称', '宝贝id', 'sku编码', '销售额']],
mdbq/mysql/uploader.py CHANGED
@@ -46,7 +46,7 @@ def count_decimal_places(num_str: str) -> Tuple[int, int]:
46
46
 
47
47
 
48
48
  class StatementCache(dict):
49
- """简单LRU缓存实现,用于SQL语句缓存"""
49
+ """LRU缓存实现,用于SQL语句缓存"""
50
50
  def __init__(self, maxsize=100):
51
51
  super().__init__()
52
52
  self._maxsize = maxsize
@@ -71,10 +71,10 @@ class StatementCache(dict):
71
71
 
72
72
  class MySQLUploader:
73
73
  """
74
- MySQL数据上传工具类
74
+ MySQL数据上传
75
75
 
76
- 提供了一系列方法用于将数据上传到MySQL数据库,支持自动建表、分表、数据验证等功能。
77
- 使用连接池管理数据库连接,提供错误重试机制。
76
+ 用于将数据上传到MySQL数据库,支持自动建表、分表、数据验证等功能。
77
+ 使用连接池管理数据库连接。
78
78
  """
79
79
  def __init__(
80
80
  self,
@@ -128,9 +128,7 @@ class MySQLUploader:
128
128
  self._max_cached_statements = 100 # 用于控制 StatementCache 类中缓存的 SQL 语句数量,最多缓存 100 条 SQL 语句
129
129
  self._table_metadata_cache = {}
130
130
  self.metadata_cache_ttl = 300 # 5分钟缓存时间
131
-
132
- # 创建连接池
133
- self.pool = self._create_connection_pool()
131
+ self.pool = self._create_connection_pool() # 创建连接池
134
132
 
135
133
  def _create_connection_pool(self) -> PooledDB:
136
134
  """
@@ -584,6 +582,8 @@ class MySQLUploader:
584
582
  elif 'varchar' in column_type_lower:
585
583
  if isinstance(value, str):
586
584
  return value.replace('\\', '\\\\').replace("'", "\\'")
585
+ else:
586
+ return str(value)
587
587
  elif 'text' in column_type_lower:
588
588
  if isinstance(value, str):
589
589
  max_length = 65535
@@ -592,7 +592,9 @@ class MySQLUploader:
592
592
  '库': db_name, '表': table_name, '列': col_name, '原始值': f'{value[:50]}...', '截断后值': f'{value[:50]}...'
593
593
  })
594
594
  value = value[:max_length]
595
- return value.replace('\\', '\\\\').replace("'", "\\'")
595
+ return value.replace('\\', '\\\\').replace("'", "\\'")
596
+ else:
597
+ return str(value)
596
598
  elif 'json' in column_type_lower:
597
599
  return json.dumps(value) if value is not None else None
598
600
  else:
@@ -1200,6 +1202,9 @@ class MySQLUploader:
1200
1202
  '失败': total_failed
1201
1203
  })
1202
1204
 
1205
+ # 更新索引
1206
+ self._update_indexes(db_name, table_name, indexes)
1207
+
1203
1208
  @_execute_with_retry
1204
1209
  def _insert_data(
1205
1210
  self,
@@ -1492,25 +1497,6 @@ class MySQLUploader:
1492
1497
  logger.error('单行插入失败', {'库': db_name, '表': table_name, '错误': str(e)})
1493
1498
  return total_inserted, total_skipped, total_failed
1494
1499
 
1495
- def close(self) -> None:
1496
- """
1497
- 关闭连接池并清理资源
1498
- 这个方法会安全地关闭数据库连接池,并清理相关资源。
1499
- 建议结束时手动调用此方法。
1500
- :raises: 可能抛出关闭连接时的异常
1501
- """
1502
- try:
1503
- if hasattr(self, 'pool') and self.pool is not None:
1504
- try:
1505
- # self.pool.close() # PooledDB 没有 close 方法
1506
- self.pool = None
1507
- except Exception as e:
1508
- logger.warning('关闭连接池时出错', {'error': str(e)})
1509
- logger.debug('finished', {'uploader.py': '连接池关闭'})
1510
- except Exception as e:
1511
- logger.error('关闭连接池失败', {'uploader.py': str(e)})
1512
- raise
1513
-
1514
1500
  def _check_pool_health(self) -> bool:
1515
1501
  """
1516
1502
  检查连接池健康状态,防止连接泄露
@@ -1583,12 +1569,6 @@ class MySQLUploader:
1583
1569
  # pandas DataFrame
1584
1570
  return f"DataFrame shape={obj.shape}, head={obj.head(1).to_dict()}"
1585
1571
  return obj
1586
-
1587
- def __enter__(self):
1588
- return self
1589
-
1590
- def __exit__(self, exc_type, exc_val, exc_tb):
1591
- self.close()
1592
1572
 
1593
1573
  def _normalize_col(self, col: str) -> str:
1594
1574
  """
@@ -1597,6 +1577,112 @@ class MySQLUploader:
1597
1577
  safe = self._validate_identifier(col)
1598
1578
  return safe if self.case_sensitive else safe.lower()
1599
1579
 
1580
+ def _update_indexes(self, db_name: str, table_name: str, indexes: Optional[List[str]]):
1581
+ """
1582
+ 更新索引,避免重复添加或更新,同时注意大小写一致性。
1583
+
1584
+ :param db_name: 数据库名
1585
+ :param table_name: 表名
1586
+ :param indexes: 需要更新的索引列列表
1587
+ """
1588
+ if not indexes:
1589
+ return
1590
+
1591
+ # 规范化索引列名
1592
+ normalized_indexes = [self._normalize_col(idx) for idx in indexes]
1593
+
1594
+ # 获取现有索引
1595
+ try:
1596
+ existing_indexes = self._get_existing_indexes(db_name, table_name)
1597
+ except Exception as e:
1598
+ logger.error('获取现有索引时发生错误', {'库': db_name, '表': table_name, '错误': str(e)})
1599
+ raise
1600
+
1601
+ # 获取表中现有的列名
1602
+ try:
1603
+ existing_columns = self._get_existing_indexes(db_name, table_name)
1604
+ except Exception as e:
1605
+ logger.error('获取现有列时发生错误', {'库': db_name, '表': table_name, '错误': str(e)})
1606
+ raise
1607
+
1608
+ # 找出需要添加的索引
1609
+ indexes_to_add = [idx for idx in normalized_indexes if idx not in existing_indexes and idx in existing_columns]
1610
+
1611
+ # 添加新索引
1612
+ for idx in indexes_to_add:
1613
+ try:
1614
+ self._add_index(db_name, table_name, idx)
1615
+ except Exception as e:
1616
+ logger.error('添加索引时发生错误', {'库': db_name, '表': table_name, '列': idx, '错误': str(e)})
1617
+ raise
1618
+
1619
+ def _get_existing_indexes(self, db_name: str, table_name: str) -> Set[str]:
1620
+ """
1621
+ 获取表中现有的索引列名。
1622
+
1623
+ :param db_name: 数据库名
1624
+ :param table_name: 表名
1625
+ :return: 现有索引列名的集合
1626
+ """
1627
+ sql = """
1628
+ SELECT COLUMN_NAME
1629
+ FROM INFORMATION_SCHEMA.STATISTICS
1630
+ WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s
1631
+ """
1632
+ existing_indexes = set()
1633
+ try:
1634
+ with self._get_connection() as conn:
1635
+ with conn.cursor() as cursor:
1636
+ cursor.execute(sql, (db_name, table_name))
1637
+ existing_indexes = {row['COLUMN_NAME'] for row in cursor.fetchall()}
1638
+ except Exception as e:
1639
+ logger.error('获取现有索引失败', {'库': db_name, '表': table_name, '错误': str(e)})
1640
+ raise
1641
+ return existing_indexes
1642
+
1643
+ def _add_index(self, db_name: str, table_name: str, column: str):
1644
+ """
1645
+ 添加索引到指定列。
1646
+
1647
+ :param db_name: 数据库名
1648
+ :param table_name: 表名
1649
+ :param column: 需要添加索引的列名
1650
+ """
1651
+ sql = f'ALTER TABLE `{db_name}`.`{table_name}` ADD INDEX `idx_{column}` (`{column}`)'
1652
+ try:
1653
+ with self._get_connection() as conn:
1654
+ with conn.cursor() as cursor:
1655
+ cursor.execute(sql)
1656
+ conn.commit()
1657
+ logger.debug('已为列创建索引', {'库': db_name, '表': table_name, '列': column})
1658
+ except Exception as e:
1659
+ logger.error('创建索引失败', {'库': db_name, '表': table_name, '列': column, '错误': str(e)})
1660
+ raise
1661
+
1662
+ def __enter__(self):
1663
+ return self
1664
+
1665
+ def close(self) -> None:
1666
+ """
1667
+ 关闭连接池并清理资源
1668
+ 这个方法会安全地关闭数据库连接池,并清理相关资源。
1669
+ 建议结束时手动调用此方法。
1670
+ :raises: 可能抛出关闭连接时的异常
1671
+ """
1672
+ try:
1673
+ if hasattr(self, 'pool') and self.pool is not None:
1674
+ try:
1675
+ self.pool = None
1676
+ except Exception as e:
1677
+ logger.warning('关闭连接池时出错', {'error': str(e)})
1678
+ logger.debug('finished', {'uploader.py': '连接池关闭'})
1679
+ except Exception as e:
1680
+ logger.error('关闭连接池失败', {'uploader.py': str(e)})
1681
+ raise
1682
+
1683
+ def __exit__(self, exc_type, exc_val, exc_tb):
1684
+ self.close()
1685
+
1600
1686
 
1601
1687
  def main():
1602
1688
  dir_path = os.path.expanduser("~")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.5
3
+ Version: 4.0.7
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,7 +1,7 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=K0JdqT-aY_eW77ySyyxnpc599EoZ9CKOLZg_w5AvAnM,17
2
+ mdbq/__version__.py,sha256=P_9fJTmoyR1p3XJm_JFEO3dJnSh6VG8WMZKVuiE0his,17
3
3
  mdbq/aggregation/__init__.py,sha256=EeDqX2Aml6SPx8363J-v1lz0EcZtgwIBYyCJV6CcEDU,40
4
- mdbq/aggregation/query_data.py,sha256=3GBdX0HWKvQ-B3NiZE_hzWbJ7sqClzCd8KTvXpVPnZ4,170452
4
+ mdbq/aggregation/query_data.py,sha256=Y9AC6xJgZmyZAezz_faRxwo80ev1MxvFEfvzLT2mP_U,165403
5
5
  mdbq/config/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
6
6
  mdbq/config/config.py,sha256=eaTfrfXQ65xLqjr5I8-HkZd_jEY1JkGinEgv3TSLeoQ,3170
7
7
  mdbq/log/__init__.py,sha256=Mpbrav0s0ifLL7lVDAuePEi1hJKiSHhxcv1byBKDl5E,15
@@ -11,8 +11,8 @@ mdbq/mysql/__init__.py,sha256=A_DPJyAoEvTSFojiI2e94zP0FKtCkkwKP1kYUCSyQzo,11
11
11
  mdbq/mysql/deduplicator.py,sha256=8v3MC6TJ0YEiExWrTP9OXAxTYnL9XbpYL2vWaER1h2M,73099
12
12
  mdbq/mysql/mysql.py,sha256=pDg771xBugCMSTWeskIFTi3pFLgaqgyG3smzf-86Wn8,56772
13
13
  mdbq/mysql/s_query.py,sha256=RnVCwMQ_n9PcAimbMWbHe9k8eil8shtCfa3LwLBZi6c,41909
14
- mdbq/mysql/unique_.py,sha256=Wgqq_PjAAD757JTa10wjYaJgssZ_C_ypU6DW56jbuyw,21074
15
- mdbq/mysql/uploader.py,sha256=bYE_VGTeEigpRFYvZ9Ob3A9vxq21NuOdrXFkv8Bm_p8,74919
14
+ mdbq/mysql/unique_.py,sha256=LZKa1LXQdy_aO78ekO1Ul2MNA-k3Sz4-4W5nF2gD8AM,21068
15
+ mdbq/mysql/uploader.py,sha256=OtLWoGflmndc7z0A1aK1Qj-XVkWtGyN4Y5YnLsRovJc,78215
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=l3zBK7wrZl0oO42-_UGylyatnIp_SBw8wDDvof9fht4,23534
26
26
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
27
27
  mdbq/spider/aikucun.py,sha256=hPRzLQvFIF4ibN8aP3Dg_ru5meac90faPyzOB22cj-o,20965
28
- mdbq-4.0.5.dist-info/METADATA,sha256=boklJ7iCN4Uh-Czst1DiQlPrKKSawDIYknmipAd9w5A,363
29
- mdbq-4.0.5.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
30
- mdbq-4.0.5.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
31
- mdbq-4.0.5.dist-info/RECORD,,
28
+ mdbq-4.0.7.dist-info/METADATA,sha256=wEh7EHOSkdCJOlxYplcNOVPax8CBXEgGM3eKDL-EQaE,363
29
+ mdbq-4.0.7.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
30
+ mdbq-4.0.7.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
31
+ mdbq-4.0.7.dist-info/RECORD,,
File without changes