以下是实现上述需求的Java代码实践步骤和示例。
OkHttp
或 Apache HttpClient
。这里使用 OkHttp
,因其简洁高效。Jsoup
。它提供了非常方便的API来解析和操作HTML文档。xml深色版本<dependencies>
<!-- OkHttp for HTTP requests -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- Jsoup for HTML parsing -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
</dependencies>
java深色版本import okhttp3.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class BaiduSERPAnalyzer {
// OkHttp客户端
private static final OkHttpClient client = new OkHttpClient();
// 百度搜索URL模板
private static final String BAIDU_SEARCH_URL = "https://www.baidu.com/s?wd=%s&pn=%d";
// 请求头 (模拟浏览器,提高成功率)
private static final Request.Builder requestBuilder = new Request.Builder()
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
/**
* 封装网站的TDK信息
*/
public static class WebsiteInfo {
public final String title;
public final String description;
public final String keywords;
public final String url;
public WebsiteInfo(String title, String description, String keywords, String url) {
this.title = title;
this.description = description;
this.keywords = keywords;
this.url = url;
}
@Override
public String toString() {
return String.format("Title: %s\nURL: %s\nDescription: %s\nKeywords: %s\n",
title, url, description, keywords);
}
}
/**
* 从百度搜索结果页面解析出目标链接列表
* @param keyword 搜索关键词
* @param numPages 要爬取的页数 (每页约10个结果)
* @return 包含链接的列表
*/
public static List<String> extractUrlsFromBaidu(String keyword, int numPages) throws IOException {
List<String> urls = new ArrayList<>();
String encodedKeyword = java.net.URLEncoder.encode(keyword, "UTF-8");
for (int page = 0; page < numPages; page++) {
String url = String.format(BAIDU_SEARCH_URL, encodedKeyword, page * 10);
Request request = requestBuilder.url(url).build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
String html = Objects.requireNonNull(response.body()).string();
Document doc = Jsoup.parse(html);
// 解析百度搜索结果中的链接
// 百度的搜索结果链接通常在 h3 标签下的 a 标签里,class 可能包含 'title-link' 或类似
// 注意:百度的HTML结构可能会变,需要根据实际情况调整选择器
Elements linkElements = doc.select("div.c-container h3 a"); // 这是一个常见选择器,可能需要调整
for (Element link : linkElements) {
String href = link.attr("href");
// 百度的链接可能是跳转链接,需要进一步解析真实URL
if (href != null && !href.isEmpty() && href.startsWith("http")) {
// 如果是直接的外链 (如 ads.baidu.com 可能是广告,需过滤)
if (!href.contains("baidu.com") || href.contains("ad.")) {
continue;
}
urls.add(href);
} else if (href != null && href.startsWith("/link?url=")) {
// 处理百度的跳转链接 /link?url=...
String realUrl = resolveBaiduRedirect(href);
if (realUrl != null) {
urls.add(realUrl);
}
}
}
// 避免过于频繁的请求
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
return urls;
}
/**
* 解析百度跳转链接,获取真实URL
* @param baiduLink 百度的跳转链接 (如 /link?url=...)
* @return 真实的目标URL
*/
private static String resolveBaiduRedirect(String baiduLink) throws IOException {
// 百度跳转链接通常是相对路径,需要补全
String fullUrl = "https://www.baidu.com" + baiduLink;
Request request = requestBuilder.url(fullUrl).build();
try (Response response = client.newCall(request).execute()) {
if (response.isRedirect()) {
// 跟随重定向
return Objects.requireNonNull(response.request().url()).toString();
} else {
// 如果没有重定向,尝试从HTML中解析
String html = Objects.requireNonNull(response.body()).string();
Document doc = Jsoup.parse(html);
Element metaRefresh = doc.selectFirst("meta[http-equiv=refresh]");
if (metaRefresh != null) {
String content = metaRefresh.attr("content");
if (content != null) {
int pos = content.toLowerCase().indexOf("url=");
if (pos != -1) {
return content.substring(pos + 4).trim();
}
}
}
}
}
return null;
}
BOLL.PORTASTYL.COM65丨SHARE.NMFZTD.ORG.CN96丨SHARE.PORTASTYL.COM98丨SWEET.PORTASTYL.COM30
ZB.CSWDYS.COM24丨MOBI.SDDANTUOJI.CN26丨WWW.HZBESTSEO.COM97丨ZUQIU.ZYZJZD.CN85
LIVE.CSWDYS.COM35丨PRETTY.JS-SJL.CN10丨IQIYI.PORTASTYL.COM37丨FOOTBALL.NMFZTD.ORG.CN71
TV.CZCJJC.CN90丨M.JS-SJL.CN29丨SOHU.NMFZTD.ORG.CN87丨FREE.CSWDYS.COM47
QQ.XLSY.CN81丨MAP.SDDANTUOJI.CN71丨ONLINE.NMFZTD.ORG.CN13丨ZUQIU.HZBESTSEO.COM71
JRS.NMFZTD.ORG.CN46丨YES.JS-SJL.CN81丨IQIYI.CZCJJC.CN52丨LIVE.NMFZTD.ORG.CN25
24K.XLSY.CN17丨MAP.ZYZJZD.CN71丨SAISHI.CZCJJC.CN29丨JRS.JS-SJL.CN46
ZHIBO.HZBESTSEO.COM36丨MAP.JS-SJL.CN57丨PRETTY.JXLGDL.COM76丨ZYZJZD.CN18
FREE.PORTASTYL.COM18丨MAP.CZCJJC.CN12丨YES.ZYZJZD.CN97丨ZUQIU.PORTASTYL.COM48
ZB.NMFZTD.ORG.CN95丨QQ.SDDANTUOJI.CN37丨MAP.CSWDYS.COM91丨ZB.HZBESTSEO.COM83
/**
* 爬取单个网站的 TDK 信息
* @param url 网站URL
* @return WebsiteInfo 对象
*/
public static WebsiteInfo fetchWebsiteTDK(String url) throws IOException {
Request request = requestBuilder.url(url).build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
System.err.println("Failed to fetch " + url + ": " + response);
return new WebsiteInfo("", "", "", url);
}
String html = Objects.requireNonNull(response.body()).string();
Document doc = Jsoup.parse(html);
String title = doc.title(); // 直接获取<title>标签内容
String description = "";
String keywords = "";
// 查找 meta description
Element descMeta = doc.selectFirst("meta[name='description'], meta[name='Description'], meta[property='og:description']");
if (descMeta != null) {
description = descMeta.attr("content");
}
// 查找 meta keywords
Element keywordsMeta = doc.selectFirst("meta[name='keywords'], meta[name='Keywords']");
if (keywordsMeta != null) {
keywords = keywordsMeta.attr("content");
}
return new WebsiteInfo(title, description, keywords, url);
}
}
/**
* 主方法:演示完整流程
*/
public static void main(String[] args) {
String keyword = "人工智能"; // 替换为你想搜索的关键词
int pages = 1; // 爬取前1页 (约10个结果)
try {
System.out.println("正在搜索关键词: " + keyword);
List<String> targetUrls = extractUrlsFromBaidu(keyword, pages);
System.out.println("共找到 " + targetUrls.size() + " 个目标网站链接。");
// 去重
targetUrls = new ArrayList<>(new java.util.LinkedHashSet<>(targetUrls));
List<WebsiteInfo> results = new ArrayList<>();
for (String url : targetUrls) {
System.out.println("正在爬取: " + url);
WebsiteInfo info = fetchWebsiteTDK(url);
results.add(info);
// 休眠避免被封
Thread.sleep(2000);
}
// 输出结果
System.out.println("\n=== 搜索关键词 '" + keyword + "' 的竞品TDK分析结果 ===");
for (int i = 0; i < results.size(); i++) {
System.out.println("\n--- 排名 " + (i + 1) + " ---");
System.out.println(results.get(i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Jsoup
只能解析静态HTML。解决方案:使用 Selenium WebDriver
+ ChromeDriver
来驱动真实浏览器,但这会显著降低速度和增加复杂性。extractUrlsFromBaidu
方法中的CSS选择器 (div.c-container h3 a
) 可能失效。需要定期检查并更新选择器。/link?url=...
)。代码中的 resolveBaiduRedirect
方法通过发起请求并跟随重定向来获取真实URL。这会增加请求次数和时间。robots.txt
文件(如 https://www.baidu.com/robots.txt
)。爬取百度搜索结果可能违反其服务条款。Thread.sleep()
是必要的,但会降低效率。虽然可以直接用Java+Jsoup+OkHttp实现一个基础的爬虫来获取百度搜索结果中网站的TDK,但面临着反爬虫、动态渲染、法律风险等严峻挑战。对于生产环境或大规模爬取,建议:
robots.txt
,控制请求频率,使用代理IP,并做好被封禁的准备。上述代码提供了一个基础框架,但在实际应用中需要根据百度当前的页面结构和反爬策略进行大量的调整和优化。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。