4、Python爬虫实战(二):Scrapy框架搭建、处理动态页面(Selenium)、数据去重与存储
上一节我们聊了基础爬虫怎么写。说实话,单靠 requests 加 BeautifulSoup 能解决 70% 的问题。但剩下那 30%,比如大规模采集、动态渲染页面、数据去重这些硬骨头,就得请出专业工具了。
这一节,我带你手搭一个 Scrapy 项目。顺便把 Selenium 塞进去处理动态页面。最后聊聊数据去重和存储的那些坑。
4.1 Scrapy 框架搭建
Scrapy 这东西,说白了就是一个爬虫的「工业流水线」。你只需要定义好「抓什么」和「怎么存」,剩下的并发、调度、重试,框架全帮你搞定。
我个人习惯先建个虚拟环境,干净利落:
pip install scrapy
scrapy startproject weibo_opinion
cd weibo_opinion
scrapy genspider weibo_hot weibo.com
项目结构长这样:
weibo_opinion/
├── spiders/
│ └── weibo_hot.py
├── items.py
├── middlewares.py
├── pipelines.py
└── settings.py
嗯,这里要注意:spiders 文件夹里放你的爬虫逻辑,items.py 定义数据结构,pipelines.py 处理数据清洗和存储。我刚开始用 Scrapy 时,总想把所有代码塞进一个文件里,结果项目一复杂就乱成一锅粥。
4.2 处理动态页面(Selenium 集成)
为什么需要 Selenium?你想想看,现在很多网站的数据都是通过 JavaScript 动态加载的。你用 requests 拿到的 HTML,可能只是个空壳子。
我在项目中遇到过这种情况:爬一个新闻网站的评论数据,直接请求返回的页面里根本没有评论内容。后来一查,评论是通过 Ajax 异步加载的。这时候就得请 Selenium 出场了。
在 Scrapy 里集成 Selenium,我推荐用中间件的方式。这样不影响爬虫的正常逻辑:
# middlewares.py
from selenium import webdriver
from scrapy.http import HtmlResponse
class SeleniumMiddleware:
def __init__(self):
self.driver = webdriver.Chrome()
def process_request(self, request, spider):
self.driver.get(request.url)
# 等待动态内容加载完成
import time
time.sleep(3)
body = self.driver.page_source
return HtmlResponse(
self.driver.current_url,
body=body,
encoding='utf-8',
request=request
)
然后在 settings.py 里启用这个中间件:
DOWNLOADER_MIDDLEWARES = {
'weibo_opinion.middlewares.SeleniumMiddleware': 543,
}
4.3 数据去重
爬虫跑起来后,你会发现一个问题:同样的数据被反复抓取。尤其是做舆情分析,重复数据会严重影响分析结果。
Scrapy 自带了一个去重机制,基于请求指纹。但说实话,这只能去重「同一个 URL」。如果同一个新闻在不同时间点内容变了,或者 URL 带参数,它就无能为力了。
我一般会自己写一个去重器。核心思路是用内容的哈希值做唯一标识:
# pipelines.py
import hashlib
import redis
class DuplicatesPipeline:
def __init__(self):
self.redis_client = redis.Redis(
host='localhost',
port=6379,
db=0
)
def process_item(self, item, spider):
# 生成内容指纹
content = item.get('content', '')
fingerprint = hashlib.md5(
content.encode('utf-8')
).hexdigest()
# 检查是否已存在
if self.redis_client.sismember(
'processed_items', fingerprint
):
raise DropItem(f"重复数据: {fingerprint}")
# 存入 Redis
self.redis_client.sadd(
'processed_items', fingerprint
)
return item
核心要点: 去重不是简单的「删掉重复的」,而是要在数据进入管道时就拦截。用 Redis 的 Set 结构做去重,性能好,还支持分布式爬虫共享去重状态。
4.4 数据存储
数据存哪里?这得看你的使用场景。我总结了几种常见方案:
| 存储方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| CSV / JSON | 小规模数据、快速验证 | 简单、无需额外服务 | 不适合大规模数据 |
| MySQL / PostgreSQL | 结构化数据、需要复杂查询 | 支持 SQL、事务 | 写入速度一般 |
| MongoDB | 非结构化数据、舆情文本 | 灵活、写入快 | 查询能力有限 |
| Elasticsearch | 全文搜索、实时分析 | 搜索快、聚合分析强 | 运维成本高 |
我个人做舆情分析时,最喜欢用 MongoDB。原因很简单:舆情数据字段不固定,今天可能多一个「情感得分」,明天可能多一个「传播层级」。MongoDB 的文档结构刚好能应对这种变化。
写一个 MongoDB Pipeline 其实很简单:
# pipelines.py
import pymongo
class MongoPipeline:
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db['opinions'].insert_one(dict(item))
return item
4.5 本章知识体系
说了这么多,我画了一张图帮你梳理整个流程。你看一眼就明白了:
你看,整个流程其实很清晰。从输入 URL 开始,经过 Scrapy 引擎调度,遇到动态页面就用 Selenium 中间件处理,数据提取后经过 Redis 去重,最后通过 Pipeline 存入数据库。
这一套组合拳打下来,基本上 90% 的舆情采集需求都能搞定。剩下的 10%,无非是反爬策略、代理 IP、登录态维持这些细节问题。后面章节我会专门讲。