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__中初始化
• 异常统一处理:基类封装重试机制,子类专注业务逻辑
• 扩展性强:新爬虫通过继承快速开发,代码复用率高
• 维护方便:反爬策略调整只需修改基类即可全局生效
二、四层架构设计:构建可维护爬虫系统
采用四层架构设计,使爬虫系统更易于维护和扩展:
- 初始化层:参数集中管理,避免硬编码
- 请求控制层:统一异常处理与重试机制
- 解析层:数据提取(需子类实现具体逻辑)
- 存储层:数据持久化接口
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反爬机制的不断升级,需要采用综合策略应对:
- 动态请求头管理
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"'
})
-
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)
-
浏览器指纹模拟与验证码处理
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("按回车继续...")
五、工程化扩展与性能优化
对于企业级应用,还需要考虑分布式架构和性能优化:
- 异步爬虫实现
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
-
数据存储优化
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秒)
• 禁止爬取个人隐私数据
• 尊重网站的服务条款