基于Python+Selenium的淘宝商品信息智能采集实践:从浏览器控制到反爬应对

各专栏更新如下👇

OAI-5G开源通信平台实践

OpenWRT常见问题分析

5G CPE 组网技术分享

Linux音视频采集及视频推拉流应用实践详解

得力工具提升工作效率


基于Python+Selenium的淘宝商品信息智能采集实践:从浏览器控制到反爬应对

随着电商数据分析需求的增长,如何高效、稳定地从淘宝等平台获取商品信息成为许多开发者和数据分析师面临的实际问题。本文将深入探讨基于Python和Selenium框架的商品信息采集方案,重点介绍如何通过连接现有浏览器、智能提取数据以及有效应对反爬机制,实现一套完整且稳健的采集流程。

1. 技术选型与环境配置

1.1 为什么选择Selenium?

淘宝等现代电商网站大量使用JavaScript动态加载内容,传统的静态页面爬取方法难以获取完整数据。Selenium作为一个浏览器自动化测试工具,能够模拟真实用户操作,等待页面完全渲染后再提取数据,是处理动态加载页面的理想选择。

1.2 核心工具与依赖

  • Python 3.8+:推荐使用虚拟环境隔离项目依赖
  • Selenium 4.x:浏览器自动化框架
  • Microsoft Edge WebDriver:与Edge浏览器版本匹配的驱动
  • BeautifulSoup 4:HTML解析库(备选方案)
  • Requests:HTTP请求库(用于图片下载)

1.3 环境初始化

复制代码
# 创建虚拟环境
 -m venv taobao_scraper
source taobao_scraper/bin/activate  # Linux/Mac
# taobao_scraper\Scripts\activate  # Windows

# 安装依赖包
pip install selenium beautifulsoup4 requests pandas

2. 核心技术实现

2.1 连接现有浏览器实例

与传统的直接启动浏览器不同,本项目通过远程调试端口连接到已在运行的Edge浏览器,复用其上下文和缓存,这能显著提高采集效率并降低被识别为机器人的风险。

复制代码
import selenium.webdriver as webdriver
import selenium.webdriver.common.by as By
from selenium.webdriver.edge.options import Options
import json
import os
import re
import requests
import time
import random

def connect_to_existing_browser(port=9222):
    """
    通过远程调试端口连接到现有Edge浏览器实例
    """
    edge_options = Options()
    edge_options.add_experimental_option("debuggerAddress", f"127.0.0.1:{port}")
    
    # 连接到正在运行的Edge实例
    driver = webdriver.Edge(options=edge_options)
    
    # 验证连接
    try:
        original_window = driver.current_window_handle
        print(f"成功连接到浏览器,当前窗口: {original_window}")
        return driver, original_window
    except Exception as e:
        print(f"连接失败: {e}")
        return None, None

启动浏览器时需添加参数

复制代码
msedge.exe --remote-debugging-port=9222 --user-data-dir="C:\EdgeProfile"

2.2 在新标签页中打开商品链接

为避免干扰用户当前浏览会话,所有采集操作都在新标签页中进行:

复制代码
def open_in_new_tab(driver, url, original_window):
    """
    在新标签页中打开指定URL,保持原窗口不变
    """
    # 记录当前所有窗口句柄
    original_handles = driver.window_handles
    
    # 执行JavaScript打开新标签页
    driver.execute_script(f"window.open('{url}', '_blank');")
    
    # 等待新窗口出现
    timeout = 10
    start_time = time.time()
    
    while time.time() - start_time < timeout:
        current_handles = driver.window_handles
        new_handles = [h for h in current_handles if h not in original_handles]
        
        if new_handles:
            new_window = new_handles[0]
            driver.switch_to.window(new_window)
            
            # 等待新页面加载
            driver.implicitly_wait(5)
            print(f"在新标签页中打开: {url}")
            return new_window
        
        time.sleep(0.5)
    
    print("未能成功打开新标签页")
    return None

2.3 智能商品信息提取

淘宝商品页面结构多样,需要智能识别不同布局并提取关键信息。以下实现支持多种页面模板:

