You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决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方法的步骤。

错误原因分析

  1. 页面结构变更:Oddsportal网站可能修改了页面DOM结构,原col-content容器的ID或类名已被替换,导致BeautifulSoup无法找到该元素,返回None。
  2. 反爬机制触发:网站检测到爬虫行为,返回了非目标页面(如人机验证页、空白页),页面中不存在目标容器。
  3. 页面加载不充分: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 08:50:45