影刀RPA店群自动化性能调优实战Python异步执行剖析与资源利用率优化自动化系统跑得不慢但跑得不够快。店群矩阵自动化突破运营极限当六十个店铺的上货任务需要三小时才能完成时瓶颈就已经在吃掉利润了。店群自动化的早期我们的注意力全在“能不能跑通”和“能不能跑稳”上。直到某次大促前夜运营团队要求所有店铺在凌晨2点到6点之间完成一轮商品更新和活动报名。系统跑了整整四个半小时差点错过了报名窗口。那次之后我们开始认真地做性能剖析。不是凭感觉优化而是精准度量每一条指令、每一次网络请求、每一次页面渲染的耗时找出真正的瓶颈。这篇文章就围绕性能度量和优化分享我们在店群自动化系统上的实战经验。temu店群自动化报活动案例一、先度量再优化全链路计时埋点没有度量就没有优化。我们做的第一件事是为每一个自动化任务建立全链路计时埋点。一个任务从创建到执行完毕会经历以下阶段调度排队等待浏览器实例获取页面导航各步骤指令执行点击、输入、等待元素等数据回传每个阶段的开始和结束时间都被精确记录。importtimefromcontextlibimportasynccontextmanagerclassTimingTracker:def__init__(self,redis,task_id):self.redisredis self.task_idtask_id self.marks{}asyncdefmark(self,phase:str):self.marks[phase]time.monotonic()awaitself.redis.hset(ftiming:{self.task_id},phase,self.marks[phase])asyncdefreport(self):phasessorted(self.marks.keys())foriinrange(1,len(phases)):durationself.marks[phases[i]]-self.marks[phases[i-1]]logger.info(fTask{self.task_id}:{phases[i-1]}-{phases[i]}:{duration:.2f}s) 在影刀流程的执行层每个指令步骤也用类似的方式记录耗时信息由Python调度代理统一收集。 这样跑了一周后数据分析显示出一个明显的模式**总执行时间中超过60%消耗在页面等待和浏览器渲染上而不是在业务操作本身。**---## 二、瓶颈定位页面等待与网络IO是真正的元凶我们把任务执行时间拆解到指令粒度发现了几个典型的慢操作-wait_element等待某个元素出现平均耗时3.2秒个别页面因为异步加载慢要等15秒--type_text 后触发前端校验页面重新渲染导致下一步 click 前必须额外等待--upload_file 的图片上传在网络波动时单个图片上传耗时超过20秒--页面首次 navigate 后的DOM稳定时间在A/B测试页面中差异极大 这些等待本身并不是影刀脚本的问题而是网页行为和网络条件决定的。 但在自动化流程中这些等待串行累积就把总时间拉得很长。**于是我们问自己能不能在不影响操作正确性的前提下压缩这些等待**---## 三、优化等待策略从固定Sleep到智能轮询早期脚本里充斥着 sleep(3) 这种硬编码等待。 页面快的时候空等页面慢的时候不够等。 我们替换为基于CDP事件驱动的智能等待监听 DOMContentLoaded、networkIdle、特定元素挂载等事件而非盲目等待。 pythonclassSmartWaiter:def__init__(self,cdp_client):self.cdpcdp_clientasyncdefwait_for_element(self,selector:str,timeout10.0):deadlinetime.monotonic()timeoutwhiletime.monotonic()deadline:elementawaitself.cdp.find_element(selector)ifelementandawaitself.cdp.is_visible(element):returnelement# 监听DOM变化事件而不是轮询awaitself.cdp.wait_for_dom_change(timeout0.5)raiseTimeoutError(fElement{selector}not visible within{timeout}s)asyncdefwait_for_network_idle(self,idle_time1.0,timeout15.0):deadlinetime.monotonic()timeoutwhiletime.monotonic()deadline:ifawaitself.cdp.is_network_idle():awaitasyncio.sleep(idle_time)ifawaitself.cdp.is_network_idle():returnawaitasyncio.sleep(0.3)logger.warning(Network did not become idle, proceeding anyway) 通过这种方式平均页面等待时间从3.2秒降到了1.1秒降幅约65%。---## 四、任务内并行化能并行的绝不串行上货流程里有很多操作彼此没有依赖关系。 比如上传商品图片和填写商品描述完全可以同时进行。 我们修改了指令执行引擎允许在指令序列中标记并行组。 json{steps:[{action:navigate,url:/product/create},{parallel:[{action:upload_file,locator:#main-image,value_from:product.main_image},{action:type_text,locator:#description,value_from:product.description},{action:upload_file,locator:#detail-images,value_from:product.detail_images}]},{action:click,locator:#submit-btn}]} Python执行引擎解析到 parallel 块时会将内部的子指令分发到线程池并发执行再统一等待结果。 pythonimportconcurrent.futuresclassParallelStepExecutor:def__init__(self,max_workers3):self.executorconcurrent.futures.ThreadPoolExecutor(max_workersmax_workers)asyncdefexecute_parallel(self,substeps:list,context:dict):loopasyncio.get_running_loop()tasks[]forstepinsubsteps:taskloop.run_in_executor(self.executor,self._sync_execute_step,step,context)tasks.append(task)resultsawaitasyncio.gather(*tasks,return_exceptionsTrue)fori,resultinenumerate(results):ifisinstance(result,Exception):logger.error(fParallel step{i}failed:{result})returnresults 上货任务中并行化将原来串行的三组操作压缩到了一组并行时间总执行时间缩短了约30%。---## 五、资源池的动态伸缩忙时扩容闲时缩容之前我们做了浏览器预热池但预热实例数是固定的。 通过性能数据分析我们发现不同时段的负载波动很大。 我们在预热池的基础上增加了动态伸缩策略 pythonclassAdaptiveBrowserPool:def__init__(self,pool_manager,metrics):self.poolpool_manager self.metricsmetricsasyncdefauto_scale(self):current_queue_depthawaitself.metrics.get_queue_depth()active_tasksawaitself.metrics.get_active_task_count()availableself.pool.available_count()# 排队任务超过可用实例2倍快速扩容ifcurrent_queue_depthavailable*2:expand_countmin(current_queue_depth//2,self.pool.max_instances-available)awaitself.pool.expand(expand_count)logger.info(fExpanded pool by{expand_count}instances)# 连续10分钟可用实例多于活跃任务3倍缩容ifavailableactive_tasks*3andself._low_load_duration600:shrink_countmax(1,(available-active_tasks)//2)awaitself.pool.shrink(shrink_count)logger.info(fShrunk pool by{shrink_count}instances) 弹性伸缩让系统在高峰时段能快速响应低谷时释放内存整体资源利用率从45%提升到72%。---## 六、调度层的异步化重构早期的调度器使用了大量同步代码和阻塞等待单个Worker的gRPC调用串行处理任务。 我们将调度引擎全面异步化利用 asyncio 和 gRPC aio 提升了任务分发的吞吐量。 pythonclassAsyncTaskDispatcher:def__init__(self,worker_stub_pool):self.workersworker_stub_poolasyncdefdispatch_batch(self,tasks:list):asyncwithasyncio.TaskGroup()astg:dispatch_tasks[]fortaskintasks:workerawaitself.select_worker(task)dispatch_tasks.append(tg.create_task(worker.ExecuteTask(task)))# 批量结果处理... 异步化后单个Master节点能同时向多个Worker分发任务调度延迟从平均200毫秒降到20毫秒。---## 七、网络层面的优化连接复用与HTTP/2Worker与平台服务器之间的HTTP请求如通过代理访问店铺页面可以通过连接复用减少握手开销。 我们在Chromium启动参数中开启了连接复用–enable-featuresNetworkService,NetworkServiceInProcess–disable-featuresUseDnsHttpsSvcb同时gRPC本身就使用HTTP/2多路复用Master与Worker之间的连接数从每个任务一个连接变成了长连接复用减少了TCP握手次数。 --- ## 八、性能监控看板与回归基线 优化效果需要持续追踪。我们在Grafana中建立了性能监控看板 - 任务P50/P95/P99执行时长趋势 - - 各阶段耗时占比调度、等待、执行 - - 浏览器实例预热命中率 - - Worker CPU/内存/网络IO利用率 - - 并行步骤占比与加速比 每次架构变更或流程更新后对比性能基线防止性能回退。 --- ## 九、踩坑与经验 **过度并行导致的资源争抢。** 曾将上传图片的并行数设得过高8个并发导致代理IP带宽跑满整体反而变慢。 后来对并行数做了限制每个店铺最多3个并行任务。 **CDP事件监听的内存泄漏。** 智能等待中注册的DOM事件监听器如果没有及时移除长时间运行会导致浏览器内存增长。 我们在等待结束后主动调用 removeEventListener并监控浏览器内存使用。 **异步改造引入的隐性竞争。** 两个协程同时读写同一个店铺的数据上下文导致偶尔使用了过时的变量。 我们给每个任务的数据上下文加了写时复制保护。 --- ## 十、写在最后 性能优化是一个持续的过程不是一次性动作。 系统规模越大微小延迟的累积效应就越明显。 通过全链路度量、智能等待、并行化、资源弹性伸缩和异步化重构我们逐步将单店铺日常运营任务的执行时间从平均8分钟缩短到了5分钟以内整体吞吐量提升了近40%。 自动化不只是让机器替你干活还要让它干得足够快。 效率就是店群运营的生命线。 --- *作者林焱*