原创

Python爬虫Scrapy

温馨提示:
本文最后更新于 2024年07月14日,已超过 279 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

1.简介

1.1Scrapy

Scrapy是一个用于Web数据抓取的强大开源框架,主要用于从网站上抓取结构化数据,如HTML和XML文件,或者API返回的数据。Scrapy使用Python语言编写,它不仅仅是一个简单的网页抓取工具,而是包含了自动化、灵活的数据处理、数据存储等功能的完整框架。Scrapy的设计理念是模块化和可扩展的,支持中间件、Item Pipelines、信号和事件,以及多种数据输出格式,如JSON、XML和CSV等。

Scrapy 官网:https://scrapy.org/
Scrapy 文档:https://docs.scrapy.org/en/latest/
GitHub:https://github.com/scrapy/scrapy/

1.2架构图

Scrapy的架构设计是模块化的,由多个组件构成,它们协同工作完成数据抓取任务。以下是Scrapy架构的主要组成部分:

  1. Scrapy Engine(引擎)
    • 负责协调数据流在Scrapy的所有组件中流动,包括Spider、Item Pipeline、Downloader和Scheduler。
  1. Scheduler(调度器)
    • 接收Scrapy Engine传来的Requests,并按照一定的顺序将它们入队,当Engine请求新的Requests时提供。
  1. Downloader(下载器)
    • 负责下载由Scheduler提供的Requests,并返回Response对象。
  1. Spiders(蜘蛛)
    • Spider是定义如何从网站上抓取数据的核心组件,它负责解析响应,提取数据(Items),以及生成新的Requests。
  1. Item Pipeline(项目管道)
    • 用于处理由Spider提取的Items,如数据清洗、验证、存储等。Pipelines按顺序执行,可以实现复杂的数据处理逻辑。
  1. Downloader Middlewares(下载中间件)
    • 位于Scrapy Engine和Downloader之间的层,用于处理Request和Response对象,如添加headers、处理异常、限制速度等。
  1. Spider Middlewares(蜘蛛中间件)
    • 位于Scrapy Engine和Spider之间的层,用于处理由Spider生成的Responses和Requests,如过滤重复的Requests。

1.3逻辑流程

  1. 请求(Request):Scrapy的爬虫会生成一个或多个Request对象,这些对象包含了待抓取页面的URL和其他相关信息,如headers和cookies。
  2. 下载(Download):Scrapy的下载器(Downloader)会处理这些Request,下载页面的内容。
  3. 响应(Response):下载完成后,下载器会生成一个Response对象,这个对象包含了页面的HTML源码或其他响应内容,以及请求的状态信息。
  4. 解析(Parse):Scrapy的爬虫会处理Response对象,使用XPath、CSS选择器或正则表达式等方法从中抽取数据,生成Item对象或新的Request对象。
  5. Item Pipeline:抽取的数据(Item对象)会被传递给Item Pipeline,进行进一步的处理,如数据清洗、验证、去重和存储到数据库等。
  6. 输出(Output):最终,数据可以被导出到不同的格式,如JSON、XML、CSV等,也可以直接存储到数据库或通过API发送到其他服务。

1.4子命令

  • scrapy startproject [project_name]:创建一个新的Scrapy项目。
  • scrapy genspider [name] [domain]:生成一个新的爬虫模板。
  • scrapy crawl [spider_name]:运行指定的爬虫。
  • scrapy settings:显示项目的配置设置。
  • scrapy fetch [url]:下载指定URL的页面并显示响应信息。
  • scrapy shell [url]:启动交互式的Scrapy Shell,用于调试XPath和CSS选择器。
  • scrapy runspider [script.py]:运行一个独立的爬虫脚本,无需创建完整的项目。
  • scrapy list:列出项目中的所有爬虫。
  • scrapy check:检查爬虫是否有语法错误或引用了不存在的中间件或管道。

1.5安装Scrapy库

pip install scrapy

2.代码编写

编写代码爬取豆瓣电影Top250榜单数据

2.1.创建项目

执行以下命令创建Scrapy项目,在当前目录会生成一个zscrapy目录,用于存放爬虫、中间件、管道、设置文件等组件。

scrapy startproject zscrapy

