上周整理书房,面对满架的书突然萌生个想法:这些年到底买了哪些书?哪些买了还没读?能不能按出版社、作者快速检索?
翻了一圈现有的图书管理软件,要么收费,要么云端同步慢得令人发指。我这个人有点强迫症,数据必须掌握在自己手里。于是把目光投向了那个常去的图书信息网站——图书大百科(book.qciss.net)。
为什么是它 为什么是它?
图书大百科的数据质量确实不错,ISBN、封面、简介、作者信息都很全,分类也细。但每次查书都要开浏览器、输网址、敲ISBN,查完还得手动复制粘贴到Excel,这操作太反人类了。
作为一个写过几年Python的人,我第一反应是:写个爬虫,把常用数据扒下来存本地。
技术选型
既然要写爬虫,requests+BeautifulSoup是标配。但这次情况特殊:
- 网站结构不算复杂,但有一些基本的反爬措施
- 数据量不小,单线程爬太慢
- 需要长期维护更新
所以最终方案:
requests:发送HTTP请求
BeautifulSoup4:解析HTML
SQLite3:本地数据库,轻量够用
fakeuseragent:随机UserAgent,避免被ban
time模块:控制爬取速度,做个体面人
核心代码解析 核心代码解析
先看看最基础的爬虫骨架:
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import time
import sqlite3
class BookSpider:
def __init__(self):
self.ua = UserAgent()
self.base_url = "https://book.qciss.net"
self.headers = {
'UserAgent': self.ua.random,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8',
'AcceptLanguage': 'zhCN,zh;q=0.8,enUS;q=0.5,en;q=0.3',
'Connection': 'keepalive',
}
self.init_db()
def init_db(self):
"""初始化SQLite数据库"""
self.conn = sqlite3.connect('books.db')
self.cursor = self.conn.cursor()
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
isbn TEXT UNIQUE,
title TEXT,
author TEXT,
publisher TEXT,
pub_date TEXT,
summary TEXT,
cover_url TEXT,
category TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
self.conn.commit()
这里的重点是fakeuseragent。很多网站会检查UserAgent,如果是Python默认的urllib头,大概率被拒。随机UA能绕过大部分基础防护。

