影刀RPA 网页分页采集的通用模式:下一页判断与循环控制 影刀RPA 网页分页采集的通用模式下一页判断与循环控制作者林焱大部分数据采集不是一页能搞定的——商品列表有几十页、搜索结果要翻页、订单记录按月分页。分页采集是RPA最经典的场景之一但很多新手写出来的分页流程总是出问题——漏采最后一页、多采了一页报错、翻页按钮变了找不到、翻页太快被限制。这篇文章把分页采集的通用模式讲清楚做成模板拿来就能用。一、分页采集的核心逻辑不管什么网站分页采集的核心逻辑都是这三步循环1. 采集当前页数据 2. 判断是否有下一页 3. 如果有 → 翻到下一页回到第1步 如果没有 → 结束循环关键是第2步怎么判断有没有下一页。二、判断是否有下一页2.1 检查下一页按钮状态最常见的判断方式——看下一页按钮是否可点击defhas_next_page():检查是否有下一页next_btnget_element(//a[contains(class, next)])ifnotnext_btn:returnFalse# 检查是否被禁用class_attrnext_btn.get_attribute(class)ifdisabledinclass_attr:returnFalse# 检查是否有disabled属性ifnext_btn.get_attribute(disabled):returnFalsereturnTrue坑1下一页按钮的选择器因网站而异拼多多店群自动化报活动上架不同网站的下一页按钮结构不同常见模式// 通用模式 //a[contains(text(), 下一页)] //a[contains(text(), Next)] //a[classnext] //li[classnext]/a //button[contains(class, next)] //a[contains(class, pagination-next)]需要根据目标网站调整选择器。坑2最后一页的下一页按钮可能不存在有些网站在最后一页时直接移除了下一页按钮而不是禁用它。需要同时处理按钮不存在和按钮被禁用两种情况。2.2 检查当前页码和总页数defget_page_info():获取当前页码和总页数# 获取总页数文本如第 3/15 页page_textget_element_text(//span[classpage-info])importrematchre.search(r(\d)\s*/\s*(\d),page_text)ifmatch:currentint(match.group(1))totalint(match.group(2))returncurrent,total ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/66397663e9d14ae98b3bf769cf2b5ee7.png#pic_center)returnNone,None# 使用current,totalget_page_info()ifcurrentandtotal:ifcurrenttotal:has_nextTrueelse:has_nextFalse2.3 检查当前页数据量defhas_next_page_by_data(expected_per_page20):通过数据量判断是否有下一页rowsget_element_list(//table//tr)data_countlen(rows)-1# 减去表头# 如果当前页数据等于每页预期数量可能还有下一页# 如果少于预期数量说明是最后一页returndata_countexpected_per_page坑最后一页也可能正好满页如果数据总量正好是每页数量的整数倍最后一页也是满的这种判断会误认为还有下一页。需要结合其他判断方式。2.4 检查URL变化current_urlget_current_url()# 翻页click_element(.next-page)wait_for_page_load()new_urlget_current_url()ifnew_url!current_url:has_nextTrue# URL变了说明翻页成功else:has_nextFalse# URL没变可能到最后一页了三、翻页操作3.1 点击下一页按钮defgoto_next_page():翻到下一页# 记录当前URL或数据标识old_identifierget_element_text(//table//tr[2]/td[1])# 第一条数据的ID# 点击下一页click_element(//a[contains(class, next)])# 等待新数据加载time.sleep(1)# 等待数据变化确保翻页成功for_inrange(10):new_identifierget_element_text(//table//tr[2]/td[1])ifnew_identifier!old_identifier:returnTrue# 数据变了翻页成功time.sleep(1)returnFalse# 翻页可能失败3.2 修改URL参数翻页有些网站通过URL参数控制页码如?page2可以直接修改URLimportredefgoto_page(page_num):直接跳转到指定页current_urlget_current_url()# 替换URL中的page参数ifpageincurrent_url:new_urlre.sub(rpage\d,fpage{page_num},current_url)else:![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a7340ba73bab424791fd5b9437aac8bb.png#pic_center)if?incurrent_url:new_urlcurrent_urlfpage{page_num}else:new_urlcurrent_urlf?page{page_num}# 打开新URLopen_url(new_url)wait_for_page_load()3.3 滚动翻页有些网站没有分页按钮而是滚动到底部自动加载下一页defscroll_to_load_more():滚动加载更多last_heightexecute_js(return document.body.scrollHeight)whileTrue:# 滚动到底部execute_js(window.scrollTo(0, document.body.scrollHeight))time.sleep(2)# 检查是否有新内容加载new_heightexecute_js(return document.body.scrollHeight)ifnew_heightlast_height:# 高度没变可能到底了# 再等一次确认time.sleep(3)new_heightexecute_js(return document.body.scrollHeight)ifnew_heightlast_height:break# 确认到底了last_heightnew_height四、完整的分页采集模板importtimeimportpandasaspdfromdatetimeimportdatetimedefpaginated_collection(collect_func,has_next_func,next_page_func,max_pages100,delay2): 通用分页采集模板 参数 - collect_func: 采集当前页数据的函数 - has_next_func: 判断是否有下一页的函数 - next_page_func: 翻到下一页的函数 - max_pages: 最大页数安全阀 - delay: 每页之间的延时 all_data[]page1errors[]print(f开始分页采集 -{datetime.now().strftime(%H:%M:%S)})whilepagemax_pages:try:# 1. 采集当前页page_datacollect_func()ifnotpage_data:print(f第{page}页无数据停止采集)breakall_data.extend(page_data)print(f第{page}页采集{len(page_data)}条累计{len(all_data)}条)# 2. 判断是否有下一页ifnothas_next_func():print(f第{page}页是最后一页采集完成)break# 3. 翻到下一页ifnotnext_page_func():print(f翻页失败停止采集)breakpage1time.sleep(delay)# 页间延时exceptExceptionase:error_infof第{page}页采集异常{e}errors.append(error_info)print(error_info)# 尝试继续下一页ifpagemax_pages:try:next_page_func()page1time.sleep(delay)except:print(翻页也失败停止采集)breakelse:break# 汇总print(f\n采集完成共{page}页{len(all_data)}条数据)iferrors:print(f错误{len(errors)}个)forerrinerrors:print(f{err})returnall_data,errors# 使用示例 # 定义采集函数defcollect_current_page():采集当前页数据rowsget_element_list(//table[classdata-table]//tr)data[]forrowinrows[1:]:# 跳过表头cellsrow.find_elements_by_css_selector(td)ifcells:row_data{id:cells[0].text.strip(),name:cells[1].text.strip(),price:cells[2].text.strip(),}data.append(row_data)returndata# 定义判断函数defcheck_has_next():检查是否有下一页next_btnget_element(//a[contains(class, next)])ifnotnext_btn:returnFalsereturndisablednotinnext_btn.get_attribute(class)# 定义翻页函数defgoto_next():翻到下一页old_first_idget_element_text(//table//tr[2]/td[1])click_element(//a[contains(class, next)])# 等待数据变化for_inrange(10):time.sleep(1)try:new_first_idget_element_text(//table//tr[2]/td[1])ifnew_first_id!old_first_id:returnTrueexcept:continuereturnFalse# 执行采集data,errorspaginated_collection(collect_funccollect_current_page,has_next_funccheck_has_next,next_page_funcgoto_next,max_pages50,delay2)# 保存结果dfpd.DataFrame(data)df.to_excel(D:\\output\\collected_data.xlsx,indexFalse)五、防重复采集5.1 记录已采集的页码collected_pagesset()defcollect_current_page(page_num):采集指定页避免重复ifpage_numincollected_pages:print(f第{page_num}页已采集跳过)return[]datado_collect()collected_pages.add(page_num)returndata5.2 记录已采集的数据IDTEMU店群矩阵自动化运营核价报活动collected_idsset()defcollect_current_page():采集当前页跳过已采集的数据raw_datado_collect()new_data[]foriteminraw_data:item_iditem[id]![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/623962c8857540ccbdf77334a715812e.png#pic_center)ifitem_idnotincollected_ids:collected_ids.add(item_id)new_data.append(item)else:print(f跳过重复数据{item_id})returnnew_data5.3 断点续采importjsonimportosdefsave_progress(page,data):保存采集进度progress{last_page:page,data_count:len(data),collected_ids:list(collected_ids),timestamp:datetime.now().isoformat()}withopen(D:\\progress.json,w)asf:json.dump(progress,f)defload_progress():加载采集进度ifnotos.path.exists(D:\\progress.json):returnNonewithopen(D:\\progress.json,r)asf:returnjson.load(f)# 使用从上次中断的地方继续progressload_progress()start_pageprogress[last_page]1ifprogresselse1ifprogress:collected_idsset(progress[collected_ids])print(f从第{start_page}页继续采集已有{len(collected_ids)}条数据)# 每采集完一页保存进度forpageinrange(start_page,max_pages1):datacollect_page(page)save_progress(page,all_data)六、避坑清单坑1翻页太快被限制短时间内翻太多页网站可能限制访问。加随机延时time.sleep(random.uniform(2,5))坑2最后一页数据重复采集翻到最后一页后下一页按钮可能还在但点击无效导致重复采集最后一页。用数据ID去重。坑3翻页后元素定位失效翻页后页面重新渲染之前的元素引用全部失效。每页重新获取元素。坑4页码跳转而非顺序翻页有些网站支持直接跳转到指定页码。如果需要跳页先跳页再等待加载。坑5数据量不确定导致死循环如果has_next永远返回True循环不会停止。必须设置max_pages安全阀。坑6翻页后URL没变有些SPA网站翻页不改变URL。用数据变化来判断翻页是否成功而不是URL变化。