复制代码
def extract_product_info(driver):
    """
    智能提取商品信息,适应不同的页面布局
    """
    product_data = {
        "title": "",
        "price": "",
        "original_price": "",
        "sales": "",
        "shop_name": "",
        "shop_rating": "",
        "location": "",
        "description": "",
        "attributes": {},
        "image_urls": []
    }
    
    # 等待页面关键元素加载
    driver.implicitly_wait(10)
    
    # 策略1: 尝试多种选择器定位商品标题
    title_selectors = [
        "h1[class*='Title']",
        "div[class*='title']",
        "h1",
        "#J_Title",
        ".tb-main-title"
    ]
    
    for selector in title_selectors:
        try:
            elements = driver.find_elements(By.CSS_SELECTOR, selector)
            for element in elements:
                text = element.text.strip()
                if text and len(text) > 5:  # 过滤过短的文本
                    product_data["title"] = text
                    break
            if product_data["title"]:
                break
        except:
            continue
    
    # 策略2: 提取价格信息(处理多种价格格式)
    price_patterns = [
        (By.CSS_SELECTOR, "span[class*='price']"),
        (By.CSS_SELECTOR, "em[class*='tb-rmb']"),
        (By.CSS_SELECTOR, "strong[class*='J_Price']"),
        (By.XPATH, "//*[contains(text(), '¥') or contains(text(), '¥')]")
    ]
    
    for method, pattern in price_patterns:
        try:
            elements = driver.find_elements(method, pattern)
            for element in elements:
                price_text = element.text.strip()
                if any(c.isdigit() for c in price_text) and ('¥' in price_text or '¥' in price_text):
                    product_data["price"] = price_text
                    break
            if product_data["price"]:
                break
        except:
            continue
    
    # 策略3: 提取销量信息
    sales_selectors = [
        "span[class*='sale']",
        "em[class*='J_Sale']",
        "li[class*='sale']",
        "*[id*='J_SellCounter']"
    ]
    
    for selector in sales_selectors:
        try:
            elements = driver.find_elements(By.CSS_SELECTOR, selector)
            for element in elements:
                text = element.text.strip()
                if "人付款" in text or "人收货" in text or "销量" in text:
                    product_data["sales"] = text
                    # 提取数字部分
                    numbers = re.findall(r'\d+\.?\d*', text)
                    if numbers:
                        product_data["sales_count"] = numbers[0]
                    break
            if product_data["sales"]:
                break
        except:
            continue
    
    # 提取商品图片URL
    extract_product_images(driver, product_data)
    
    return product_data

2.4 图片下载与本地保存

图片下载是商品信息采集的关键环节,需要处理多种图片格式和命名规则:

复制代码
def extract_product_images(driver, product_data):
    """
    提取商品图片并下载到本地
    """
    image_elements = driver.find_elements(By.CSS_SELECTOR, "img")
    image_urls = []
    
    for img in image_elements:
        src = img.get_attribute("src") or img.get_attribute("data-src")
        
        # 过滤无效URL和图标
        if src and "http" in src and not any(x in src.lower() for x in ["icon", "logo", "sprites"]):
            # 处理淘宝图片的特殊格式
            if "taobaocdn.com" in src or "alicdn.com" in src:
                # 尝试获取高清大图
                if "_.jpg" in src:
                    hd_src = src.replace("_50x50.jpg", "").replace("_230x230.jpg", "")
                    if hd_src not in image_urls:
                        image_urls.append(hd_src)
                else:
                    if src not in image_urls:
                        image_urls.append(src)
    
    product_data["image_urls"] = image_urls[:10]  # 限制最多10张图片
    return image_urls

