1. 项目背景与核心价值
这个Python爬虫项目的核心目标是解决个人资产管理中的痛点:当我们需要整理自己在各大平台的订单记录、物流投递信息时,往往面临数据分散、格式不统一的问题。想象一下,你在电商平台买了东西,在理财平台有投资记录,在各种服务订阅平台有消费记录——这些数据分散在不同平台,手动整理费时费力。
我开发这个工具最初是为了解决自己的需求:每个月都要花几个小时整理各个平台的消费记录。通过自动化爬虫技术,我们可以实现:
- 一键抓取多个平台的订单/投递数据
- 统一存储为结构化的CSV文件
- 持久化保存到SQLite数据库
- 支持后续的数据分析和可视化
这个方案特别适合:
- 个人财务管理者
- 自由职业者需要统计项目收支
- 电商从业者管理多个平台订单
- 任何需要定期整理消费记录的人
2. 技术架构设计
2.1 整体技术栈选择
这个项目采用了Python作为主要开发语言,主要基于以下考虑:
- Python有丰富的爬虫生态(Requests、BeautifulSoup、Selenium等)
- 数据处理库成熟(Pandas对CSV的支持极佳)
- SQLite作为轻量级数据库完美契合个人使用场景
- 跨平台兼容性好
技术组件清单:
- 爬虫核心:Requests + BeautifulSoup
- 动态页面处理:Selenium(备选)
- 数据处理:Pandas
- 数据库:SQLite3(Python内置)
- 日志记录:logging模块
2.2 关键设计决策
在设计过程中,有几个关键决策点值得讨论:
-
为什么选择混合存储方案(CSV+SQLite)?
- CSV:人类可读,便于快速查看和Excel处理
- SQLite:支持复杂查询,适合长期数据积累
- 两者结合既满足了即时使用需求,又为后续分析打下基础
-
如何处理不同平台的反爬机制?
- 随机User-Agent轮换
- 请求间隔随机化
- 必要时使用Selenium模拟真人操作
- 重要数据设置本地缓存避免频繁请求
-
数据模型设计考量
python复制class Order:
def __init__(self):
self.platform = "" # 平台名称
self.order_id = "" # 订单号
self.date = "" # 下单日期
self.amount = 0.0 # 金额
self.status = "" # 状态
self.items = [] # 商品清单
3. 核心实现细节
3.1 爬虫模块实现
以电商平台为例,爬取订单数据的关键步骤:
- 登录认证处理
python复制def login(username, password):
session = requests.Session()
login_data = {
'username': username,
'password': password
}
response = session.post(LOGIN_URL, data=login_data)
if 'login_success' not in response.text:
raise Exception("登录失败")
return session
- 订单列表抓取
python复制def fetch_orders(session, page=1):
params = {
'page': page,
'size': 20
}
response = session.get(ORDER_LIST_URL, params=params)
soup = BeautifulSoup(response.text, 'html.parser')
orders = []
for item in soup.select('.order-item'):
order = Order()
order.platform = "某电商平台"
order.order_id = item.select('.order-id')[0].text.strip()
# 其他字段解析...
orders.append(order)
return orders
- 分页处理与异常重试
python复制def fetch_all_orders(session, max_page=5):
all_orders = []
for page in range(1, max_page+1):
try:
orders = fetch_orders(session, page)
if not orders:
break
all_orders.extend(orders)
time.sleep(random.uniform(1, 3)) # 随机延迟
except Exception as e:
logging.error(f"第{page}页抓取失败: {str(e)}")
continue
return all_orders
3.2 数据存储实现
- CSV导出实现
python复制def save_to_csv(orders, filename):
import pandas as pd
data = {
'平台': [o.platform for o in orders],
'订单号': [o.order_id for o in orders],
'日期': [o.date for o in orders],
'金额': [o.amount for o in orders],
'状态': [o.status for o in orders]
}
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding='utf-8-sig')
- SQLite存储实现
python复制def init_db(db_file):
import sqlite3
conn = sqlite3.connect(db_file)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS orders
(id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT,
order_id TEXT UNIQUE,
date TEXT,
amount REAL,
status TEXT)''')
conn.commit()
return conn
def save_to_db(conn, orders):
c = conn.cursor()
for order in orders:
try:
c.execute("INSERT INTO orders (platform, order_id, date, amount, status) VALUES (?, ?, ?, ?, ?)",
(order.platform, order.order_id, order.date, order.amount, order.status))
except sqlite3.IntegrityError:
# 订单已存在则更新
c.execute("UPDATE orders SET status=?, amount=? WHERE order_id=?",
(order.status, order.amount, order.order_id))
conn.commit()
4. 高级功能与优化
4.1 多平台适配架构
为了实现支持多个平台,我们采用插件式设计:
code复制├── platforms/
│ ├── __init__.py
│ ├── base_platform.py # 基础抽象类
│ ├── platform_a.py # 平台A实现
│ ├── platform_b.py # 平台B实现
│ └── ...
├── config.yaml # 平台配置
└── main.py # 主程序
基础抽象类定义:
python复制from abc import ABC, abstractmethod
class BasePlatform(ABC):
@abstractmethod
def login(self, username, password):
pass
@abstractmethod
def fetch_orders(self, **kwargs):
pass
@property
@abstractmethod
def platform_name(self):
pass
4.2 性能优化技巧
- 异步请求优化
python复制import aiohttp
import asyncio
async def fetch_order_details(session, order_ids):
async with aiohttp.ClientSession() as session:
tasks = []
for order_id in order_ids:
task = asyncio.create_task(
fetch_single_order(session, order_id)
)
tasks.append(task)
return await asyncio.gather(*tasks)
- 缓存机制实现
python复制from datetime import datetime, timedelta
import pickle
import os
def get_cache_key(platform, date):
return f"{platform}_{date.strftime('%Y%m%d')}.pkl"
def load_from_cache(cache_key, expiry_hours=24):
if not os.path.exists(f"cache/{cache_key}"):
return None
file_time = datetime.fromtimestamp(os.path.getmtime(f"cache/{cache_key}"))
if datetime.now() - file_time > timedelta(hours=expiry_hours):
return None
with open(f"cache/{cache_key}", 'rb') as f:
return pickle.load(f)
def save_to_cache(cache_key, data):
os.makedirs("cache", exist_ok=True)
with open(f"cache/{cache_key}", 'wb') as f:
pickle.dump(data, f)
5. 实战问题与解决方案
5.1 常见反爬应对策略
-
验证码识别
- 简单验证码:使用Tesseract OCR
- 复杂验证码:第三方打码平台(需付费)
- 最佳实践:尽量保存cookies避免重复验证
-
动态加载数据
- 使用Selenium模拟滚动加载
- 分析XHR请求直接获取接口数据
- 示例:
python复制from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def selenium_scrape(driver, url):
driver.get(url)
try:
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "order-item"))
)
except TimeoutException:
print("页面加载超时")
# 模拟滚动加载
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
return driver.page_source
5.2 数据清洗难点
- 金额格式统一
python复制def normalize_amount(amount_str):
import re
# 处理 ¥199.00 / $199 / 199元 等格式
match = re.search(r'[\d,.]+', amount_str)
if not match:
return 0.0
num_str = match.group().replace(',', '')
try:
return float(num_str)
except ValueError:
return 0.0
- 日期标准化
python复制from datetime import datetime
def parse_date(date_str):
formats = [
'%Y-%m-%d %H:%M:%S',
'%Y/%m/%d %H:%M',
'%Y年%m月%d日',
'%b %d, %Y' # Oct 10, 2023
]
for fmt in formats:
try:
return datetime.strptime(date_str, fmt)
except ValueError:
continue
return datetime.now() # 默认返回当前时间
6. 项目部署与使用指南
6.1 环境配置
推荐使用conda创建独立环境:
bash复制conda create -n order_spider python=3.8
conda activate order_spider
pip install requests beautifulsoup4 selenium pandas
对于需要处理动态页面的情况,还需安装浏览器驱动:
bash复制# Chrome驱动
brew install chromedriver # MacOS
choco install chromedriver # Windows
6.2 配置文件示例
config.yaml示例:
yaml复制platforms:
platform_a:
username: "your_username"
password: "your_password"
active: true
platform_b:
username: "email@example.com"
password: "secure_password"
active: true
storage:
csv_path: "./exports"
db_path: "./data/orders.db"
settings:
request_interval: 1.5 # 秒
max_retry: 3
6.3 主程序流程
python复制def main():
# 初始化
config = load_config()
db_conn = init_db(config['storage']['db_path'])
# 多平台处理
for platform_name, platform_config in config['platforms'].items():
if not platform_config['active']:
continue
platform = get_platform_class(platform_name)()
try:
# 登录
session = platform.login(
platform_config['username'],
platform_config['password']
)
# 抓取订单
orders = platform.fetch_orders(session)
# 存储数据
csv_file = f"{config['storage']['csv_path']}/{platform_name}_{datetime.now().strftime('%Y%m%d')}.csv"
save_to_csv(orders, csv_file)
save_to_db(db_conn, orders)
except Exception as e:
logging.error(f"{platform_name}处理失败: {str(e)}")
db_conn.close()
7. 扩展思路与进阶方向
- 数据可视化扩展
- 使用Matplotlib/Plotly生成月度消费趋势图
- 按平台分类统计饼图
- 示例:
python复制import matplotlib.pyplot as plt
def plot_spending_by_month(df):
df['date'] = pd.to_datetime(df['date'])
monthly = df.groupby(df['date'].dt.to_period('M'))['amount'].sum()
plt.figure(figsize=(10, 6))
monthly.plot(kind='bar')
plt.title('月度消费统计')
plt.ylabel('金额')
plt.savefig('monthly_spending.png')
-
自动化邮件报告
- 定期运行脚本
- 生成PDF报告
- 通过SMTP发送结果
-
移动端适配
- 使用Flask/Django创建简单Web界面
- 或打包为Android/iOS应用(Kivy/BeeWare)
-
云存储集成
- 自动备份到Google Drive/Dropbox
- 或同步到私有云NAS
这个项目最有趣的地方在于,你可以根据自己的需求不断扩展功能。比如我后来添加了:
- 自动归类消费类型(餐饮、购物、娱乐等)
- 与记账软件对接
- 异常消费提醒功能
每个使用者都可以基于这个基础框架,打造最适合自己使用的个性化资产管理工具。
