【開源解析】基于PyQt5+Folium的谷歌地圖應用開發:從入門到實戰

🌐【開源解析】基于PyQt5+Folium的谷歌地圖應用開發:從入門到實戰請添加圖片描述

🌈 個人主頁:創客白澤 - CSDN博客
🔥 系列專欄:🐍《Python開源項目實戰》
💡 熱愛不止于代碼,熱情源自每一個靈感閃現的夜晚。愿以開源之火,點亮前行之路。
👍 如果覺得這篇文章有幫助,歡迎您一鍵三連,分享給更多人哦

請添加圖片描述

在這里插入圖片描述

📌 概述

在當今數據可視化與地理信息系統的交叉領域,交互式地圖應用已成為不可或缺的工具。本文將深入剖析一個基于Python技術棧(PyQt5+Folium+Geopy)開發的**"谷歌地圖"桌面應用**,它集成了地址解析、地圖標注、距離測量等實用功能,并提供了三種不同的地圖樣式選擇。

相較于傳統Web地圖應用,本項目的創新點在于:

  • 桌面端集成:通過PyQt5實現原生應用體驗
  • 混合渲染技術:結合Folium的HTML生成與QWebEngineView的嵌入式渲染
  • 跨框架通信:實現Python與JavaScript的雙向交互
  • 輕量級架構:無需復雜GIS系統即可實現核心功能

🛠? 功能特性

核心功能矩陣

功能模塊實現技術特色說明
地理編碼Geopy/Nominatim支持全球地址解析
地圖渲染Folium+Leaflet三種專業地圖樣式
距離測量Geodesic算法高精度大圓距離計算
交互界面PyQt5響應式桌面UI
地圖導出HTML5可獨立運行的網頁地圖

特色功能詳解

  1. 智能地址解析:基于OpenStreetMap的Nominatim服務,支持模糊地址匹配
  2. 實時距離測量:選擇兩個標記點即可顯示精確的球面距離
  3. 動態標記高亮:可視化連線輔助空間關系分析
  4. 多地圖樣式:街道圖、衛星圖、地形圖一鍵切換
  5. 跨平臺運行:生成的HTML地圖可在任何瀏覽器查看

🎨 效果展示

街道地圖

在這里插入圖片描述

衛星地圖

在這里插入圖片描述
在這里插入圖片描述

地形圖

在這里插入圖片描述
在這里插入圖片描述

距離測量演示

在這里插入圖片描述

🧩 實現步驟詳解

1. 環境搭建

pip install PyQt5 folium geopy PyQtWebEngine

2. 核心架構設計

在這里插入圖片描述

3. 關鍵技術實現

3.1 混合地圖渲染
def initialize_map(self):# 加載Leaflet庫html = """<!DOCTYPE html><html><head><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script></head><body><div id="map"></div><script>// JavaScript地圖控制邏輯var map = L.map('map').setView([39.9042, 116.4074], 4);</script></body></html>"""self.map_view.setHtml(html)
3.2 跨語言通信
# Python調用JavaScript
self.map_view.page().runJavaScript("addMarker(39.9, 116.4, '北京', '中國首都');")# JavaScript回調Python
self.map_view.page().runJavaScript("""map.on('click', function(e) {console.log(e.latlng);});
""")
3.3 距離測量算法
from geopy.distance import geodesicdef calculate_distance(loc1, loc2):"""使用Vincenty公式計算球面距離"""return geodesic((loc1['latitude'], loc1['longitude']),(loc2['latitude'], loc2['longitude'])).kilometers

🔍 代碼深度解析

1. 地理編碼服務封裝

def geocode_location(self, address):try:location = self.geolocator.geocode(address)if location:return (location.latitude, location.longitude)return Noneexcept (GeocoderTimedOut, GeocoderServiceError) as e:# 實現自動重試機制time.sleep(0.5)return self.geocode_location(address)

優化點:增加了異常處理和自動重試機制,提高服務穩定性

2. 動態標記管理

def update_embedded_map(self):# 使用JS批量操作DOM元素js_clear = "clearMarkers();"js_add_markers = []for loc in self.locations:js_add_markers.append(f"addMarker({loc['latitude']}, {loc['longitude']}, "f"'{loc['name']}', '{loc['address']}');")self.map_view.page().runJavaScript(js_clear + "".join(js_add_markers))

