mengtaoguo commited on
Commit
d34acc7
·
verified ·
1 Parent(s): a4dd1be

Update bs4_quality_scanner.py

Browse files
Files changed (1) hide show
  1. bs4_quality_scanner.py +76 -33
bs4_quality_scanner.py CHANGED
@@ -8,24 +8,58 @@ url = os.environ.get("SUPABASE_URL")
8
  key = os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
9
  supabase = create_client(url, key)
10
 
11
- def analyze_value(val):
12
- s = str(val or "").strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  is_empty = not s or s.startswith("[Empty") or s.lower() == "none"
 
 
 
 
14
  has_garbage = bool(re.search(r'[ÂÃÅÐÑÒÓÔÕÖר]', s))
15
- return is_empty, has_garbage, len(s)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  def run_bs4_audit():
18
- # --- 补全后的完整字段列表 ---
19
- target_columns = [
20
- 'latin_name', 'common_name', 'family', 'title',
21
- 'usda_hardiness', 'known_hazards', 'habitats', 'range',
22
- 'physical_characteristics', 'edible_uses', 'medicinal_uses',
23
- 'other_uses', 'cultivation_details', 'propagation'
24
- ]
25
-
26
- stats = {col: {'filled': 0, 'empty': 0, 'dirty': 0, 'len_sum': 0, 'score_sum': 0} for col in target_columns}
27
 
28
- log(f"\n[{time.strftime('%H:%M:%S')}] 📋 开始全量巡检...")
29
 
30
  offset = 0
31
  limit = 200
@@ -35,50 +69,59 @@ def run_bs4_audit():
35
 
36
  for row in res.data:
37
  for col in target_columns:
38
- is_emp, is_dirty, length = analyze_value(row.get(col))
39
- stats[col]['filled'] += 0 if is_emp else 1
40
- stats[col]['empty'] += 1 if is_emp else 0
41
- stats[col]['dirty'] += 1 if is_dirty else 0
42
- stats[col]['len_sum'] += length
43
- stats[col]['score_sum'] += 0 if is_emp else (70 if is_dirty else 100)
 
 
 
 
 
 
 
 
 
44
 
45
  offset += len(res.data)
46
 
47
- # --- 输出控制台报告 ---
48
- log("\n" + "="*80)
49
- log(f"{'字段名称':<25} | {'填充':<8} | {'空':<8} | {'均长':<8} | {'分'}")
50
- log("-" * 80)
51
 
52
  report_batch = []
53
- # 排序:分从低到高,让你一眼看到最惨的字段
54
  sorted_cols = sorted(stats.items(), key=lambda x: (x[1]['score_sum']/offset if offset>0 else 0))
55
 
56
  for col, data in sorted_cols:
57
  avg_len = int(data['len_sum'] / data['filled']) if data['filled'] > 0 else 0
58
- score = round(data['score_sum'] / offset, 2)
59
 
60
- log(f"{col:<25} | {data['filled']:<8} | {data['empty']:<8} | {avg_len:<8} | {score}")
61
 
 
62
  report_batch.append({
63
  "field_name": col,
64
  "total_records": offset,
65
  "filled_count": data['filled'],
66
  "empty_count": data['empty'],
67
- "dirty_count": data['dirty'],
68
  "avg_char_count": avg_len,
69
- "quality_score": score,
70
  "updated_at": "now()"
71
  })
72
 
73
- # --- 回写数据库 ---
74
  supabase.table("bs4_field_quality_report").upsert(report_batch).execute()
75
- log("="*80)
76
- log(f"✅ 报告已同步至 SQL 大盘。下次巡检将在 10 分钟后...")
77
 
78
  if __name__ == "__main__":
79
  while True:
80
  try:
81
  run_bs4_audit()
82
  except Exception as e:
83
- log(f"❌ 运行错: {e}")
84
- time.sleep(600) # 10分钟间隔
 
8
  key = os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
9
  supabase = create_client(url, key)
10
 
11
+ # --- 定义每个字段的“正常范围”和“异常规则” ---
12
+ # min_len: 低于此长度疑似抓取不全; max_len: 高于此长度疑似抓错位置或吞了后面内容
13
+ FIELD_RULES = {
14
+ 'latin_name': {'min': 5, 'max': 80},
15
+ 'common_name': {'min': 2, 'max': 100},
16
+ 'family': {'min': 3, 'max': 50},
17
+ 'title': {'min': 10, 'max': 200},
18
+ 'usda_hardiness': {'min': 1, 'max': 15},
19
+ 'known_hazards': {'min': 4, 'max': 2000},
20
+ 'habitats': {'min': 5, 'max': 1000},
21
+ 'range': {'min': 5, 'max': 1000},
22
+ 'physical_characteristics': {'min': 20, 'max': 5000},
23
+ 'edible_uses': {'min': 10, 'max': 5000},
24
+ 'medicinal_uses': {'min': 10, 'max': 5000},
25
+ 'other_uses': {'min': 10, 'max': 5000},
26
+ 'cultivation_details': {'min': 20, 'max': 8000},
27
+ 'propagation': {'min': 20, 'max': 4000}
28
+ }
29
+
30
+ def analyze_value_pro(field_name, val):
31
+ """增强版检测:判定空值、乱码及离群异常"""
32
+ raw_s = str(val or "")
33
+ s = raw_s.strip()
34
+
35
  is_empty = not s or s.startswith("[Empty") or s.lower() == "none"
