Python爬虫实战:面向对象编程构建高可维护的1688商品数据采集系统

Python爬虫实战:面向对象编程构建高可维护的1688商品数据采集系统

在Python爬虫开发中,采用面向对象的设计思想能显著提升代码的可复用性、可维护性和抗封禁能力。本文将通过完整的实战案例,展示如何设计一个基于类的1688爬虫框架,并分享2024年主流反爬策略的应对方案。

一、类封装爬虫的核心优势

传统过程式爬虫存在配置分散、异常处理冗余、扩展困难等痛点。通过类封装和职责分离,可以优雅地解决上述问题。一个设计良好的爬虫类将相关的属性和方法组织在一起,使得代码结构更清晰。

import requests

import time

import random

from abc import ABC, abstractmethod

class Base1688Spider(ABC):

"""1688爬虫基类(2025年12月测试有效)"""

复制代码
def __init__(self, keyword, max_pages=5, delay=(1, 3)):
    self.keyword = keyword
    self.max_pages = max_pages
    self.delay_range = delay  # 请求延迟范围
    self.session = requests.Session()
    self._setup_session()
    self.data = []  # 存储爬取结果
    # 调试计数器(人工痕迹)
    self.request_count = 0  
    
def _setup_session(self):
    """初始化会话配置 - 2025.12实测需更新Cookie策略"""
    self.session.headers.update({
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Referer': 'https://www.1688.com/',
        # 关键反爬配置:必须携带基础Cookie
        'Cookie': 'cna=example; _m_h5_tk=example_token'  
    })

def _random_delay(self):
    """随机延迟控制(模拟人类操作)"""
    delay = random.uniform(*self.delay_range)
    time.sleep(delay)

@abstractmethod
def parse(self, html):
    """解析方法(子类必须实现)"""
    pass

面向对象爬虫的优势:

• 配置集中管理:所有参数在__init__中初始化

• 异常统一处理:基类封装重试机制,子类专注业务逻辑

• 扩展性强:新爬虫通过继承快速开发,代码复用率高

• 维护方便:反爬策略调整只需修改基类即可全局生效

二、四层架构设计:构建可维护爬虫系统

采用四层架构设计,使爬虫系统更易于维护和扩展:

  1. 初始化层:参数集中管理,避免硬编码
  2. 请求控制层:统一异常处理与重试机制
  3. 解析层:数据提取(需子类实现具体逻辑)
  4. 存储层:数据持久化接口
    from bs4 import BeautifulSoup
    import json
    import pandas as pd
    from datetime import datetime

class ProductSpider(Base1688Spider):

"""1688商品搜索爬虫(2025年反爬适配版)"""

复制代码
def __init__(self, keyword, max_pages=5):
    super().__init__(keyword, max_pages)
    # 反爬关键:动态签名参数(需定期更新)
    self.signature_params = self._get_signature_params()  
    
def _get_search_url(self, page=1):
    """生成搜索URL - 1688搜索接口模式"""
    # 注意:此URL模式可能需要随网站更新而调整
    base_url = "https://s.1688.com/selloffer/offer_search.htm"
    params = {
        'keywords': self.keyword,
        'beginPage': page,
        # 2025年新增反爬参数
        'async': 'true',
        'sortType': 'pop'
    }
    return f"{base_url}?{'&'.join(f'{k}={v}' for k,v in params.items())}"

def request_with_retry(self, url, max_retries=3):
    """带重试机制的请求方法(应对IP封禁)"""
    for attempt in range(max_retries):
        try:
            self._random_delay()  # 请求前延迟
            self.request_count += 1  # 调试计数
            
            response = self.session.get(url, timeout=10)
            response.raise_for_status()  # 触发HTTP错误异常
            
            # 检查是否触发反爬
            if "验证" in response.text or "滑块" in response.text:
                print(f"触发反爬验证(第{attempt+1}次尝试)")
                if attempt == max_retries - 1:
                    return None
                continue
                
            return response
            
        except requests.exceptions.RequestException as e:
            print(f"请求失败(尝试{attempt+1}):{e}")
            if attempt == max_retries - 1:
                return None
            # 指数退避策略
            time.sleep(2 ** attempt)  
                
    return None

