Files
F6--/张阳脚本/竞品系统数据导出/快修哥历史维修记录_获取明细.py
2026-06-02 15:08:26 +08:00

195 lines
8.4 KiB
Python

import requests
import time
import os
import datetime
import json
import urllib3
from openpyxl import Workbook, load_workbook
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
DETAIL_HEADERS = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyODg1MCwibmJmIjoxNzgwMTI1MjUwLCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.fiumtw9xCYWj-euP_LCQdrT9Wd4OsVopuHQpt3Qaae8En4lPA7aaqOfpXVF8gwxtoayfpjATtIaMomkUcnglYqZBCUTC50bc6IHYFgRrYl_7h4g9BCIHwGEswYbvFiQfAB5Q4gLFptzEJ1W2pjHnrNgmum5syQR3fsR5_25OayQ_KI6HWdtR3wReuInl0PQcDJs5jxdeId2ViDuYnl1x7TDFoIIwPov46H4KViUrBKFwr6iaTcNwrpl0thPBZjLJ8StTj50JwL1tRe71LbHkavD3MGsqs9_ulJaFZgyu2UYpl6cO0Let2zk9w-k2echh7P1ajQg7LfO2hEJ-c6RHXg',
'Content-Type': 'application/json;charset=utf-8',
'Connection': 'keep-alive',
'Origin': 'http://www.kuaixiuge.com',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://www.kuaixiuge.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
}
MAX_DETAIL = 99999
OUTPUT_DIR = r'D:\Idea Project\F6+宜搭+其它(1)\张阳脚本\文件输出'
INPUT_FILE = os.path.join(OUTPUT_DIR, '快修哥历史维修记录_合并去重_无明细_20260530_140130.xlsx')
OUTPUT_FILE = os.path.join(OUTPUT_DIR, f'快修哥历史维修记录_含明细_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx')
PROGRESS_FILE = os.path.join(OUTPUT_DIR, os.path.basename(INPUT_FILE).replace('.xlsx', '_进度.json'))
SERVICE_COLUMNS = [
'serviceid', 'servicename', 'servicetype', 'servicenumber',
'serviceprice', 'servicehj', 'timefee', 'isinquiry', 'isinterimpurchase',
]
WXXM_COLUMNS = [
'xmdm', 'xmname', 'xmprice', 'hour',
]
PARTS_COLUMNS = [
'pcode', 'pname', 'price', 'count', 'amount', 'unit',
]
def fetch_detail(gch, cid='24574'):
params = {'cid': cid, 'gch': gch}
url = 'http://saas.hks360.com:84/WXinfoservice/GetAllServices'
try:
requests.options(url, params=params, headers=DETAIL_HEADERS, verify=False, timeout=10)
time.sleep(0.15)
resp = requests.get(url, params=params, headers=DETAIL_HEADERS, verify=False, timeout=15)
if resp.status_code == 200:
data = resp.json()
rows = (data.get('rows') or []) if isinstance(data, dict) else []
return rows, True
print(f" [X] HTTP {resp.status_code} gch={gch}")
return [], False
except Exception as e:
print(f" [X] 异常 gch={gch}, {e}")
return [], False
def parse_detail_rows(detail_rows):
result = []
for row in detail_rows:
service_info = {k: row.get(k) for k in SERVICE_COLUMNS}
wxxm_list = row.get('wxxmList', []) or []
parts_list = row.get('wxinfopartsList', []) or []
has_any = False
if wxxm_list:
has_any = True
for xm in wxxm_list:
merged = dict(service_info)
for k in WXXM_COLUMNS:
merged['xm_' + k] = xm.get(k)
result.append(merged)
if parts_list:
has_any = True
for part in parts_list:
merged = dict(service_info)
for k in PARTS_COLUMNS:
merged['part_' + k] = part.get(k)
result.append(merged)
if not has_any:
result.append(service_info)
return result
DETAIL_HEADER = (['明细_serviceid', '明细_服务名称', '明细_服务类型', '明细_服务数量',
'明细_服务单价', '明细_服务合计', '明细_工时费', '明细_是否询价', '明细_是否中间采购',
'明细_项目代码', '明细_项目名称', '明细_项目价格', '明细_项目工时',
'明细_配件编码', '明细_配件名称', '明细_配件单价', '明细_配件数量', '明细_配件金额', '明细_配件单位'])
DETAIL_XM_KEYS = ['serviceid', 'servicename', 'servicetype', 'servicenumber',
'serviceprice', 'servicehj', 'timefee', 'isinquiry', 'isinterimpurchase',
'xm_xmdm', 'xm_xmname', 'xm_xmprice', 'xm_hour']
DETAIL_PART_KEYS = ['part_pcode', 'part_pname', 'part_price', 'part_count', 'part_amount', 'part_unit']
DETAIL_OUTPUT_KEYS = DETAIL_XM_KEYS + DETAIL_PART_KEYS
def _save_xlsx(main_data, all_detail_rows, headers_row, output_path):
expanded_rows = []
for main_row, detail_items in zip(main_data, all_detail_rows):
if detail_items:
for d in detail_items:
row_vals = list(main_row) + [d.get(k, '') for k in DETAIL_OUTPUT_KEYS]
expanded_rows.append(row_vals)
else:
expanded_rows.append(list(main_row) + [''] * len(DETAIL_OUTPUT_KEYS))
out_wb = Workbook()
out_ws = out_wb.active
out_ws.title = '历史维修记录(含明细)'
out_ws.append(list(headers_row) + list(DETAIL_HEADER))
for row_vals in expanded_rows:
out_ws.append(row_vals)
for cell in out_ws['A']:
cell.number_format = '@'
out_ws.auto_filter.ref = out_ws.dimensions
out_wb.save(output_path)
print(f"数据已保存至 {output_path} ({len(expanded_rows)} 行)")
print(f"读取输入文件: {INPUT_FILE}")
wb = load_workbook(INPUT_FILE)
ws = wb.active
headers_row = [cell.value for cell in ws[1]]
main_data = []
gch_list = []
for row in ws.iter_rows(min_row=2, values_only=True):
row_list = list(row)
gch_str = str(int(row_list[0])) if isinstance(row_list[0], float) else str(row_list[0])
row_list[0] = gch_str
main_data.append(row_list)
gch_list.append(gch_str)
print(f"{len(gch_list)} 条工单待获取明细,本次取前 {MAX_DETAIL}")
all_detail_rows = [None] * len(gch_list)
fetch_count = min(len(gch_list), MAX_DETAIL)
start_idx = 0
if os.path.exists(PROGRESS_FILE):
with open(PROGRESS_FILE, 'r', encoding='utf-8') as f:
saved = json.load(f)
saved_data = saved.get('detail_data', [])
if len(saved_data) == len(gch_list):
for idx, item in enumerate(saved_data):
if idx < len(all_detail_rows) and item is not None:
all_detail_rows[idx] = item
start_idx = saved.get('next_idx', 0)
else:
print(f"[!] 进度文件与输入不匹配 (进度{len(saved_data)}条, 输入{len(gch_list)}条), 从头开始")
if start_idx >= fetch_count:
print(f"[!] 已完成全部 {fetch_count} 条, 无需重新请求")
else:
print(f"[断点续传] 从第 {start_idx + 1}/{fetch_count} 条继续 (已完成 {start_idx} 条)")
consecutive_fails = 0
SAVE_ON_FAILS = 10
SLEEP_SEC = 0.8
def save_progress():
os.makedirs(OUTPUT_DIR, exist_ok=True)
with open(PROGRESS_FILE, 'w', encoding='utf-8') as f:
json.dump({'next_idx': i + 1, 'detail_data': all_detail_rows}, f, ensure_ascii=False)
for i in range(start_idx, fetch_count):
gch = gch_list[i]
if (i + 1) % 10 == 0 or i == fetch_count - 1:
print(f"正在获取明细 {i + 1}/{fetch_count} ... (连续失败: {consecutive_fails})")
time.sleep(SLEEP_SEC)
raw, ok = fetch_detail(gch)
all_detail_rows[i] = parse_detail_rows(raw)
if not ok:
consecutive_fails += 1
if consecutive_fails >= SAVE_ON_FAILS:
print(f"\n[!!] 连续失败 {consecutive_fails} 次,保存已获取数据后终止 (下次从第 {i - consecutive_fails + 1} 条继续)")
_save_xlsx(main_data, all_detail_rows, headers_row, OUTPUT_FILE)
raise SystemExit(1)
else:
consecutive_fails = 0
save_progress()
print("\n全部完成! 正在最终保存...")
with_detail = sum(1 for d in all_detail_rows if d)
empty_detail = sum(1 for d in all_detail_rows if d is not None and len(d) == 0)
none_detail = sum(1 for d in all_detail_rows if d is None)
total_detail_rows = sum(len(d) for d in all_detail_rows if d)
print(f" 有明细={with_detail}条, 无明细(空)={empty_detail}条, 未请求={none_detail}条, 明细行={total_detail_rows}")
_save_xlsx(main_data, all_detail_rows, headers_row, OUTPUT_FILE)
if os.path.exists(PROGRESS_FILE):
os.remove(PROGRESS_FILE)
print("进度文件已清理")