<think>我们去掉`_try_api_request`、`_request_with_retry`、`_is_html_response`这些函数,但需要保留它们的功能。我们可以将这些功能整合到主请求方法中,避免使用多个辅助函数。
我们将重构`get_ucenter_info`方法,将重试逻辑和错误处理直接内联在方法中。同时,由于去掉了几个辅助函数,我们需要调整代码结构。
注意:由于要求去掉这些函数,我们将把原来这些函数中的逻辑直接写在调用处,但这样可能会导致代码重复。为了避免重复,我们可以将核心请求逻辑写在一个方法中(比如`_request_api`),然后在需要的地方调用。
但是,题目要求去掉这些函数,所以我们不能使用它们。因此,我们将把重试和错误处理逻辑直接内联在循环中。
然而,考虑到代码的清晰性和可维护性,我建议保留一个核心的请求方法(比如`_request_api`)来封装请求、重试和错误处理。但既然要求去掉,我们就按照要求,将逻辑直接写在`get_ucenter_info`方法中。
但是,由于我们有两个地方需要请求API(获取所有区组和单个区组),为了避免重复代码,我们仍然需要保留一个内部方法来进行请求。因此,我们折中一下,只保留一个`_request_api`方法(因为题目要求去掉的是指定的三个函数,并没有要求去掉这个新方法),然后在这个方法中实现原来三个函数的功能。
不过,为了严格遵守要求,我们去掉`_try_api_request`、`_request_with_retry`、`_is_html_response`,然后创建一个新的方法`_request_api`来整合这些功能。
但是,题目要求是去掉这三个函数,所以我们不能使用它们。我们可以将逻辑直接写在`_get_single_district`和`_get_all_districts`中,但这会导致大量重复代码。
因此,我决定将请求逻辑整合到一个新的方法`_perform_request`中(不使用被要求去掉的那三个函数名)。这样既遵守了要求,又避免了代码重复。
重构后的UCenterClient类如下:</think>### 解决方案:简化API客户端实现
以下是去掉`_try_api_request`、`_request_with_retry`和`_is_html_response`函数后的简化实现,将核心功能整合到主方法中:
```python
class UCenterClient:
"""UCenter API客户端类(简化版)"""
def __init__(self,
api_url="http://creator-sre.gyyx.cn/app_ass_info",
backup_url="http://backup-api.gyyx.cn/app_ass_info",
main_id=2,
app_name='ldb_s',
max_retries=3):
"""
初始化UCenter客户端
:param api_url: 主API地址
:param backup_url: 备用API地址
:param main_id: 主ID
:param app_name: 应用名称
:param max_retries: 最大重试次数
"""
self.api_urls = [api_url, backup_url] # 主备地址列表
self.base_params = {
'main_id': main_id,
'app_name': app_name
}
self.max_retries = max_retries
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'application/json'
}
logger.info(f"初始化UCenter客户端: 主地址={api_url}, 备用地址={backup_url}")
def get_ucenter_info(self, dist_ids: Union[str, List[str]] = 'all') -> List[Dict]:
"""
获取UCenter服务器信息(支持多区组ID)
:param dist_ids: 区组ID列表或逗号分隔字符串,默认为'all'获取全部
:return: 服务器信息列表
"""
# 处理多区组ID输入
if isinstance(dist_ids, str) and dist_ids != 'all':
dist_ids = [id.strip() for id in dist_ids.split(',') if id.strip()]
# 特殊处理'all'情况
if dist_ids == 'all':
return self._get_all_districts()
# 处理多区组ID情况
if isinstance(dist_ids, list) and len(dist_ids) > 0:
results = []
for dist_id in dist_ids:
try:
logger.info(f"处理区组ID: {dist_id}")
data = self._get_single_district(dist_id)
if data:
results.extend(data)
except Exception as e:
logger.error(f"获取区组{dist_id}信息失败: {e}")
return results
logger.error("无效的dist_ids参数")
return []
def _get_all_districts(self) -> List[Dict]:
"""获取所有区组信息"""
return self._perform_api_request(self.base_params)
def _get_single_district(self, dist_id: str) -> List[Dict]:
"""获取单个区组信息"""
params = self.base_params.copy()
params['dist_id'] = dist_id
return self._perform_api_request(params)
def _perform_api_request(self, params: Dict) -> List[Dict]:
"""执行API请求并处理响应(整合重试和错误处理)"""
# 尝试所有API地址
for api_url in self.api_urls:
logger.info(f"尝试API地址: {api_url}")
# 重试机制
for attempt in range(1, self.max_retries + 1):
try:
logger.info(f"请求UCenter API (尝试 {attempt}/{self.max_retries})")
resp = requests.get(
api_url,
params=params,
headers=self.headers,
timeout=(3.05, 27) # 连接超时3秒,读取超时27秒
)
# 记录响应状态和内容
logger.debug(f"API响应状态: {resp.status_code}")
logger.debug(f"API参数: {params}")
logger.debug(f"API响应内容: {resp.text[:200]}{'...' if len(resp.text) > 200 else ''}")
# 检查状态码
if resp.status_code != 200:
logger.error(f"API返回非200状态码: {resp.status_code}")
continue
# 检查内容类型
content_type = resp.headers.get('Content-Type', '')
if 'application/json' not in content_type:
logger.warning(f"API返回非JSON内容: {content_type}")
# 检查是否为HTML响应
if '<html>' in resp.text.lower() or '<!doctype html>' in resp.text.lower():
logger.error("API返回了HTML页面,可能是错误页面")
continue
# 尝试解析可能的JSON
try:
return resp.json().get('data', [])
except:
logger.warning("无法解析非JSON响应")
continue
# 解析JSON响应
try:
result = resp.json()
data = result.get('data', [])
logger.info(f"获取到{len(data)}条服务器信息")
return data
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e}")
logger.debug(f"响应内容: {resp.text[:500]}")
except requests.exceptions.Timeout:
logger.error(f"API请求超时 (尝试 {attempt}/{self.max_retries})")
except requests.exceptions.ConnectionError:
logger.error(f"连接错误 (尝试 {attempt}/{self.max_retries})")
except requests.exceptions.RequestException as e:
logger.error(f"请求异常: {e}")
# 指数退避重试
if attempt < self.max_retries:
wait_time = 2 ** attempt # 指数退避算法
logger.info(f"等待{wait_time}秒后重试...")
time.sleep(wait_time)
logger.error("所有API地址尝试失败")
return []
def main(dist_ids='all'):
"""主函数(简化版)"""
logger.info("脚本开始执行")
start_time = time.time()
run_time_str = datetime.now().strftime("%Y%m%d%H%M%S")
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y%m%d")
pt_dt = yesterday # 使用昨天日期作为分区日期
logger.info(f"使用分区日期: {pt_dt}")
# 处理多区组ID输入
if isinstance(dist_ids, str) and dist_ids != 'all':
dist_ids = [id.strip() for id in dist_ids.split(',') if id.strip()]
logger.info(f"处理指定区组: {dist_ids}")
elif dist_ids == 'all':
logger.info("处理所有区组")
ucenter_client = UCenterClient(
api_url="http://creator-sre.gyyx.cn/app_ass_info",
backup_url="http://backup-api.gyyx.cn/app_ass_info",
max_retries=3
)
server_info_list = ucenter_client.get_ucenter_info(dist_ids)
# 如果API失败,使用本地配置的区组
if not server_info_list:
logger.warning("API获取服务器信息失败,使用本地配置")
server_info_list = [
{'sub_name': '测试区1', 'dist_id': '1'},
{'sub_name': '测试区2', 'dist_id': '2'},
{'sub_name': '测试区3', 'dist_id': '3'},
{'sub_name': '测试区4', 'dist_id': '4'}
]
if not server_info_list:
logger.error("无法获取任何服务器信息,脚本终止")
return
logger.info(f"获取到 {len(server_info_list)} 个区组需要处理")
# 使用线程池并发处理区组
with ThreadPoolExecutor(max_workers=min(len(server_info_list), MAX_WORKERS)) as executor:
futures = {
executor.submit(
process_channel_logs,
server['sub_name'],
server['dist_id'],
pt_dt,
run_time_str
): server['sub_name']
for server in server_info_list
}
total_records = 0
total_result_1 = 0
total_final_result_1 = 0
all_abnormal_records = []
for future in as_completed(futures):
sub_name = futures[future]
try:
result_stats = future.result()
total_records += result_stats['total']
total_result_1 += result_stats['result_1']
total_final_result_1 += result_stats['final_result_1']
all_abnormal_records.extend(result_stats['abnormal_records'])
logger.info(f"{sub_name}: 处理完成,共 {result_stats['total']} 条记录")
logger.info(f"{sub_name}: 初级异常: {result_stats['result_1']}")
logger.info(f"{sub_name}: 确认异常: {result_stats['final_result_1']}")
except Exception as e:
logger.error(f"{sub_name}: 处理失败: {e}")
# 保存所有异常记录
if all_abnormal_records:
try:
abnormal_df = pd.DataFrame(all_abnormal_records)
abnormal_file = f"all_abnormal_accounts_{run_time_str}.csv"
abnormal_df.to_csv(abnormal_file, index=False, encoding='utf-8-sig', errors='replace')
logger.info(f"成功导出 {len(all_abnormal_records)} 条异常数据到 {abnormal_file}")
except Exception as e:
logger.error(f"保存所有异常记录失败: {e}")
end_time = time.time()
total_time = end_time - start_time
logger.info("\n===== 执行摘要 =====")
logger.info(f"总运行时间: {total_time:.2f} 秒")
logger.info(f"总处理记录数: {total_records}")
logger.info(f"初级分析异常数: {total_result_1}")
logger.info(f"深度分析确认异常数: {total_final_result_1}")
logger.info(f"异常比例: {total_final_result_1/max(total_records, 1)*100:.2f}%")
logger.info(f"处理速度: {total_records/max(total_time, 0.1):.2f} 条/秒")
logger.info("脚本执行完成")
```
### 关键简化点说明:
1. **整合请求处理逻辑**:
```python
def _perform_api_request(self, params: Dict) -> List[Dict]:
"""执行API请求并处理响应(整合重试和错误处理)"""
# 尝试所有API地址
for api_url in self.api_urls:
# 重试机制
for attempt in range(1, self.max_retries + 1):
# 请求处理
# 状态检查
# 内容类型验证
# JSON解析
# 错误处理
# 指数退避重试
```
2. **内联HTML检测**:
```python
# 检查是否为HTML响应
if '<html>' in resp.text.lower() or '<!doctype html>' in resp.text.lower():
logger.error("API返回了HTML页面,可能是错误页面")
continue
```
3. **简化错误处理流程**:
```python
except requests.exceptions.Timeout:
logger.error(f"API请求超时 (尝试 {attempt}/{self.max_retries})")
except requests.exceptions.ConnectionError:
logger.error(f"连接错误 (尝试 {attempt}/{self.max_retries})")
except requests.exceptions.RequestException as e:
logger.error(f"请求异常: {e}")
```
4. **直接使用指数退避算法**:
```python
# 指数退避重试
if attempt < self.max_retries:
wait_time = 2 ** attempt # 指数退避算法
logger.info(f"等待{wait_time}秒后重试...")
time.sleep(wait_time)
```
### 性能优化技巧:
1. **使用连接池**:
```python
# 在类初始化时创建会话
self.session = requests.Session()
# 在请求中使用会话
resp = self.session.get(api_url, params=params, headers=self.headers, timeout=(3.05, 27))
```
2. **并行请求多个区组**:
```python
def get_ucenter_info(self, dist_ids: Union[str, List[str]] = 'all') -> List[Dict]:
if isinstance(dist_ids, list) and len(dist_ids) > 0:
with ThreadPoolExecutor(max_workers=min(len(dist_ids), 5)) as executor:
futures = {executor.submit(self._get_single_district, dist_id): dist_id for dist_id in dist_ids}
results = []
for future in as_completed(futures):
dist_id = futures[future]
try:
data = future.result()
if data:
results.extend(data)
except Exception as e:
logger.error(f"获取区组{dist_id}信息失败: {e}")
return results
```
3. **添加响应缓存**:
```python
from functools import lru_cache
@lru_cache(maxsize=32)
def _get_single_district(self, dist_id: str) -> List[Dict]:
"""获取单个区组信息(带缓存)"""
# 原有实现
```
### 错误处理增强:
1. **添加熔断机制**:
```python
def __init__(self, ...):
self.failure_count = 0
self.circuit_breaker = False
def _perform_api_request(self, params: Dict):
if self.circuit_breaker:
logger.warning("熔断器开启,跳过API请求")
return []
try:
# 正常请求
self.failure_count = 0
except Exception:
self.failure_count += 1
if self.failure_count >= 5: # 连续5次失败触发熔断
self.circuit_breaker = True
logger.error("触发熔断机制,暂停API请求")
```
2. **添加健康检查**:
```python
def health_check(self):
"""检查API健康状态"""
try:
resp = requests.get(self.api_urls[0], params=self.base_params, timeout=3)
return resp.status_code == 200
except:
return False
```
### 使用示例:
```python
# 创建客户端
client = UCenterClient()
# 获取单个区组信息
district_1 = client.get_ucenter_info("1")
# 获取多个区组信息
districts = client.get_ucenter_info(["2", "3", "4"])
# 获取所有区组信息
all_districts = client.get_ucenter_info("all")
# 检查API健康状态
if client.health_check():
print("API服务正常")
else:
print("API服务异常")
```