电商桌面自动化实战:用RPA实现抖店批量铺货
做电商多店铺运营的都知道,铺货是最典型的重复性劳动------筛选、编辑、定价、发布,每个商品都要走一遍相同的流程。
本文从技术角度解析如何用桌面自动化(RPA)实现抖店批量铺货,以及实际落地中遇到的几个关键问题。
一、铺货自动化的核心流程
手动铺货的完整流程:
- 打开抖店货源页面
- 按条件筛选商品(类目、价格、好评率、发货率等)
- 逐个点击"去铺货"
- 进入商品编辑页
- 处理主图(裁剪比例、生成视频)
- 计算并设置价格
- 填写商品信息
- 发布
自动化需要解决的,就是把这8个步骤全部程序化。
二、技术架构设计
2.1 整体架构
┌─────────────────────────────────────────┐
│ 用户交互层 │
│ 对话式UI(自然语言指令解析) │
├─────────────────────────────────────────┤
│ 任务调度层 │
│ 任务队列 + 排队机制 + 定时触发 │
├─────────────────────────────────────────┤
│ 自动化执行层 │
│ 浏览器自动化 + 页面元素操作 │
├─────────────────────────────────────────┤
│ 数据管理层 │
│ 本地SQLite + 独立浏览器配置 │
└─────────────────────────────────────────┘
核心设计要点:
- 每个店铺独立浏览器实例,避免Cookie/Session串号
- 任务队列机制,同一店铺的任务串行执行,避免并发冲突
- 对话式指令解析,降低用户学习成本
2.2 独立浏览器环境
多店铺管理的核心技术难点是浏览器隔离:
python
import os
class BrowserManager:
"""每个店铺使用独立的浏览器配置目录"""
def __init__(self, base_data_dir: str):
self.base_data_dir = base_data_dir
def get_browser_profile(self, shop_id: str) -> str:
"""
为每个店铺返回独立的浏览器配置目录
确保Cookie、Session、缓存完全隔离
"""
profile_dir = os.path.join(
self.base_data_dir, 'browsers', f'shop_{shop_id}'
)
os.makedirs(profile_dir, exist_ok=True)
return profile_dir
def launch_browser(self, shop_id: str, url: str):
"""启动指定店铺的浏览器实例"""
profile = self.get_browser_profile(shop_id)
# 使用独立的用户数据目录启动浏览器
browser = launch(
headless=False,
user_data_dir=profile,
args=['--no-sandbox']
)
page = browser.new_page()
page.goto(url)
return browser, page
三、选品策略的实现
3.1 筛选条件数据模型
python
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class SelectionStrategy:
"""选品策略配置"""
name: str # 策略名称
category: Optional[str] = None # 类目筛选
price_min: float = 0 # 最低价格
price_max: float = 9999 # 最高价格
min_rating: float = 0.95 # 最低好评率
min_ship_rate: float = 0.98 # 最低发货率
max_return_rate: float = 0.05 # 最高退货率
free_shipping: bool = True # 是否包邮
seven_day_return: bool = True # 是否7天无理由
blocked_suppliers: list = field(default_factory=list) # 屏蔽供应商
blocked_keywords: list = field(default_factory=list) # 屏蔽关键词
target_count: int = 50 # 目标铺货数量
def should_include(self, product: dict) -> bool:
"""判断商品是否符合筛选条件"""
# 供应商屏蔽
if product.get('supplier') in self.blocked_suppliers:
return False
# 关键词屏蔽
title = product.get('title', '')
for kw in self.blocked_keywords:
if kw in title:
return False
# 条件筛选
if not (self.price_min <= product.get('price', 0) <= self.price_max):
return False
if product.get('rating', 0) < self.min_rating:
return False
if product.get('ship_rate', 0) < self.min_ship_rate:
return False
if product.get('return_rate', 1) > self.max_return_rate:
return False
return True
3.2 自动筛选与执行
python
class ListingBot:
"""铺货自动化执行引擎"""
def __init__(self, strategy: SelectionStrategy, browser_manager: BrowserManager):
self.strategy = strategy
self.browser_mgr = browser_manager
self.success_count = 0
self.fail_count = 0
async def run(self, shop_id: str):
"""执行完整的铺货流程"""
browser, page = self.browser_mgr.launch_browser(
shop_id,
'https://fxg.jinritemai.com/ffp/goods/source'
)
# 第一步:按策略筛选商品
products = await self._filter_products(page)
# 第二步:逐个执行铺货
for product in products[:self.strategy.target_count]:
try:
await self._list_product(page, product)
self.success_count += 1
except Exception as e:
self.fail_count += 1
self._log(f'商品 {product["title"]} 铺货失败: {e}')
await browser.close()
async def _filter_products(self, page) -> list:
"""在货源页面按策略条件筛选商品"""
products = []
items = await page.query_selector_all('.product-item')
for item in items:
product_data = {
'title': await item.inner_text('.product-title'),
'price': float(await item.inner_text('.price')),
'rating': float(await item.inner_text('.rating')),
'supplier': await item.inner_text('.supplier-name'),
}
if self.strategy.should_include(product_data):
products.append(product_data)
return products
async def _list_product(self, page, product: dict):
"""执行单个商品的铺货操作"""
# 点击"去铺货"
await page.click('.list-btn')
await page.wait_for_load_state('networkidle')
# 设置价格(加价逻辑)
base_price = product['price']
final_price = base_price * (1 + self.markup_rate / 100) + self.fixed_markup
await page.fill('.price-input', str(round(final_price, 2)))
# 处理主图
await self._process_main_image(page)
# 发布
await page.click('.publish-btn')
await page.wait_for_selector('.success-toast')
四、定价逻辑的实现
python
class PriceCalculator:
"""定价计算器"""
def __init__(self, markup_rate: float = 100, fixed_markup: float = 0):
"""
markup_rate: 加价百分比(100 = 基准价的2倍)
fixed_markup: 固定加价金额
"""
self.markup_rate = markup_rate
self.fixed_markup = fixed_markup
def calculate(self, supply_price: float) -> float:
"""
计算最终售价
供货价50 + 加价100% + 固定加价10 = 50*2 + 10 = 110
"""
return supply_price * (1 + self.markup_rate / 100) + self.fixed_markup
五、任务队列与调度
多店铺场景下,任务调度是关键:
python
import asyncio
from collections import defaultdict
class TaskScheduler:
"""
任务调度器
同一店铺的任务串行执行,不同店铺的任务并行执行
"""
def __init__(self):
self.queues = defaultdict(asyncio.Queue) # 每个店铺一个队列
self.running = {}
async def submit(self, shop_id: str, task):
"""提交任务到指定店铺的队列"""
await self.queues[shop_id].put(task)
async def run_shop(self, shop_id: str):
"""消费指定店铺的任务队列"""
while True:
task = await self.queues[shop_id].get()
self.running[shop_id] = task
try:
await task.execute()
finally:
self.running.pop(shop_id, None)
self.queues[shop_id].task_done()
def is_shop_busy(self, shop_id: str) -> bool:
"""检查店铺是否正在执行任务"""
return shop_id in self.running
六、落地过程中的几个问题
问题一:页面元素变化导致脚本失效
抖店的页面结构会不定期更新,选择器会失效。建议用文本匹配+属性匹配的组合定位,而不是依赖固定的CSS选择器。
问题二:操作频率过高被检测
建议每次操作之间加随机延迟(2-5秒),模拟人工操作节奏。任务之间也要有间隔。
问题三:浏览器进程泄漏
长时间运行后,浏览器进程可能没有正确关闭。建议定期检查并清理僵尸进程。
问题四:登录状态过期
Cookie和Session有有效期,需要定期检测登录状态,过期后提醒用户重新授权。
七、总结
电商铺货自动化的核心难点不是技术实现,而是稳定性和可维护性。页面结构会变、登录状态会过期、操作频率要控制------这些都是生产环境中必须处理的问题。
建议采用"对话式UI + 任务队列 + 独立浏览器"的架构,既能降低用户操作门槛,又能保证多店铺并发的稳定性。