如何用Python大规模抓取多分类动态加载的超市网站商品数据
问题分析与解决方案
你的代码在单分类场景运行正常,但扩展到多分类循环时崩溃,核心原因包括浏览器实例频繁创建销毁导致资源耗尽、固定等待时间不可靠、缺少异常处理,以及可能触发网站反爬机制。以下是具体改进方案和大规模爬虫最佳实践:
一、修复代码中的明显错误
你的getHtmlDynamic函数存在返回值错误:函数内生成的是soup对象,但最后返回未定义的html变量,这会直接导致后续调用失败。修正如下:
def getHtmlDynamic(url, driver): driver.get(url) # 后续用显式等待替代固定sleep soup = BeautifulSoup(driver.page_source, 'html5lib') return soup
二、核心改进方案
1. 复用浏览器实例
每次循环创建新Chrome实例会消耗大量系统资源,且容易被网站识别为爬虫。改为在循环外初始化一次浏览器,循环内复用:
import requests import json import pandas as pd from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By import time import random def getHtmlDynamic(url, driver): driver.get(url) # 显式等待价格元素加载完成,替代固定sleep try: WebDriverWait(driver, 15).until( EC.presence_of_all_elements_located((By.CLASS_NAME, 'contenedor-precio')) ) except Exception as e: print(f"等待元素超时: {e}") soup = BeautifulSoup(driver.page_source, 'html5lib') return soup def getProductsAndPrices(html): try: prodsJson = html.find_all('script', {'type': 'application/ld+json'}) if len(prodsJson) < 2: print("未找到商品JSON数据") return pd.DataFrame() dfProds = pd.json_normalize(json.loads(prodsJson[1].contents[0])['itemListElement']) pricesList = html.find_all('div', {'class': 'contenedor-precio'}) prices = [] for row in pricesList: price_row = row.find_all('span') # 只取最后一个span(通常是最终价格),避免重复采集 if price_row: prices.append(price_row[-1].text.strip()) # 确保价格列表和商品数量匹配 if len(prices) >= len(dfProds): dfProds['price'] = prices[:len(dfProds)] else: dfProds['price'] = None print("价格数量与商品不匹配") return dfProds except Exception as e: print(f"解析商品数据失败: {e}") return pd.DataFrame() # 初始化浏览器(循环外只执行一次) driver = webdriver.Chrome() dfProductsConsolidated = pd.DataFrame([]) for idx, category in enumerate(dfCategories['categoryURL'][:10]): print(f"正在抓取第{idx+1}个分类: {category}") htmlProducts = getHtmlDynamic(url=category, driver=driver) dfProds = getProductsAndPrices(htmlProducts) if not dfProds.empty: dfProductsConsolidated = pd.concat([dfProductsConsolidated, dfProds], ignore_index=True) # 随机延迟,避免频繁请求 time.sleep(random.randint(2, 5)) # 最后关闭浏览器 driver.quit() # 保存数据 dfProductsConsolidated.to_csv('products.csv', index=False)
2. 用显式等待替代固定sleep
time.sleep(20)是盲等,既浪费时间又不可靠。改用WebDriverWait等待目标元素(如价格容器)加载完成,确保动态内容已渲染。
3. 增加异常处理
在数据解析和元素等待环节加入try-except,单个分类抓取失败不会导致整个程序崩溃,同时打印错误信息便于排查。
三、大规模爬虫最佳实践
1. 优先抓取API接口
动态加载的内容通常来自后端API,打开浏览器开发者工具(F12)的"网络"面板,过滤XHR/Fetch请求,找到返回商品和价格的API接口,直接用requests请求比Selenium高效10倍以上。比如你页面中的application/ld+json数据,或者价格加载的AJAX请求。
2. 控制请求频率
- 加入随机延迟(如2-5秒),避免短时间内大量请求触发反爬。
- 不要一次性抓取所有100个分类,可分批次抓取,比如每次抓10个,间隔一段时间再抓下一批。
3. 数据分批保存
每抓取几个分类就将数据写入文件,避免程序崩溃导致全部数据丢失。
4. 避免被反爬检测
- 为浏览器设置用户代理(UA),模拟真实浏览器:
options = webdriver.ChromeOptions() options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36") driver = webdriver.Chrome(options=options) - 必要时使用代理IP,避免IP被封禁。
5. 遵守网站规则
查看网站的robots.txt,确认允许抓取的内容,避免违反网站条款。
内容的提问来源于stack exchange,提问作者Gianluca Peretti
相关产品推荐
相关产品推荐

