使用 Spring Boot 实现爬虫 + IP 代理池的使用
使用 Spring Boot 实现爬虫 + IP 代理池
在许多爬虫任务中,使用 IP 代理池是一种常见的方式,尤其是当需要大规模抓取数据时。通过切换 IP 可以避免 IP 被封禁的问题,提升爬虫的稳定性和抓取效率。本文将介绍如何在 Spring Boot 项目中实现一个爬虫,结合 IP 代理池来抓取目标网站的数据。文章会详细讲解爬虫实现部分、IP 代理池的构建、常见问题以及如何处理这些问题。
1. 项目概述
我们通过 Spring Boot 框架搭建爬虫应用,目标网站为猎聘网(Liepin)。爬虫将定期抓取职位信息,并将数据存入数据库。为了保证抓取的稳定性和速度,我们使用了 IP 代理池来绕过网站对单个 IP 频繁请求的限制。
1.1 爬虫的功能需求
- 多页数据抓取:通过翻页功能获取多页职位数据。
- 代理池使用:为了避免封禁,每次请求使用不同的代理 IP。
- IP池动态管理:定期检查代理 IP 的有效性,失效时及时更换。
1.2 技术栈
- Spring Boot:用于构建爬虫服务。
- HttpClient:执行 HTTP 请求。
- Gson:解析 JSON 数据。
- MySQL:存储爬取的职位数据。
- 线程池:提高代理 IP 验证的效率。
2. 爬虫部分
2.1 使用 Spring Boot 实现爬虫
我们将爬虫实现拆分为多个部分:设置请求头、构造 HTTP 请求、解析 JSON 数据和存储数据。
2.1.1 设置请求头
首先,我们需要模拟浏览器的请求头,以避免请求被反爬机制拦截。使用 HttpPost 方法设置请求头,如下所示:
HttpPost httpPost = new HttpPost("https://api-c.liepin.com/api/com.liepin.searchfront4c.pc-search-job");
// 设置请求头
httpPost.setHeader("Accept", "application/json, text/plain, */*");
httpPost.setHeader("Accept-Encoding", "gzip, deflate, br, zstd");
httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9");
httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0");
2.1.2 构造请求体
爬虫需要模拟表单提交,传递查询参数。例如,我们要查询城市为“410”的职位,并且一次抓取 40 条数据。请求体可以如下设置:
String jsonData = "{\"data\":{\"mainSearchPcConditionForm\":{\"city\":\"410\",\"dq\":\"410\",\"currentPage\":\"0\",\"pageSize\":40}}}";
StringEntity entity = new StringEntity(jsonData, "UTF-8");
httpPost.setEntity(entity);
2.1.3 发送请求并获取响应
通过 CloseableHttpClient 执行 HTTP 请求并获取响应。我们使用 EntityUtils.toString() 来解析响应内容:
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity responseEntity = response.getEntity();
String responseJson = EntityUtils.toString(responseEntity);
// 解析数据
jobList = cleanData(responseJson);
jobDao.insertList(jobList);
}
2.1.4 解析响应数据
猎聘网返回的数据是 JSON 格式,我们使用 Gson 库进行解析。假设返回的数据格式如下:
{
"data": {
"data": {
"jobCardList": [
{
"job": { "title": "Java Developer", "salary": "15k-25k" },
"comp": { "compName": "Tech Co." },
"dq": "Beijing"
}
]
}
}
}
我们通过以下方法提取职位信息:
private List<Job> cleanData(String responseJson) {
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(responseJson, JsonObject.class);
JsonObject dataObject = jsonObject.getAsJsonObject("data").getAsJsonObject("data");
JsonArray jobCardList = dataObject.getAsJsonArray("jobCardList");
List<Job> jobList = new ArrayList<>();
for (JsonElement jobElement : jobCardList) {
JsonObject jobObject = jobElement.getAsJsonObject();
JsonObject compObject = jobObject.getAsJsonObject("comp");
JsonObject jobDetails = jobObject.getAsJsonObject("job");
String jobName = jobDetails.get("title").getAsString();
String company = compObject.get("compName").getAsString();
String salary = jobDetails.get("salary").getAsString();
String city = jobDetails.get("dq").getAsString();
Job job = new Job(jobName, company, salary, city);
jobList.add(job);
}
return jobList;
}
3. IP 代理池的实现
为了避免被目标网站封禁,我们使用 IP 代理池。代理池的核心功能包括:获取代理 IP、验证代理 IP 是否有效、处理失效的 IP。
3.1 IP 代理池的设计
代理池实现的关键在于获取代理 IP 并定期验证它们的有效性。代理 IP 的有效期通常较短,过期后需要从池中删除。
3.1.1 获取 IP 列表
我们从代理提供商获取代理 IP 地址列表,通过调用 API 获取动态 IP:
public static void getIpList() throws IOException, InterruptedException {
System.out.println("正在抓取IP......");
String apiUrl = "http://proxy.siyetian.com/apis_get.html?token=YOUR_TOKEN&limit=10&type=0&data_format=json";
String resultJsonStr = getData(apiUrl);
JSONObject jsonObject = JSON.parseObject(resultJsonStr);
JSONArray data = jsonObject.getJSONArray("data");
for (int i = 0; i < data.size(); i++) {
JSONObject ipData = data.getJSONObject(i);
String ipAddress = ipData.getString("ip");
int port = ipData.getIntValue("port");
AgencyIp agencyIp = new AgencyIp();
agencyIp.setAddress(ipAddress);
agencyIp.setPort(port);
System.out.println(agencyIp.toString());
// 启动线程验证IP有效性
pool.execute(() -> {
if (checkIpAddress(agencyIp)) {
try {
ipQueue.put(agencyIp); // 将有效的IP加入队列
System.out.println("代理IP加入池中:" + agencyIp);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("队列操作中发生中断:" + e.getMessage());
}
} else {
System.out.println("代理IP无效:" + agencyIp);
}
});
}
}
3.1.2 验证 IP 是否有效
我们需要通过 HTTP 请求来验证代理 IP 的有效性。若 IP 返回 200 状态码,则认为 IP 有效:
private static boolean checkIpAddress(AgencyIp agencyIp) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(agencyIp.getAddress(), agencyIp.getPort()));
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL("https://api-c.liepin.com/api/com.liepin.searchfront4c.pc-search-job").openConnection(proxy);
connection.setConnectTimeout(3000); // 设置连接超时
connection.setReadTimeout(3000); // 设置读取超时
connection.setUseCaches(false);
if (connection.getResponseCode() == 200) {
System.out.println(agencyIp.getAddress() + " 该IP有效");
return true;
}
} catch (IOException e) {
System.out.println(agencyIp.getAddress() + " 该IP无效,原因:" + e.getMessage());
}
return false;
}
3.2 常见问题及解决
-
IP 失效:代理 IP 的有效期通常较短,使用时需要实时检测。我们使用了
checkIpAddress方法来定期验证 IP 是否有效,并在无效时从池中移除。 -
IP 超时:代理 IP 请求可能会因为超时等原因失败。我们在设置连接时配置了超时限制,确保连接不会无限期挂起。
-
代理池耗尽:如果代理池的 IP 被耗尽,爬虫会在一定时间内休
眠,等待新的 IP 加入。
4. 总结
通过本文的讲解,我们构建了一个基于 Spring Boot 的爬虫,并结合 IP 代理池来避免 IP 被封禁的问题。我们实现了以下功能:
- 使用 IP 代理池来动态切换 IP 地址。
- 定期验证代理 IP 的有效性,失效时从池中移除。
- 使用多线程提高代理 IP 验证和爬取效率。
通过这个爬虫,我们可以高效稳定地抓取猎聘网的数据,并避免因 IP 被封禁导致爬取失败的问题。
更多推荐
所有评论(0)