第93课 - 项目实战:构建一个完整的数据爬虫
学习目标
- 理解并设计一个完整的网络爬虫项目结构。
- 掌握使用 Scrapy 框架进行高效、规范的数据爬取。
- 学会将爬取的数据持久化到文件(CSV、JSON)和数据库(SQLite)中。
- 能够处理基本的反爬策略(如设置User-Agent、下载延迟)。
核心概念
一个完整的爬虫不仅仅是"请求-解析-存储"这么简单。在生产环境中,我们需要考虑代码结构、效率、稳定性和对目标网站的友好性。Scrapy 是一个为了爬取网站数据、提取结构性数据而编写的应用框架,它是 Python 中最强大、最专业的爬虫框架。
爬虫工作流程简述:
- 发起请求:向目标网站的URL发送HTTP请求。
- 获取响应:接收网站返回的HTML或JSON等格式的响应。
- 解析数据:从响应中提取出我们需要的结构化数据(如标题、价格、链接)。
- 存储数据:将解析出的数据保存到文件或数据库中。
- 发现新链接:在解析过程中,发现新的URL,将其加入待爬队列(这就是"爬虫"名称的由来)。
代码示例
我们将使用 Scrapy 框架,构建一个爬取"豆瓣电影Top250"的爬虫。项目结构如下:
douban_spider/
│
├── scrapy.cfg # 项目的部署配置文件
└── douban_spider/
├── __init__.py
├── items.py # 定义要爬取的数据结构
├── middlewares.py # 中间件,可处理请求/响应
├── pipelines.py # 管道,用于处理爬取到的数据(如存储)
├── settings.py # 项目的设置文件
└── spiders/
└── top250.py # 爬虫主逻辑
1. 安装 Scrapy:
pip install scrapy
2. 创建项目:
scrapy startproject douban_spider
cd douban_spider
scrapy genspider top250 "movie.douban.com"
3. 定义数据项 (items.py):
import scrapy
class MovieItem(scrapy.Item):
# 定义我们要爬取的字段
title = scrapy.Field() # 电影名
rank = scrapy.Field() # 排名
score = scrapy.Field() # 评分
quote = scrapy.Field() # 一句话评价
link = scrapy.Field() # 电影详情页链接
4. 编写爬虫 (spiders/top250.py):
import scrapy
from douban_spider.items import MovieItem
class Top250Spider(scrapy.Spider):
name = 'top250' # 爬虫的唯一名称
allowed_domains = ['movie.douban.com'] # 允许爬取的域名
start_urls = ['https://movie.douban.com/top250'] # 起始URL
def parse(self, response):
# 解析每一页的电影列表
movie_list = response.css('.grid_view li')
for movie in movie_list:
item = MovieItem()
# 使用CSS选择器提取数据
item['rank'] = movie.css('.pic em::text').get()
item['title'] = movie.css('.hd a span:nth-child(1)::text').get()
item['score'] = movie.css('.rating_num::text').get()
item['quote'] = movie.css('.quote .inq::text').get()
item['link'] = movie.css('.hd a::attr(href)').get()
yield item # 将item交给pipeline处理
# 寻找下一页链接,实现翻页
next_page = response.css('.paginator .next a::attr(href)').get()
if next_page is not None:
# 使用 response.follow 跟踪链接,回调parse方法
yield response.follow(next_page, self.parse)
5. 设置数据管道 (pipelines.py):
import json
import sqlite3
class JsonPipeline:
"""将数据保存为JSON文件"""
def open_spider(self, spider):
self.file = open('movies.json', 'w', encoding='utf-8')
def close_spider(self, spider):
self.file.close()
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file.write(line)
return item
class SQLitePipeline:
"""将数据保存到SQLite数据库"""
def open_spider(self, spider):
self.conn = sqlite3.connect('movies.db')
self.cursor = self.conn.cursor()
# 创建表(如果不存在)
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS top250 (
rank TEXT,
title TEXT,
score TEXT,
quote TEXT,
link TEXT
)
''')
def close_spider(self, spider):
self.conn.commit()
self.conn.close()
def process_item(self, item, spider):
self.cursor.execute('''
INSERT INTO top250 (rank, title, score, quote, link)
VALUES (?, ?, ?, ?, ?)
''', (item['rank'], item['title'], item['score'], item['quote'], item['link']))
self.conn.commit()
return item
6. 配置设置 (settings.py):
# 关键配置项
BOT_NAME = 'douban_spider'
ROBOTSTXT_OBEY = True # 遵守robots.txt协议
CONCURRENT_REQUESTS = 16 # 并发请求数
DOWNLOAD_DELAY = 2 # 每次请求间隔2秒,防止过快被封
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
# 启用数据管道,数字表示优先级,越小越先执行
ITEM_PIPELINES = {
'douban_spider.pipelines.JsonPipeline': 300,
'douban_spider.pipelines.SQLitePipeline': 300,
}
7. 运行爬虫:
scrapy crawl top250 -o movies.csv # 也可以直接输出为CSV文件
实践练习
-
(基础)修改爬虫:修改上面的
Top250Spider,使其额外爬取电影的导演信息,并将其添加到MovieItem中。- 预期输出:
movies.json文件中每部电影都包含director字段。 - 提示:豆瓣Top250页面上导演信息通常在
.bd p标签内。
- 预期输出:
-
(进阶)爬取其他网站:选择一个你感兴趣的、结构清晰的网站(如一个博客列表页),使用 Scrapy 为其创建一个爬虫,爬取文章的标题、发布时间和摘要。
- 要求:使用
scrapy shell先调试选择器,确保准确。 - 预期输出:生成一个包含目标数据的
articles.json文件。
- 要求:使用
-
(挑战)增加反爬处理:在当前的豆瓣爬虫基础上,实现一个随机更换
User-Agent的中间件。- 要求:在
middlewares.py中定义一个RandomUserAgentMiddleware,并在settings.py中启用它。 - 提示:可以准备一个包含多个 User-Agent 的列表,每次请求随机选取一个。
- 要求:在
常见错误
- 不遵守
robots.txt:虽然设置ROBOTSTXT_OBEY = False可以强行爬取,但这是不友好的行为,可能导致IP被封。请先查看网站的/robots.txt文件。 - 请求过于频繁:不设置
DOWNLOAD_DELAY或设置得过小,很容易触发网站的反爬机制,导致返回403、429状态码或需要验证码。 - 选择器失效:网站的前端结构可能更新,导致CSS或XPath选择器失效。使用
scrapy shell实时调试是验证选择器的最佳方式。 - 数据编码问题:在存储中文等非ASCII字符时,确保使用
ensure_ascii=False(JSON)或正确的文件编码(如utf-8)。
小结
本节课我们通过一个完整的实战项目,学习了如何使用 Scrapy 框架构建一个健壮的数据爬虫。关键点包括:
- Scrapy框架:它提供了清晰的项目结构、高效的数据提取机制(选择器)和强大的中间件/管道系统。
- 数据持久化:通过自定义 Pipeline,我们可以灵活地将数据存储到文件(JSON, CSV)或数据库(如 SQLite)中。
- 反爬策略:设置下载延迟、遵守 robots.txt、使用真实的 User-Agent 是基本且必要的礼貌行为。
- 完整流程:一个爬虫项目经历了"发起请求 -> 响应处理 -> 数据提取 -> 数据存储 -> 发现新链接"的循环。
你现在已经具备了构建生产级别数据爬虫的基础能力。在下一课中,我们将学习如何将爬取到的数据通过 RESTful API 服务提供给其他应用使用。
练习编辑器
python
Loading...