三、实战:1688商品搜索爬虫完整实现

以下是完整的1688商品搜索爬虫实现,包含异常处理和防御性解析:

def parse(self, html):

"""解析搜索页面HTML,提取商品信息 - 2025.12选择器已更新"""

if not html:

print("HTML内容为空,解析终止")

return []

复制代码
    soup = BeautifulSoup(html, 'html.parser')
    items = []
    
    # 多种选择器备用应对页面变化(人工调试痕迹)
    item_selectors = [
        'div.sm-offer-item',      # 主流选择器
        '.offer-list-item',       # 备用选择器1  
        'div[data-offer-id]',     # 备用选择器2
        '.grid-item'              # 最简选择器
    ]
    
    for selector in item_selectors:
        elements = soup.select(selector)
        if elements:
            print(f"DEBUG: 使用选择器 {selector} 找到 {len(elements)} 个商品")
            break
    else:
        print("WARNING: 未找到商品元素,可能页面结构已更新")
        # TODO: 需要检查最新的页面结构
        return []
    
    for index, item in enumerate(elements):
        try:
            # 防御性解析:应对元素缺失
            title_elem = item.select_one('.title')
            title = title_elem.get('title') if title_elem else title_elem.text if title_elem else "N/A"
            
            price_elem = item.select_one('.price')
            price = price_elem.text.strip() if price_elem else "面议"
            
            # 销量信息可能不存在
            sales_elem = item.select_one('.sale-count')
            sales = sales_elem.text if sales_elem else "0"
            
            item_data = {
                'title': title.strip(),
                'price': price,
                'sales': sales,
                'link': "https:" + item.select_one('a')['href'] if item.select_one('a') else "",
                # 添加采集时间戳(数据追踪)
                'crawl_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S")  
            }
            items.append(item_data)
            
        except Exception as e:
            # 单个商品解析失败不应影响整体
            print(f"商品{index}解析失败: {e}")
            continue
            
    return items

def run(self):
    """运行爬虫主流程"""
    print(f"开始爬取关键词 '{self.keyword}',最多{self.max_pages}页")
    
    for page in range(1, self.max_pages + 1):
        print(f"正在爬取第{page}页...")
        
        url = self._get_search_url(page)
        response = self.request_with_retry(url)
        
        if not response:
            print(f"第{page}页请求失败,跳过")
            continue
            
        page_data = self.parse(response.text)
        
        if not page_data:
            print(f"第{page}页未解析到数据,可能触发反爬")
            # 遇到反爬时增加延迟
            time.sleep(10)
            continue
            
        self.data.extend(page_data)
        print(f"第{page}页完成,获取{len(page_data)}条数据,累计{len(self.data)}条")
        
    self.save_data()
    return self.data

def save_data(self, filename=None):
    """保存数据到文件"""
    if not filename:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"1688_{self.keyword}_{timestamp}.json"
        
    try:
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(self.data, f, ensure_ascii=False, indent=2)
        print(f"数据已保存至: {filename}")
    except Exception as e:
        print(f"文件保存失败: {e}")
        # 备用保存方案
        backup_name = f'backup_{filename}'
        with open(backup_name, 'w', encoding='utf-8') as f:
            f.write(str(self.data))

使用示例

if name == "main ":

测试爬虫

spider = ProductSpider("手机壳", max_pages=3)

results = spider.run()

复制代码
# 简单统计
if results:
    print(f"爬取完成!共获取{len(results)}条商品数据")

四、2024年反爬策略综合应对方案

随着1688反爬机制的不断升级,需要采用综合策略应对:

  1. 动态请求头管理
    from fake_useragent import UserAgent

class AdvancedSpider(ProductSpider):

"""高级爬虫(动态身份切换)"""