def download_images(image_urls, product_id, product_title, base_dir="products"):
    """
    下载图片并保存到本地,按规则命名
    """
    # 创建商品目录
    safe_title = re.sub(r'[\\/*?:"<>|]', "", product_title)[:50]
    product_dir = os.path.join(base_dir, f"{product_id}_{safe_title}")
    os.makedirs(product_dir, exist_ok=True)
    
    image_paths = []
    
    for i, url in enumerate(image_urls):
        try:
            # 添加请求头模拟浏览器
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
                'Referer': 'https://item.taobao.com/'
            }
            
            response = requests.get(url, headers=headers, timeout=10)
            
            # 根据内容类型确定文件扩展名
            content_type = response.headers.get('content-type', '')
            if 'jpeg' in content_type or 'jpg' in content_type:
                ext = 'jpg'
            elif 'png' in content_type:
                ext = 'png'
            elif 'gif' in content_type:
                ext = 'gif'
            else:
                # 从URL推断扩展名
                if url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')):
                    ext = url.split('.')[-1].lower()
                    if ext == 'jpeg':
                        ext = 'jpg'
                else:
                    ext = 'jpg'
            
            # 生成文件名
            filename = f"product_{product_id}_image_{i+1}.{ext}"
            filepath = os.path.join(product_dir, filename)
            
            # 保存图片
            with open(filepath, 'wb') as f:
                f.write(response.content)
            
            image_paths.append(filepath)
            print(f"图片下载成功: {filename}")
            
            # 随机延迟,避免请求过快
            time.sleep(random.uniform(0.5, 1.5))
            
        except Exception as e:
            print(f"图片下载失败 {url}: {e}")
            continue
    
    return image_paths

2.5 数据结构化保存

采集的数据需要结构化保存以便后续分析:

复制代码
def save_product_data(product_data, image_paths, product_id, product_title):
    """
    将商品数据保存为JSON文件
    """
    safe_title = re.sub(r'[\\/*?:"<>|]', "", product_title)[:50]
    product_dir = os.path.join("products", f"{product_id}_{safe_title}")
    
    # 准备保存的数据
    save_data = {
        "product_id": product_id,
        "title": product_data["title"],
        "price": product_data["price"],
        "original_price": product_data.get("original_price", ""),
        "sales": product_data["sales"],
        "sales_count": product_data.get("sales_count", ""),
        "shop_name": product_data.get("shop_name", ""),
        "shop_rating": product_data.get("shop_rating", ""),
        "location": product_data.get("location", ""),
        "description": product_data.get("description", ""),
        "attributes": product_data.get("attributes", {}),
        "image_local_paths": image_paths,
        "crawl_time": time.strftime("%Y-%m-%d %H:%M:%S"),
        "source_url": driver.current_url
    }
    
    # 保存为JSON
    json_path = os.path.join(product_dir, "product_info.json")
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump(save_data, f, ensure_ascii=False, indent=2)
    
    # 同时保存为CSV(便于Excel查看)
    csv_path = os.path.join(product_dir, "product_info.csv")
    csv_data = {
        "字段": ["商品ID", "标题", "价格", "销量", "店铺名", "所在地", "采集时间"],
        "值": [product_id, save_data["title"], save_data["price"], 
               save_data["sales"], save_data["shop_name"], 
               save_data["location"], save_data["crawl_time"]]
    }
    
    # 简化版CSV保存
    import csv
    with open(csv_path, 'w', newline='', encoding='utf-8-sig') as f:
        writer = csv.writer(f)
        writer.writerow(["字段", "值"])
        for field, value in zip(csv_data["字段"], csv_data["值"]):
            writer.writerow([field, value])
    
    print(f"数据已保存到: {product_dir}")
    return json_path

3. 反爬机制与应对策略

淘宝拥有强大的反爬虫系统,包括IP封锁、行为分析、验证码等多种防护机制。

3.1 主要反爬机制分析

防护机制 检测方式 应对策略
IP频率限制 同一IP请求频率监控 使用代理IP池,控制请求间隔
行为分析 鼠标轨迹、点击模式分析 模拟人类随机行为,添加操作延迟
JavaScript挑战 动态内容加载验证 使用Selenium等待完全渲染
验证码系统 滑动、点选验证码 识别时暂停或使用打码服务
请求头验证 HTTP头完整性检查 完善请求头信息,模拟真实浏览器

3.2 具体应对措施