爬虫的绅士礼仪
写爬虫不是打仗,要给对方服务器留点喘息空间。我在代码里加了两个小细节:
def fetch_book(self, isbn):
"""根据ISBN获取图书详情"""
time.sleep(random.uniform(1, 3)) 随机延时13秒
try:
url = f"{self.base_url}/book/{isbn}"
response = requests.get(url, headers=self.headers, timeout=10)
if response.status_code == 200:
检查是否被重定向到验证页面
if len(response.text) < 5000: 验证页面通常很小
print(f"触发反爬机制,暂停60秒")
time.sleep(60)
return None
return response.text
elif response.status_code == 404:
print(f"ISBN {isbn} 不存在")
return None
else:
print(f"请求失败: {response.status_code}")
return None
except Exception as e:
print(f"请求异常: {e}")
return None
这里有两个关键点:
- 随机延时:固定间隔容易被识别为爬虫
- 响应长度判断:很多反爬页面跳转到验证码时,HTML内容会变得很短,通过长度能快速识别
数据解析的艺术
BeautifulSoup的用法很简单,但实际解析时有很多坑:
def parse_book_page(self, html):
"""解析图书详情页"""
soup = BeautifulSoup(html, 'html.parser')
book_data = {}
获取书名 多种选择器备选
title_elem = (
soup.select_one('h1.booktitle') or
soup.select_one('.bookinfo h2') or
soup.select_one('div.bookname')
)
book_data['title'] = title_elem.text.strip() if title_elem else '未知'
获取ISBN 通常藏在某个角落
isbn_elem = soup.find('span', string=re.compile(r'ISBN'))
if isbn_elem:
找到父元素再找相邻元素
parent = isbn_elem.find_parent('li')
if parent:
book_data['isbn'] = parent.text.replace('ISBN', '').strip()
获取图书简介 可能折叠在"更多"里
summary_elem = (
soup.select_one('div.booksummary') or
soup.select_one('div.intro') or
soup.select_one('div.desc')
)
if summary_elem:
有些简介有"查看更多"按钮,需要展开
more_btn = summary_elem.find('button', text=re.compile(r'更多|展开'))
if more_btn:
这里可以模拟点击,但静态爬取只能拿现有文本
pass
book_data['summary'] = summary_elem.text.strip()
return book_data
重点在于选择器的冗余设计。网站改版是常事,一套选择器失效了,还有备胎。
数据存储的巧思
SQLite用起来简单,但直接插入大量数据容易出问题。我用了事务批量提交和UPSERT:
def save_books_batch(self, books_list):
"""批量保存图书"""
sql = '''
INSERT OR REPLACE INTO books
(isbn, title, author, publisher, pub_date, summary, cover_url, category)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
'''
data = []
for book in books_list:
data.append((
book.get('isbn'),
book.get('title'),
book.get('author'),
book.get('publisher'),
book.get('pub_date'),
book.get('summary'),
book.get('cover_url'),
book.get('category')
))
try:
self.cursor.executemany(sql, data)
self.conn.commit()
print(f"批量保存 {len(data)} 条记录成功")
except sqlite3.Error as e:
print(f"数据库错误: {e}")
self.conn.rollback()
INSERT OR REPLACE 相当于UPSERT操作,ISBN重复时会自动更新,非常适合定期增量更新。
#### 如何获取ISBN列表
有了爬虫,还需要ISBN。我从几个渠道获取:
- 豆瓣读书的公开榜单(注意爬取频率)
- 图书馆公开API(有些高校图书馆提供OPAC接口)
- 手动导入:写个简单的命令行工具,支持从CSV批量导入ISBN
def import_isbn_from_csv(self, csv_file):
"""从CSV导入ISBN列表"""
import csv
isbn_list = []
with open(csv_file, 'r', encoding='utf8') as f:
reader = csv.DictReader(f)
for row in reader:
isbn = row.get('isbn', '').replace('', '')
if isbn and len(isbn) in [10, 13]:
isbn_list.append(isbn)
return isbn_list
运行效果
目前我已经爬了大约3000本常用图书的数据,查询速度秒级响应。在本地建了个简单的Flask应用:
from flask import Flask, request, jsonify
import sqlite3
app = Flask(__name__)
@app.route('/search')
def search():
keyword = request.args.get('q', '')
conn = sqlite3.connect('books.db')
cursor = conn.cursor()
模糊搜索书名或作者
cursor.execute('''
SELECT isbn, title, author, publisher
FROM books
WHERE title LIKE ? OR author LIKE ?
LIMIT 20
''', (f'%{keyword}%', f'%{keyword}%'))
results = cursor.fetchall()
conn.close()
return jsonify([{
'isbn': r[0],
'title': r[1],
'author': r[2],
'publisher': r[3]
} for r in results])
if __name__ == '__main__':
app.run(debug=True)
现在找书只需要打开浏览器输入http://localhost:5000/search?q=余华,所有作品一目了然。
踩坑记录
-
编码问题:有些老书的简介里有特殊字符,requests默认编码可能解析错误,需要手动指定
response.encoding = 'utf8' -
连接池耗尽:爬取几千本书后,requests会报连接错误。解决方案是使用Session并限制最大连接数:
self.session = requests.Session() adapter = requests.adapters.HTTPAdapter( pool_connections=10, pool_maxsize=20, max_retries=3 ) self.session.mount('http://', adapter) self.session.mount('https://', adapter) -
数据去重:同一个ISBN可能对应多个版本的图书,我加了版本字段来区分
最后
这个项目让我意识到,很多看似简单的网站背后,数据价值其实很大。图书大百科的数据质量不错,如果能合理利用,完全可以打造一个私人图书馆管理系统。
代码已经整理好放在GitHub上,有需要的朋友自取。如果你也有图书管理的需求,不妨动手试试,过程本身就是一种乐趣。
(PS:爬虫要遵守robots.txt,我看了book.qciss.net/robots.txt,没有禁止爬取,但还是加了延时,做个体面人。)
图书大百科book.qciss.net
浙公网安备 33010602011771号