1. 项目概述
古籍数字化是当前文化传承领域的重要趋势,但许多珍贵的古籍目录仍分散在各个文化机构的网站上,缺乏统一的结构化整理。这个项目将带你用Python爬虫技术,从公开的古籍目录网站抓取分页数据,并将其转化为结构化的清单。
我曾参与过多个文化遗产数字化项目,发现手工整理古籍目录不仅耗时耗力,而且容易出错。通过这个实战教程,你将掌握一套完整的解决方案,能够自动化地从文化资源网站采集古籍目录信息,为后续的研究、检索和数字化工作打下基础。
2. 技术选型与准备
2.1 核心工具选择
对于古籍目录采集这种相对简单的爬虫任务,我推荐使用以下工具组合:
- Requests:轻量级的HTTP库,适合处理静态页面
- BeautifulSoup:HTML解析利器,能快速提取结构化数据
- Pandas:数据处理和分析的瑞士军刀
python复制# 基础环境安装
pip install requests beautifulsoup4 pandas
2.2 目标网站分析
在开始编码前,我们需要对目标网站进行仔细分析。以国家图书馆古籍目录为例:
- 打开开发者工具(F12),观察页面结构
- 检查分页机制(URL参数或JavaScript加载)
- 查看robots.txt文件,确认爬取限制
- 测试请求频率,避免给服务器造成压力
重要提示:务必遵守网站的爬取规则,设置合理的请求间隔(建议3-5秒/次),避免对公共服务造成负担。
3. 分页爬取实现
3.1 基础爬取框架
首先构建一个基础爬虫框架,处理单页内容:
python复制import requests
from bs4 import BeautifulSoup
def get_page(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...'
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
def parse_page(html):
soup = BeautifulSoup(html, 'html.parser')
# 这里添加具体的解析逻辑
items = []
return items
3.2 分页处理策略
古籍目录通常采用以下几种分页方式:
- URL参数分页:如
page=2 - 滚动加载:通过AJAX请求
- "加载更多"按钮:触发JavaScript
对于最常见的URL参数分页,我们可以这样处理:
python复制base_url = "http://example.com/ancient-books?page="
for page in range(1, total_pages+1):
url = base_url + str(page)
html = get_page(url)
if html:
items = parse_page(html)
# 处理获取到的数据
time.sleep(3) # 礼貌爬取
4. 数据解析与结构化
4.1 定位关键元素
使用BeautifulSoup定位古籍信息元素:
python复制def parse_page(html):
soup = BeautifulSoup(html, 'html.parser')
book_list = []
for item in soup.select('.book-item'): # 根据实际CSS选择器调整
title = item.select_one('.title').get_text(strip=True)
author = item.select_one('.author').get_text(strip=True)
dynasty = item.select_one('.dynasty').get_text(strip=True)
# 其他字段...
book_list.append({
'title': title,
'author': author,
'dynasty': dynasty,
# 其他字段...
})
return book_list
4.2 数据清洗技巧
古籍数据常有不规范问题,需要特别处理:
- 去除多余空格和特殊字符
- 统一朝代名称(如"明"和"明代")
- 处理缺失字段
- 标准化日期格式
python复制def clean_dynasty(text):
# 统一朝代名称
dynasty_map = {
'明': '明代',
'清': '清代',
# 其他映射...
}
return dynasty_map.get(text, text)
5. 数据存储与导出
5.1 结构化存储方案
将采集的数据转换为结构化格式:
python复制import pandas as pd
def save_to_csv(data, filename):
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding='utf-8-sig')
5.2 进阶存储选项
对于大型项目,可以考虑:
- SQLite数据库
- MongoDB(适合非结构化数据)
- Elasticsearch(支持全文检索)
python复制# SQLite示例
import sqlite3
def save_to_sqlite(data, db_file):
conn = sqlite3.connect(db_file)
c = conn.cursor()
# 创建表
c.execute('''CREATE TABLE IF NOT EXISTS ancient_books
(title text, author text, dynasty text)''')
# 插入数据
for item in data:
c.execute("INSERT INTO ancient_books VALUES (?,?,?)",
(item['title'], item['author'], item['dynasty']))
conn.commit()
conn.close()
6. 反爬策略应对
6.1 常见反爬措施
古籍网站可能采取的反爬手段:
- User-Agent检测
- IP频率限制
- 验证码
- JavaScript渲染
6.2 应对方案
python复制# 1. 轮换User-Agent
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...',
# 其他UA...
]
# 2. 使用代理IP(如需)
proxies = {
'http': 'http://proxy.example.com:8080',
'https': 'https://proxy.example.com:8080',
}
# 3. 处理JavaScript渲染
# 可以使用Selenium或Pyppeteer
7. 项目优化建议
7.1 性能优化
- 使用多线程/异步请求
- 实现断点续爬
- 缓存已爬取页面
python复制# 断点续爬示例
import os
def get_last_page():
if os.path.exists('progress.txt'):
with open('progress.txt', 'r') as f:
return int(f.read())
return 1
def save_progress(page):
with open('progress.txt', 'w') as f:
f.write(str(page))
7.2 数据质量监控
- 实现数据校验规则
- 记录爬取日志
- 设置异常警报
python复制def validate_item(item):
required_fields = ['title', 'author', 'dynasty']
for field in required_fields:
if field not in item or not item[field]:
return False
return True
8. 完整代码示例
python复制import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
class AncientBookSpider:
def __init__(self):
self.base_url = "http://example.com/ancient-books?page="
self.data = []
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...'
}
def get_page(self, page):
url = self.base_url + str(page)
try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.text
except Exception as e:
print(f"Page {page} 请求失败: {e}")
return None
def parse_page(self, html):
soup = BeautifulSoup(html, 'html.parser')
items = []
for item in soup.select('.book-item'):
data = {
'title': item.select_one('.title').get_text(strip=True),
'author': item.select_one('.author').get_text(strip=True),
'dynasty': self.clean_dynasty(
item.select_one('.dynasty').get_text(strip=True)
),
'url': item.select_one('a')['href']
}
if self.validate_item(data):
items.append(data)
return items
def clean_dynasty(self, text):
dynasty_map = {'明': '明代', '清': '清代'}
return dynasty_map.get(text, text)
def validate_item(self, item):
return all(item.values())
def run(self, start_page=1, end_page=10):
for page in range(start_page, end_page+1):
print(f"正在爬取第 {page} 页...")
html = self.get_page(page)
if html:
items = self.parse_page(html)
self.data.extend(items)
print(f"获取到 {len(items)} 条记录")
time.sleep(random.uniform(2, 5))
self.save_data()
def save_data(self):
if not self.data:
print("没有数据可保存")
return
df = pd.DataFrame(self.data)
df.to_csv('ancient_books.csv', index=False, encoding='utf-8-sig')
print(f"已保存 {len(self.data)} 条记录到 ancient_books.csv")
if __name__ == '__main__':
spider = AncientBookSpider()
spider.run(start_page=1, end_page=5)
9. 常见问题解决
9.1 页面结构变化
问题:网站改版导致选择器失效
解决方案:
- 实现选择器配置化
- 添加自动检测机制
- 设置邮件通知
python复制# 配置化选择器示例
SELECTORS = {
'item': '.book-item',
'title': '.title',
'author': '.author',
# 其他选择器...
}
def parse_page(self, html):
soup = BeautifulSoup(html, 'html.parser')
items = []
for item in soup.select(SELECTORS['item']):
data = {
'title': item.select_one(SELECTORS['title']).get_text(strip=True),
# 其他字段...
}
items.append(data)
return items
9.2 数据不完整
问题:部分字段缺失或格式不一致
解决方案:
- 添加默认值处理
- 实现数据清洗管道
- 记录数据质量问题
python复制def parse_field(self, element, default=''):
if element:
return element.get_text(strip=True)
return default
10. 项目扩展思路
- OCR整合:将古籍扫描件与目录关联
- 知识图谱:构建古籍关系网络
- 全文检索:实现内容搜索功能
- 可视化展示:制作古籍时空分布图
python复制# 可视化示例 - 使用matplotlib
import matplotlib.pyplot as plt
def plot_dynasty_distribution(data):
dynasty_counts = data['dynasty'].value_counts()
dynasty_counts.plot(kind='bar')
plt.title('古籍朝代分布')
plt.xlabel('朝代')
plt.ylabel('数量')
plt.show()
在实际项目中,我发现古籍数字化工作最困难的部分往往不是技术实现,而是数据质量的把控。建议在爬虫开发阶段就建立完善的数据校验机制,这将为后续的数据利用节省大量时间。
