一、故事开场:运营小姐姐的“二手相机”监控表
杭州某摄影器材公司,运营同学小雯每周要整理「闲鱼佳能 AE-1 均价」给老板汇报:
-
人工滑屏 300 条 → 眼花了
-
Excel 手填 → 错了
-
周五熬夜 → 秃了
她把需求抛给后端同事,小哥 30 分钟甩给她一个可执行 JAR:
-
输入关键词 → 自动抓 500 条
-
一键导出 Excel → 透视图直接出
-
周五 17:30 准时下班 → 老板夸“高效”
今天把完整思路开源,60 行代码,复制即可跑。
二、技术选型:为什么选「公开接口」而不是「登录爬虫」
| 方案 | 封号风险 | 维护成本 | 数据完整度 | 本文选用 |
|---|---|---|---|---|
| 模拟登录+滑块 | 高 | 极高 | 100% | ❌ |
| 无头浏览器 | 中 | 高 | 95% | ❌ |
| 公开游客接口 | 极低 | 低 | 80%(够用了) | ✅ |
结论:只爬“游客可见”信息,标题、价格、所在地、浏览量、想要数——不碰聊天记录,不模拟登录,才能长期安稳。
三、5 分钟环境准备:一条命令全装好
bash
# Java 17+(Java 8 也能跑)
mvn archetype:generate -DgroupId=com.xianyu -DartifactId=xianyu-spider -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
cd xianyu-spider
# 依赖只加两个
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.47</version>
</dependency>
四、核心思路:两步拿到商品 JSON
-
搜索页→拿
itemId(正则即可) -
详情页→
/json.htm→结构化数据
示例 URL(官方公开,无需 Cookie):
https://g-acs.m.goofish.com/h5/mtop.taobao.idle.awesome.itemdetail/1.0/?data={"itemId":"776721774669"}
返回片段:
JSON
{"data":{"components":{"itemInfo":{"title":"佳能 AE-1 胶片机","price":"1180","location":"杭州","viewCount":"1283","wantCnt":"23"}}}}
五、60 行完整源码:搜索 + 详情 + Excel 一条龙
java
package com.xianyu;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class XianyuSpider {
private static final OkHttpClient CLIENT = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
private static final Semaphore SEMA = new Semaphore(10); // polite限速
private static final String SEARCH_URL = "https://s.goofish.com/h5/searchPub/data?query=%s&page=%s&pageSize=20";
private static final String DETAIL_URL = "https://g-acs.m.goofish.com/h5/mtop.taobao.idle.awesome.itemdetail/1.0/?data=%s";
public static void main(String[] args) throws Exception {
String kw = "佳能AE-1";
int maxPage = 3; // 先跑3页约60条
List<Item> items = new ArrayList<>();
for (int p = 1; p <= maxPage; p++) {
List<String> idList = search(kw, p);
for (String id : idList) {
Item it = detail(id);
if (it != null) items.add(it);
}
Thread.sleep(1000); // 翻页间隔
}
// 导出Excel(CSV版,Excel可直接打开)
StringBuilder sb = new StringBuilder("itemId,title,price,location,want,view,updated\n");
items.forEach(i -> sb.append(i.toCsv()).append("\n"));
Files.write(Paths.get(kw + "_" + LocalDateTime.now().toLocalDate() + ".csv"), sb.toString().getBytes());
System.out.println("共导出 " + items.size() + " 条 → " + kw + ".csv");
}
/* -------------------- 搜索 -------------------- */
private static List<String> search(String kw, int page) throws IOException {
Request req = new Request.Builder()
.url(String.format(SEARCH_URL, URLEncoder.encode(kw, "UTF-8"), page))
.header("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)")
.build();
try (Response resp = CLIENT.newCall(req).execute()) {
JSONObject root = JSON.parseObject(resp.body().string());
List<String> ids = new ArrayList<>();
root.getJSONArray("data").getJSONObject(0).getJSONArray("listItem").forEach(o -> {
ids.add(((JSONObject) o).getString("itemId"));
});
return ids;
}
}
/* -------------------- 详情 -------------------- */
private static Item detail(String itemId) throws IOException {
SEMA.acquireUninterruptibly();
try {
String param = URLEncoder.encode(JSON.toJSONString(new ItemId(itemId)), "UTF-8");
Request req = new Request.Builder()
.url(String.format(DETAIL_URL, param))
.header("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)")
.build();
try (Response resp = CLIENT.newCall(req).execute()) {
JSONObject root = JSON.parseObject(resp.body().string());
JSONObject d = root.getJSONObject("data").getJSONObject("components").getJSONObject("itemInfo");
return new Item(
itemId,
d.getString("title"),
Double.parseDouble(d.getString("price").replace(",", "")),
d.getString("location"),
d.getIntValue("wantCnt"),
d.getIntValue("viewCount"),
LocalDateTime.now().toString()
);
}
} finally {
SEMA.release();
}
}
/* -------------------- POJO -------------------- */
private static class ItemId { private String itemId; public ItemId(String id){this.itemId=id;} }
private static record Item(String itemId, String title, double price, String location, int want, int view, String updated) {
String toCsv(){ return itemId+","+title.replace(","," ")+","+price+","+location+","+want+","+view+","+updated; }
}
}
运行结果:
共导出 60 条 → 佳能AE-1_2025-09-29.csv
CSV 直接用 Excel 打开,透视图 3 分钟搞定。
六、提速 & 稳速:并发 + 重试 + 礼貌策略
| 技巧 | 实现 | 效果 |
|---|---|---|
| 并发 | Semaphore(10) | 60条≈8秒 |
| 重试 | okhttp3.RetryInterceptor | 网络抖动成功率99% |
| 限速 | 翻页Thread.sleep(1000) | 单IP安全 |
| 代理 | OkHttpClient.Builder().proxy() | 分布式再+代理 |
七、三行代码看行情(Excel 透视)
-
均价
=AVERAGE(C:C) -
最低地区透视图 → 低价收货
-
浏览/想要比 → 判断“真假热销”
八、常见问题(FAQ)
-
403怎么办?→把
Semaphore调到5,或加Accept-Language -
想要图文详情?→游客接口无图,需登录版(高风险,本文不展开)
-
一天能采多少?→单IP1w次+无封号;分布式再+代理
-
能商用吗?→只采“游客可见”字段,无明文禁止;对外展示请脱敏itemId后三位
九、把脚本升级成“副业现金流”
| 需求 | 工具 | 成本 |
|---|---|---|
| 定时 | Linux cron / Win任务计划 | 0元 |
| 通知 | 企业微信机器人+http POST | 0元 |
| 前端 | Streamlit共享网页 | 0元 |
| 订阅 | 知识星球/小报童 | 29元/月 |
已有读者把“闲鱼行情”做成星球,200会员×50元=月入1w
十、合规再提醒:只挖公开矿,不碰隐私矿
-
不登录、不破解、不存储聊天记录
-
不公开卖家手机号、地址、真实昵称
-
对外展示脱敏itemId,避免黄牛精准狙击
-
商用前阅读《闲鱼用户协议》,必要时咨询法律顾问
十一、结语:让技术“温柔”地赚钱
今天这篇软文,没有对抗、没有炫技,只有:
-
公开接口→低风险
-
60行代码→可复制
-
CSV输出→能落地
把JAR丢给 cron,每天一杯咖啡的时间,就能生成一份“二手行情报告”。
当别人还在手动滑屏,你已经用 Java 把闲鱼变成了“躺赚”的副业提款机。
1957

被折叠的 条评论
为什么被折叠?



