因项目需要爬取XX市XX区的poi信息,上网找了很多的教程,都需要注册高德的开发控制台,通过调用高德提供的api方式获取poi信息,但是有次数限制。所以自己搞了个"爬虫"。
1、思路:
打开高德地图浏览时,拖地地图,会调用https://ditu.amap.com/service/regeo?longitude=XXX&latitude=XXX接口,此接口是不需要鉴权的,该接口的作用是定位,将你所处位置的gps点输入进去后,将会返回附近的几个定位点。思路油然而生,我将所要爬取范围的所有gps点一个个的调用这个接口,不就可以获取到这个区域的poi信息了嘛,说干就干
2、代码及说明
写代码前考虑一个问题,如果是爬某个区,但是这个区在地图上的形状是不规则的,怎么办?用个矩形将不规则图形包围起来嘛,简单!但是效率低,我也懒得去研究其他方法,也别想其他方法了,因为接口调用是有限制的,同一ip开多线程去调用,会被封ip和mac地址(我已经被封了,幸好我机智,发现了另外一个,第一个:https://www.amap.com/ ,第二个:https://ditu.amap.com/)。如果你有代理ip池,当我没说,将你的地图分割成一个个小的矩形,分别用多线程尽情去轰炸高德的两个域名把。
(1)我是将爬取到的数据放到mysql数据库中,上sql和代码:
sql
CREATE TABLE `t_poi` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`x` varchar(50) DEFAULT NULL,
`y` varchar(50) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4571 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
dbUtil:
package com.cm.test1.poi;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class DbUtil {
public static final String URL = "jdbc:mysql://xx.xx.xx.xx:3306/xx";
public static final String USER = "root";
public static final String PASSWORD = "root";
private static Connection conn = null;
static{
try {
//1.加载驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获得数据库连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
return conn;
}
public static String insert="INSERT INTO `cm`.`t_poi`( `name`, `x`, `y`, `type`, `address`) VALUES ('%s', '%s', '%s', '%s', '%s')";
public static String query="select count(1) count from t_poi where name = '%s' ";
public static void create(String name,String x,String y,String type,String address)
throws SQLException {
Connection conn= getConnection();
String sql=String.format(insert,name,x,y,type,address);
PreparedStatement ptmt = conn.prepareStatement(sql);
ptmt.execute();
}
public static boolean query(String name) throws SQLException {
Connection conn= getConnection();
String sql=String.format(query,name);
PreparedStatement ptmt = conn.prepareStatement(sql);
ResultSet rs= ptmt.executeQuery();
while(rs.next()){
return rs.getInt("count")==1?true:false;
}
return false;
}
}
main方法:
package com..cm.test1.poi;/*
import cn.hutool.db.DbUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;
*/
/**
* @Description
* @Author zhangchuang
* @Date 2020/5/25 5:38 下午
**//*
public class GusuShang {
static RestTemplate restTemplate = new RestTemplate();
static HttpEntity<MultiValueMap<String, String>> httpEntity;
static {
HttpHeaders header = new HttpHeaders();
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("Connection", "keep-alive");
map.add("Accept-Language", "zh-CN,zh;q=0.9");
// 需求需要传参为form-data格式
header.setContentType(MediaType.MULTIPART_FORM_DATA);
httpEntity = new HttpEntity<>(map, header);
}
public static void main(String[] args) throws IOException, InterruptedException, SQLException {
minX = 150.267134;
maxY = 36.511519;
minY = 36.219654;
maxX = 150.122539;
//String url = "https://www.amap.com/service/regeo?longitude=%s&latitude=%s";
String url = "https://ditu.amap.com/service/regeo?longitude=%s&latitude=%s";
double value = 0.0001;
long count = 0;
for (double i = maxY; i >= minY; i -= value) {
for (double j = minX; j <= maxX; j += value) {
count++;
JSONObject jsonObject = httpGet(url, j, i);
if (ObjectUtils.isEmpty(jsonObject) || jsonObject.getInteger("status") != 1) {
System.out.println("调用接口返回值为 null" + jsonObject);
} else {
System.out.println("调用接口返回值为 " + jsonObject.toJSONString());
if (!"XX区".equals(jsonObject.getJSONObject("data").getString("district"))) {
continue;
}
JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("poi_list");
if (ObjectUtils.isEmpty(jsonArray)) {
System.out.println("jsonArray is empty");
continue;
}
int size = jsonArray.size();
for (int k = 0; k < size; k++) {
try {
JSONObject jo = jsonArray.getJSONObject(k);
String name = jo.getString("name");
if (DbUtil.query(name)) {
continue;
}
String type = jo.getString("type");
String x = jo.getString("longitude");
String y = jo.getString("latitude");
String address = jo.getString("address");
DbUtil.create(name, x, y, type, address);
} catch (Exception e) {
System.out.println(e);
}
}
}
}
}
System.out.println("count=" + count);
}
static JSONObject httpGet(String url, double x, double y) throws IOException {
try {
url = String.format(url, (x + "").substring(0, 10), (y + "").substring(0, 9));
JSONObject jsonObject = restTemplate.getForObject(url, JSONObject.class, httpEntity);
return jsonObject;
} catch (Exception e) {
System.out.println(e);
}
return null;
}
static class Point {
double x;
double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
}
(2)代码简单说明和需要改动的参数说明
2.1、sql和DbUtil没什么好说的吧,简单的sql和jdbc连接。
2.2、下面说mian中的参数
2.3、
minX = 120.577235;
maxY = 31.271818;
minY = 31.269659;
maxX = 120.662379;
minX是矩形在地图左上角点位的经度
maxY是矩形在地图左上角点位的纬度
maxX是矩形在地图右下角点位的经度
minY是矩形在地图左上角点位的纬度
哦对,还有个高德地图点位拾取的工具:
https://lbs.amap.com/console/show/picker
点击页面就可以在网站的上半部分看到点击的gpw点位。
2.4、
//String url = "https://www.amap.com/service/regeo?longitude=%s&latitude=%s";
String url = "https://ditu.amap.com/service/regeo?longitude=%s&latitude=%s";
当第一个被封了之后,切换第二个,两个效果一样
2.5、
double value = 0.0001;
这个值是决定了统计的经度,值越小,粒度越细,收集到的点位越多,但是循环此处会呈几何倍数的增长,慎重修改。我目前是经纬度共用了这一个值,也可以将经纬度使用不同的值控制,自行修改。
2.6、
if (!"XX区".equals(jsonObject.getJSONObject("data").getString("district"))) {
continue;
}
如果是想要爬取某个区的poi点,需要修改XX区的值,因为矩形框会包括其他区域,所以需要过滤下,如果不考虑,则注释掉,否则会将爬取维度定为”区“。如果想爬多个区,就修改下if的条件,将多个区放在list中,用contains来判断。
3、总结
可能我这个不是啥好东西,但是我确实用这个方法爬出了一个区5000多个高质量定位,商户信息爬的比较少,大的建筑基本都能爬到,不同需求不同处理方法,希望对你有用,谢谢。