性能優化:減少Python-JS通信次數,使用批量操作提升渲染效率

3. 地圖樣式熱切換

self.map_styles = {"🌍 街道地圖": {"url": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png","attr": "OpenStreetMap"},"🛰? 衛星地圖": {"url": "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}","attr": "Google"}
}def update_map_style(self):style = next(s for s in self.map_styles if self.style_buttons[s].isChecked())js = f"""map.eachLayer(layer => {{if (layer instanceof L.TileLayer) {{map.removeLayer(layer);}}}});L.tileLayer('{self.map_styles[style]["url"]}', {{attribution: '{self.map_styles[style]["attr"]}'}}).addTo(map);"""self.map_view.page().runJavaScript(js)

📥 源碼下載

import folium
from geopy.geocoders import Nominatim
from geopy.distance import geodesic  # 添加距離計算功能
from geopy.exc import GeocoderTimedOut, GeocoderServiceError
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem,QRadioButton, QGroupBox, QFileDialog, QMessageBox, QScrollArea)
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtGui import QIcon
import sys
import webbrowser
import os
import timeclass SimpleMapViewerApp(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("谷歌桌面地圖")self.setGeometry(100, 100, 1200, 800)self.geolocator = Nominatim(user_agent="simple_map_viewer")self.locations = []self.current_map_file = os.path.join(os.path.expanduser("~"), "map.html")self.map_view = Noneself.map_initialized = Falseself.selected_markers = []  # 存儲選中的標記用于距離計算# 地圖樣式選項self.map_styles = {"🌍 街道地圖": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png","🛰? 衛星地圖": "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}","?? 地形圖": "https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}"}# 創建UIself.create_widgets()# 延遲初始化地圖,確保WebEngineView完全加載QTimer.singleShot(500, self.initialize_map)def create_widgets(self):# 主窗口布局main_widget = QWidget()self.setCentralWidget(main_widget)main_layout = QHBoxLayout(main_widget)# 左側控制面板control_panel = QWidget()control_panel.setMinimumWidth(350)control_panel.setMaximumWidth(400)control_layout = QVBoxLayout(control_panel)# 地圖樣式選擇style_group = QGroupBox("🗂? 地圖樣式")style_layout = QVBoxLayout()self.style_buttons = []for style_name in self.map_styles:btn = QRadioButton(style_name)btn.toggled.connect(lambda checked, name=style_name: self.update_map_style() if checked else None)style_layout.addWidget(btn)self.style_buttons.append(btn)self.style_buttons[0].setChecked(True)style_group.setLayout(style_layout)control_layout.addWidget(style_group)# 搜索框search_group = QGroupBox("🔍 位置搜索")search_layout = QVBoxLayout()self.search_entry = QLineEdit()self.search_entry.setPlaceholderText("輸入地址或地名...")search_layout.addWidget(self.search_entry)search_btn = QPushButton("搜索")search_btn.setIcon(QIcon.fromTheme("edit-find"))search_btn.clicked.connect(self.search_location)search_layout.addWidget(search_btn)search_group.setLayout(search_layout)control_layout.addWidget(search_group)# 位置列表list_group = QGroupBox("📍 位置列表")list_layout = QVBoxLayout()self.location_list = QTreeWidget()self.location_list.setHeaderLabels(["名稱", "地址"])self.location_list.setColumnWidth(0, 150)self.location_list.setSelectionMode(QTreeWidget.ExtendedSelection)self.location_list.itemSelectionChanged.connect(self.on_location_selection_changed)  # 添加選擇變化事件list_layout.addWidget(self.location_list)# 距離顯示標簽self.distance_label = QLabel("兩地距離: 未選擇")self.distance_label.setAlignment(Qt.AlignCenter)self.distance_label.setStyleSheet("font-weight: bold; color: #2c3e50;")list_layout.addWidget(self.distance_label)# 列表操作按鈕list_btn_layout = QHBoxLayout()remove_btn = QPushButton("🗑? 刪除選中")remove_btn.clicked.connect(self.remove_location)list_btn_layout.addWidget(remove_btn)clear_btn = QPushButton("🧹 清空列表")clear_btn.clicked.connect(self.clear_locations)list_btn_layout.addWidget(clear_btn)list_layout.addLayout(list_btn_layout)list_group.setLayout(list_layout)control_layout.addWidget(list_group)# 添加位置表單form_group = QGroupBox("? 添加位置")form_layout = QVBoxLayout()name_layout = QHBoxLayout()name_layout.addWidget(QLabel("名稱:"))self.name_entry = QLineEdit()name_layout.addWidget(self.name_entry)form_layout.addLayout(name_layout)addr_layout = QHBoxLayout()addr_layout.addWidget(QLabel("地址:"))self.address_entry = QLineEdit()addr_layout.addWidget(self.address_entry)form_layout.addLayout(addr_layout)add_btn = QPushButton("? 添加位置")add_btn.clicked.connect(self.add_location)form_layout.addWidget(add_btn)form_group.setLayout(form_layout)control_layout.addWidget(form_group)# 地圖操作按鈕map_btn_group = QGroupBox("🛠? 地圖操作")map_btn_layout = QHBoxLayout()create_btn = QPushButton("🖨? 生成地圖")create_btn.clicked.connect(self.create_map)map_btn_layout.addWidget(create_btn)show_btn = QPushButton("👀 查看地圖")show_btn.clicked.connect(self.show_map)map_btn_layout.addWidget(show_btn)map_btn_group.setLayout(map_btn_layout)control_layout.addWidget(map_btn_group)control_layout.addStretch()# 右側地圖預覽self.map_view = QWebEngineView()self.map_view.setHtml(self.get_empty_html())# 添加到主布局main_layout.addWidget(control_panel)main_layout.addWidget(self.map_view, stretch=1)def get_empty_html(self):"""返回初始空白HTML"""return """<!DOCTYPE html><html><head><title>地圖預覽</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body><div id="map" style="height:100%;width:100%;"></div></body></html>"""def initialize_map(self):"""初始化地圖,確保Leaflet庫正確加載"""html = """<!DOCTYPE html><html><head><title>地圖預覽</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="crossorigin=""/><style>body { margin: 0; padding: 0; }#map { height: 100vh; width: 100%; }</style></head><body><div id="map"></div><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="crossorigin=""></script><script>var map = L.map('map').setView([39.9042, 116.4074], 4);var osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'});osmLayer.addTo(map);// 存儲標記的數組var markers = [];var selectedMarkers = [];var line = null;function clearMarkers() {for (var i = 0; i < markers.length; i++) {map.removeLayer(markers[i]);}markers = [];if (line) {map.removeLayer(line);line = null;}}function addMarker(lat, lng, name, address) {var marker = L.marker([lat, lng]).addTo(map).bindPopup('<b>' + name + '</b><br>' + address).bindTooltip(name);markers.push(marker);return marker;}function setView(lat, lng, zoom) {map.setView([lat, lng], zoom);}function highlightMarkers(markerIndices) {// 重置所有標記樣式for (var i = 0; i < markers.length; i++) {markers[i].setIcon(L.icon({iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png',iconSize: [25, 41],iconAnchor: [12, 41],popupAnchor: [1, -34],shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png',shadowSize: [41, 41]}));}// 清除之前的線if (line) {map.removeLayer(line);line = null;}// 高亮選中的標記selectedMarkers = [];for (var i = 0; i < markerIndices.length; i++) {var idx = markerIndices[i];if (idx >= 0 && idx < markers.length) {markers[idx].setIcon(L.icon({iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',iconSize: [25, 41],iconAnchor: [12, 41],popupAnchor: [1, -34],shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png',shadowSize: [41, 41]}));selectedMarkers.push(markers[idx]);}}// 如果選中了兩個點,繪制連線if (selectedMarkers.length === 2) {var latlngs = [selectedMarkers[0].getLatLng(),selectedMarkers[1].getLatLng()];line = L.polyline(latlngs, {color: 'red',weight: 3,opacity: 0.7,dashArray: '10, 10'}).addTo(map);}}</script></body></html>"""self.map_view.setHtml(html)self.map_initialized = TrueQTimer.singleShot(500, self.update_map_style)def on_location_selection_changed(self):"""當位置列表選擇變化時觸發"""selected_items = self.location_list.selectedItems()selected_indices = [self.location_list.indexOfTopLevelItem(item) for item in selected_items]# 更新地圖上的高亮標記if self.map_initialized:js = f"highlightMarkers({selected_indices});"self.map_view.page().runJavaScript(js)# 計算并顯示距離if len(selected_indices) == 2:loc1 = self.locations[selected_indices[0]]loc2 = self.locations[selected_indices[1]]# 使用geodesic計算兩點間距離point1 = (loc1['latitude'], loc1['longitude'])point2 = (loc2['latitude'], loc2['longitude'])distance = geodesic(point1, point2).kilometersself.distance_label.setText(f"兩地距離: {distance:.2f} 公里")else:self.distance_label.setText("兩地距離: 請選擇兩個地點")def update_map_style(self):"""更新地圖樣式"""if not self.map_initialized:returnfor btn in self.style_buttons:if btn.isChecked():style_name = btn.text()tiles = self.map_styles[style_name]breakjs = f"""var newLayer = L.tileLayer('{tiles}', {{attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}});// 先清除所有瓦片圖層map.eachLayer(function(layer) {{if (layer instanceof L.TileLayer) {{map.removeLayer(layer);}}}});// 添加新圖層newLayer.addTo(map);"""self.map_view.page().runJavaScript(js)self.update_embedded_map()def geocode_location(self, address):"""將地址轉換為經緯度"""try:location = self.geolocator.geocode(address)if location:return (location.latitude, location.longitude)return Noneexcept (GeocoderTimedOut, GeocoderServiceError) as e:QMessageBox.critical(self, "錯誤", f"地理編碼錯誤: {e}")return Nonedef add_location(self):"""添加位置到列表"""name = self.name_entry.text().strip()address = self.address_entry.text().strip()if not name or not address:QMessageBox.warning(self, "警告", "請填寫名稱和地址")returncoords = self.geocode_location(address)if coords:self.locations.append({'name': name,'address': address,'latitude': coords[0],'longitude': coords[1]})item = QTreeWidgetItem([name, address])self.location_list.addTopLevelItem(item)self.name_entry.clear()self.address_entry.clear()self.update_embedded_map()else:QMessageBox.critical(self, "錯誤", f"無法找到地址: {address}")def remove_location(self):"""刪除選中的位置"""selected = self.location_list.selectedItems()if not selected:QMessageBox.warning(self, "警告", "請先選擇要刪除的位置")returnfor item in selected:index = self.location_list.indexOfTopLevelItem(item)if 0 <= index < len(self.locations):del self.locations[index]self.location_list.takeTopLevelItem(index)self.update_embedded_map()self.distance_label.setText("兩地距離: 未選擇")  # 清除距離顯示def clear_locations(self):"""清空所有位置"""if not self.locations:returnreply = QMessageBox.question(self, '確認', '確定要清空所有位置嗎?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)if reply == QMessageBox.Yes:self.locations.clear()self.location_list.clear()self.update_embedded_map()self.distance_label.setText("兩地距離: 未選擇")  # 清除距離顯示def search_location(self):"""搜索位置并定位"""query = self.search_entry.text().strip()if not query:QMessageBox.warning(self, "警告", "請輸入搜索內容")returntry:location = self.geolocator.geocode(query)if location:js = f"""setView({location.latitude}, {location.longitude}, 15);addMarker({location.latitude}, {location.longitude}, '{query.replace("'", "\\'")}', '');"""self.map_view.page().runJavaScript(js)else:QMessageBox.information(self, "提示", "未找到匹配的位置")except Exception as e:QMessageBox.critical(self, "搜索錯誤", str(e))def create_map(self):"""創建地圖并添加所有位置標記"""if not self.locations:QMessageBox.warning(self, "警告", "沒有可顯示的位置")return# 使用folium創建地圖first_loc = self.locations[0]map_obj = folium.Map(location=[first_loc['latitude'], first_loc['longitude']],zoom_start=12)# 根據當前選擇的樣式設置地圖瓦片for btn in self.style_buttons:if btn.isChecked():style_name = btn.text()tiles = self.map_styles[style_name]breakif style_name == "🌍 街道地圖":tiles = "OpenStreetMap"attr = Noneelif style_name == "🛰? 衛星地圖":tiles = "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"attr = "Google Satellite"elif style_name == "?? 地形圖":tiles = "https://mt1.google.com/vt/lyrs=p&x={x}&y={y}&z={z}"attr = "Google Terrain"if attr:folium.TileLayer(tiles=tiles, attr=attr, name=style_name).add_to(map_obj)else:folium.TileLayer(tiles=tiles, name=style_name).add_to(map_obj)# 添加所有位置標記for loc in self.locations:folium.Marker(location=[loc['latitude'], loc['longitude']],popup=f"<b>{loc['name']}</b><br>{loc['address']}",tooltip=loc['name']).add_to(map_obj)# 如果有兩個點被選中,添加連線selected_items = self.location_list.selectedItems()if len(selected_items) == 2:loc1 = self.locations[self.location_list.indexOfTopLevelItem(selected_items[0])]loc2 = self.locations[self.location_list.indexOfTopLevelItem(selected_items[1])]# 添加兩點間連線folium.PolyLine(locations=[[loc1['latitude'], loc1['longitude']],[loc2['latitude'], loc2['longitude']]],color='red',weight=3,opacity=0.7,dash_array='10, 10').add_to(map_obj)# 計算并顯示距離distance = geodesic((loc1['latitude'], loc1['longitude']),(loc2['latitude'], loc2['longitude'])).kilometers# 在兩點中間添加距離標簽midpoint = [(loc1['latitude'] + loc2['latitude']) / 2,(loc1['longitude'] + loc2['longitude']) / 2]folium.Marker(location=midpoint,icon=folium.DivIcon(icon_size=(150, 36),icon_anchor=(75, 18),html=f'<div style="font-size: 12pt; color: red; background: white; padding: 2px; border-radius: 3px;">{distance:.2f} km</div>'),tooltip=f"直線距離: {distance:.2f} 公里").add_to(map_obj)# 保存地圖file_path, _ = QFileDialog.getSaveFileName(self, "保存地圖", self.current_map_file, "HTML Files (*.html)")if file_path:self.current_map_file = file_pathmap_obj.save(self.current_map_file)QMessageBox.information(self, "成功", f"地圖已生成: {self.current_map_file}")def show_map(self):"""在瀏覽器中顯示地圖"""if not os.path.exists(self.current_map_file):QMessageBox.critical(self, "錯誤", "請先生成地圖")returnwebbrowser.open('file://' + os.path.realpath(self.current_map_file))def update_embedded_map(self):"""更新內嵌地圖視圖"""if not self.map_initialized or not hasattr(self, 'map_view'):return# 清除所有標記self.map_view.page().runJavaScript("clearMarkers();")if not self.locations:# 如果沒有位置,重置到默認視圖self.map_view.page().runJavaScript("setView(39.9042, 116.4074, 4);")return# 設置地圖中心和縮放級別first_loc = self.locations[0]self.map_view.page().runJavaScript(f"setView({first_loc['latitude']}, {first_loc['longitude']}, 12);")# 添加標記for loc in self.locations:js = f"""addMarker({loc['latitude']}, {loc['longitude']}, '{loc['name'].replace("'", "\\'")}', '{loc['address'].replace("'", "\\'")}');"""self.map_view.page().runJavaScript(js)def main():app = QApplication(sys.argv)app.setStyle('Fusion')  # 使用Fusion風格使UI更現代window = SimpleMapViewerApp()window.show()sys.exit(app.exec_())if __name__ == "__main__":main()

🚀 擴展方向

  1. 數據持久化:集成SQLite存儲位置數據
  2. 軌跡繪制:支持路徑規劃和導航功能
  3. POI搜索:接入更多地理編碼服務提供商
  4. 3D視圖:集成Cesium實現三維可視化
  5. 插件系統:支持功能模塊動態加載

💡 總結

本文詳細剖析了基于PyQt5和Folium的地圖應用開發全流程,關鍵技術點包括:

  1. 混合渲染架構:巧妙結合桌面應用的性能優勢和Web地圖的靈活性
  2. 精確距離測量:使用geodesic算法實現專業級距離計算
  3. 響應式UI設計:通過PyQt5構建美觀易用的交互界面
  4. 跨框架通信:實現Python邏輯與JavaScript渲染的無縫銜接

該解決方案特別適合以下場景:

  • 企業內網GIS系統
  • 科研數據可視化
  • 物流路徑規劃
  • 教學演示工具

未來展望:隨著WebAssembly技術的發展,這類混合架構應用將獲得更接近原生應用的性能表現,在地理信息領域具有廣闊的應用前景。


版權聲明:本文采用CC BY-NC-SA 4.0協議,轉載請注明出處。商業轉載請聯系作者授權。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/907274.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/907274.shtml
英文地址,請注明出處:http://en.pswp.cn/news/907274.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

篇章五 數據結構——鏈表(一)

目錄 1.ArrayList的缺陷 2. 鏈表 2.1 鏈表的概念及結構 2.2 鏈表結構 1. 單向或者雙向 2.帶頭或者不帶頭 3.循環或者非循環 2.3 鏈表的實現 1.完整代碼 2.圖解 3.顯示方法 4.鏈表大小 5. 鏈表是否存在 key 值 6.頭插法 7.尾插法 8.中間插入 9.刪除key值節點 10.…

數據庫相關面試

數據庫相關面試 Mysql MySQL中的事務隔離級別及其特點 參考&#xff1a;事務的隔離級別&#xff1a;可重復讀 未提交讀(Read Uncommitted)&#xff1a;允許臟讀&#xff0c;也就是可能讀取到其他會話中未提交事務修改的數據 已提交讀(Read Committed)&#xff1a;只能讀取到…

基于Scrapy的天貓商品數據爬取與分析實戰(含API簽名破解與可視化)

基于Scrapy的天貓商品數據爬取與分析實戰&#xff08;含API簽名破解與可視化&#xff09; 本文以華為Mate 60 Pro為例&#xff0c;詳細介紹如何使用Scrapy框架爬取天貓商品數據&#xff0c;涵蓋API簽名破解、反爬應對、數據存儲及可視化全流程&#xff0c;適合爬蟲進階學習者實…

【C++進階篇】哈希表的模擬實現(賦源碼)

這里寫目錄標題 前言一. 開放地址法實現哈希表1.1 閉散列結構定義1.2 構造函數1.3 插入&#xff08;線性探測&#xff09;1.3.1 傳統寫法1.3.2 現代寫法 1.4 查找1.5 刪除 二. 鏈地址法實現哈希表&#xff08;哈希桶&#xff09;2.1 開散列結構定義2.2 構造函數2.3 插入2.4 查找…

07-后端Web實戰(部門管理)

5. 修改部門 對于任何業務的修改功能來說&#xff0c;一般都會分為兩步進行&#xff1a;查詢回顯、修改數據。 5.1 查詢回顯 5.1.1 需求 當我們點擊 "編輯" 的時候&#xff0c;需要根據ID查詢部門數據&#xff0c;然后用于頁面回顯展示。 5.1.2 接口描述 參照參照…

深度解析項目集方向或目標根本性轉變的應對策略 —— 項目集管理實戰指南

在復雜多變的商業環境中&#xff0c;項目集管理面臨著重重挑戰&#xff0c;而項目集方向或目標的根本性轉變無疑是其中最具沖擊力的問題之一。本文將深度剖析這一難題&#xff0c;為項目集管理從業者提供實用、新穎且富有價值的應對策略&#xff0c;助力大家在項目集管理的復雜…

JAVA面試復習知識點

面試中遇到的題目&#xff0c;記錄復習&#xff08;持續更新&#xff09; Java基礎 1.String的最大長度 https://www.cnblogs.com/wupeixuan/p/12187756.html 2.集合 Collection接口的實現&#xff1a; List接口&#xff1a;ArraryList、LinkedList、Vector Set接口&#xff1a…

【燒腦算法】定長滑動窗口:算法題中的“窗口”智慧

目錄 引言 定長滑動窗口習題剖析 3439. 重新安排會議得到最多空余時間 I 2134. 最少交換次數來組合所有的 1 II 1297. 子串的最大出現次數 2653. 滑動子數組的美麗值 567. 字符串的排列 438. 找到字符串中所有字母異位詞 30. 串聯所有單詞的子串 220. 存在重復元素 II…

JWT安全:接收無簽名令牌.【簽名算法設置為none繞過驗證】

JWT安全&#xff1a;假密鑰【簽名隨便寫實現越權繞過.】 JSON Web 令牌 (JWT)是一種在系統之間發送加密簽名 JSON 數據的標準化格式。理論上&#xff0c;它們可以包含任何類型的數據&#xff0c;但最常用于在身份驗證、會話處理和訪問控制機制中發送有關用戶的信息(“聲明”)。…

XGBoost與SHAP深度解析:從算法原理到實戰價值

在機器學習領域&#xff0c;XGBoost以其卓越的性能長期占據Kaggle競賽和工業界的主流地位&#xff0c;而SHAP&#xff08;SHapley Additive exPlanations&#xff09;則成為模型可解釋性的標桿工具。本文將深度解析兩者的技術內核&#xff0c;并通過實戰案例揭示其結合應用的實…

Java SE Cloneable接口和深/淺拷貝

Java為我們提供了各種各樣功能的接口&#xff0c;Clonable接口就是其中之一。 它通常配合Object類的 clone方法使用。這個方法可以為我們創建一個對象的拷貝&#xff0c;即復制一個對象。在進入本文的主要內容之前&#xff0c;先來對訪問限定符 protected進行一個解剖。 1.再…

Python學習(3) ----- Python的函數定義及其使用

Python 中函數是組織好的、可重復使用的代碼塊&#xff0c;用于實現單一或相關聯的功能。下面是函數定義和使用的完整說明&#xff1a; &#x1f4cc; 一、函數定義語法 def 函數名(參數1, 參數2默認值, *args, **kwargs):"""函數說明文檔"""函…

vue2使用el-tree實現兩棵樹間節點的拖拽復制

原文鏈接&#xff1a;兩棵el-tree的節點跨樹拖拽實現 參照這篇文章&#xff0c;把它做成組件&#xff0c;新增左側樹&#xff08;可拖出&#xff09;被拖節點變灰提示&#xff1b; 拖拽中&#xff1a; 拖拽后&#xff1a; TreeDragComponent.vue <template><!-- …

智變與重構:AI 賦能基礎教育教學的范式轉型研究報告

一、研究背景與核心價值 &#xff08;一&#xff09;技術驅動下的教育轉型浪潮 在全球數字化轉型加速的背景下&#xff0c;人工智能作為核心技術力量&#xff0c;正重塑基礎教育生態。據《人工智能賦能未來教育研究報告》指出&#xff0c;我國教育數字化戰略行動已推動超 70…

Go語言中Print、Printf和Println的區別及使用場景詳解

在Go語言的fmt包中&#xff0c;Print、Printf和Println是三個基礎但功能各異的輸出函數。本文將從多個維度進行詳細對比分析&#xff0c;并給出具體的使用建議。 1. 核心區別深度解析 1.1. 函數簽名與基本行為 func Print(a ...interface{}) (n int, err error) func Printf…

高端制造行業 VMware 替代案例合集:10+ 頭部新能源、汽車、半導體制造商以國產虛擬化支持 MES、PLM 等核心應用系統

在“中國制造 2025”政策的推動下&#xff0c;國內的新能源、汽車制造、半導體、高端裝備等高端制造產業迎來了蓬勃發展&#xff0c;成為全球制造業版圖中舉足輕重的力量。訂單數量的激增與國產化轉型的趨勢&#xff0c;也為高端制造企業的 IT 基礎設施帶來了新的挑戰&#xff…

Spring Ai | 從零帶你一起走進AI項目(中英)

目錄 Thinking Study question pox.xml Maven Gradle Configure API Key Use the AI Client Question Thinking 讓數據變得更加貼近用戶的想法 Study question null pox.xml 添加依賴 Maven <dependencies><dependency><groupId>org.springfram…

LiveGBS作為下級平臺GB28181國標級聯2016|2022對接海康大華宇視華為政務公安內網等GB28181國標平臺查看級聯狀態及會話

LiveGBS作為下級平臺GB28181國標級聯2016|2022對接海康大華宇視華為政務公安內網等GB28181國標平臺查看級聯狀態及會話 1、GB/T28181級聯概述2、搭建GB28181國標流媒體平臺3、獲取上級平臺接入信息3.1、向下級提供信息3.2、上級國標平臺添加下級域3.3、接入LiveGBS示例 4、配置…

卸載 Office PLUS

Office PLUS作為微軟官方推出的智能辦公提效工具&#xff0c;自2015年問世以來&#xff0c;憑借其豐富的模板資源和便捷的智能功能&#xff0c;迅速贏得了廣大職場人士和學生的青睞。本文將全面介紹Office PLUS的發展歷程、核心功能、可能帶來的使用問題&#xff0c;以及如何徹…

影響沉金價格的因素如何體現在多層電路板制造上?

隨著科技的不斷發展&#xff0c;電子產品越來越普及&#xff0c;對電路板的需求也越來越大。多層電路板作為電子產品的核心部件&#xff0c;其性能和質量直接影響到整個產品的穩定性和可靠性。在多層電路板的生產過程中&#xff0c;沉金工藝是一種常用的表面處理方法&#xff0…