目录结构

  • __init__.py: Python包的初始化文件。
  • items.py: 定义项目中使用的数据项(Items)的文件。
  • middlewares.py: 定义项目的中间件的文件。
  • pipelines.py: 定义项目的管道(Pipelines)的文件。
  • settings.py: 项目的配置文件,用于设置各种参数。
  • spiders: 存放爬虫文件的子目录

2.2.定义Item

在Scrapy中,Items用于定义你从网页上抓取的数据结构。Items本质上是包含字段的容器,这些字段通常代表了你想要从网页中提取的数据。

import scrapy

class DoubanItem(scrapy.Item):
    pic = scrapy.Field()  # 电影图片
    ranking = scrapy.Field()  # 电影排名
    title = scrapy.Field()  # 电影名字
    movieInfo = scrapy.Field()  # 电影的描述信息,包括导演、主演、电影类型等等
    star = scrapy.Field()  # 电影评分
    quote = scrapy.Field()  # 电影中最经典或者说脍炙人口的一句话
    pass

# 定义新闻数据的字段
class SinaNewsItem(scrapy.Item):
    title = scrapy.Field()  # 新闻标题
    ctime = scrapy.Field()  # 新闻发布时间
    url = scrapy.Field()  # 新闻原始url
    raw_key_words = scrapy.Field()  # 新闻关键词(爬取的关键词)
    content = scrapy.Field()  # 新闻的具体内容
    cate = scrapy.Field()  # 新闻类别 

2.3.编写爬虫脚本

用于抓取豆瓣电影Top 250榜单的信息

# 导入Scrapy模块和系统模块
import scrapy
# 导入Scrapy的Request类,用于发送HTTP请求
from scrapy.http import Request
# 导入Selector类,用于解析HTML和XML文档
from scrapy.selector import Selector
# 导入自定义的DoubanItem类,用于存储爬取到的数据
from zscrapy.items import DoubanItem
# 导入urljoin函数,用于拼接URL
from urllib.parse import urljoin
# 导入CrawlerProcess类,用于运行爬虫
from scrapy.crawler import CrawlerProcess
# 导入get_project_settings函数,用于获取项目的设置
from scrapy.utils.project import get_project_settings

# 定义了一个名为Douban的爬虫类,继承自scrapy.spiders.Spider
class Douban(scrapy.spiders.Spider):
    # 设置爬虫的名称
    name = "douban"
    # 设置允许爬取的域名
    allowed_domains = ["douban.com"]
    # 设置起始URL
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        # 初始化一个DoubanItem实例
        item = DoubanItem()
        # 使用Selector解析响应对象
        selector = Selector(response)
        # 获得所有class="item"的div元素集
        Movies = selector.xpath('//div[@class="item"]')
        # 将提取到的信息存储到DoubanItem实例中,并使用yield发送给Scrapy进行进一步处理或存储
        for eachMovie in Movies:
            pic = eachMovie.xpath('div[@class="pic"]/a/img/@src').extract()
            ranking = eachMovie.xpath('div[@class="pic"]/em/text()').extract()[0]
            title = eachMovie.xpath('div[@class="info"]/div[@class="hd"]/a/span/text()').extract() # 多个span标签
            fullTitle = "".join(title) # 将多个字符串无缝连接起来
            movieInfo =eachMovie.xpath('div[@class="info"]/div[@class="bd"]/p/text()').extract()
            movieInfo = movieInfo[0].strip("\n  ")
            #获取评份
            star = eachMovie.xpath('div[@class="info"]/div[@class="bd"]/div[@class="star"]/span/text()').extract()[0]
            #经典输出
            quote = eachMovie.xpath('div[@class="info"]/div[@class="bd"]/p[@class="quote"]/span/text()').extract()
            # quote可能为空,因此需要先进行判断
            if quote:
             quote = quote[0]
            else:
             quote = ''
            item['pic'] = "".join(pic)
            item['ranking'] = ranking
            item['title'] = fullTitle
            item['movieInfo'] = movieInfo
            item['star'] = star
            item['quote'] = quote
            #print("采集数据:",item)
            yield item
        # 翻页读取
        nextLink = selector.xpath('//span[@class="next"]/link/@href').extract()
        # 检查是否存在“下一页”链接,如果有,则构造新的请求并调用自身parse方法进行递归爬取
        if nextLink:
            nextLink = nextLink[0]
            yield Request(urljoin(response.url, nextLink), callback=self.parse)

