为何未修改的网页爬虫代码每次运行输出结果不一致?
问题分析与解决方案
你的爬虫结果不稳定,核心原因是页面交互后未等待数据完全渲染就抓取内容,加上一些不稳定的选择器和资源复用问题,导致每次运行结果不一致。以下是针对性的修复方案:
核心问题拆解
- 点击"Show All"后立即获取HTML,此时表格数据可能还在异步加载,导致抓取到不完整的内容
popup.wait_for_load_state("domcontentloaded")仅等待弹窗DOM加载完成,但动态渲染的表格数据并未加载完毕- 依赖
tables[7]这种固定索引定位表格,页面结构稍有变动就会出错,且加载不完全时索引对应的表格也会异常 - 每次循环重新启动浏览器,不仅效率低,还可能因浏览器启动/关闭的状态问题导致交互异常
修复方案与代码修改
1. 复用浏览器实例,避免重复启动关闭
把浏览器启动移到循环外,每次循环只打开新页面,减少资源开销和状态异常。
2. 精准等待数据加载完成
点击"Show All"后,等待表格的具体元素出现(比如表格的tbody行),确保数据完全渲染后再抓取HTML。
3. 替换不稳定的选择器
不用text="%s"匹配赛马名称,改用更精准的文本定位方式,避免文本匹配的歧义。
4. 移除全局变量,改用参数传递
全局变量容易导致循环中的状态混乱,改成函数参数传递状态变量,避免意外的状态覆盖。
5. 动态定位目标表格
不再依赖固定索引定位表格,通过表格内容关键词匹配,提升鲁棒性。
修改后的完整代码
import pandas as pd import xlwings as xw from playwright.sync_api import sync_playwright # 初始化Excel连接 wb = xw.Book("jockeyclub.xlsx") rc1 = wb.sheets['Race Card 1'] def scrape_ranking(page, horse_name, sheet_name, tablerow): print(f"处理马匹: {horse_name}") # 点击赛马名称(精准文本定位) with page.expect_popup() as popup_info: page.click(f"text='{horse_name}'") popup = popup_info.value # 等待弹窗内网络请求完全结束 popup.wait_for_load_state("networkidle") # 点击Show All按钮 show_all_btn = popup.get_by_alt_text("Show All") show_all_btn.click() # 等待表格数据加载:等待表格行元素出现,超时10秒 popup.wait_for_selector("table tbody tr", timeout=10000) # 再次等待网络空闲,确保所有数据渲染完成 popup.wait_for_load_state("networkidle") html = popup.content() popup.close() # 仅关闭弹窗,保留浏览器实例 # 动态定位目标表格:通过表格内容关键词匹配 tables = pd.read_html(html) target_df = None for table in tables: # 假设目标表格包含Race Date这类标识性字段 if "Race Date" in str(table.columns) or "Race Date" in str(table.values): target_df = table break if target_df is not None: print(target_df) # 写入Excel rc1.range(f'A{tablerow-1}').value = horse_name with pd.ExcelWriter("jockeyclub.xlsx", engine="openpyxl", mode='a', if_sheet_exists='overlay') as writer: target_df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=tablerow) # 根据表格实际行数调整下一个表格的起始位置 return tablerow + len(target_df) + 5 else: print(f"未找到{horse_name}的有效表格") return tablerow + 10 def main(): rownum = 6 tablerow = 20 url = "https://racing.hkjc.com/racing/information/English/raCing/RaceCard.aspx?RaceDate=2023/04/15&Racecourse=ST&RaceNo=1" with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(url) # 等待主页面加载完成 page.wait_for_load_state("networkidle") while rownum <= 13: horse_name = rc1.range(f'E{rownum}').value if not horse_name: print(f"第{rownum}行无马匹名称,跳过") rownum += 1 continue # 调用抓取函数,更新下一个表格的起始行 tablerow = scrape_ranking(page, horse_name, "Race Card 1", tablerow) rownum += 1 browser.close() if __name__ == "__main__": main()
关键修改说明
- 浏览器复用:全程只启动一次浏览器,循环内复用页面,减少资源开销和状态异常
- 等待策略优化:使用
networkidle等待网络请求完成,同时等待表格元素出现,确保数据完全渲染 - 动态表格定位:不再依赖固定索引,通过表格内容关键词匹配目标表格,避免页面结构变动导致的错误
- 状态管理优化:移除全局变量,改用参数传递状态,避免循环中的状态混乱
- 异常防护:增加对空马匹名称的判断,避免无效点击操作
内容的提问来源于stack exchange,提问作者NNBananas
相关产品推荐
相关产品推荐

