先说一个典型的场景:用户在代购独立站前台粘贴了一条淘宝或1688的链接,敲下回车,后台系统就开始自动抓取商品标题、价格、SKU等信息,整个过程不需要人工干预。这个功能看似不复杂,但要做好、做稳、做快,背后涉及链路解析、API调用、模拟请求、异步队列、反爬容灾等一系列工程决策。本文就来拆解这个过程,结合Taoify跨境电商的实际架构,讲一讲如何用HttpClient + Jsoup + 正则表达式,搭建一个稳定、可扩展的商品采集模块。
一、采集流程设计
当用户在前台粘贴一个链接,敲下回车,后台就开始紧张工作了。整个采集链路大致包含这么几个阶段:
链接解析 → 提取商品ID → 调用平台API或模拟请求获取HTML → 解析HTML提取结构化数据 → 映射到内部商品模型 → 异步保存到数据库
流程听起来简单,但每一步都有坑。比如,不同平台的链接结构不同,淘宝的是"id=xxxxx",1688的可能又带路径参数。再比如,API有调用频次限制,模拟请求有封IP风险。所以,一个好的采集模块,既要能处理各种类型的链接,又要有足够的容错能力。
下面的示意图清晰地展示了整个链条:用户粘贴链接后,系统先做链接解析,再根据平台规则选择采集方式(API或模拟请求),然后通过异步队列完成数据提取与入库,最终通过WebSocket通知前端。

