项目报告
🕰️ 城市记忆项目报告
📖 项目信息
| 这个项目属于哪个课程 | 2024数据采集与融合技术实践 |
|---|---|
| 组名 | 在大大的数据里面挖呀挖呀挖 |
| 项目简介 | 项目名称:城市记忆 Logo: 项目需求:整合城市历史文化资源:需要以交互式的方式展示城市的历史发展、新旧照片、方言特色以及名人故事等内容。增强用户参与感:为用户提供交互性体验,如照片上色、语音生成、内容检索等。利用多媒体技术提升展示效果:通过地图、视频、音频、图像等多种形式,全方位展示城市记忆,构建沉浸式体验。 项目开展技术路线:python、html、JavaScript、flask、mysql |
| 团队成员学号 | 102202114农晨曦,102202118杨美荔、102202144傅钰、102202147赖越、102202150魏雨萱、102202152张静雯 |
| 这个项目的目标 | 建立多模块融合的城市记忆平台:打造一个集历史资源展示、文化传承和科技互动为一体的平台。 提升用户体验与交互性:通过地图导航、名人故事展示、黑白照片上色和方言音频生成等功能,让用户更生动地了解城市文化。 促进文化保护与传承:利用现代技术对城市历史资源进行数字化存档,帮助大众更便捷地探索与分享城市记忆。 探索跨学科技术融合:结合深度学习、多模态技术与地理信息系统等技术,提升文化展示的深度与广度。 |
| 其他参考文献 | 近20 年城市记忆研究综述 城市记忆与城市形态——从心理学、社会学视角探讨城市历史文化的延续 星火认知大模型Web API文档 |
💻 项目源码
https://gitee.com/wei-yuxuan6/myproject/tree/master/实践作业
🔍 项目概述
1️⃣ 项目背景
城市记忆承载着一座城市的历史、文化与情感,其背后的故事和影像是研究城市变迁与文化保护的重要资源。然而,许多城市的历史资源缺乏系统化的整理与展示,普通大众难以方便地接触这些内容。随着人工智能与多媒体技术的快速发展,利用技术手段将历史内容与现代形式结合,可以更好地传递城市文化,增强公众的文化认同感。
2️⃣ 项目Logo