36
+ if is_empty:
37
+ return True, False, 0
38
+
39
+ # 1. 乱码检测
40
  has_garbage = bool(re.search(r'[ÂÃÅÐÑÒÓÔÕÖר]', s))
41
+
42
+ # 2. 离群值/异常检测 (Outlier)
43
+ is_outlier = False
44
+ length = len(s)
45
+
46
+ if field_name in FIELD_RULES:
47
+ rule = FIELD_RULES[field_name]
48
+ # 长度异常判定
49
+ if length < rule['min'] or length > rule['max']:
50
+ is_outlier = True
51
+ # 结构异常判定:如果包含超过 5 个连续空格,或者包含 HTML 标签残余
52
+ if " " in raw_s or "<div>" in raw_s.lower() or "<span>" in raw_s.lower():
53
+ is_outlier = True
54
+
55
+ return False, (has_garbage or is_outlier), length
56
 
57
  def run_bs4_audit():
58
+ target_columns = list(FIELD_RULES.keys())
59
+ # 增加 outlier 统计项
60
+ stats = {col: {'filled': 0, 'empty': 0, 'outlier': 0, 'len_sum': 0, 'score_sum': 0} for col in target_columns}
 
 
 
 
 
 
61
 
62
+ log(f"\n[{time.strftime('%H:%M:%S')}] 🛡️ 启动离群值扫描 (深度体模式)...")
63
 
64
  offset = 0
65
  limit = 200
 
69
 
70
  for row in res.data:
71
  for col in target_columns:
72
+ is_emp, is_bad, length = analyze_value_pro(col, row.get(col))
73
+
74
+ if is_emp:
75
+ stats[col]['empty'] += 1
76
+ else:
77
+ stats[col]['filled'] += 1
78
+ stats[col]['len_sum'] += length
79
+ if is_bad:
80
+ stats[col]['outlier'] += 1
81
+
82
+ # 评分逻辑优化:离群异常和乱码按 50 分计(警告级),空值 0 分
83
+ if is_emp: score = 0
84
+ elif is_bad: score = 50
85
+ else: score = 100
86
+ stats[col]['score_sum'] += score
87
 
88
  offset += len(res.data)
89
 
90
+ # --- 控制台可视化报告 ---
91
+ log("\n" + "="*95)
92
+ log(f"{'字段名称':<25} | {'填充':<6} | {'异常/离群':<10} | {'':<6} | {'均长':<6} | {'健康分'}")
93
+ log("-" * 95)
94
 
95
  report_batch = []
96
+ # 按健康分从低到高排序
97
  sorted_cols = sorted(stats.items(), key=lambda x: (x[1]['score_sum']/offset if offset>0 else 0))
98
 
99
  for col, data in sorted_cols:
100
  avg_len = int(data['len_sum'] / data['filled']) if data['filled'] > 0 else 0
101
+ total_score = round(data['score_sum'] / offset, 2)
102
 
103
+ log(f"{col:<25} | {data['filled']:<6} | {data['outlier']:<10} | {data['empty']:<6} | {avg_len:<6} | {total_score}")
104
 
105
+ # 为了兼容你之前的数据库表,我们将 outlier 存入原本的 dirty_count 字段,或者你可以扩充字段
106
  report_batch.append({
107
  "field_name": col,
108
  "total_records": offset,
109
  "filled_count": data['filled'],
110
  "empty_count": data['empty'],
111
+ "dirty_count": data['outlier'], # 现在这个字段代表广义的“坏数据”
112
  "avg_char_count": avg_len,
113
+ "quality_score": total_score,
114
  "updated_at": "now()"
115
  })
116
 
 
117
  supabase.table("bs4_field_quality_report").upsert(report_batch).execute()
118
+ log("="*95)
119
+ log(f"✅ 深度巡检完成异常记录已标记,下次巡检 10 分钟后...")
120
 
121
  if __name__ == "__main__":
122
  while True:
123
  try:
124
  run_bs4_audit()
125
  except Exception as e:
126
+ log(f"❌ 运行错: {e}")
127
+ time.sleep(600)