二、淘宝商品采集实现(基于官方API)
淘宝这边,优先推荐使用官方API。好处很明显:稳定、合规、不用担心反爬。只要拿到了淘宝开放平台的AppKey,调用taobao.item.get接口就能拿到完整的商品数据,包括标题、价格、图片、属性等,比解析HTML靠谱得多。
核心逻辑分三步:从URL中提取商品ID,构建API请求,解析返回结果。来看一下代码实现(关键部分):
@Service
public class TaobaoProductCollector {
@Autowired
private RestTemplate restTemplate;
public ProductInfo collect(String url) {
// 从URL中提取商品ID
String pattern = "id=(\\d+)";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(url);
String itemId = m.find() ? m.group(1) : null;
// 构建API请求
String apiUrl = "https://eco.taobao.com/router/rest";
Map params = new HashMap<>();
params.put("method", "taobao.item.get");
params.put("app_key", APP_KEY);
params.put("fields", "num_iid,title,price,pic_url,props_name");
params.put("num_iid", itemId);
params.put("sign", generateSign(params));
String response = restTemplate.postForObject(apiUrl, params, String.class);
return parseResponse(response);
}
}
这里面有几个细节需要注意:一是签名算法必须按淘宝开放平台的规范来,二是字段列表要精准,避免不必要的流量消耗。如果能拿到API文档,建议一次性把库存、SKU、轮播图等信息都要过来,减少后续补采的麻烦。
三、1688商品采集实现(模拟请求)
1688这边的情况就复杂一些。开放平台的门槛较高,小规模采集不一定能申请下来。这时候,很多团队会选择模拟浏览器请求的方式——说白了,就是用HttpClient发一个带User-Agent和Accept头的GET请求,拿到HTML后交给Jsoup去解析。
原理不复杂,我们来拆开看:
@Component
public class AlibabaProductCollector {
public ProductInfo collect(String url) {
// 设置袋里IP池,防止被封
CloseableHttpClient httpClient = HttpClients.custom()
.setProxy(new HttpHost(PROXY_HOST, PROXY_PORT))
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectionRequestTimeout(5000)
.setSocketTimeout(10000)
.build())
.build();
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
httpGet.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
String html = EntityUtils.toString(response.getEntity(), "UTF-8");
Document doc = Jsoup.parse(html);
ProductInfo product = new ProductInfo();
// 1688页面解析逻辑
product.setTitle(doc.select(".d-title h1").text());
product.setPrice(doc.select(".price").first().text());
// 解析SKU信息
Elements skuElements = doc.select(".sku-items li");
List skus = skuElements.stream().map(e -> {
SKU sku = new SKU();
sku.setName(e.select(".sku-name").text());
sku.setValue(e.select(".sku-value").text());
return sku;
}).collect(Collectors.toList());
product.setSkus(skus);
return product;
}
}
}
模拟请求的灵活性确实不错,但有两个硬伤:一是网页结构随时可能变化,一旦1688改版,解析代码就得跟着调整;二是反爬压力大,频繁请求很可能被封IP。所以,袋里池是必须的——用阿里云弹性IP池动态切换,是个比较稳妥的方案。
另外,强烈建议对模拟请求的模块做好错误重试。一次性请求失败后,换IP再试一次,连续失败三次再报异常,能显著提升采集成功率。
四、异步任务队列设计
采集操作有一个特点:耗时比较长。一次完整的采集,从发起请求到解析入库,少则一两秒,多则十来秒。如果在用户点击“采集”按钮的瞬间同步等待,体验就太差了。所以,异步处理是必须的。
业内通用的做法是引入消息队列。所有采集请求先入队,后端消费者按节奏处理,处理完后再通过WebSocket通知前端。Taoify跨境电商这里用的是阿里云MNS,核心代码示例如下:
@Service
public class CollectService {
@Autowired
private MnsClient mnsClient;
public void submitCollectTask(String url, String platform, String userId) {
CollectTask task = new CollectTask(url, platform, userId);
// 发送到消息队列
mnsClient.sendMessage(QueueName.PRODUCT_COLLECT_QUEUE, JSON.toJSONString(task));
}
@MnsListener(queueName = "PRODUCT_COLLECT_QUEUE")
public void handleCollectTask(String message) {
CollectTask task = JSON.parseObject(message, CollectTask.class);
ProductInfo product = collect(task.getUrl(), task.getPlatform());
// 存入数据库,关联用户
productMapper.insert(product);
// 发送WebSocket通知前端采集完成
webSocketService.sendNotification(task.getUserId(), product);
}
}
这里有一个值得注意的点:消费端的逻辑应该是幂等的。万一消息被重复消费(MQ特性或者网络重试导致的),不能出现重复插入数据的问题。建议在入库前先做一次去重校验,比如根据URL+用户ID的唯一索引来防重。
消息队列选型上,阿里云MNS、RocketMQ、RabbitMQ都可以,关键看团队的技术栈和运维能力。Taoify选择MNS主要是因为它与阿里云其他服务的集成度高,监控和告警也比较省心。
五、反爬策略与容灾
聊到采集,反爬是个绕不开的话题。不管是淘宝还是1688,对异常请求的识别和拦截都在不断升级。我们需要做的,是尽可能让请求看起来像正常的用户行为。
核心策略大致包括:
- 袋里IP池:每个请求都随机使用池中IP,避免单一IP的高频访问。阿里云弹性IP池可以按需创建和释放,成本可控。
- 请求频率控制:同一个IP的请求间隔不能太短,建议至少间隔3-5秒。
- User-Agent随机化:固定用同一个UA容易被识别,最好维护一个UA列表,每次请求随机取一个。
- 请求头完整性:Cookie、Referer等字段最好也带上,尤其是需要登录态的页面。
即使做了这些,仍然可能出现采集失败。这时候容灾机制就派上用场了。我们配置了重试策略:单次失败后,自动切换IP重试,最多3次。如果3次都失败,就把任务扔进死信队列(死信表),后续人工介入排查原因。
这套机制跑下来,日处理百万级链接是没问题的。当然,前提是做好监控和告警,一旦某个链路失败率升高,能及时发现问题并修复。
以上就是商品采集模块从设计到落地的关键环节。从API调用到模拟请求,从同步阻塞到异步解耦,每一步都直接影响用户体验和系统稳定性。希望这些经验能给正在搭建或优化采集模块的团队一些参考。
