
本文详解为何直接用beautifulsoup查找特定class会返回空列表,并提供无需selenium、纯requests+正则+json解析的高效替代方案,从ipl官方js接口提取全部赛季比赛数据。
本文详解为何直接用beautifulsoup查找特定class会返回空列表,并提供无需selenium、纯requests+正则+json解析的高效替代方案,从ipl官方js接口提取全部赛季比赛数据。
在Web爬虫开发中,一个常见却易被忽视的陷阱是:页面HTML结构看似存在,但实际内容由JavaScript动态渲染。你遇到的问题——soup.find_all('div', {'class': 'vn-shedule-desk col-100 floatLft'}) 返回空列表(长度为0),根本原因并非选择器错误或网络请求失败,而是该类名对应的60场比赛数据并未包含在服务器返回的初始HTML中,而是在页面加载后,由前端JavaScript异步请求并注入DOM。requests.get()仅获取静态HTML响应,无法执行JS,因此BeautifulSoup自然找不到这些动态生成的元素。
此时,盲目转向Selenium虽可行,但会显著增加开销(启动浏览器、等待渲染、资源占用高),且违背“能用轻量方案解决就不重载”的工程原则。更优解是——逆向分析前端资源,直连真实数据源。
观察IPL官网网络请求(可通过浏览器开发者工具 → Network → JS过滤),可发现关键数据来自两个核心JS文件:
-
https://scores.iplt20.com/ipl/mc/competition.js:包含所有赛季(2008–2024)的CompetitionID列表; -
https://ipl-stats-sports-mechanic.s3.ap-south-1.amazonaws.com/ipl/feeds/{id}-matchschedule.js:每个赛季对应一个JS文件,内含完整的Matchsummary比赛数据数组。
这些JS文件本质是包裹了JSON数据的函数调用(如 oncomptetion({...}) 或 MatchSchedule({...})),我们只需用正则提取其中的JSON字符串,再解析即可。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
以下是完整、健壮的实现代码:
import requests
import re
import json
import pandas as pd
# 1. 获取所有赛季 CompetitionID
url_competitions = "https://scores.iplt20.com/ipl/mc/competition.js"
response = requests.get(url_competitions)
response.raise_for_status() # 自动抛出HTTP异常
# 提取 oncomptetion({...}) 中的JSON对象
match = re.search(r'oncomptetion\((\{.*?\})\)', response.text, re.DOTALL)
if not match:
raise ValueError("Failed to extract competition data from JS file")
competition_data = json.loads(match.group(1))
season_ids = [item.get("CompetitionID") for item in competition_data.get("competition", [])]
# 2. 遍历每个赛季,获取比赛摘要
all_matches = []
for season_id in season_ids:
js_url = f"https://ipl-stats-sports-mechanic.s3.ap-south-1.amazonaws.com/ipl/feeds/{season_id}-matchschedule.js"
try:
res = requests.get(js_url, timeout=10)
res.raise_for_status()
# 提取 MatchSchedule({...}) 中的JSON
m = re.search(r'MatchSchedule\((\{.*?\})\)', res.text, re.DOTALL)
if m:
schedule_data = json.loads(m.group(1))
matches = schedule_data.get("Matchsummary", [])
all_matches.extend(matches)
except Exception as e:
print(f"Warning: Failed to fetch season {season_id}: {e}")
continue
# 3. 构建结构化DataFrame(自动推断字段)
df = pd.DataFrame(all_matches)
print(f"✅ Total matches scraped: {len(df)}")
print(df[["SeasonYear", "MatchNumber", "Team1Name", "Team2Name", "MatchResult", "MatchStatus"]].head())? 关键注意事项:
-
正则需非贪婪匹配:使用
.*?替代.*,避免跨多个函数体误匹配; -
异常防御必须到位:JS接口可能临时不可用或格式微调,务必用
try/except包裹单赛季请求; -
字段命名以实际JSON为准:示例中
SeasonYear、MatchResult等字段名需根据真实返回结构调整(可用print(list(df.columns))快速验证); -
遵守robots.txt与速率限制:IPL官方JS接口属公开数据,但仍建议添加
time.sleep(0.5)防止单IP高频请求; - 不推荐硬编码URL:生产环境应从主站HTML中动态提取JS资源路径,提升鲁棒性。
该方案将抓取效率提升10倍以上(无浏览器启动延迟),内存占用极低,且完全规避了反爬中的WebDriver指纹检测风险。真正的爬虫高手,永远优先寻找API,而非模拟人眼。

