# 程序入口
if __name__ =="__main__":
    # 创建CrawlerProcess类对象并传入项目设置信息参数
    process = CrawlerProcess(get_project_settings())
    # 设置需要启动的爬虫名称
    process.crawl("douban")
    # 启动爬虫
    process.start()

2.4.设置文件

在settings.py文件中设置代理,设置的User-Agent字符串模拟了一个运行在Windows 7操作系统上特定版本的Safari浏览器。设置USER_AGENT是为了绕过网站的防爬机制。

USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50'

2.5.数据库

创建数据库zscrapy,创建表douban

create database zscrapy charset utf8mb4 collate utf8mb4_general_ci;

CREATE TABLE `douban`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '电影名称',
  `pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '电影图片',
  `movieInfo` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述信息',
  `star` float(3, 1) NOT NULL DEFAULT 0.0 COMMENT '电影评分',
  `quote` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '经典语句',
  `ranking` int(0) NOT NULL DEFAULT 0 COMMENT '电影排名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

安装pymysql库,pymysql是一个Python的MySQL数据库驱动,允许Python程序与MySQL数据库进行交互。

 pip install pymysql

在settings.py中添加MySQL连接配置

MYSQL_HOST = 'localhost'  # 数据库地址
MYSQL_DBNAME = 'zscrapy'  # 数据库名字
MYSQL_USER = 'root'  # 数据库登录名
MYSQL_PASSWD = 'Video201@'  # 数据库登录密码

# 激活ZscrapyPipeline管道,优先级301(数值越小,优先级越高)
ITEM_PIPELINES = {
    'zscrapy.pipelines.ZscrapyPipeline': 301,
}

2.6.管道

Item Pipelines负责接收从爬虫传递过来的item,并对这些item执行一系列的处理,比如清洗数据、验证数据、去重、持久化数据到数据库等。以下代码定义了一个Scrapy项目中的Item Pipeline,其目的是将爬取到的数据存储到MySQL数据库中。

# 导入pymysql模块,这是一个Python的MySQL数据库驱动。
import pymysql
# 导入项目中定义的item模块,包含爬取数据的结构定义。
import zscrapy.items
# 从项目设置模块导入设置,用于获取数据库连接信息。
from zscrapy import settings

# 定义一个名为ZscrapyPipeline的类,继承自object,这是Item Pipeline的基本类
class ZscrapyPipeline(object):
    # 在Python中,self是一个指向实例本身的引用,它是类的方法中的第一个参数。
    # 定义初始化方法,当创建类的实例时自动调用
    def __init__(self):
        # 使用pymysql.connect()方法建立到MySQL数据库的连接
        self.connect = pymysql.connect(
            host=settings.MYSQL_HOST,
            db=settings.MYSQL_DBNAME,
            user=settings.MYSQL_USER,
            passwd=settings.MYSQL_PASSWD,
            charset='utf8',
            use_unicode=True)
        # 创建游标对象,用于执行SQL语句
        self.cursor = self.connect.cursor()

    # 定义process_item方法,该方法将由Scrapy调用,用于处理每个爬取到的item
    def process_item(self, item, spider):
        try:
            # 检查item是否是DoubanItem类型,如果是,执行插入到douban表的SQL语句。
            if isinstance(item, zscrapy.items.DoubanItem):
                # 执行SQL插入语句,参数是一个SQL字符串和一个元组,元组中的值将替换SQL字符串中的占位符
                self.cursor.execute(
                    """insert into douban(title,pic,ranking,movieInfo,star,quote)
                      value (%s,%s,%s,%s,%s,%s)""",
                    (item['title'],
                     item['pic'],
                     item['ranking'],
                     item['movieInfo'],
                     item['star'],
                     item['quote']))
            else:
                self.cursor.execute(
                    """insert into sina_news(title,ctime,url,raw_key_words,content,cate)
                      value (%s,%s,%s,%s,%s,%s)""",
                    (item['title'],
                     item['ctime'],
                     item['url'],
                     item['raw_key_words'],
                     item['content'],
                     item['cate']))
            # 提交事务,确保数据被持久化到数据库中
            self.connect.commit()
        except Exception as err:
            # 捕获在执行SQL语句或提交事务过程中可能出现的异常,并打印错误信息
            print("数据插入==>错误信息为:" + str(err))
        # 返回处理过的item,以便它可以被下一个Item Pipeline处理或直接传递给Scrapy的其他组件
        return item
正文到此结束