复制代码
def setup_stealth_options():
    """
    配置浏览器选项以降低被检测风险
    """
    edge_options = Options()
    
    # 基本反检测设置
    edge_options.add_argument('--disable-blink-features=AutomationControlled')
    edge_options.add_experimental_option('excludeSwitches', ['enable-automation'])
    edge_options.add_experimental_option('useAutomationExtension', False)
    
    # 修改WebDriver属性
    edge_options.add_argument("--disable-web-security")
    edge_options.add_argument("--allow-running-insecure-content")
    
    # 设置用户代理
    user_agents = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
    ]
    edge_options.add_argument(f'user-agent={random.choice(user_agents)}')
    
    return edge_options

def human_like_behavior(driver):
    """
    模拟人类浏览行为
    """
    # 随机滚动页面
    scroll_scripts = [
        "window.scrollTo(0, document.body.scrollHeight * 0.3);",
        "window.scrollTo(0, document.body.scrollHeight * 0.7);",
        "window.scrollTo(0, document.body.scrollHeight);"
    ]
    
    for script in scroll_scripts:
        driver.execute_script(script)
        time.sleep(random.uniform(1, 2))
    
    # 随机鼠标移动模拟(通过JavaScript)
    move_script = """
    var event = new MouseEvent('mousemove', {
        view: window,
        bubbles: true,
        cancelable: true,
        clientX: %d,
        clientY: %d
    });
    document.elementFromPoint(%d, %d).dispatchEvent(event);
    """
    
    for _ in range(random.randint(2, 4)):
        x = random.randint(100, 800)
        y = random.randint(100, 600)
        driver.execute_script(move_script % (x, y, x, y))
        time.sleep(random.uniform(0.5, 1))

3.3 代理IP管理与轮换

复制代码
class ProxyManager:
    """
    代理IP管理器,支持自动轮换
    """
    def __init__(self, proxy_list=None):
        self.proxies = proxy_list or []
        self.current_index = 0
        self.failed_proxies = set()
    
    def get_proxy(self):
        """获取下一个可用代理"""
        if not self.proxies:
            return None
        
        max_attempts = len(self.proxies)
        for _ in range(max_attempts):
            proxy = self.proxies[self.current_index]
            self.current_index = (self.current_index + 1) % len(self.proxies)
            
            if proxy not in self.failed_proxies:
                return proxy
        
        return None
    
    def mark_failed(self, proxy):
        """标记失败代理"""
        self.failed_proxies.add(proxy)
        print(f"代理标记为失败: {proxy}")
    
    def test_proxy(self, proxy, test_url="http://httpbin.org/ip", timeout=5):
        """测试代理是否可用"""
        try:
            proxies = {
                "http": proxy,
                "https": proxy
            }
            response = requests.get(test_url, proxies=proxies, timeout=timeout)
            return response.status_code == 200
        except:
            return False

4. 完整工作流程与主程序

复制代码
def main():
    """
    主程序:完整的商品信息采集流程
    """
    print("=== 淘宝商品信息采集系统 ===")
    
    # 1. 连接到现有浏览器
    driver, original_window = connect_to_existing_browser(port=9222)
    if not driver:
        print("无法连接到浏览器,请确保已启动Edge并启用远程调试")
        return
    
    try:
        # 2. 获取用户输入的商品链接
        product_url = input("请输入淘宝商品链接: ").strip()
        
        if not product_url.startswith("http"):
            print("链接格式不正确")
            return
        
        # 3. 从URL提取商品ID
        product_id = extract_product_id(product_url)
        if not product_id:
            print("无法从链接中提取商品ID")
            return
        
        # 4. 在新标签页中打开商品链接
        print(f"正在打开商品链接: {product_url}")
        new_window = open_in_new_tab(driver, product_url, original_window)
        
        if not new_window:
            print("打开商品页面失败")
            return
        
        # 5. 等待页面加载并提取信息
        print("正在提取商品信息...")
        time.sleep(3)  # 初始等待
        
        product_data = extract_product_info(driver)
        
        if not product_data["title"]:
            print("未能提取到商品信息,可能页面结构不匹配")
            # 可以尝试备用提取方法
            product_data = alternative_extract_method(driver)
        
        # 6. 下载图片
        print("正在下载商品图片...")
        image_paths = download_images(
            product_data["image_urls"], 
            product_id, 
            product_data["title"]
        )
        
        # 7. 保存数据
        print("正在保存商品数据...")
        json_path = save_product_data(
            product_data, 
            image_paths, 
            product_id, 
            product_data["title"]
        )
        
        print(f"\n采集完成!")
        print(f"商品标题: {product_data['title']}")
        print(f"商品价格: {product_data.get('price', 'N/A')}")
        print(f"销量信息: {product_data.get('sales', 'N/A')}")
        print(f"图片数量: {len(image_paths)}张")
        print(f"数据位置: {os.path.dirname(json_path)}")
        
        # 8. 关闭新标签页,返回原窗口
        driver.close()
        driver.switch_to.window(original_window)
        
    except Exception as e:
        print(f"采集过程中出现错误: {e}")
        import traceback
        traceback.print_exc()
    
    finally:
        print("\n采集结束,浏览器保持打开状态")
        # 不关闭浏览器,仅断开驱动连接
        driver.quit()

