作为一名长期从事数据采集工作的开发者,我经常需要从各类网站获取公开数据进行分析。豆瓣图书Top250榜单是一个非常适合新手练手的数据采集项目,它结构清晰、数据规范,同时又能覆盖数据采集的多个核心环节。下面我将分享一套完整的采集方案,包含从基础请求到数据解析的全流程实现。
在开始采集前,我们需要准备好Python开发环境。我推荐使用Python 3.6+版本,并安装以下核心库:
bash复制pip install requests beautifulsoup4 lxml
选择这些工具的原因很明确:
requests:比Python内置的urllib更简洁易用,社区支持更好beautifulsoup4:HTML解析神器,支持多种解析方式lxml:解析速度快,内存占用低,是BeautifulSoup推荐的解析器注意:在实际工作中,建议使用虚拟环境管理项目依赖,避免不同项目间的库版本冲突。可以使用
python -m venv venv创建虚拟环境。
让我们从最基本的HTTP请求开始。豆瓣Top250采用分页设计,每页显示25条数据,共10页。以下是获取第一页数据的代码:
python复制import requests
url = "https://book.douban.com/top250"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url, headers=headers)
print(response.status_code) # 打印响应状态码
这里有几个关键点需要注意:
豆瓣使用start参数控制分页,每页25条数据。要获取全部数据,我们需要循环处理分页:
python复制for page in range(0, 10):
params = {"start": page * 25}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
print(f"成功获取第{page+1}页数据")
else:
print(f"获取第{page+1}页数据失败,状态码:{response.status_code}")
获取到HTML响应后,我们需要从中提取有用的数据。BeautifulSoup是处理HTML的利器:
python复制from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "lxml")
book_items = soup.select("tr.item") # 选择所有图书条目
豆瓣图书页面的每个图书条目都包含在一个tr标签中,class为"item"。我们可以通过CSS选择器精准定位这些元素。
每本图书包含多种信息,我们需要分别提取:
python复制for item in book_items:
# 书名
title = item.select_one("div.pl2 a").get_text(strip=True)
# 作者/出版社/出版年份等信息
pub_info = item.select_one("p.pl").get_text(strip=True)
# 评分
rating = item.select_one("span.rating_nums").get_text(strip=True)
# 评价人数
rating_count = item.select_one("span.pl").get_text(strip=True)[1:-1]
# 价格
price = item.select_one("span.buy-info").get_text(strip=True).split("/")[-1]
print(f"书名:{title}")
print(f"信息:{pub_info}")
print(f"评分:{rating}({rating_count}人评价)")
print(f"价格:{price}")
print("-" * 50)
原始数据往往需要清洗才能使用:
python复制def clean_pub_info(info):
"""清洗出版信息"""
parts = info.split("/")
author = parts[0].strip()
publisher = parts[-3].strip() if len(parts) >= 3 else ""
pub_date = parts[-2].strip() if len(parts) >= 2 else ""
price = parts[-1].strip() if len(parts) >= 1 else ""
return author, publisher, pub_date, price
除了基本的User-Agent,还可以添加更多请求头字段:
python复制headers = {
"User-Agent": "Mozilla/5.0...",
"Accept": "text/html,application/xhtml+xml...",
"Accept-Language": "zh-CN,zh;q=0.9",
"Referer": "https://book.douban.com/",
"Connection": "keep-alive"
}
避免过快请求导致被封:
python复制import time
import random
for page in range(0, 10):
time.sleep(random.uniform(1, 3)) # 随机等待1-3秒
# 发起请求...
对于大规模采集,建议使用代理IP:
python复制proxies = {
"http": "http://your_proxy:port",
"https": "http://your_proxy:port"
}
response = requests.get(url, headers=headers, proxies=proxies)
最简单的数据存储方式:
python复制import csv
with open("douban_top250.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["书名", "作者", "出版社", "出版日期", "评分", "评价人数", "价格"])
for book in books:
writer.writerow([...]) # 填入实际数据
对于更复杂的需求,可以使用SQLite或MySQL:
python复制import sqlite3
conn = sqlite3.connect("books.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
author TEXT,
publisher TEXT,
pub_date TEXT,
rating REAL,
rating_count INTEGER,
price TEXT
)
""")
# 插入数据
cursor.execute("INSERT INTO books VALUES (?,?,?,?,?,?,?,?)", [...])
conn.commit()
可能原因及解决方案:
调试技巧:
try-except处理可能缺失的字段应对策略:
使用aiohttp实现异步请求,大幅提升采集速度:
python复制import aiohttp
import asyncio
async def fetch_page(session, page):
url = f"https://book.douban.com/top250?start={page*25}"
async with session.get(url, headers=headers) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch_page(session, page) for page in range(10)]
pages = await asyncio.gather(*tasks)
# 处理页面...
实现自动化监控脚本,定期检查:
添加数据校验逻辑,确保采集的数据:
在实际项目中,我通常会先采集少量数据进行检查,确认无误后再进行大规模采集。同时建议定期备份已采集的数据,避免意外丢失。