丹青识画系统Python爬虫实战:自动构建艺术图像数据集
丹青识画系统Python爬虫实战:自动构建艺术图像数据集
你有没有想过,要训练一个能看懂世界名画的AI模型,第一步最头疼的是什么?没错,就是数据。成千上万张高清画作图片,还要配上画家、年代、风格、主题这些详细的标签,手动收集简直是不可能完成的任务。
我之前就遇到过这个难题。当时想做一个艺术风格分类的小项目,光是找几百张带标签的图片就花了好几天,效率低不说,标签还不统一。后来我发现,其实很多博物馆和艺术网站都有公开的数字藏品,如果能自动把它们“搬”下来,再让AI帮忙打上标签,不就完美了吗?
今天,我就来分享一套完整的实战方案:用Python写个爬虫,从网上自动抓取艺术图像,然后调用丹青识画系统的API,让AI给每张画作自动分析并打上丰富的标签。最终,我们就能得到一个规整的、带有多维度标签的大规模艺术图像数据集。整个过程完全自动化,特别适合需要大量艺术图像数据的研究者、开发者或者艺术爱好者。
1. 为什么需要自动化构建艺术数据集?
手动收集艺术图像数据,通常意味着你要一个个打开博物馆网站,右键保存图片,然后再去查资料,把画家、创作年份、艺术流派等信息填到表格里。这个过程有三大痛点:
第一是效率极低。一个人的精力有限,一天能整理几十张高质量数据已经算很快了。但对于机器学习项目来说,几百张数据往往只是起步,想要模型效果好,几千甚至上万张数据是常态。
第二是标签不一致。不同来源对同一幅画的描述可能不同,比如“后印象派”和“印象派后期”,手动整理很容易出现混乱,给后续的数据清洗和模型训练带来麻烦。
第三是信息维度单一。我们通常只能找到最基础的元数据(如画名、作者)。但一幅画所包含的信息远不止这些:它的构图特点、色彩运用、情感表达、画面中的物体等等,这些更深层的标签对于训练一个“懂画”的AI至关重要,而人工标注这些标签的成本高得惊人。
我们的解决方案正好能解决这些问题。爬虫负责高效、批量地获取原始图像和基础元数据;丹青识画系统则扮演一个不知疲倦的“艺术顾问”,通过API为每一幅画提供统一、丰富、多维度的深度分析标签。两者结合,就能以极低的成本,构建出一个高质量、高价值的数据集。
2. 实战前的准备工作
工欲善其事,必先利其器。在开始写代码之前,我们需要把环境和工具准备好。
2.1 核心工具与库
我们的项目主要依赖以下几个Python库,你可以通过pip命令一键安装:
requests/aiohttp: 用于向目标网站和丹青识画API发送网络请求。requests简单易用,aiohttp则用于异步请求,能大幅提升爬取速度。BeautifulSoup4/lxml: 这是解析网页HTML的利器。当爬虫拿到网页源代码后,就用它们来提取我们需要的图片链接和文本信息。Pandas: 数据处理和分析的核心。我们将用它来清洗、整理元数据,并最终保存为结构化的表格(如CSV文件)。Pillow(PIL): Python的图像处理库。可以用来验证下载的图片是否完整,或者进行简单的格式转换。os,json,time: Python的标准库,分别用于管理文件路径、处理JSON格式的API返回数据,以及在请求间添加延时以避免被封。
安装命令很简单,打开你的终端或命令行,输入:
pip install requests beautifulsoup4 pandas pillow aiohttp
2.2 目标网站分析与策略制定
不是所有网站都适合爬取。在选择目标时,我们要优先考虑那些对公众开放、有清晰版权声明(通常为CC0或公共领域)的机构,例如:
- 纽约大都会艺术博物馆:其官网提供了海量的高清艺术品图像和元数据API。
- 芝加哥艺术博物馆:同样有丰富的开放获取资源。
- 维基艺术:一个大型的线上艺术百科全书。
在动手之前,一定要做这几件事:
- 查看
robots.txt:访问目标网站/robots.txt,了解网站允许或禁止爬虫访问的路径。尊重规则是爬虫工程师的基本素养。 - 分析网页结构:用浏览器的“开发者工具”(F12打开),查看图片和元数据在网页HTML中的具体位置。找到包裹它们的标签和属性(如
<img src="...">,<div class="artist-name">)。 - 规划数据存储结构:想好最终的数据集怎么放。我建议的文件夹结构如下:
art_dataset/ ├── images/ # 存放所有下载的画作图片 │ ├── van_gogh_001.jpg │ ├── monet_001.jpg │ └── ... ├── metadata_raw.json # 爬虫获取的原始元数据 ├── metadata_enriched.csv # 合并了API分析结果的最终元数据表 └── spider_log.txt # 运行日志,记录成功与失败
2.3 获取丹青识画系统API密钥
这是让我们的数据集“增值”的关键。你需要访问丹青识画系统的官方网站,注册账号并申请API访问权限。成功后会获得一个唯一的API密钥(通常是一串字符),它就像一把钥匙,每次调用API时都需要带上它。
请妥善保管这个密钥,不要把它直接硬编码在代码里然后上传到公开的代码仓库(如GitHub),这会导致密钥泄露。安全的做法是把它保存在本地的环境变量或一个单独的配置文件中。
3. 爬虫核心:抓取画作图像与元数据
接下来,我们进入实战环节,一步步构建爬虫的核心功能。
3.1 基础爬虫搭建:请求与解析
我们以抓取一个模拟的艺术画廊列表页为例。假设列表页列出了多幅画作,点击每幅画进入详情页才能看到大图和详细信息。
首先,我们写一个函数来获取并解析列表页,提取所有画作详情页的链接。
import requests
from bs4 import BeautifulSoup
import time
def get_artwork_links(list_url, headers):
"""
从列表页获取所有画作详情页的链接。
"""
try:
response = requests.get(list_url, headers=headers, timeout=10)
response.raise_for_status() # 检查请求是否成功
soup = BeautifulSoup(response.content, 'html.parser')
# 假设每个画作卡片由一个带有特定class的<a>标签包裹
artwork_cards = soup.find_all('a', class_='artwork-link')
links = [card['href'] for card in artwork_cards if card.get('href')]
# 处理相对链接,将其转换为绝对链接
base_url = "https://example-art-museum.org"
full_links = [base_url + link if link.startswith('/') else link for link in links]
print(f"从列表页找到了 {len(full_links)} 个画作链接。")
return full_links
except requests.RequestException as e:
print(f"获取列表页失败: {e}")
return []
# 使用示例
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
list_url = "https://example-art-museum.org/collection"
artwork_urls = get_artwork_links(list_url, headers)
time.sleep(1) # 礼貌性延时,避免请求过快
3.2 详情页数据提取
拿到详情页链接后,我们逐个访问,提取高清图片地址和基础元数据。
def scrape_artwork_detail(detail_url, headers):
"""
从画作详情页提取图像URL和元数据。
"""
artwork_data = {}
try:
resp = requests.get(detail_url, headers=headers, timeout=10)
resp.raise_for_status()
soup = BeautifulSoup(resp.content, 'html.parser')
# 1. 提取高清图片URL (假设是第一个高分辨率img标签)
img_tag = soup.find('img', class_='high-res-image')
if img_tag and img_tag.get('src'):
image_url = img_tag['src']
# 确保是绝对URL
if image_url.startswith('//'):
image_url = 'https:' + image_url
elif image_url.startswith('/'):
image_url = 'https://example-art-museum.org' + image_url
artwork_data['image_url'] = image_url
else:
artwork_data['image_url'] = None
print(f"未在 {detail_url} 找到图片。")
# 2. 提取元数据 (根据实际网页结构调整选择器)
artwork_data['title'] = soup.find('h1', class_='artwork-title').get_text(strip=True) if soup.find('h1', class_='artwork-title') else 'Unknown'
artwork_data['artist'] = soup.find('span', class_='artist-name').get_text(strip=True) if soup.find('span', class_='artist-name') else 'Unknown'
artwork_data['year'] = soup.find('span', class_='creation-year').get_text(strip=True) if soup.find('span', class_='creation-year') else 'Unknown'
artwork_data['style'] = soup.find('a', class_='art-movement').get_text(strip=True) if soup.find('a', class_='art-movement') else 'Unknown'
artwork_data['source_url'] = detail_url
return artwork_data
except Exception as e:
print(f"解析详情页 {detail_url} 时出错: {e}")
return None
3.3 图像下载与本地存储
提取到图片URL后,我们需要将其下载到本地images文件夹,并以合理的方式命名(例如艺术家_画作ID.jpg)。
import os
from urllib.parse import urlparse
def download_image(image_url, save_dir, artwork_id, headers):
"""
下载图片并保存到本地。
"""
if not image_url:
return None
try:
# 从URL中提取文件扩展名
parsed_url = urlparse(image_url)
file_ext = os.path.splitext(parsed_url.path)[1]
if not file_ext:
file_ext = '.jpg' # 默认扩展名
# 生成文件名和保存路径
filename = f"{artwork_id}{file_ext}"
save_path = os.path.join(save_dir, filename)
# 下载图片
img_response = requests.get(image_url, headers=headers, timeout=30, stream=True)
img_response.raise_for_status()
with open(save_path, 'wb') as f:
for chunk in img_response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"图片已保存: {save_path}")
return filename # 返回保存的文件名,用于后续记录
except Exception as e:
print(f"下载图片失败 {image_url}: {e}")
return None
# 使用示例
os.makedirs('./art_dataset/images', exist_ok=True)
for idx, url in enumerate(artwork_urls[:3]): # 先试3个
data = scrape_artwork_detail(url, headers)
if data and data.get('image_url'):
file_id = f"art_{idx:04d}"
saved_name = download_image(data['image_url'], './art_dataset/images', file_id, headers)
if saved_name:
data['local_image_path'] = saved_name # 记录本地路径
# 将data保存到元数据列表...
time.sleep(2) # 重要!在请求间加入延时
3.4 应对反爬虫策略
直接按上面的代码跑,很可能很快就被网站屏蔽了。我们需要一些策略来模拟真人行为:
- 设置请求头:最重要的就是
User-Agent,让它看起来像一个真实的浏览器。可以准备一个列表轮流使用。 - 添加请求延时:在请求之间随机休眠一段时间,比如
time.sleep(random.uniform(1, 3))。 - 处理Cookie和Session:对于需要登录或复杂交互的网站,使用
requests.Session()来维持会话状态。 - 使用代理IP:如果请求频率非常高,可以考虑使用代理IP池来分散请求来源。
- 错误重试机制:网络请求可能失败,添加重试逻辑可以提高鲁棒性。
import random
def polite_request(url, headers, session=None, retries=3):
"""一个带有延时和重试机制的礼貌请求函数"""
for attempt in range(retries):
try:
time.sleep(random.uniform(1, 2)) # 随机延时1-2秒
if session:
resp = session.get(url, headers=headers, timeout=10)
else:
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
return resp
except requests.RequestException as e:
print(f"请求失败 (尝试 {attempt+1}/{retries}): {e}")
if attempt == retries - 1:
return None
time.sleep(2 ** attempt) # 指数退避延时
return None
4. 数据增值:调用丹青识画API进行分析
爬虫拿到了“原材料”(图片和基础信息),现在该请出我们的“艺术顾问”——丹青识画系统API,来为每幅画作添加深度洞察。
4.1 调用API获取画作分析
假设丹青识画API提供了一个接口,我们可以上传图片或提供图片URL,它返回一个包含多项分析结果的JSON数据。
import json
def analyze_artwork_with_api(image_path_or_url, api_key, analysis_type='full'):
"""
调用丹青识画API分析画作。
:param image_path_or_url: 本地图片路径或在线图片URL
:param api_key: 你的API密钥
:param analysis_type: 分析类型,如 'full', 'style', 'object'等
:return: API返回的JSON数据
"""
api_endpoint = "https://api.danqing.com/v1/analyze" # 示例端点,需替换为真实地址
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
# 根据参数判断是上传文件还是传递URL
payload = {
'analysis_type': analysis_type
}
if image_path_or_url.startswith('http'):
payload['image_url'] = image_path_or_url
data = json.dumps(payload)
response = requests.post(api_endpoint, headers=headers, data=data)
else:
# 假设API支持multipart/form-data文件上传
with open(image_path_or_url, 'rb') as img_file:
files = {'image': img_file}
data = {'analysis_type': analysis_type}
response = requests.post(api_endpoint, headers=headers, files=files, data=data)
if response.status_code == 200:
return response.json()
else:
print(f"API调用失败,状态码: {response.status_code}, 响应: {response.text}")
return None
# 使用示例
api_key = "YOUR_API_KEY_HERE" # 请务必从安全的地方读取,不要硬编码
local_image_path = "./art_dataset/images/art_0001.jpg"
analysis_result = analyze_artwork_with_api(local_image_path, api_key)
if analysis_result:
print("API分析成功!")
# 打印出一些关键分析结果
print(f"风格分析: {analysis_result.get('style', {})}")
print(f"检测到的物体: {analysis_result.get('objects', [])}")
print(f"色彩分析: {analysis_result.get('color_palette', [])}")
4.2 解析与整合API返回数据
API返回的数据可能很丰富。我们需要从中提取出对我们构建数据集最有用的标签,并整合到之前的元数据中。
def enrich_metadata(basic_metadata, api_result):
"""
将API分析结果整合到基础元数据中。
"""
if not api_result:
return basic_metadata
enriched = basic_metadata.copy()
# 提取并整合风格标签 (假设API返回一个风格列表,按置信度排序)
styles = api_result.get('style_prediction', [])
if styles:
# 取置信度最高的风格作为主风格,所有风格作为列表
enriched['primary_style'] = styles[0].get('name') if isinstance(styles[0], dict) else styles[0]
enriched['all_styles'] = [s.get('name') if isinstance(s, dict) else s for s in styles]
# 提取检测到的物体/场景标签
objects = api_result.get('detected_objects', [])
if objects:
# 假设每个物体是一个字典,包含'name'和'confidence'
enriched['objects'] = [obj.get('name') for obj in objects if obj.get('confidence', 0) > 0.5]
# 提取情感或主题标签
emotions = api_result.get('emotion_tags', [])
if emotions:
enriched['emotion_tags'] = emotions
# 提取主色调
colors = api_result.get('dominant_colors', [])
if colors:
enriched['dominant_colors'] = colors[:3] # 取前三个主色
# 记录API分析时间
enriched['api_analysis_time'] = api_result.get('processing_time')
return enriched
# 整合流程示例
basic_data = {'title': 'Starry Night', 'artist': 'Vincent van Gogh', ...}
api_data = analyze_artwork_with_api('./images/art_0001.jpg', api_key)
final_artwork_record = enrich_metadata(basic_data, api_data)
5. 数据清洗、存储与项目管理
最后一步,我们把所有处理好的数据规整地保存起来,并考虑如何管理这个可能持续运行的项目。
5.1 数据清洗与格式化
爬取和API返回的数据可能存在缺失值、格式不一致等问题,需要进行清洗。
import pandas as pd
def clean_and_format_data(artwork_records):
"""
清洗和格式化所有画作记录。
"""
df = pd.DataFrame(artwork_records)
# 1. 处理缺失值
# 将某些字段的缺失值设为‘Unknown’
columns_to_fill = ['artist', 'year', 'style']
for col in columns_to_fill:
df[col].fillna('Unknown', inplace=True)
# 2. 规范化格式
# 例如,确保年份是字符串或整数,去除多余空格
df['title'] = df['title'].astype(str).str.strip()
df['artist'] = df['artist'].astype(str).str.strip()
# 3. 展开列表型标签(如all_styles, objects)为用分号分隔的字符串,便于CSV存储
list_columns = ['all_styles', 'objects', 'emotion_tags']
for col in list_columns:
if col in df.columns:
df[col] = df[col].apply(lambda x: ';'.join(x) if isinstance(x, list) else x)
return df
# 假设artwork_list是包含所有画作记录(基础+API)的字典列表
cleaned_df = clean_and_format_data(artwork_list)
print(cleaned_df.head())
5.2 数据存储方案
将清洗后的数据保存为通用格式,方便后续使用。
def save_dataset(dataframe, image_dir, output_dir='./art_dataset'):
"""
保存数据集:元数据CSV和图片文件夹。
"""
os.makedirs(output_dir, exist_ok=True)
# 1. 保存元数据为CSV
csv_path = os.path.join(output_dir, 'metadata_enriched.csv')
dataframe.to_csv(csv_path, index=False, encoding='utf-8-sig') # utf-8-sig支持中文
print(f"元数据已保存至: {csv_path}")
# 2. 图片已经保存在image_dir中,这里可以记录一下统计信息
image_count = len([f for f in os.listdir(image_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))])
print(f"数据集包含 {len(dataframe)} 条记录,{image_count} 张图片。")
# (可选)3. 保存一份原始API响应的JSON,以备深度分析
# json_path = os.path.join(output_dir, 'raw_api_responses.json')
# with open(json_path, 'w') as f:
# json.dump(artwork_list, f, indent=2)
# 执行保存
save_dataset(cleaned_df, './art_dataset/images')
5.3 构建健壮的爬虫脚本
将以上所有步骤整合到一个主函数中,并加入日志和错误处理,形成一个可以稳定运行的脚本。
import logging
def main():
"""主爬虫流程"""
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# 1. 初始化
headers = {...}
api_key = os.getenv('DANQING_API_KEY') # 从环境变量读取密钥
base_save_dir = './art_dataset'
os.makedirs(os.path.join(base_save_dir, 'images'), exist_ok=True)
all_artwork_data = []
# 2. 获取画作链接列表 (这里可以循环多个列表页)
list_urls = ["https://example-art-museum.org/collection?page=1"]
all_detail_urls = []
for l_url in list_urls:
links = get_artwork_links(l_url, headers)
all_detail_urls.extend(links)
time.sleep(random.uniform(2, 4))
# 3. 遍历每个详情页,抓取数据并调用API
for idx, detail_url in enumerate(all_detail_urls):
logger.info(f"处理 ({idx+1}/{len(all_detail_urls)}): {detail_url}")
try:
# 抓取基础数据
basic_meta = scrape_artwork_detail(detail_url, headers)
if not basic_meta or not basic_meta.get('image_url'):
logger.warning(f"跳过,未获取到有效数据或图片: {detail_url}")
continue
# 下载图片
file_id = f"art_{idx:05d}"
saved_filename = download_image(basic_meta['image_url'],
os.path.join(base_save_dir, 'images'),
file_id,
headers)
if not saved_filename:
continue
basic_meta['local_image_path'] = saved_filename
# 调用API分析
local_img_path = os.path.join(base_save_dir, 'images', saved_filename)
api_result = analyze_artwork_with_api(local_img_path, api_key)
# 整合数据
final_record = enrich_metadata(basic_meta, api_result)
all_artwork_data.append(final_record)
# 每处理10条,保存一次进度(防止中途出错全部丢失)
if (idx + 1) % 10 == 0:
temp_df = pd.DataFrame(all_artwork_data)
temp_df.to_csv(os.path.join(base_save_dir, 'metadata_temp.csv'), index=False)
logger.info(f"已保存进度,处理了 {idx+1} 条记录。")
# 礼貌延时
time.sleep(random.uniform(3, 6))
except Exception as e:
logger.error(f"处理 {detail_url} 时发生严重错误: {e}", exc_info=True)
continue
# 4. 最终清洗与保存
logger.info("所有画作处理完成,开始清洗和保存最终数据集。")
final_df = clean_and_format_data(all_artwork_data)
save_dataset(final_df, os.path.join(base_save_dir, 'images'), base_save_dir)
logger.info("数据集构建完成!")
if __name__ == '__main__':
main()
6. 总结与展望
走完这一整套流程,你会发现自动化构建数据集虽然前期需要一些开发投入,但一旦跑通,其效率是手动收集无法比拟的。这套方法的核心价值在于“爬虫解决量,API解决质”,它不仅能快速获取海量图像,还能为每张图像附上专业、统一、多维度的语义标签,极大地提升了数据集的可用性和价值。
在实际操作中,有几个点需要特别注意。一是法律与伦理,务必只爬取明确声明可以公开、免费使用的资源,并严格遵守网站的robots.txt协议。二是稳定性,网络环境和API服务都可能出现波动,完善的错误处理和日志记录是保证长时间稳定运行的关键。三是数据质量,定期抽样检查下载的图片和API生成的标签,确保流程没有出现系统性偏差。
未来,这个流程还有很多可以优化的地方。比如,引入异步IO库(如aiohttp)可以让爬取速度提升一个数量级;设计更复杂的调度器,可以同时从多个博物馆网站抓取数据;甚至可以将API返回的JSON原始响应也保存下来,为后续更精细的数据分析和模型训练提供原材料。
希望这个实战指南能为你打开一扇门。艺术数据的世界很大,有了自动化的工具,你可以更自由地去探索和创造。不妨就从一个小目标开始,先试着构建一个专注于某个画家或某个流派的小型数据集,体验一下从零到一的完整过程。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐



所有评论(0)