4、Scrapy框架入门:从零搭建你的舆情采集流水线
说实话,Scrapy是我在舆情项目里用得最多的框架。没有之一。
为什么?因为舆情数据源太杂了——新闻网站、论坛、社交媒体,每个站点的结构都不一样。你需要一个足够灵活、又足够稳定的爬虫框架。Scrapy正好满足这个需求。
这一章,我会带你从架构原理到实战跑通,完整走一遍Scrapy的入门流程。嗯,咱们直接开始。
4.1 Scrapy架构解析:它到底是怎么工作的?
先看一张图。这张图我画了好几年才觉得满意,它把Scrapy的核心流程讲得清清楚楚。
这张图里,Scrapy Engine是大脑,所有组件都围着它转。我个人习惯把Engine想象成一个项目经理——它不干活,但知道谁该干什么。
流程其实很简单:
- Engine从Scheduler拿一个请求(比如要爬的URL)
- 交给Downloader去互联网上下载页面
- 下载完的响应传给Spider,你写的解析代码就在这里跑
- Spider产出两种东西:Item(结构化数据)和新请求(继续爬)
- Item交给Pipeline做后处理(存数据库、写文件等)
- 新请求送回Scheduler,循环继续
核心要点:Scrapy是异步非阻塞的。你写一个Spider,它可以同时处理几十上百个请求。我在做舆情监控时,经常一个Spider同时爬5个新闻网站,速度完全跟得上。
4.2 创建第一个Scrapy项目
好,理论讲完了。咱们动手。
先确认你装了Scrapy:
pip install scrapy
然后创建项目。我习惯用这个命令:
scrapy startproject weibo_opinion
你会看到这样的目录结构:
weibo_opinion/
scrapy.cfg # 项目配置文件
weibo_opinion/ # Python模块
__init__.py
items.py # 定义数据结构
middlewares.py # 中间件(高级功能)
pipelines.py # 数据管道
settings.py # 项目设置
spiders/ # 你的爬虫放这里
__init__.py
我的习惯:创建完项目后,第一件事是打开settings.py,把 ROBOTSTXT_OBEY = False 设一下。舆情采集很多时候需要爬的内容在robots.txt里被禁止了,但为了数据完整性,我们通常不遵守。当然,注意合规。
4.3 Spider编写与运行
Spider是核心。你写的所有解析逻辑都在这里。
咱们写一个简单的舆情Spider,爬一个模拟的新闻列表页:
# weibo_opinion/spiders/news_spider.py
import scrapy
class NewsSpider(scrapy.Spider):
name = "news" # Spider的名字,运行时会用到
# 起始URL列表
start_urls = [
'https://example-news.com/hot',
]
def parse(self, response):
"""解析页面,提取新闻标题和摘要"""
# 提取所有新闻条目
articles = response.css('div.article-item')
for article in articles:
yield {
'title': article.css('h2.title::text').get(),
'summary': article.css('p.summary::text').get(),
'url': article.css('a::attr(href)').get(),
'publish_time': article.css('span.time::text').get(),
}
# 翻页逻辑:找到下一页链接
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield scrapy.Request(
url=response.urljoin(next_page),
callback=self.parse
)
运行它:
scrapy crawl news -o news_data.json
就这么简单。你爬到的数据会存到 news_data.json 里。
我曾经踩过的坑:第一次写Spider时,我忘了加 response.urljoin(),结果翻页链接全是相对路径,爬虫只爬了第一页就停了。嗯,这个细节很重要——相对路径一定要转成绝对URL。
4.4 Item Pipeline:数据流的最后一公里
Spider产出的数据是字典,但真实项目里你需要更规范的结构。这时候Item就派上用场了。
先在 items.py 里定义:
# weibo_opinion/items.py
import scrapy
class NewsItem(scrapy.Item):
title = scrapy.Field() # 新闻标题
summary = scrapy.Field() # 摘要
url = scrapy.Field() # 原文链接
publish_time = scrapy.Field() # 发布时间
source = scrapy.Field() # 来源网站
crawl_time = scrapy.Field() # 采集时间
然后在Spider里用Item替换字典:
from weibo_opinion.items import NewsItem
def parse(self, response):
for article in articles:
item = NewsItem()
item['title'] = article.css('h2.title::text').get()
item['summary'] = article.css('p.summary::text').get()
item['url'] = article.css('a::attr(href)').get()
item['publish_time'] = article.css('span.time::text').get()
item['source'] = 'example-news'
item['crawl_time'] = datetime.now().isoformat()
yield item
Pipeline负责处理这些Item。比如清洗数据、去重、存数据库:
# weibo_opinion/pipelines.py
import json
from datetime import datetime
class CleanDataPipeline:
"""清洗数据:去除空值、格式化时间"""
def process_item(self, item, spider):
# 去除标题前后空格
if item.get('title'):
item['title'] = item['title'].strip()
# 如果标题为空,跳过这个item
if not item.get('title'):
raise DropItem(f"标题为空,丢弃: {item.get('url')}")
return item
class SaveToJsonPipeline:
"""保存到JSON文件"""
def open_spider(self, spider):
self.file = open('opinion_data.json', 'a', 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
别忘了在 settings.py 里启用Pipeline:
ITEM_PIPELINES = {
'weibo_opinion.pipelines.CleanDataPipeline': 300,
'weibo_opinion.pipelines.SaveToJsonPipeline': 400,
}
数字代表执行顺序,数值越小越先执行。
数据流总结:
Spider 产出 Item → Pipeline 1(清洗)→ Pipeline 2(去重)→ Pipeline 3(存储)
每个Pipeline只做一件事,职责单一。我见过有人把所有逻辑塞到一个Pipeline里,结果改一个功能要动整个文件——千万别这么干。
4.5 避坑指南:我踩过的三个坑
最后分享几个实战中容易翻车的地方:
- 请求频率太高被封IP——在settings.py里设置
DOWNLOAD_DELAY = 2,每次请求间隔2秒。舆情采集不是抢票,慢一点稳一点。 - 编码问题导致乱码——Scrapy默认用UTF-8,但有些老网站是GBK。在Spider里加一句
response.encoding = 'gbk'就能解决。 - Item字段拼写错误——Field名字写错了不会报错,但数据会丢。我习惯在Pipeline里加一个字段校验,跑之前先打印一下item.keys()。
好了,这一章的内容就到这里。你跟着把代码敲一遍,跑通一个完整的舆情采集流程,Scrapy的基本功就算打牢了。