外卖霸王餐CPS取链架构俱美开放平台设计方案

张开发
2026/4/9 19:17:55 15 分钟阅读

分享文章

外卖霸王餐CPS取链架构俱美开放平台设计方案
外卖霸王餐CPS取链架构俱美开放平台设计方案在构建外卖霸王餐CPS系统时最核心且最具挑战性的环节并非后端逻辑处理而是上游取链Link Acquisition。本文将基于俱美开放平台(http://www.baodanbao.com.cn)的技术栈详细阐述外卖霸王餐CPS取链的架构设计与核心代码实现。一、 取链架构设计思路取链架构本质上是一个“自动化浏览器集群”与“数据清洗中心”的结合体。其核心流程为指令下发 - 浏览器渲染 - 网络拦截 - 数据提取 - 本地入库。渲染层Renderer由于外卖平台页面高度依赖JavaScript动态加载普通的Http请求无法获取到真实数据。我们采用Headless Chrome集群Puppeteer/Selenium或基于JDK 15的WebView组件来执行页面渲染。拦截层Interceptor在浏览器渲染过程中通过监听网络请求Network Interception直接捕获XHR/Fetch接口的响应体而非解析DOM。这能有效避开DOM混淆和CSS反爬。调度层Scheduler使用Quartz或XXL-JOB定时调度任务分城市、分商圈轮询获取优惠券列表。存储层Storage将清洗后的优惠券数据Coupon存入MySQL并推送到Redis缓存供API接口实时读取。二、 核心取链代码实现为了模拟真实用户环境我们使用Java结合Selenium WebDriver来管理浏览器实例。以下代码展示了如何初始化一个具备防检测能力的浏览器实例并监听网络请求。首先定义浏览器管理器packagebaodanbao.com.cn.browser;importorg.openqa.selenium.Proxy;importorg.openqa.selenium.WebDriver;importorg.openqa.selenium.chrome.ChromeDriver;importorg.openqa.selenium.chrome.ChromeOptions;importorg.springframework.stereotype.Component;importjavax.annotation.PostConstruct;importjava.util.HashMap;importjava.util.Map;/** * author baodanbao.com.cn * 浏览器驱动管理器 (基于Selenium) */ComponentpublicclassBrowserManager{privateWebDriverdriver;PostConstructpublicvoidinit(){System.setProperty(webdriver.chrome.driver,/path/to/chromedriver);ChromeOptionsoptionsnewChromeOptions();// 1. 隐身模式与无头模式 (生产环境开启headless)options.addArguments(--no-sandbox);options.addArguments(--disable-dev-shm-usage);options.addArguments(--disable-blink-featuresAutomationControlled);options.addArguments(--disable-extensions);options.addArguments(--window-size1920,1080);// options.addArguments(--headless); // 生产环境开启// 2. 防止webdriver特征被检测options.setExperimentalOption(useAutomationExtension,false);MapString,ObjectprefsnewHashMap();prefs.put(profile.managed_default_content_settings.images,2);// 不加载图片提升速度options.setExperimentalOption(prefs,prefs);this.drivernewChromeDriver(options);// 移除window.navigator.webdriver标志((JavascriptExecutor)driver).executeScript(Object.defineProperty(navigator, webdriver, {get: () undefined}));}publicWebDrivergetDriver(){returndriver;}}三、 网络请求拦截与数据解析核心逻辑在于如何从浏览器的Network Log中提取出我们需要的API响应。美团/饿了么的优惠券列表通常由特定的XHR接口返回我们需要通过正则匹配或URL关键字来过滤出这些请求。实现一个具体的取链服务类packagebaodanbao.com.cn.service;importbaodanbao.com.cn.browser.BrowserManager;importbaodanbao.com.cn.entity.Coupon;importbaodanbao.com.cn.mapper.CouponMapper;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONArray;importcom.alibaba.fastjson.JSONObject;importorg.openqa.selenium.*;importorg.openqa.selenium.logging.LogEntries;importorg.openqa.selenium.logging.LogType;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.scheduling.annotation.Scheduled;importorg.springframework.stereotype.Service;importjava.math.BigDecimal;importjava.util.ArrayList;importjava.util.List;importjava.util.Set;importjava.util.concurrent.TimeUnit;/** * author baodanbao.com.cn * 外卖平台取链核心服务 */ServicepublicclassLinkAcquisitionService{AutowiredprivateBrowserManagerbrowserManager;AutowiredprivateCouponMappercouponMapper;// 美团优惠券列表接口特征privatestaticfinalStringMEITUAN_API_PATTERN/meishi/coupon/list;// 饿了么优惠券接口特征privatestaticfinalStringELEME_API_PATTERN/restapi/eus/v1/coupon;/** * 定时任务每15分钟执行一次商圈取链 */Scheduled(fixedRate15*60*1000)publicvoidfetchCouponLinks(){WebDriverdriverbrowserManager.getDriver();ListCouponcouponListnewArrayList();try{// 1. 访问目标商圈页面 (以杭州为例)// 注意实际URL需根据商圈ID构造driver.get(https://meishi.meituan.com/i/?ci20stid1000);TimeUnit.SECONDS.sleep(5);// 等待页面加载// 2. 获取浏览器日志 (Performance Log)LogEntrieslogEntriesdriver.manage().logs().get(LogType.BROWSER);SetStringprocessedUrlsnewHashSet();// 防止重复处理for(LogEntryentry:logEntries){Stringmessageentry.getMessage();// 3. 解析日志寻找包含特定API特征的请求if(message.contains(MEITUAN_API_PATTERN)||message.contains(ELEME_API_PATTERN)){// 提取URL (实际应用中需要更复杂的JSON解析)StringapiUrlextractUrlFromLog(message);// 避免重复处理同一个接口if(processedUrls.contains(apiUrl))continue;processedUrls.add(apiUrl);// 4. 解析接口返回的JSON数据StringjsonResponsefetchResponse(apiUrl,driver);// 这里需要结合CDP (Chrome DevTools Protocol)ListCouponparsedCouponsparseCouponData(jsonResponse);couponList.addAll(parsedCoupons);}}// 5. 数据入库if(!couponList.isEmpty()){saveOrUpdateCoupons(couponList);}}catch(Exceptione){e.printStackTrace();}}/** * 解析JSON数据并转换为Coupon实体 * param json 响应字符串 * return 优惠券列表 */privateListCouponparseCouponData(Stringjson){ListCouponlistnewArrayList();try{JSONObjectrootJSON.parseObject(json);// 根据美团/饿了么的实际JSON结构进行解析JSONArraydataroot.getJSONArray(data);if(data!null){for(inti0;idata.size();i){JSONObjectitemdata.getJSONObject(i);CouponcouponnewCoupon();coupon.setCouponId(item.getString(id));coupon.setTitle(item.getString(title));coupon.setAmount(item.getBigDecimal(amount));coupon.setCondition(item.getString(condition));// 构造跳转链接 (CPS链接通常需要带上推广PID)coupon.setJumpUrl(https://coupon.meituan.com/detail?idcoupon.getCouponId()pidYOUR_PID);list.add(coupon);}}}catch(Exceptione){e.printStackTrace();}returnlist;}/** * 批量保存或更新优惠券 */privatevoidsaveOrUpdateCoupons(ListCouponcoupons){for(Couponcoupon:coupons){// 简单的去重逻辑根据CouponId查询存在则更新否则插入CouponexistcouponMapper.selectById(coupon.getCouponId());if(existnull){couponMapper.insert(coupon);}else{// 如果金额或状态有变则更新if(!exist.getAmount().equals(coupon.getAmount())){couponMapper.update(coupon);}}}}// 辅助方法从日志中提取URL和获取响应体在实际Selenium中较复杂// 通常需要使用DevTools接口或在Node.js环境中配合Puppeteer更方便。// 此处仅为示意逻辑。privateStringextractUrlFromLog(Stringlog){/* ... */}privateStringfetchResponse(Stringurl,WebDriverdriver){/* ... */}}四、 高可用与反封策略取链架构必须面对IP封锁和账号封禁的风险因此必须引入以下策略分布式代理池集成Redis维护动态IP池每次请求随机切换IP。Cookie池管理将有效的用户Cookie存储在Redis中每次取链随机选取一个Cookie注入浏览器模拟真实用户登录态。请求频率控制使用令牌桶算法控制浏览器的请求频率避免触发风控。实现一个简单的Cookie注入逻辑packagebaodanbao.com.cn.util;importbaodanbao.com.cn.browser.BrowserManager;importorg.openqa.selenium.Cookie;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Component;importjavax.annotation.Resource;importjava.util.Random;importjava.util.Set;/** * author baodanbao.com.cn * Cookie管理工具类 */ComponentpublicclassCookieManager{ResourceprivateStringRedisTemplateredisTemplate;ResourceprivateBrowserManagerbrowserManager;privatestaticfinalStringREDIS_COOKIE_KEYmt:cookies:pool;/** * 随机获取一个Cookie并注入到当前浏览器 */publicvoidinjectRandomCookie(){SetStringcookiesredisTemplate.opsForSet().members(REDIS_COOKIE_KEY);if(cookiesnull||cookies.isEmpty()){System.out.println(Cookie池为空请补充Cookie);return;}// 随机选择一个String[]cookieArraycookies.toArray(newString[0]);StringrandomCookieStrcookieArray[newRandom().nextInt(cookieArray.length)];// 解析并注入WebDriverdriverbrowserManager.getDriver();try{// 需要先访问域名否则无法设置Cookiedriver.get(https://meishi.meituan.com);TimeUnit.SECONDS.sleep(2);// 解析Cookie字符串并添加String[]pairsrandomCookieStr.split(;);for(Stringpair:pairs){String[]kvpair.trim().split(,2);if(kv.length2){driver.manage().addCookie(newCookie(kv[0],kv[1]));}}// 刷新页面使Cookie生效driver.navigate().refresh();}catch(Exceptione){e.printStackTrace();// 注入失败从Redis中移除该CookieredisTemplate.opsForSet().remove(REDIS_COOKIE_KEY,randomCookieStr);}}}本文著作权归 俱美开放平台 转载请注明出处

更多文章