🕹️ 功能模块设计
1️⃣ 时空地图
功能描述:
- 用户通过交互式地图探索城市历史文化资源。
- 点击地图标记点:显示对应城市的方言音频、新旧照片对比、历史发展时间轴信息。
- 查看名人故事:点击标记点的“名人故事”按钮跳转到城市的名人集页面,展示与该城市相关的名人列表。
- 名人故事展示:点击名人列表中的名人姓名,可以查看详细的名人故事,包括其生平事迹、贡献和与城市的关联性。
技术实现:
- 使用高德地图 API 提供地图展示与交互功能。
- 数据库存储包含城市地理信息及相关文化资源。
- 名人资源使用Spark 4.0 Ultra搜索并整理名人集和名人故事。
2️⃣ 照片上色
功能描述:
- 用户上传黑白照片,系统自动将其转换为色彩丰富的上色照片,直观展现历史影像的色彩还原效果。
- 支持多种照片格式,快速处理。
- 生成对比图,展示黑白原图与上色图的差异。
技术实现:
- 使用百度图片上色接口提供高精度的图片上色服务。
- 后端实现文件上传和上色结果的高效存储与展示。
3️⃣ 方言之声
功能描述:
- 用户输入一段文本并选择城市名称,系统自动生成对应城市方言的音频文件。
- 支持多种方言生成,包括普通话、粤语、东北话等。
- 生成音频后提供播放功能,用户可以在线收听或下载保存。
技术实现:
- 使用讯飞星火语音合成 API 实现高质量的多方言语音合成。
4️⃣ 时光影像
功能描述:
- 用户输入城市名称,系统搜索该城市的老视频并在页面中展示。
- 视频内容涵盖城市建设、名胜古迹、历史活动等主题。
- 视频可在线播放,支持用户分享和下载功能。
技术实现:
- 后端通过查询 MySQL 数据库,根据用户输入的城市关键词匹配相关视频链接。
- 视频资源通过动态渲染展示在页面,支持跨平台兼容的播放功能。
🔧 技术路线
1️⃣ 前端
- 使用 HTML + CSS + JavaScript 开发实现页面布局与动画效果框架,支持动态渲染和响应式设计,兼容多种设备(PC 和移动端),提升视觉吸引力。
- 高德地图 API 集成,用于实现地图标记与交互功能。
2️⃣ 后端
- 基于 Flask 框架开发 RESTful API,与前端交互。
- 集成第三方服务接口(高德地图API、百度AI图片上色接口、讯飞星火语音合成API、Spark4.0 UltraAPI)。
- 提供与 MySQL 数据库的高效交互,支持查询与数据更新。
3️⃣ 数据库
- 使用 MySQL 数据库存储视频链接、名人信息和其他相关数据。
- 提供灵活的表结构设计,便于数据检索与扩展。
4️⃣ AI 技术
- 图片上色:集成百度AI图片上色接口,实现黑白图像智能上色。
- 语音合成:调用讯飞星火语音合成API生成高质量方言语音。
- 数据检索:借助 Spark4.0 UltraAPI 快速检索与展示名人集和名人故事数据。
5️⃣ 爬虫数据采集
- 使用 Python 爬虫技术 (如 Scrapy、Requests、BeautifulSoup 等)从各类公共数据源(如百科、城市历史网站、档案馆、哔哩哔哩)抓取城市历史数据、名人故事、黑白照片、老视频等内容,并存入数据库供后续处理和展示。
- 在搜索到名人之后使用Selenium同时爬取名人照片并展示。
- 动态加载和解析页面数据,清洗并规范化抓取到的信息,确保数据质量和一致性。
6️⃣ 多媒体资源管理
- 结合阿里云OSS(对象存储服务)管理照片、音频与视频资源,确保高效访问与展示。
💻 数据采集与后端搭建
1️⃣ 数据采集
数据采集模块是该项目的基础,负责从多种公共数据源中获取城市历史文化相关信息,并将其存入数据库。我们采用了以下技术与步骤:
- 爬虫技术:使用 Python 的 Scrapy、Requests 和 BeautifulSoup 库,从互联网公共资源(如城市历史网站、档案馆、社会化平台等)抓取数据。数据内容包括:城市的历史资料、方言录音、老照片、视频资源、名人故事等。
- Selenium:对于动态页面和复杂内容,我们使用 Selenium 提取网页内容,并进一步获取名人照片等多媒体资源。
- 数据清洗与规范化:采集的数据需要经过清洗,去除无关信息、修复数据格式,并规范化存储,以确保数据的准确性与一致性。
import time
import random
import requests
import os
import logging
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from fake_useragent import UserAgent
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlencode
from bs4 import BeautifulSoup
# ----------------------------- 配置日志 -----------------------------
logging.basicConfig(
filename='video_scraper.log',
filemode='a',
format='%(asctime)s - %(levelname)s - %(message)s',
level=logging.INFO
)
# ------------------------- 辅助函数定义 -------------------------
def get_random_user_agent():
"""
使用fake_useragent库获取随机的User-Agent。
如果失败,则使用预定义的User-Agent列表。
"""
try:
ua = UserAgent()
return ua.random
except Exception:
# 预定义的User-Agent列表
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
# 添加更多的User-Agent以增加多样性
]
return random.choice(user_agents)
def sanitize_filename(filename):
"""
清理文件名,移除非法字符。
"""
return "".join(c for c in filename if c.isalnum() or c in (' ', '_', '-')).rstrip()
def simulate_human_behavior(driver):
"""
使用Selenium模拟一些人类行为,如滚动页面和移动鼠标,以降低被检测的风险。
"""
actions = webdriver.ActionChains(driver)
# 随机滚动页面
for _ in range(3):
actions.send_keys(webdriver.common.keys.Keys.PAGE_DOWN)
actions.perform()
time.sleep(random.uniform(0.5, 1.5))
def get_cookies_and_cities(chrome_driver_path, login_url):
"""
使用Selenium打开浏览器,登录并获取Cookies和城市列表。
"""
# 配置Chrome选项
chrome_options = Options()
user_agent = get_random_user_agent()
chrome_options.add_argument(f'user-agent={user_agent}')
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--allow-insecure-localhost')
chrome_options.add_argument('--start-maximized')
# 移除无头模式,确保浏览器窗口可见
# chrome_options.add_argument('--headless') # 已移除
# 隐藏Selenium特征
chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])
chrome_options.add_experimental_option('useAutomationExtension', False)
# 初始化WebDriver
service = Service(chrome_driver_path)
driver = webdriver.Chrome(service=service, options=chrome_options)
# 隐藏webdriver属性
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
})
try:
# 打开登录页面
driver.get(login_url)
print("浏览器已打开,请手动完成登录。完成后,请在终端按 Enter 键继续...")
# 模拟人类行为(可选)
simulate_human_behavior(driver)
# 等待用户完成登录
input("登录完成后,请在终端按 Enter 键继续...")
# 等待页面跳转到 index 页面
WebDriverWait(driver, 20).until(EC.url_contains('/index'))
print("已成功登录并跳转到主页。")
# 获取Cookies
cookies_list = driver.get_cookies()
cookies = {cookie['name']: cookie['value'] for cookie in cookies_list}
logging.info("成功获取到 Cookies:%s", cookies)
print("获取到的 Cookies:", cookies)
# 获取城市列表
driver.get('https://zhongguoyuyan.cn/index')
# 等待页面加载完成
WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'div.itemOC___TrObD a'))
)
html = driver.page_source
soup = BeautifulSoup(html, 'html.parser')
city_links = soup.select('div.itemOC___TrObD a')
cities = []
for a_tag in city_links:
href = a_tag.get('href', '')
title = a_tag.get('title', '').strip()
# 提取城市标号
parts = href.split('/')
if len(parts) >= 3:
cid = parts[2]
cities.append({'name': title, 'cid': cid})
logging.info(f"共找到 {len(cities)} 个城市。")
print(f"共找到 {len(cities)} 个城市。")
return cookies, cities
finally:
# 关闭浏览器
driver.quit()
def download_video(session, video_url, headers, video_path):
"""
下载单个视频。
"""
try:
logging.info(f"开始下载视频:{video_url} 到 {video_path}")
with session.get(
video_url,
headers=headers,
stream=True,
timeout=30
) as r:
r.raise_for_status()
with open(video_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
logging.info(f"下载完成:{video_path}")
except Exception as e:
logging.error(f"下载视频时发生错误:{e},视频URL: {video_url}")
def download_videos_for_city_category(session, city, headers, download_dir, category, max_workers=5):
"""
下载指定城市和类别的所有视频。
"""
cid = city['cid']
city_name = city['name']
category_encoded = category
api_url = f"https://zhongguoyuyan.cn/api/common/entries/category?cid={cid}&category={category_encoded}"
referer = f"https://zhongguoyuyan.cn/collection/{cid}/default"
headers['Referer'] = referer
logging.info(f"{city_name} - {category}:开始下载。Referer: {referer}")
try:
response = session.get(
api_url,
headers=headers,
timeout=10
)
if response.status_code == 200:
data = response.json()
if isinstance(data, list):
logging.info(f"{city_name} - {category}:获取到 {len(data)} 个视频条目。")
if not data:
logging.info(f"{city_name} - {category}:无视频数据。")
return
# 使用多线程下载视频
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for item in data:
video_info = item.get("video", {})
video_url = video_info.get("url")
# 提取 'word' 字段的值
word = ""
records = item.get("records", [])
if records:
word_list = [record.get("word", "") for record in records if "word" in record]
word = "_".join(word_list)
else:
word = item.get("word", "")
# 如果 'word' 为空,则使用 'entry' 字段
if not word:
word = item.get("entry", "")
# 如果仍然为空,则使用视频标题
if not word:
word = video_info.get("title", "video")
# 清理文件名
filename_sanitized = sanitize_filename(word)
# 组织目录结构:downloads/城市名称/类别/
city_dir = os.path.join(download_dir, city_name, category)
os.makedirs(city_dir, exist_ok=True)
video_path = os.path.join(city_dir, f"{filename_sanitized}.mp4")
if video_url:
futures.append(
executor.submit(
download_video,
session=session,
video_url=video_url,
headers=headers,
video_path=video_path
)
)
else:
logging.warning(f"{city_name} - {category}:未找到视频 URL。")
# 等待所有下载任务完成
for future in as_completed(futures):
pass # 所有日志已经在download_video函数中记录
else:
logging.error(f"{city_name} - {category}:API 返回的数据类型不是列表,而是 {type(data)}。")
else:
logging.error(f"{city_name} - {category}:API 请求失败,状态码: {response.status_code},URL: {api_url}")
logging.error(f"响应内容:{response.text}")
except Exception as e:
logging.error(f"{city_name} - {category}:请求出错:{e},URL: {api_url}")
def main():
# 配置参数
chrome_driver_path = r"D:\ChromeDriver\chromedriver-win64\chromedriver.exe" # 更新为您的ChromeDriver路径
login_url = 'https://zhongguoyuyan.cn/user/login' # 登录页面URL
# 获取Cookies和城市列表
cookies, cities = get_cookies_and_cities(chrome_driver_path, login_url)
if not cookies or not cities:
logging.error("未能获取到Cookies或城市信息,退出脚本。")
return
# API请求配置
headers = {
"User-Agent": get_random_user_agent(),
"Referer": "", # 初始Referer,在请求时更新
"Accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9"
}
# 创建requests会话并更新Cookies
session = requests.Session()
session.cookies.update(cookies)
# 定义要下载的类别列表
categories = ["说唱表演", "文化视频"]
# 下载所有视频
for city in cities:
for category in categories:
download_videos_for_city_category(
session=session,
city=city,
headers=headers,
download_dir="downloads",
category=category,
max_workers=5
)
# 随机延迟以避免频繁请求
time.sleep(random.uniform(1, 3))
logging.info("所有视频下载任务完成。")
print("所有视频下载任务完成。")
if __name__ == "__main__":
main()
2️⃣ 后端搭建
后端部分主要通过 Flask 框架实现,具体步骤如下:
- Flask API 开发:设计 RESTful API,用于处理前端请求,提供数据接口,支持数据查询、添加和更新。
- 数据库设计:使用 MySQL 数据库管理所有城市文化数据,包括历史事件、方言音频、老照片、视频和名人故事等,设计灵活的数据表结构以支持高效查询。
- 第三方 API 集成:为了实现多媒体功能,后端集成了多种第三方 API,如高德地图、百度图片上色接口和讯飞星火语音合成API。
部分代码
from flask import Flask, request, jsonify
import requests
import base64
from flask_cors import CORS # 添加跨域支持
from flask import render_template
app = Flask(__name__)
CORS(app) # 启用CORS
# 替换为你的百度AI API Key和Secret Key
API_KEY = "qmkycs5Xseo3UAvJiWWe2z0Y"
SECRET_KEY = "Qx5K0umGkTFTe5vwexvq6DaLi8OePP89"
@app.route("/")
def index():
return render_template("index.html")
def get_access_token():
""" 获取百度AI的Access Token """
acc_url = "https://aip.baidubce.com/oauth/2.0/token"
params = {
"grant_type": "client_credentials",
"client_id": API_KEY,
"client_secret": SECRET_KEY
}
try:
response_data = requests.post(acc_url, params=params)
response_data.raise_for_status() # 检查是否有HTTP错误
return response_data.json().get("access_token")
except requests.RequestException as e:
print(f"获取Access Token失败: {e}")
return None
@app.route("/api/colorize", methods=["POST"])
def colorize_image():
if "image" not in request.files:
return jsonify({"error": "未上传图片"}), 400
image_file = request.files["image"]
image_base64 = base64.b64encode(image_file.read()).decode("utf-8")
access_token = get_access_token()
if not access_token:
return jsonify({"error": "获取Access Token失败"}), 500
url = f"https://aip.baidubce.com/rest/2.0/image-process/v1/colourize?access_token={access_token}"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
payload = {"image": image_base64}
try:
# 发送请求
response = requests.post(url, data=payload, headers=headers)
response.raise_for_status() # 检查是否有HTTP错误
# 打印原始响应内容,调试使用
print("原始响应状态码:", response.status_code)
print("原始响应内容:", response.text)
# 尝试解析响应
response_json = response.json()
print("JSON解析成功:", response_json)
# 检查响应中是否包含colored_image
if "image" in response_json:
return jsonify({"colored_image": response_json["image"]})
else:
error_message = response_json.get("error_msg", "未知错误")
return jsonify({"error": error_message}), 400
except requests.RequestException as e:
print(f"请求失败: {e}")
return jsonify({"error": f"请求失败: {str(e)}"}), 500
except ValueError as ve:
print(f"响应解析失败: {ve}")
print("原始响应内容:", response.text)
return jsonify({"error": f"响应解析失败: {str(ve)}", "raw_response": response.text}), 500
except Exception as e:
print(f"未知错误: {e}")
return jsonify({"error": f"未知错误: {str(e)}"}), 500
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5000)
---
📈 总结
通过本项目的实施,我们成功构建了一个结合多媒体技术与数据采集的城市记忆平台。该平台不仅增强了用户的交互体验,还通过高科技手段实现了城市文化的数字化存档与展示。项目的技术难点主要集中在数据的采集、清洗与存储,以及多种第三方接口的集成。通过团队成员的协作与不懈努力,最终实现了目标功能,并为未来的文化保护与传承提供了有效的技术支持。
✨ 致谢
本项目完成得益于团队的通力合作与文档资源的支持,感谢指导老师的帮助与建议!
浙公网安备 33010602011771号