def extract_product_id(url):
    """
    从淘宝商品URL中提取商品ID
    """
    patterns = [
        r'id=(\d+)',
        r'/(\d+)\.html',
        r'item/(\d+)'
    ]
    
    for pattern in patterns:
        match = re.search(pattern, url)
        if match:
            return match.group(1)
    
    return None

if __name__ == "__main__":
    main()

5. 最佳实践与注意事项

5.1 合规性建议

  1. 遵守robots.txt:检查淘宝的robots.txt文件,遵守爬取规则
  2. 控制请求频率:单次请求间隔建议在3-5秒,避免对服务器造成压力
  3. 数据使用限制:仅将数据用于个人学习或分析,不用于商业竞争或侵权用途

5.2 性能优化技巧

  • 缓存利用:充分利用浏览器缓存,减少重复下载
  • 并发控制:合理控制同时打开的标签页数量(建议不超过3个)
  • 错误重试:实现指数退避算法的重试机制,提高鲁棒性

5.3 维护与扩展

  • 定期更新选择器:淘宝页面结构可能变化,需定期维护元素选择器
  • 日志记录:实现详细的日志系统,便于问题追踪和调试
  • 模块化设计:将各个功能模块分离,便于维护和扩展

6. 总结

本文详细介绍了一套基于Python+Selenium的淘宝商品信息采集系统,该系统通过连接现有浏览器实例、在新标签页中操作、智能提取页面信息以及多维度反爬应对策略,实现了高效稳健的商品数据采集。与直接使用API或简单HTTP请求相比,这种方法能更好地处理动态加载内容,模拟真实用户行为,显著降低被封锁的风险。

需要注意的是,网络爬虫技术应在法律和道德框架内使用,尊重网站服务条款,控制采集频率,避免对目标网站造成不必要负担。随着反爬技术的不断升级,爬虫策略也需要持续调整和优化,本文提供的方案为这一领域的实践提供了可靠的技术参考。

相关推荐
HsuHeinrich1 小时前
利用表格探索宜居城市
python·数据可视化
过尽漉雪千山1 小时前
Anaconda的虚拟环境下使用清华源镜像安装Pytorch
人工智能·pytorch·python·深度学习·机器学习
碧海银沙音频科技研究院1 小时前
CLIP(对比语言-图像预训练)在长尾图像分类应用
python·深度学习·分类
Dxxyyyy1 小时前
零基础学JAVA--Day41(IO文件流+IO流原理+InputStream+OutputStream)
java·开发语言·python
jiuweiC1 小时前
python 虚拟环境-windows
开发语言·windows·python
free-elcmacom1 小时前
机器学习入门<5>支持向量机形象教学:寻找最安全的“三八线”,人人都能懂的算法核心
人工智能·python·算法·机器学习·支持向量机
月亮!2 小时前
人工智能发展现状与未来
人工智能·python·selenium·测试工具·开源·测试用例
天草二十六_简村人2 小时前
jenkins打包制作Python镜像,并推送至docker仓库,部署到k8s
后端·python·docker·容器·kubernetes·jenkins
weixin_457760002 小时前
GIOU (Generalized Intersection over Union) 详解
pytorch·python