影刀RPA 网页动态内容采集:AJAX数据拦截 影刀RPA 网页动态内容采集AJAX数据拦截作者林焱什么情况用什么现在大部分网站的数据不在HTML源码里而是通过AJAX异步加载。用requests获取的HTML是空壳真正数据要等JS执行后才出现。在影刀RPA里有三种方式处理直接请求API接口、用浏览器等待渲染后提取、拦截AJAX响应数据。适用场景SPA单页应用数据采集、动态加载的列表/图表、AJAX分页数据、需要JS计算才能显示的数据。怎么做方式一直接请求API最高效店群矩阵自动化突破运营极限importrequestsimportjsondefscrape_via_api():直接请求AJAX接口# 1. 先在浏览器F12 → Network → XHR中找到数据接口# 2. 分析请求参数# 3. 直接请求APIapi_urlhttps://example.com/api/data/listheaders{User-Agent:Mozilla/5.0...,Accept:application/json,Referer:https://example.com/list,}params{page:1,size:20,type:all}responserequests.get(api_url,paramsparams,headersheaders,timeout10)dataresponse.json()# 解析JSON数据itemsdata.get(data,{}).get(list,[])results[]foriteminitems:results.append({名称:item.get(name,),价格:item.get(price,0),库存:item.get(stock,0),})returnresults# 分页采集所有数据defscrape_all_pages(api_url,headersNone,page_size20):采集所有分页数据all_data[]page1whileTrue:params{page:page,size:page_size}responserequests.get(api_url,paramsparams,headersheaders,timeout10)ifresponse.status_code!200:breakdataresponse.json()itemsdata.get(data,{}).get(list,[])ifnotitems:breakall_data.extend(items)print(f第{page}页:{len(items)}条, 累计{len(all_data)}条)# 检查是否最后一页totaldata.get(data,{}).get(total,0)iflen(all_data)total:breakpage1time.sleep(1)returnall_data方式二影刀浏览器等待渲染【打开网页】→ 目标页面URL 【等待元素出现】→ 数据容器选择器超时15秒 【提取相似元素数据】→ 提取渲染后的数据 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a70ad18ae29743bea6dc8338fde8cccc.png#pic_center) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/000abe4babf44287803b192ec40ef616.png#pic_center)# 在影刀中等待AJAX数据渲染完成后再提取# 1. 等待特定元素出现表示数据已加载# 影刀指令【等待元素出现】→ .data-item → 超时15秒# 2. 等待数据数量稳定defwait_for_data_stable(selector,max_wait15):等待数据加载稳定importtime prev_count0stable_count0for_inrange(max_wait):time.sleep(1)# 在影刀中用【获取相似元素数量】current_countget_element_count(selector)ifcurrent_countprev_countandcurrent_count0:stable_count1ifstable_count2:returncurrent_countelse:stable_count0prev_countcurrent_countreturnprev_count方式三拦截AJAX响应在影刀浏览器中用JavaScript拦截XHR/Fetch响应// 影刀【执行JavaScript代码】节点// 在页面加载前注入拦截代码// 拦截XMLHttpRequestvaroriginalXHROpenXMLHttpRequest.prototype.open;varoriginalXHRSendXMLHttpRequest.prototype.send;window._capturedAPIs[];XMLHttpRequest.prototype.openfunction(method,url){this._urlurl;this._methodmethod;returnoriginalXHROpen.apply(this,arguments);};XMLHttpRequest.prototype.sendfunction(body){varselfthis;this.addEventListener(load,function(){if(self._urlself._url.includes(/api/)){try{varresponseDataJSON.parse(self.responseText);window._capturedAPIs.push({url:self._url,method:self._method,status:self.status,data:responseData});}catch(e){}}});returnoriginalXHRSend.apply(this,arguments);};// 拦截FetchvaroriginalFetchwindow.fetch;window.fetchfunction(){varurlarguments[0];if(typeofurlstringurl.includes(/api/)){returnoriginalFetch.apply(this,arguments).then(function(response){varclonedresponse.clone();cloned.json().then(function(data){window._capturedAPIs.push({url:url,method:GET,data:data});}).catch(function(){});returnresponse;});}returnoriginalFetch.apply(this,arguments);};然后在影刀中获取拦截到的数据# 影刀【执行JavaScript代码】# 获取拦截到的API数据js_get_capturedreturn JSON.stringify(window._capturedAPIs);# 执行后得到JSON字符串# 在Python中解析importjson captured_datajson.loads(captured_json)forapiincaptured_data:print(fURL:{api[url]})ifdatainapiandlistinapi[data]:itemsapi[data][list]print(f 数据量:{len(items)})foriteminitems:print(f -{item})SPA应用数据采集defscrape_spa_data(url,wait_selector,extract_func): SPA单页应用数据采集 wait_selector: 数据加载完成后的元素选择器 extract_func: 数据提取函数 # 影刀中# 1. 【打开网页】url# 2. 【等待元素出现】wait_selector → 超时15秒# 3. 【提取数据】# 对于SPA路由切换不需要重新打开页面# 只需点击导航链接然后等待新内容加载# 【点击元素】→ 导航链接# 【等待元素出现】→ 新页面特征元素# 【提取数据】pass# SPA路由切换采集defscrape_spa_routes(base_url,routes):采集SPA多个路由页面all_data{}![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e073ef408fc54e5c81cc8b8f319bed7d.png#pic_center)# 第一次打开页面# 【打开网页】base_urlforroute_name,route_pathinroutes.items():# SPA不需要重新打开页面只需修改URL# 影刀中【执行JavaScript代码】js_navigatefwindow.history.pushState(null, , {route_path});# 触发路由更新js_dispatchwindow.dispatchEvent(new PopStateEvent(popstate));# 等待新页面数据加载# 【等待元素出现】→ route_name对应的选择器# 提取数据# 【提取相似元素数据】all_data[route_name]extracted_datareturnall_dataPOST请求采集defscrape_via_post(api_url,payload,headersNone):POST方式请求数据ifheadersisNone:headers{User-Agent:Mozilla/5.0...,Content-Type:application/json,}responserequests.post(api_url,jsonpayload,# JSON格式headersheaders,timeout10)[video(video-4ALNERVS-1784736628485)(type-csdn)(url-https://live.csdn.net/v/embed/524992)(image-https://v-blog.csdnimg.cn/asset/b59aed2f01d4fe8583467562aaf4dcfd/cover/Cover0.jpg)(title-temu店群自动化报活动案例)]returnresponse.json()# 很多网站的搜索接口是POSTdatascrape_via_post(api_urlhttps://example.com/api/search,payload{keyword:RPA,page:1,filters:{category:software,price_min:0}})有什么坑坑1API需要特定请求头# 问题直接请求API返回403或空数据# 原因缺少必要的请求头# 解决从浏览器复制完整请求头headers{User-Agent:Mozilla/5.0...,Accept:application/json, text/plain, */*,Accept-Language:zh-CN,zh;q0.9,Referer:https://example.com/list,X-Requested-With:XMLHttpRequest,# 关键头Authorization:Bearer token...,# 如果需要认证Cookie:session_idxxx;,# 如果需要登录}# 在影刀中可以从浏览器获取Cookie# 【获取浏览器Cookie】→ 传给Python坑2数据加密传输# 问题API返回的数据是加密的# 如response {data: AES加密字符串}# 解决1找前端解密代码# F12 → Sources → 搜索decrypt → 找到解密函数# 解决2放弃API用浏览器渲染后提取# 影刀中【打开网页】→ 【等待渲染】→ 【提取DOM数据】# 解决3在浏览器中执行JS解密![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7ed4048290b2417e8a2f98772abc39ad.png#pic_center)# 影刀【执行JavaScript代码】# 先找到页面上的解密函数然后调用它js_decrypt var encryptedData arguments[0]; // 使用页面的解密函数 return decryptFunction(encryptedData); 坑3AJAX请求有频率限制# 问题快速请求API返回429Too Many Requests# 解决降低频率 重试defsafe_api_request(url,paramsNone,max_retries3):带重试的API请求forattemptinrange(max_retries):responserequests.get(url,paramsparams,headersheaders,timeout10)ifresponse.status_code200:returnresponse.json()elifresponse.status_code429:wait_time(attempt1)*5# 5, 10, 15秒递增print(f被限流等待{wait_time}秒...)time.sleep(wait_time)else:print(f请求失败:{response.status_code})time.sleep(2)returnNone坑4数据加载有延迟# 问题点击翻页后立即提取数据拿到的是旧数据# 解决等待新数据加载# 方案1等待特定元素变化# 影刀中# 【点击元素】→ 下一页# 【等待元素消失】→ .loading-spinner加载动画消失# 【等待元素出现】→ .data-item新数据出现# 【等待】1秒额外等待# 方案2比较数据指纹![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3368335041cd4af2b8d7997f172c5a3e.png#pic_center)defwait_for_data_change(old_fingerprint,timeout10):等待数据变化for_inrange(timeout):time.sleep(1)# 获取当前数据指纹如第一条数据的IDcurrent_fpget_first_item_id()ifcurrent_fp!old_fingerprint:returnTruereturnFalse坑5GraphQL接口采集# 问题网站用GraphQL一个接口查所有数据# 请求格式和REST不同# GraphQL请求示例defscrape_graphql(endpoint,query,variablesNone):GraphQL接口采集payload{query:query,variables:variablesor{}}responserequests.post(endpoint,jsonpayload,headers{Content-Type:application/json,...},timeout10)returnresponse.json()![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/64ec0c19e2554d19bbf82847740a597c.png#pic_center)# 使用query query GetProducts($page: Int, $size: Int) { products(page: $page, size: $size) { id name price stock } } datascrape_graphql(https://example.com/graphql,query,variables{page:1,size:20})productsdata[data][products]