4、Scrapy框架入门:从零搭建你的舆情采集流水线

说实话,Scrapy是我在舆情项目里用得最多的框架。没有之一。

为什么?因为舆情数据源太杂了——新闻网站、论坛、社交媒体,每个站点的结构都不一样。你需要一个足够灵活、又足够稳定的爬虫框架。Scrapy正好满足这个需求。

这一章,我会带你从架构原理到实战跑通,完整走一遍Scrapy的入门流程。嗯,咱们直接开始。

4.1 Scrapy架构解析:它到底是怎么工作的?

先看一张图。这张图我画了好几年才觉得满意,它把Scrapy的核心流程讲得清清楚楚。

Scrapy Engine Scheduler Downloader Spider Item Pipeline 互联网 请求 下一个请求 请求 响应 响应 响应 Item + 新请求 Item 图例: 引擎(核心调度) 调度器(请求队列) 下载器(网络请求) Spider(解析) Pipeline(存储)

这张图里,Scrapy Engine是大脑,所有组件都围着它转。我个人习惯把Engine想象成一个项目经理——它不干活,但知道谁该干什么。

流程其实很简单:

  1. EngineScheduler拿一个请求(比如要爬的URL)
  2. 交给Downloader去互联网上下载页面
  3. 下载完的响应传给Spider,你写的解析代码就在这里跑
  4. Spider产出两种东西:Item(结构化数据)和新请求(继续爬)
  5. Item交给Pipeline做后处理(存数据库、写文件等)
  6. 新请求送回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的基本功就算打牢了。


专注资料整理