解决Oddsportal赛事赔率爬虫的AttributeError异常
解决Oddsportal爬虫AttributeError问题:'NoneType' object has no attribute 'find'
问题背景
此前可正常运行的Oddsportal赛事及赔率爬虫,当前运行时出现AttributeError,报错信息显示'NoneType' object has no attribute 'find',错误发生在parse_data函数中,查找id="col-content"的div后调用find方法的步骤。
错误原因分析
- 页面结构变更:Oddsportal网站可能修改了页面DOM结构,原
col-content容器的ID或类名已被替换,导致BeautifulSoup无法找到该元素,返回None。 - 反爬机制触发:网站检测到爬虫行为,返回了非目标页面(如人机验证页、空白页),页面中不存在目标容器。
- 页面加载不充分:
implicitly_wait的等待时间不足以让页面完全加载,导致解析时目标元素尚未渲染。
解决方案
1. 验证并更新页面选择器
手动访问目标URL(https://www.oddsportal.com/matches/soccer),使用浏览器开发者工具(F12)定位赛事表格的父容器:
- 找到包含赛事数据的表格(原
table-main类),查看它的父级容器的ID或类名,替换代码中的选择器。 - 例如,如果新的容器ID是
main-content,则将soup.find('div', {'id': 'col-content'})修改为soup.find('div', {'id': 'main-content'})。
2. 增强反爬规避措施
修改Driver类的初始化代码,添加浏览器伪装配置:
class Driver: def __init__(self): options = webdriver.ChromeOptions() options.add_argument("--headless") # 添加User-Agent模拟真实浏览器 options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") # 规避自动化检测 options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option('useAutomationExtension', False) # 抑制日志 options.add_experimental_option('excludeSwitches', ['enable-logging']) self.driver = webdriver.Chrome(options=options)
3. 添加空值判断,防止程序崩溃
在parse_data函数中,对查找的元素进行空值检查,避免直接调用方法导致报错:
def parse_data(url, return_urls=False): browser = create_driver() browser.get(url) # 替换隐式等待为显式等待,确保目标元素加载完成 from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC try: WebDriverWait(browser, 25).until( EC.presence_of_element_located((By.ID, 'col-content')) ) except: print(f"页面加载超时或未找到目标容器,URL: {url}") browser.quit() return GameData() if not return_urls else (GameData(), []) soup = bs(browser.page_source, "lxml") div = soup.find('div', {'id': 'col-content'}) if not div: print(f"无法找到col-content容器,URL: {url}") browser.quit() return GameData() if not return_urls else (GameData(), []) table = div.find('table', {'class': 'table-main'}) if not table: print(f"无法找到赛事表格,URL: {url}") browser.quit() return GameData() if not return_urls else (GameData(), []) # 后续原有逻辑... h1 = soup.find('h1').text print(h1) m = re.search(r'\d+ \w+ \d{4}$', h1) if not m: game_date = datetime.now().strftime("%d %b %Y") else: game_date = m[0] game_data = GameData() for row in generate_matches(table): game_data.date.append(game_date) game_data.time.append(row[0]) game_data.game.append(row[1]) # Score present? if ':' not in row[2]: # No, shift a few columns right: row[5], row[4], row[3], row[2] = row[4], row[3], row[2], nan game_data.score.append(row[2]) game_data.home_odds.append(nan if row[3] == '-' else row[3]) game_data.draw_odds.append(nan if row[4] == '-' else row[4]) game_data.away_odds.append(nan if row[5] == '-' else row[5]) game_data.country.append(row[6]) game_data.league.append(row[7]) if return_urls: span = soup.find('span', {'class': 'next-games-date'}) urls = [] if span: a_tags = span.findAll('a') urls = ['https://www.oddsportal.com' + a_tag['href'] for a_tag in a_tags] return game_data, urls browser.quit() return game_data
4. 优化线程池与资源清理
在主函数中,确保每个线程的浏览器实例正确关闭,避免资源泄漏:
if __name__ == '__main__': games = None pool = ThreadPool(5) try: game_data_today, urls = pool.apply(parse_data, args=('https://www.oddsportal.com/matches/soccer', True)) urls.pop(1) game_data_results = pool.imap(parse_data, urls) # 调整循环次数,匹配urls的数量 total_days = 1 + len(urls) for i in range(total_days): try: game_data = game_data_today if i == 0 else next(game_data_results) result = pd.DataFrame(game_data.__dict__) if games is None: games = result else: games = pd.concat([games, result], ignore_index=True) except StopIteration: break except Exception as e: print(f"处理数据时出错: {e}") continue print(games) finally: pool.close() pool.join() # 清理资源 del threadLocal import gc gc.collect()
内容的提问来源于stack exchange,提问作者Rander
相关产品推荐
相关产品推荐

