1. 项目概述最近在整理球星卡收藏时发现卡淘平台上有大量高清图片资源手动保存效率太低。于是花了两个晚上写了个简单的爬虫工具专门用于抓取卡淘平台的球星卡图片。这个项目虽然技术难度不高但涉及到的反爬策略和图片处理细节值得分享。我采用的方案是PythonRequestsBeautifulSoup组合配合少量反反爬技巧最终实现了稳定高效的图片采集。整个过程没有使用任何复杂框架核心代码不到100行特别适合Python初学者练手。下面就把这个项目的完整实现过程和踩坑经验分享给大家。2. 核心需求解析2.1 目标网站分析卡淘平台是一个专业的球星卡交易平台其图片资源有以下几个特点图片存储在独立CDN域名下主站采用动态加载技术有基础的反爬机制但不严格图片URL有规律可循通过Chrome开发者工具分析发现商品详情页的图片是通过AJAX加载的实际图片地址都指向img.xxx.com域名每个商品有唯一的ID编号图片命名规则为ID_序号.jpg2.2 技术选型考量选择RequestsBeautifulSoup方案主要基于轻量级不需要复杂框架学习成本低两个库API都很简单扩展性强可以方便地添加代理、headers等性能足够对于中小规模采集完全够用相比之下Scrapy虽然功能更强大但学习曲线陡峭而且对于这个简单项目显得过于重量级。3. 实现过程详解3.1 环境准备基础环境配置# 安装依赖库 pip install requests beautifulsoup4 pillow # 项目结构 ├── config.py # 配置文件 ├── main.py # 主程序 ├── utils.py # 工具函数 └── images/ # 图片存储目录3.2 核心代码实现3.2.1 请求模块封装import requests from bs4 import BeautifulSoup import time headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.katao.com/ } def get_html(url): try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() return resp.text except Exception as e: print(f请求失败: {e}) return None3.2.2 图片解析与下载import os from urllib.parse import urljoin def parse_images(html, base_url): soup BeautifulSoup(html, html.parser) img_tags soup.select(.product-image img) image_urls [] for img in img_tags: src img.get(data-src) or img.get(src) if src and http not in src: src urljoin(base_url, src) image_urls.append(src) return image_urls def download_image(url, save_path): if not os.path.exists(save_path): os.makedirs(save_path) filename url.split(/)[-1] filepath os.path.join(save_path, filename) try: resp requests.get(url, headersheaders, streamTrue) with open(filepath, wb) as f: for chunk in resp.iter_content(1024): f.write(chunk) print(f下载成功: {filename}) except Exception as e: print(f下载失败: {e})3.3 反爬应对策略卡淘平台采用了以下几种基础反爬措施User-Agent检测请求频率限制关键数据动态加载应对方案轮换User-Agent添加随机延迟(1-3秒)模拟浏览器headers处理动态加载内容改进后的请求函数import random def get_html_enhanced(url): 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/605.1.15, Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 ] headers[User-Agent] random.choice(user_agents) time.sleep(random.uniform(1, 3)) return get_html(url)4. 项目优化与扩展4.1 性能优化技巧使用Session保持连接session requests.Session() session.headers.update(headers) # 后续所有请求使用session resp session.get(url)多线程下载from concurrent.futures import ThreadPoolExecutor def batch_download(urls, save_path, max_workers5): with ThreadPoolExecutor(max_workersmax_workers) as executor: for url in urls: executor.submit(download_image, url, save_path)断点续传支持def download_with_resume(url, save_path): # 检查本地已下载部分 # 添加Range头继续下载 pass4.2 功能扩展方向自动分类存储def organize_images_by_category(): # 根据图片特征自动分类 pass图片质量检测from PIL import Image def check_image_quality(filepath): with Image.open(filepath) as img: return img.size, img.mode元数据提取def extract_metadata(filepath): # 从图片中提取EXIF等信息 pass5. 常见问题与解决方案5.1 请求被拒绝症状返回403状态码 解决方案检查headers是否完整添加Cookies使用代理IP5.2 图片下载不完整症状图片文件损坏 解决方案使用stream模式下载添加重试机制验证文件完整性5.3 动态内容加载失败症状获取不到图片URL 解决方案使用Selenium模拟浏览器分析AJAX接口直接调用后端API6. 完整项目代码以下是整合后的完整实现# config.py BASE_URL https://www.katao.com SAVE_PATH ./images MAX_RETRY 3 TIMEOUT 10 # main.py import os import time import random import requests from bs4 import BeautifulSoup from urllib.parse import urljoin from config import BASE_URL, SAVE_PATH, MAX_RETRY, TIMEOUT class KataoSpider: def __init__(self): self.session requests.Session() self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: BASE_URL } self.session.headers.update(self.headers) def get_html(self, url): for _ in range(MAX_RETRY): try: time.sleep(random.uniform(1, 3)) resp self.session.get(url, timeoutTIMEOUT) resp.raise_for_status() return resp.text except Exception as e: print(f请求失败: {e}) return None def parse_images(self, html): soup BeautifulSoup(html, html.parser) return [img.get(data-src) or img.get(src) for img in soup.select(.product-image img)] def download_image(self, url, save_dirSAVE_PATH): if not url.startswith(http): url urljoin(BASE_URL, url) if not os.path.exists(save_dir): os.makedirs(save_dir) filename os.path.join(save_dir, url.split(/)[-1]) try: resp self.session.get(url, streamTrue) with open(filename, wb) as f: for chunk in resp.iter_content(1024): f.write(chunk) print(f下载成功: {filename}) return True except Exception as e: print(f下载失败: {e}) return False def run(self, start_url): html self.get_html(start_url) if not html: return False image_urls self.parse_images(html) for url in image_urls: self.download_image(url) return True if __name__ __main__: spider KataoSpider() product_url f{BASE_URL}/product/12345 # 示例商品URL spider.run(product_url)7. 法律与道德注意事项遵守robots.txt规则控制请求频率(建议≥2秒/次)仅用于个人学习用途不采集敏感或隐私数据尊重版权声明在实际操作中我通常会添加以下限制每天最多采集1000张图片夜间(0:00-6:00)不运行爬虫不采集用户个人信息图片仅用于个人收藏研究这个项目虽然简单但涵盖了爬虫开发的核心要点。通过这个案例我们可以学习到基础爬虫的实现原理常见反爬策略的应对方法Python网络编程的基本技巧代码组织与项目结构设计对于想要深入学习的朋友建议接下来可以研究Scrapy框架的使用分布式爬虫架构验证码识别技术数据清洗与存储优化