5 changed files with 529 additions and 0 deletions
@ -0,0 +1,529 @@ |
|||
import javax.imageio.ImageIO; |
|||
import java.awt.*; |
|||
import java.awt.image.BufferedImage; |
|||
import java.io.File; |
|||
import java.net.URI; |
|||
import java.net.http.HttpClient; |
|||
import java.net.http.HttpRequest; |
|||
import java.net.http.HttpResponse; |
|||
import java.util.*; |
|||
import java.util.List; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
// ========== 1. 泛型接口:定义通用数据处理行为 ==========
|
|||
/** |
|||
* 通用数据爬虫接口(泛型:T-爬取数据类型,K-数据唯一标识类型) |
|||
*/ |
|||
interface DataCrawler<T, K> { |
|||
// 爬取数据
|
|||
T crawlData(K key) throws Exception; |
|||
// 解析数据
|
|||
Map<String, Object> parseData(T rawData); |
|||
// 保存数据
|
|||
void saveData(K key, Map<String, Object> data); |
|||
} |
|||
|
|||
/** |
|||
* 通用数据可视化接口(泛型:T-可视化数据类型) |
|||
*/ |
|||
interface DataVisualizer<T> { |
|||
void generateVisualization(String title, T data); |
|||
} |
|||
|
|||
// ========== 2. 抽象泛型父类:爬虫基类 ==========
|
|||
abstract class AbstractDataCrawler<T, K> implements DataCrawler<T, K> { |
|||
// 泛型集合:存储爬取的原始数据(K-标识,T-原始数据)
|
|||
protected Map<K, T> rawDataMap = new HashMap<>(); |
|||
// 泛型集合:存储解析后的结构化数据(K-标识,Map-结构化数据)
|
|||
protected Map<K, Map<String, Object>> parsedDataMap = new LinkedHashMap<>(); |
|||
// 泛型集合:存储爬取失败的标识
|
|||
protected List<K> failedKeys = new LinkedList<>(); |
|||
|
|||
// 通用HTTP请求方法(泛型返回值)
|
|||
protected String doHttpGet(String url) throws Exception { |
|||
HttpClient client = HttpClient.newHttpClient(); |
|||
HttpRequest request = HttpRequest.newBuilder() |
|||
.uri(URI.create(url)) |
|||
.GET() |
|||
.build(); |
|||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); |
|||
if (response.statusCode() != 200) { |
|||
throw new RuntimeException("HTTP请求失败,状态码:" + response.statusCode()); |
|||
} |
|||
return response.body(); |
|||
} |
|||
|
|||
// 通用失败记录方法
|
|||
protected void recordFailure(K key, Exception e) { |
|||
failedKeys.add(key); |
|||
System.err.println("❌ 爬取" + key + "失败:" + e.getMessage()); |
|||
} |
|||
|
|||
// 泛型方法:获取解析后的数据
|
|||
public <V> V getParsedValue(K key, String field, Class<V> type) { |
|||
if (parsedDataMap.containsKey(key) && parsedDataMap.get(key).containsKey(field)) { |
|||
Object value = parsedDataMap.get(key).get(field); |
|||
if (type.isInstance(value)) { |
|||
return type.cast(value); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
// 抽象方法:获取API地址(子类实现)
|
|||
protected abstract String getApiUrl(K key); |
|||
} |
|||
|
|||
// ========== 3. 天气爬虫子类(泛型实现) ==========
|
|||
class WeatherCrawler extends AbstractDataCrawler<String, String> implements DataVisualizer<Map<String, List<?>>> { |
|||
// 城市经纬度映射(泛型集合)
|
|||
private Map<String, String[]> cityLatLonMap = new HashMap<>(); |
|||
|
|||
// 初始化城市数据
|
|||
public WeatherCrawler() { |
|||
cityLatLonMap.put("西安", new String[]{"34.2644", "108.9497"}); |
|||
cityLatLonMap.put("成都", new String[]{"30.5728", "104.0668"}); |
|||
cityLatLonMap.put("兰州", new String[]{"36.0611", "103.8343"}); |
|||
cityLatLonMap.put("乌鲁木齐", new String[]{"43.8256", "87.6168"}); |
|||
} |
|||
|
|||
@Override |
|||
public String crawlData(String cityName) throws Exception { |
|||
if (!cityLatLonMap.containsKey(cityName)) { |
|||
throw new IllegalArgumentException("未配置城市:" + cityName + "的经纬度"); |
|||
} |
|||
String url = getApiUrl(cityName); |
|||
System.out.println("🌐 正在爬取" + cityName + "天气数据:" + url); |
|||
String rawData = doHttpGet(url); |
|||
rawDataMap.put(cityName, rawData); |
|||
return rawData; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, Object> parseData(String rawData) { |
|||
Map<String, Object> parsedData = new HashMap<>(); |
|||
try { |
|||
List<String> times = parseTimes(rawData); |
|||
List<Double> temps = parseTemperatures(rawData); |
|||
parsedData.put("times", times); |
|||
parsedData.put("temps", temps); |
|||
parsedData.put("minTemp", temps.stream().mapToDouble(Double::doubleValue).min().orElse(0)); |
|||
parsedData.put("maxTemp", temps.stream().mapToDouble(Double::doubleValue).max().orElse(0)); |
|||
} catch (Exception e) { |
|||
System.err.println("❌ 解析天气数据失败:" + e.getMessage()); |
|||
} |
|||
return parsedData; |
|||
} |
|||
|
|||
@Override |
|||
public void saveData(String cityName, Map<String, Object> data) { |
|||
parsedDataMap.put(cityName, data); |
|||
System.out.println("💾 " + cityName + "天气数据已保存,解析字段数:" + data.size()); |
|||
} |
|||
|
|||
@Override |
|||
protected String getApiUrl(String cityName) { |
|||
String[] latLon = cityLatLonMap.get(cityName); |
|||
return String.format( |
|||
"https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s&hourly=temperature_2m&past_days=1&forecast_days=3", |
|||
latLon[0], latLon[1] |
|||
); |
|||
} |
|||
|
|||
// 解析时间(复用原有逻辑)
|
|||
private List<String> parseTimes(String json) { |
|||
List<String> times = new ArrayList<>(); |
|||
try { |
|||
Pattern pattern = Pattern.compile("\"time\":\\[([^\\]]+)\\]"); |
|||
Matcher matcher = pattern.matcher(json); |
|||
if (matcher.find()) { |
|||
String timeStr = matcher.group(1); |
|||
String[] timeArray = timeStr.split(","); |
|||
for (String t : timeArray) { |
|||
t = t.trim().replace("\"", ""); |
|||
if (!t.isEmpty()) { |
|||
if (t.contains("T")) { |
|||
String date = t.substring(5, 10); |
|||
String time = t.substring(11, 16); |
|||
times.add(date + "\n" + time); |
|||
} else { |
|||
times.add(t); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
System.err.println("❌ 解析时间失败:" + e.getMessage()); |
|||
} |
|||
return times; |
|||
} |
|||
|
|||
// 解析温度(复用原有逻辑)
|
|||
private List<Double> parseTemperatures(String json) { |
|||
List<Double> temps = new ArrayList<>(); |
|||
try { |
|||
Pattern pattern = Pattern.compile("\"temperature_2m\":\\[([^\\]]+)\\]"); |
|||
Matcher matcher = pattern.matcher(json); |
|||
if (matcher.find()) { |
|||
String tempStr = matcher.group(1); |
|||
String[] tempArray = tempStr.split(","); |
|||
for (String t : tempArray) { |
|||
t = t.trim(); |
|||
if (!t.isEmpty()) { |
|||
try { |
|||
temps.add(Double.parseDouble(t)); |
|||
} catch (NumberFormatException e) { |
|||
System.err.println("⚠️ 无法解析温度值: " + t); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
System.err.println("❌ 解析温度失败:" + e.getMessage()); |
|||
} |
|||
return temps; |
|||
} |
|||
|
|||
// 天气数据可视化(泛型实现)
|
|||
@Override |
|||
public void generateVisualization(String cityName, Map<String, List<?>> data) { |
|||
List<String> times = (List<String>) data.get("times"); |
|||
List<Double> temps = (List<Double>) data.get("temps"); |
|||
if (times == null || temps == null || times.isEmpty() || temps.isEmpty()) { |
|||
System.out.println("⚠️ " + cityName + " 数据无效,跳过绘图"); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
int width = 1400; |
|||
int height = 700; |
|||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); |
|||
Graphics2D g2d = image.createGraphics(); |
|||
|
|||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
|||
g2d.setColor(Color.WHITE); |
|||
g2d.fillRect(0, 0, width, height); |
|||
|
|||
int marginLeft = 120; |
|||
int marginRight = 60; |
|||
int marginTop = 80; |
|||
int marginBottom = 120; |
|||
|
|||
int chartWidth = width - marginLeft - marginRight; |
|||
int chartHeight = height - marginTop - marginBottom; |
|||
|
|||
double minTemp = temps.stream().mapToDouble(Double::doubleValue).min().orElse(0); |
|||
double maxTemp = temps.stream().mapToDouble(Double::doubleValue).max().orElse(0); |
|||
double tempRange = maxTemp - minTemp; |
|||
|
|||
// 画网格
|
|||
g2d.setColor(Color.LIGHT_GRAY); |
|||
g2d.setStroke(new BasicStroke(1)); |
|||
int numYLines = 10; |
|||
for (int i = 0; i <= numYLines; i++) { |
|||
int y = marginTop + (chartHeight * i / numYLines); |
|||
g2d.drawLine(marginLeft, y, marginLeft + chartWidth, y); |
|||
double temp = maxTemp - (tempRange * i / numYLines); |
|||
String label = String.format("%.1f°C", temp); |
|||
g2d.setColor(Color.BLACK); |
|||
g2d.setFont(new Font("Arial", Font.PLAIN, 12)); |
|||
g2d.drawString(label, marginLeft - 50, y + 4); |
|||
g2d.setColor(Color.LIGHT_GRAY); |
|||
} |
|||
|
|||
// 画坐标轴
|
|||
g2d.setColor(Color.BLACK); |
|||
g2d.setStroke(new BasicStroke(2)); |
|||
g2d.drawLine(marginLeft, marginTop, marginLeft, marginTop + chartHeight); |
|||
g2d.drawLine(marginLeft, marginTop + chartHeight, marginLeft + chartWidth, marginTop + chartHeight); |
|||
|
|||
// 标题
|
|||
g2d.setFont(new Font("Arial", Font.BOLD, 20)); |
|||
String title = cityName + " 逐小时温度变化(过去1天+未来3天)"; |
|||
g2d.drawString(title, width / 2 - 250, 45); |
|||
|
|||
// 轴标签
|
|||
g2d.setFont(new Font("Arial", Font.PLAIN, 14)); |
|||
g2d.drawString("时间", width / 2 - 20, height - 40); |
|||
|
|||
Graphics2D g2dRotated = (Graphics2D) g2d.create(); |
|||
g2dRotated.rotate(-Math.PI / 2); |
|||
g2dRotated.drawString("温度 (°C)", -height / 2, 35); |
|||
g2dRotated.dispose(); |
|||
|
|||
// 画数据
|
|||
if (temps.size() > 1) { |
|||
int[] xPoints = new int[temps.size()]; |
|||
int[] yPoints = new int[temps.size()]; |
|||
|
|||
for (int i = 0; i < temps.size(); i++) { |
|||
int x = marginLeft + (chartWidth * i / (temps.size() - 1)); |
|||
int y = marginTop + chartHeight - (int) ((temps.get(i) - minTemp) * chartHeight / tempRange); |
|||
xPoints[i] = x; |
|||
yPoints[i] = y; |
|||
} |
|||
|
|||
g2d.setColor(new Color(255, 0, 0, 180)); |
|||
g2d.setStroke(new BasicStroke(2.5f)); |
|||
for (int i = 0; i < temps.size() - 1; i++) { |
|||
g2d.drawLine(xPoints[i], yPoints[i], xPoints[i + 1], yPoints[i + 1]); |
|||
} |
|||
|
|||
g2d.setColor(Color.RED); |
|||
for (int i = 0; i < temps.size(); i++) { |
|||
g2d.fillOval(xPoints[i] - 3, yPoints[i] - 3, 6, 6); |
|||
if (i % 12 == 0) { |
|||
g2d.setColor(Color.BLUE); |
|||
g2d.setFont(new Font("Arial", Font.PLAIN, 10)); |
|||
g2d.drawString(String.format("%.1f", temps.get(i)), xPoints[i] + 5, yPoints[i] - 5); |
|||
g2d.setColor(Color.RED); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// X轴标签
|
|||
g2d.setColor(Color.BLACK); |
|||
g2d.setFont(new Font("Arial", Font.PLAIN, 9)); |
|||
int step = Math.max(1, times.size() / 15); |
|||
for (int i = 0; i < times.size(); i += step) { |
|||
int x = marginLeft + (chartWidth * i / (times.size() - 1)); |
|||
int y = marginTop + chartHeight + 15; |
|||
String label = times.get(i); |
|||
if (label.contains("\n")) { |
|||
String[] lines = label.split("\n"); |
|||
g2d.drawString(lines[0], x - 20, y); |
|||
g2d.drawString(lines[1], x - 20, y + 12); |
|||
} else if (label.length() > 10) { |
|||
g2d.drawString(label.substring(0, 10), x - 20, y + 5); |
|||
} else { |
|||
g2d.drawString(label, x - 15, y + 5); |
|||
} |
|||
} |
|||
|
|||
g2d.dispose(); |
|||
|
|||
String fileName = cityName + "_温度图.png"; |
|||
File outputFile = new File(fileName); |
|||
ImageIO.write(image, "PNG", outputFile); |
|||
System.out.println("💾 " + cityName + "图表已保存为:" + outputFile.getAbsolutePath()); |
|||
|
|||
} catch (Exception e) { |
|||
System.err.println("❌ 生成" + cityName + "图表失败:" + e.getMessage()); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ========== 4. 城市特色爬虫子类(扩展新类型爬虫) ==========
|
|||
class CityInfoCrawler extends AbstractDataCrawler<Map<String, String>, String> { |
|||
// 模拟城市特色数据(实际可替换为真实爬虫逻辑)
|
|||
private Map<String, Map<String, String>> cityInfoSource = new HashMap<>(); |
|||
|
|||
public CityInfoCrawler() { |
|||
// 初始化城市特色数据
|
|||
Map<String, String> xiAnInfo = new HashMap<>(); |
|||
xiAnInfo.put("别名", "十三朝古都"); |
|||
xiAnInfo.put("地标", "兵马俑、大雁塔"); |
|||
xiAnInfo.put("美食", "肉夹馍、泡馍"); |
|||
xiAnInfo.put("经纬度", "34.2644, 108.9497"); |
|||
cityInfoSource.put("西安", xiAnInfo); |
|||
|
|||
Map<String, String> chengDuInfo = new HashMap<>(); |
|||
chengDuInfo.put("别名", "天府之国"); |
|||
chengDuInfo.put("地标", "大熊猫基地、宽窄巷子"); |
|||
chengDuInfo.put("美食", "火锅、串串"); |
|||
chengDuInfo.put("经纬度", "30.5728, 104.0668"); |
|||
cityInfoSource.put("成都", chengDuInfo); |
|||
|
|||
Map<String, String> lanZhouInfo = new HashMap<>(); |
|||
lanZhouInfo.put("别名", "黄河之都"); |
|||
lanZhouInfo.put("地标", "黄河铁桥、白塔山"); |
|||
lanZhouInfo.put("美食", "牛肉面、甜醅子"); |
|||
lanZhouInfo.put("经纬度", "36.0611, 103.8343"); |
|||
cityInfoSource.put("兰州", lanZhouInfo); |
|||
|
|||
Map<String, String> wuLuMuQiInfo = new HashMap<>(); |
|||
wuLuMuQiInfo.put("别名", "亚心之都"); |
|||
wuLuMuQiInfo.put("地标", "天山、国际大巴扎"); |
|||
wuLuMuQiInfo.put("美食", "羊肉串、手抓饭"); |
|||
wuLuMuQiInfo.put("经纬度", "43.8256, 87.6168"); |
|||
cityInfoSource.put("乌鲁木齐", wuLuMuQiInfo); |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, String> crawlData(String cityName) throws Exception { |
|||
if (!cityInfoSource.containsKey(cityName)) { |
|||
throw new IllegalArgumentException("未找到" + cityName + "的特色数据"); |
|||
} |
|||
System.out.println("🌐 正在爬取" + cityName + "城市特色数据"); |
|||
Map<String, String> rawData = cityInfoSource.get(cityName); |
|||
rawDataMap.put(cityName, rawData); |
|||
return rawData; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, Object> parseData(Map<String, String> rawData) { |
|||
// 转换为通用Map结构(可扩展解析逻辑)
|
|||
Map<String, Object> parsedData = new HashMap<>(rawData); |
|||
// 新增解析字段:经纬度拆分
|
|||
String latLon = (String) rawData.get("经纬度"); |
|||
if (latLon != null) { |
|||
String[] latLonArr = latLon.split(","); |
|||
parsedData.put("纬度", Double.parseDouble(latLonArr[0].trim())); |
|||
parsedData.put("经度", Double.parseDouble(latLonArr[1].trim())); |
|||
} |
|||
return parsedData; |
|||
} |
|||
|
|||
@Override |
|||
public void saveData(String cityName, Map<String, Object> data) { |
|||
parsedDataMap.put(cityName, data); |
|||
System.out.println("💾 " + cityName + "城市特色数据已保存,解析字段数:" + data.size()); |
|||
} |
|||
|
|||
@Override |
|||
protected String getApiUrl(String key) { |
|||
// 模拟API地址(实际可替换为真实城市信息API)
|
|||
return "https://api.example.com/cityinfo?name=" + key; |
|||
} |
|||
|
|||
// 扩展方法:打印城市特色
|
|||
public void printCityInfo(String cityName) { |
|||
Map<String, Object> info = parsedDataMap.get(cityName); |
|||
if (info == null) { |
|||
System.out.println("⚠️ 未找到" + cityName + "的特色数据"); |
|||
return; |
|||
} |
|||
System.out.println("\n🏙️ 【" + cityName + "】城市特色"); |
|||
for (Map.Entry<String, Object> entry : info.entrySet()) { |
|||
System.out.println(" " + entry.getKey() + ":" + entry.getValue()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ========== 5. 爬虫平台类(统一管理多类型爬虫) ==========
|
|||
class CrawlerPlatform { |
|||
// 泛型集合:管理所有爬虫(K-爬虫类型标识,V-爬虫实例)
|
|||
private Map<String, DataCrawler<?, ?>> crawlerMap = new HashMap<>(); |
|||
// 泛型集合:管理所有可视化器
|
|||
private Map<String, DataVisualizer<?>> visualizerMap = new HashMap<>(); |
|||
|
|||
// 注册爬虫
|
|||
public <T, K> void registerCrawler(String crawlerType, DataCrawler<T, K> crawler) { |
|||
crawlerMap.put(crawlerType, crawler); |
|||
System.out.println("✅ 注册爬虫成功:" + crawlerType); |
|||
} |
|||
|
|||
// 注册可视化器
|
|||
public <T> void registerVisualizer(String visualizerType, DataVisualizer<T> visualizer) { |
|||
visualizerMap.put(visualizerType, visualizer); |
|||
System.out.println("✅ 注册可视化器成功:" + visualizerType); |
|||
} |
|||
|
|||
// 执行爬虫(泛型方法)
|
|||
@SuppressWarnings("unchecked") |
|||
public <T, K> void runCrawler(String crawlerType, K key) { |
|||
DataCrawler<T, K> crawler = (DataCrawler<T, K>) crawlerMap.get(crawlerType); |
|||
if (crawler == null) { |
|||
System.err.println("❌ 未找到爬虫:" + crawlerType); |
|||
return; |
|||
} |
|||
try { |
|||
// 爬取 -> 解析 -> 保存
|
|||
T rawData = crawler.crawlData(key); |
|||
Map<String, Object> parsedData = crawler.parseData(rawData); |
|||
crawler.saveData(key, parsedData); |
|||
} catch (Exception e) { |
|||
((AbstractDataCrawler<T, K>) crawler).recordFailure(key, e); |
|||
} |
|||
} |
|||
|
|||
// 执行可视化
|
|||
@SuppressWarnings("unchecked") |
|||
public <T> void runVisualization(String visualizerType, String title, T data) { |
|||
DataVisualizer<T> visualizer = (DataVisualizer<T>) visualizerMap.get(visualizerType); |
|||
if (visualizer == null) { |
|||
System.err.println("❌ 未找到可视化器:" + visualizerType); |
|||
return; |
|||
} |
|||
visualizer.generateVisualization(title, data); |
|||
} |
|||
|
|||
// 获取爬虫实例(泛型方法)
|
|||
@SuppressWarnings("unchecked") |
|||
public <T, K> DataCrawler<T, K> getCrawler(String crawlerType) { |
|||
return (DataCrawler<T, K>) crawlerMap.get(crawlerType); |
|||
} |
|||
|
|||
// 获取可视化器实例
|
|||
@SuppressWarnings("unchecked") |
|||
public <T> DataVisualizer<T> getVisualizer(String visualizerType) { |
|||
return (DataVisualizer<T>) visualizerMap.get(visualizerType); |
|||
} |
|||
|
|||
// 打印平台统计信息
|
|||
public void printPlatformStats() { |
|||
System.out.println("\n📊 爬虫平台统计信息"); |
|||
System.out.println(" 已注册爬虫数:" + crawlerMap.size()); |
|||
System.out.println(" 已注册可视化器数:" + visualizerMap.size()); |
|||
// 遍历爬虫统计数据
|
|||
for (Map.Entry<String, DataCrawler<?, ?>> entry : crawlerMap.entrySet()) { |
|||
AbstractDataCrawler<?, ?> crawler = (AbstractDataCrawler<?, ?>) entry.getValue(); |
|||
System.out.println(" " + entry.getKey() + ":爬取成功数=" + crawler.rawDataMap.size() + ",失败数=" + crawler.failedKeys.size()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ========== 6. 主程序(平台入口) ==========
|
|||
public class WeatherMain { |
|||
public static void main(String[] args) { |
|||
System.out.println("🚀 启动通用爬虫平台...\n"); |
|||
|
|||
// 1. 初始化平台
|
|||
CrawlerPlatform platform = new CrawlerPlatform(); |
|||
|
|||
// 2. 注册爬虫和可视化器
|
|||
WeatherCrawler weatherCrawler = new WeatherCrawler(); |
|||
CityInfoCrawler cityInfoCrawler = new CityInfoCrawler(); |
|||
platform.registerCrawler("weather", weatherCrawler); |
|||
platform.registerCrawler("cityInfo", cityInfoCrawler); |
|||
platform.registerVisualizer("weatherChart", weatherCrawler); |
|||
|
|||
// 3. 定义待爬取的城市列表(泛型集合)
|
|||
List<String> cities = new ArrayList<>(Arrays.asList("西安", "成都", "兰州", "乌鲁木齐")); |
|||
|
|||
// 4. 执行城市特色爬虫
|
|||
System.out.println("\n========== 执行城市特色爬虫 =========="); |
|||
for (String city : cities) { |
|||
platform.runCrawler("cityInfo", city); |
|||
cityInfoCrawler.printCityInfo(city); |
|||
} |
|||
|
|||
// 5. 执行天气爬虫 + 可视化
|
|||
System.out.println("\n========== 执行天气爬虫 + 可视化 =========="); |
|||
for (String city : cities) { |
|||
platform.runCrawler("weather", city); |
|||
// 获取解析后的天气数据并可视化
|
|||
Map<String, Object> weatherData = weatherCrawler.parsedDataMap.get(city); |
|||
if (weatherData != null) { |
|||
Map<String, List<?>> visualData = new HashMap<>(); |
|||
visualData.put("times", (List<String>) weatherData.get("times")); |
|||
visualData.put("temps", (List<Double>) weatherData.get("temps")); |
|||
platform.runVisualization("weatherChart", city, visualData); |
|||
} |
|||
} |
|||
|
|||
// 6. 平台统计
|
|||
platform.printPlatformStats(); |
|||
|
|||
// 7. 泛型方法演示:获取指定类型的解析值
|
|||
System.out.println("\n========== 泛型方法演示 =========="); |
|||
Double xiAnMaxTemp = weatherCrawler.getParsedValue("西安", "maxTemp", Double.class); |
|||
String chengDuFood = cityInfoCrawler.getParsedValue("成都", "美食", String.class); |
|||
System.out.println(" 西安最高温度:" + xiAnMaxTemp + "°C"); |
|||
System.out.println(" 成都特色美食:" + chengDuFood); |
|||
|
|||
System.out.println("\n🎉 爬虫平台执行完毕!"); |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 56 KiB |
Loading…
Reference in new issue