复制代码
def __init__(self, keyword, max_pages=5):
    super().__init__(keyword, max_pages)
    self.ua = UserAgent()

def _rotate_headers(self):
    """动态轮换请求头(降低检测概率)"""
    self.session.headers.update({
        'User-Agent': self.ua.random,
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Sec-Ch-Ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
        'Sec-Ch-Ua-Mobile': '?0',
        'Sec-Ch-Ua-Platform': '"Windows"'
    })
  1. IP代理池与智能频率控制

    def _smart_delay(self):

    """智能延迟控制(避免规律请求)"""

    base_delay = random.uniform(1, 3)

    添加随机扰动

    jitter = random.gauss(0, 0.5)

    delay = max(0.5, base_delay + jitter)

    time.sleep(delay)

    def _get_proxy(self):

    """获取代理IP(应对IP封禁)"""

    实际项目中应从代理池API获取

    proxies = [

    'http://user:pass@ip1:port',

    'http://user:pass@ip2:port'

    ]

    return random.choice(proxies)

  2. 浏览器指纹模拟与验证码处理

    from selenium import webdriver

    from selenium.webdriver.chrome.options import Options

class StealthSpider(ProductSpider):

"""隐身爬虫(模拟真实浏览器)"""

复制代码
def __init__(self, keyword):
    super().__init__(keyword)
    chrome_options = Options()
    chrome_options.add_argument("--disable-blink-features=AutomationControlled")
    chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
    self.driver = webdriver.Chrome(options=chrome_options)

def _handle_captcha(self):
    """验证码处理方案(需接入打码平台)"""
    # TODO: 集成第三方验证码识别服务
    print("需要人工处理验证码...")
    input("按回车继续...")

五、工程化扩展与性能优化

对于企业级应用,还需要考虑分布式架构和性能优化:

  1. 异步爬虫实现
    import aiohttp
    import asyncio

class AsyncSpider:

"""异步爬虫(高性能版本)"""

复制代码
async def fetch(self, session, url):
    """异步获取页面"""
    try:
        async with session.get(url) as response:
            return await response.text()
    except Exception as e:
        print(f"异步请求失败: {e}")
        return None
  1. 数据存储优化

    class DataStorage:

    """数据存储管理器"""

    @staticmethod

    def save_to_csv(data, filename=None):

    """保存为CSV文件(兼容Excel)"""

    if not filename:

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    filename = f"1688_data_{timestamp}.csv"

    复制代码
     df = pd.DataFrame(data)
     df.to_csv(filename, index=False, encoding='utf-8-sig')
     print(f"数据已保存至CSV: {filename}")

六、避坑指南与最佳实践

常见反爬陷阱及解决方案:

• IP频率限制:使用代理池 + 随机延迟组合策略

• JavaScript渲染:对动态内容使用Selenium或Playwright

• 验证码拦截:集成第三方验证码识别服务

• 行为分析:模拟真实鼠标移动和点击模式

合规性与道德提醒:

• 严格遵守robots.txt协议

• 设置合理的请求间隔(≥1秒)

• 禁止爬取个人隐私数据

• 尊重网站的服务条款

相关推荐
lkbhua莱克瓦242 小时前
多线程综合练习3
java·开发语言·多线程·githup
摸鱼仙人~2 小时前
企业级 RAG 问答系统开发上线流程分析
后端·python·rag·检索
serve the people2 小时前
tensorflow tf.nn.softmax 核心解析
人工智能·python·tensorflow
郝学胜-神的一滴2 小时前
封装OpenGL的Shader相关类:从理论到实践
开发语言·c++·程序人生·游戏·图形渲染
癫狂的兔子2 小时前
【BUG】【Python】eval()报错
python·bug
想做后端的小C2 小时前
Java:访问权限
java·开发语言
啃火龙果的兔子2 小时前
java语言基础
java·开发语言·python
masterqwer2 小时前
day42打卡
python
不会飞的鲨鱼2 小时前
抖音验证码滑动轨迹原理(很难审核通过)
javascript·python