1. 项目背景与需求分析
最近在长沙出差时,突然想吃西贝莜面村的黄米凉糕和羊肉串,但打开地图APP发现搜索结果里混杂了大量广告和无关信息。作为一个Java开发者,我决定自己写个POI下载器来精准获取长沙所有西贝门店的位置数据。
POI(Point of Interest)即兴趣点数据,包含商家的名称、地址、坐标等信息。通过程序获取这类数据主要有两种方式:调用地图API(如高德/百度地图开放平台)或直接爬取网页数据。考虑到API调用有配额限制且需要注册,我选择了后者这种更"极客"的方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与工具准备
2.1 核心工具链
选用Java生态中的经典组合:
- Jsoup:轻量级HTML解析库,比正则表达式更可靠
- HttpClient:处理HTTP请求和会话管理
- Lombok:减少样板代码(需在IDE安装插件)
- FastJson:解析JSON格式的响应数据
java复制// build.gradle依赖配置
dependencies {
implementation 'org.jsoup:jsoup:1.15.3'
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
implementation 'com.alibaba:fastjson:1.2.83'
compileOnly 'org.projectlombok:lombok:1.18.24'
}
2.2 反爬策略应对
大众点评等网站常见的防护措施:
- User-Agent检测:需要模拟主流浏览器
- IP频率限制:建议控制在5秒/次
- 验证码触发:避免连续快速请求
- 动态参数:观察XHR请求中的时间戳等参数
3. 网页结构分析与数据定位
3.1 目标网站选择
经过对比,选用大众点评作为数据源,其优势在于:
- 门店信息完整(地址、电话、评分等)
- 分页结构清晰
- 无需登录即可查看基础信息
分析URL规律发现:
code复制https://www.dianping.com/changsha/ch10/r801
其中ch10表示餐饮分类,r801对应西贝莜面村的分类ID
3.2 HTML节点解析
使用Chrome开发者工具检查元素,发现关键数据特征:
- 门店列表容器:
<div class="shop-list"> - 单个门店卡片:
<div class="shop-item"> - 名称节点:
<h4 class="shop-name"> - 地址信息:
<span class="addr">
java复制// Jsoup解析示例
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0...")
.get();
Elements shops = doc.select(".shop-list .shop-item");
shops.forEach(shop -> {
String name = shop.select(".shop-name").text();
String address = shop.select(".addr").text();
// 其他字段提取...
});
4. 核心代码实现
4.1 请求控制器
java复制public class PoiCrawler {
private static final String BASE_URL = "https://www.dianping.com";
private static final int DELAY = 5000; // 5秒延迟
public List<ShopInfo> crawl(int maxPages) {
List<ShopInfo> results = new ArrayList<>();
CloseableHttpClient client = HttpClients.createDefault();
try {
for (int page = 1; page <= maxPages; page++) {
String url = buildUrl(page);
HttpGet request = new HttpGet(url);
setHeaders(request); // 设置请求头
String html = client.execute(request,
response -> EntityUtils.toString(response.getEntity()));
results.addAll(parseHtml(html));
Thread.sleep(DELAY); // 遵守爬虫礼仪
}
} catch (Exception e) {
log.error("爬取失败", e);
}
return results;
}
}
4.2 数据模型设计
java复制@Data // Lombok注解
public class ShopInfo {
private String name;
private String address;
private String phone;
private Double rating;
private String region; // 行政区划
private GeoPoint location; // 经纬度
@Data
public static class GeoPoint {
private Double lat;
private Double lng;
}
}
4.3 地理编码处理
地址文本需要转换为经纬度坐标:
java复制public GeoPoint geocode(String address) {
// 实际应调用地理编码API,这里演示伪代码
String apiUrl = "https://geocoder.api/...";
String json = httpGet(apiUrl);
JSONObject obj = JSON.parseObject(json);
return new GeoPoint(
obj.getDouble("lat"),
obj.getDouble("lng")
);
}
5. 数据存储与可视化
5.1 结果导出
支持多种格式输出:
java复制public void export(List<ShopInfo> shops, ExportType type) {
switch (type) {
case CSV:
// 使用OpenCSV
break;
case JSON:
// FastJSON序列化
break;
case EXCEL:
// Apache POI
break;
}
}
5.2 地图标注
将结果导入QGIS或百度地图生成可视化:
code复制名称,地址,经度,纬度
西贝莜面村(德思勤店),雨花区湘府中路...,112.996,28.109
...
6. 实际运行结果
在长沙地区共获取到7家门店信息:
| 门店名称 | 行政区 | 详细地址 | 评分 |
|---|---|---|---|
| 西贝莜面村(德思勤店) | 雨花区 | 湘府中路18号 | 4.5 |
| 西贝莜面村(悦方ID Mall店) | 天心区 | 坡子街216号 | 4.3 |
| ... | ... | ... | ... |
7. 开发注意事项
-
法律合规:
- 仅采集公开可见数据
- 不绕过任何反爬措施
- 控制请求频率(建议>5秒/次)
-
异常处理:
java复制try { // 网络请求代码 } catch (IOException e) { if (e instanceof HttpStatusException) { HttpStatusException ex = (HttpStatusException)e; if (ex.getStatusCode() == 403) { log.warn("触发反爬机制,建议更换IP"); } } } -
性能优化:
- 使用连接池管理HTTPClient
- 对地理编码结果做本地缓存
- 采用生产者-消费者模式处理多页数据
这个项目让我深刻体会到,有时候自己动手写工具比依赖商业产品更高效。整个过程涉及了网络爬虫、HTML解析、数据清洗等多个实用技能点,对Java开发者来说是很好的练手项目。
