其他分享
首页 > 其他分享> > 【爬虫】从零开始使用 Scrapy

【爬虫】从零开始使用 Scrapy

作者:互联网

一. 概述

最近有一个爬虫相关的需求,需要使用 scrapy 框架来爬取数据,所以学习了一下这个非常强大的爬虫框架,这里将自己的学习过程记录下来,希望对有同样需求的小伙伴提供一些帮助。

本文主要从下面几个方面进行介绍:

二. 我的学习过程

学习一个新的技术,首先就是去阅读它的官方文档,因为官方文档写的是比较全面的而且权威。

scrapy 官方文档地址https://docs.scrapy.org/en/latest/

上面是 scrapy 的官方文档地址,文档是英文的,如果英文比较好建议直接看英文文档,其实自己的英语也不是很好,但是一直强迫自己看英文文档,遇到不认识的单词,就是用 chrome 的 一个叫做 沙拉查词 的插件翻译,翻译完就记下这些单词,慢慢的读这些英文技术文档就没有太大问题了。

如果学习的时间比较充足,可以看完整个文档再进行实践开发,如果需要快速上手,可以看文档中的快速开始。因为自己在开发需求之前有空闲的时间,所以把它的文档看了七七八八。

Scrapy 简介

下面根据自己阅读官方文档的过程做一个总结:

Scrapy 是一个快速强大的高级 web 抓取框架,用于抓取网站和从网页中提取结构化数据,它可以用于从数据挖掘到监控和自动化测试等广泛的用途。

Scrapy 提供了许多强大的功能来使抓取变得简单高效,例如:

从前面的介绍可以看出 scrapy 的功能非常强大,如果要掌握全部功能,需要花费大量的时间,并且也没有那个必要,只是需要的时候再去查阅官方文档即可。对于一般的网站都没有特别的反爬虫措施,除非一些数据比较敏感的网站,可能需要输入图形验证码之类的,个人觉得对于一般的网站,在抓取网页的过程中合理设置请求头,控制爬取的速度都能够将网页抓取下来。获取到网页内容之后,我们开发的内容就是根据需求解析出需要的结构化数据,所以重点是掌握 scrapy 的选择器。

Scrapy 选择器

scrapy 使用的选择器包括如下:

XPath 表达式非常强大,是 Scrapy 选择器的基础,事实上,CSS 选择器在后台被转换为 XPath。虽然可能不如 CSS 选择器流行,但 XPath 表达式提供了更强大的功能,因为除了导航结构之外,它还可以查看内容。使用 XPath 您可以选择以下内容:选择包含文本“下一页”的链接,这使得 XPath 非常适合抓取任务。

XPath 常用规则

表达式 描述
nodename 选取此节点的所有子节点
/ 从当前节点选区直接子节点
// 从当前节点选取子孙节点
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
text() 获取节点中的文本

三. 需求分析

通过前面的介绍我们大概了解了 Scrapy 的特性,接下来,自己模拟一个实际的需求,该需求是在网上找的,只是用来学习 Scrapy 的一个 demo,需求如下:

目标网站:站长之家 https://top.chinaz.com/

需求:在站长之家的网站排行板块中,提供了行业排名、地区排名等多种分类网站排行数据。现在请你任选一种感兴趣的排名方式,摘取其中的数据。

image-20220107145943621

字段要求,一共5个字段,分别是:

技术要求:使用 scrapy 编写爬虫,最终将提取到的数据存到 mongodb 中;

四. 搭建项目

前面已经介绍了需求,现在我们开始从零搭建一个 scrapy 的项目,因为 scrapy 是使用 python 开发的,所以需要提前安装 python 的环境,推荐使用 Anaconda,关于 Anaconda 的安装可以查阅其官方文档,这里默认已经安装好了。

1. 安装 scrapy

CMD 控制台使用如下命令安装 scrapy :

pip install scrapy

安装完成后输入 scrapy 可以看到如下输出:

D:\my-projects>scrapy
Scrapy 2.5.1 - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  bench         Run quick benchmark test
  check         Check spider contracts
  commands
  crawl         Run a spider
  edit          Edit spider
  fetch         Fetch a URL using the Scrapy downloader
  genspider     Generate new spider using pre-defined templates
  list          List available spiders
  parse         Parse URL (using its spider) and print the results
  runspider     Run a self-contained spider (without creating a project)
  settings      Get settings values
  shell         Interactive scraping console
  startproject  Create new project
  version       Print Scrapy version
  view          Open URL in browser, as seen by Scrapy

Use "scrapy <command> -h" to see more info about a command

2. 创建项目

语法:scrapy startproject <project_name> [project_dir]

project_dir 目录下创建一个名为 <project_name> 的新 Scrapy 项目,如果 project_dir 未指定,表示当前目录。

项目名只能使用数字、字母、下划线组成

使用如下命令创建一个叫做 scrapy_demo 的项目:

scrapy startproject scrapy_demo

输出内容如下:

D:\my-projects>scrapy startproject scrapy_demo
New Scrapy project 'scrapy_demo', using template directory 'e:\anaconda3\lib\site-packages\scrapy\templates\project', created in:
    D:\my-projects\scrapy_demo

You can start your first spider with:
    cd scrapy_demo
    scrapy genspider example example.com

自动生成的项目目录结构如下:

scrapy_demo
├── scrapy_demo
│   ├── items.py       # 数据模型文件
│   ├── middlewares.py # 中间件文件,配置所有中间件
│   ├── pipelines.py   #  pipeline 文件,用于存放自定义pipeline的处理逻辑,比如配置保存数据库的逻辑
│   ├── settings.py    # 项目的配置文件,自定义的外部配置都可以放在这里
│   └── spiders        # Spider类文件夹,我们编写的解析代码均存放在这里
└── scrapy.cfg         # 项目的部署配置文件

3. 生成 spider 文件

语法:scrapy genspider [-t template] <name> <domain>

如果在 scrapy 项目中调用,将在当前项目的 spiders 文件夹中创建一个新的 spider 文件,该<name>参数设置为 spider 的name,而<domain>用于生成allowed_domains start_urls 的属性值。

执行下面的命令,生成 spider 文件:

scrapy genspider tech_web top.chinaz.com

输出内容如下:

D:\my-projects\scrapy_demo>scrapy genspider tech_web top.chinaz.com
Created spider 'tech_web' using template 'basic' in module:
  scrapy_demo.spiders.tech_web

生成的 spider 文件内容如下图所示:

image-20220107155030530

五. 代码实现

按照前面的步骤,我们已经完成项目的搭建,接下来开始正式实现需求。

1. 在 items.py 文件中定义采集的字段

前面已经知道要采集的字段,所以我们需要在 items.py 文件中定义采集的字段以及一些其他需要的字段,如下所示:

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class ScrapyDemoItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    _id = scrapy.Field()  # 保存到 mongodb 中的 _id
    web_name = scrapy.Field()  # 网站名称
    domain = scrapy.Field()  # 网站域名
    rank = scrapy.Field()  # 排名
    score = scrapy.Field()  # 得分
    abstract = scrapy.Field()  # 摘要
    create_time = scrapy.Field()  # 创建时间
    update_time = scrapy.Field()  # 更新时间

2. 在 spider 文件中编写采集逻辑

我们这里打算采集网络科技网站排行榜,它的地址为:https://top.chinaz.com/hangye/index_wangluo.html,在正式编写代码之前,我们可以使用 scrapy 提供的 shell 工具进行测试,通过交互式的方式解析需要的字段,使用方式如下:

语法: scrapy shell [url]

scrapy shell https://top.chinaz.com/hangye/index_wangluo.html --nolog

如果使用上面的命令输出如下:

D:\my-projects\scrapy_demo>scrapy shell https://top.chinaz.com/hangye/index_wangluo.html --nolog
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x0000011BEAE84748>
[s]   item       {}
[s]   request    <GET https://top.chinaz.com/hangye/index_wangluo.html>
[s]   response   <200 https://top.chinaz.com/hangye/index_wangluo.html>
[s]   settings   <scrapy.settings.Settings object at 0x0000011BEB0F1B88>
[s]   spider     <TechWebSpider 'tech_web' at 0x11beb538488>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
In [1]: 
 

此时你可以使用 response.text 来检查我们是否获取了整个页面的源码,scrapy的所有资源解析操作都被集成在了response这个对象中,使用 Tab 建可以提示补全相关的内容。

接下来我们可以在浏览器中分析需要抓取的页面的信息

image-20220107163221087

解析网页的 spider 代码如下:

import datetime
from hashlib import md5
import scrapy
from scrapy_demo.items import ScrapyDemoItem


class TechWebSpider(scrapy.Spider):
    name = 'tech_web'
    allowed_domains = ['top.chinaz.com']
    url = 'https://top.chinaz.com/hangye/index_wangluo_{}.html'
    pagesize = 1
    start_urls = ['https://top.chinaz.com/hangye/index_wangluo.html']

    def parse(self, response):
        li_list = response.xpath('//ul[@class="listCentent"]/li')
        for li in li_list:
            web_name = li.xpath('.//h3/a/text()').get()
            domain = li.xpath('.//h3/span/text()').get()
            abstract = li.xpath('.//div[@class="CentTxt"]/p/text()').get(default='')
            rank = li.xpath('.//div[@class="RtCRateCent"]/strong/text()').get(default='')
            score = li.xpath('.//div[@class="RtCRateCent"]/span/text()').get(default='')
            # 封装数据
            item = ScrapyDemoItem()
            date = datetime.datetime.now()
            item['_id'] = md5(str(web_name).encode('utf-8')).hexdigest()
            item['web_name'] = web_name
            item['domain'] = domain
            item['abstract'] = abstract
            item['rank'] = rank
            item['score'] = score
            item['create_time'] = date
            item['update_time'] = date
            yield item
        # 构造下一页的请求
        self.pagesize = self.pagesize + 1
        url = self.url.format(self.pagesize)
        if len(li_list) > 0:
            yield scrapy.Request(url=url, callback=self.parse)

Spider parse方法:所有的parse方法都必须返回 Item 对象(目前可以理解为数据项)或者 Request 对象(下一条请求)。这里所有的parse的意思是不是特指Spider类中生成的parse方法,而是所有具备解析功能的函数都应该返回 Item 或者 Request。

启动 spider :

语法:scrapy crawl <spider>

其中的 <spider> 是我们 spider 文件中 name 属性的值,我们在 scrapy 项目中可以通过 scrapy list 命令查看,如下所示:

D:\my-projects\scrapy_demo>scrapy list
tech_web

所以我们可以使用下面的命令启动我们创建的这个 spider :

scrapy crawl tech_web

那么现在有一个问题是我需要将数据保存应该如何做呢?

Scrapy 提供了许多Feed exports的方法,可以将输出数据保存为json, json lines, csv, xml

在启动命令后面加 -o xx.json 就可以将文件保存为json格式。

例如使用如下命令将抓取的数据保存到一个 json 文件中:

scrapy crawl tech_web -o result.json

打开保存的 json 文件,发现出现了乱码,出于历史原因,JSON 输出使用安全数字编码(\uXXXX序列),如果想要 UTF-8 用于 JSON,请使用 FEED_EXPORT_ENCODING = 'utf-8'。官方文档对该字段的说明如下:

image-20220107174023299

3. 保存数据到 mongodb

前面我们介绍了如何将采集的结构化数据保存到 json 文件中,下面将介绍如何将采集的数据保存到 mongodb 中,保存到其他数据库也是类似的。

首先由于我们需要保存数据到 mongodb 中,所以这里先用 docker 部署一个 mongodb 数据库,如果已经有了 mongodb 数据库,就不需要这个操作。

docker 部署 mongodb 地址:https://hub.docker.com/r/bitnami/mongodb

使用下面的命令启动一个 mongodb 数据库:

docker run --name mongodb  -e MONGODB_ROOT_PASSWORD=password123 -p 27017:27017  bitnami/mongodb:latest

其次需要介绍一下 scrapypipeline ,在每一个item 被抓取之后,都会被发送到 pipeline 中,每个 pipeline 都是一个实现简单方法的 python 类,

它们接收一个 item 并对其执行操作,同时决定该 item 是应该继续进入下一个 pipeline 还是被丢弃不再处理。

pipeline 的典型用途如下:

编写自己的 pipeline

每个 pipeline 组件都是一个必须实现 process_item 方法的 Python 类:

此外,它们还可以实现以下方法:

知道了 pipeline 的作用和定义方法后,我们定义一个保存数据到 mongodbpipeline ,如下所示:

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import pymongo


class ScrapyDemoPipeline:
    def process_item(self, item, spider):
        return item


class SaveToMongoPipeline:
    
    def __init__(self, mongo_uri, mongo_db, collection):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
        self.collection = collection

    @classmethod
    def from_crawler(cls, crawler):
        # 从 settings.py 文件中读取对应的配置
        env = crawler.settings.get('ENV', 'local')
        config = crawler.settings.get('CONFIG')
        config = config[env]
        mongo_uri = config.MONGO_URI
        mongo_db = config.MONGODB_DB
        collection = config.COLLECTION_NAME
        # 返回当前 pipeline 的实例,传入从 settings 中读取的配置
        return cls(
            mongo_uri=mongo_uri,
            mongo_db=mongo_db,
            collection=collection,
        )

    def open_spider(self, spider):
        """
        打开 spider 的时候调用一次,可以在这里创建数据的连接
        """
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def process_item(self, item, spider):
        """
        每一个 item 都会调用这个方法,可以在这里清洗数据,并保存到数据库
        """
        adapter = ItemAdapter(item)
        coll = self.db[self.collection]
        # 使用 ItemAdapter 的 asdict() 方法可以处理嵌套的 item 格式,获取 json 字符串
        doc = adapter.asdict()
        count = coll.find({'_id': doc.get('_id')}).count()
        if count == 0:
            coll.insert_one(doc)
        else:
            del doc['create_time']
            coll.update_one({"_id": doc.get('_id')}, {'$set': doc})
        return item

    def close_spider(self, spider):
        """
        spider 关闭的时候调用一次,可以在这里关闭数据库连接,释放资源
        """
        self.client.close()

如果要让自己的 pipeline 生效, 需要配置在 settings.py 文件中,如下所示:

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'scrapy_demo.pipelines.ScrapyDemoPipeline': 300,
   'scrapy_demo.pipelines.SaveToMongoPipeline': 400,
}

后面的数字决定了 pipeline 的执行顺序,它的执行顺序为从低到高,数字越大越后执行,自定义的数字范围为 0 - 1000

上述的pipeline 中的 from_crawler 方法使用了 settings 中配置的 mongodb 的地址,settings.py 文件的配置如下所示:

# Scrapy settings for scrapy_demo project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'scrapy_demo'

SPIDER_MODULES = ['scrapy_demo.spiders']
NEWSPIDER_MODULE = 'scrapy_demo.spiders'

FEED_EXPORT_ENCODING = 'utf-8'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'Connection': 'keep-alive',
    'User-Agent': USER_AGENT
}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'scrapy_demo.pipelines.ScrapyDemoPipeline': 300,
    'scrapy_demo.pipelines.SaveToMongoPipeline': 400,
}


class LocalConfig:
    # 本地环境mongeDB地址
    MONGODB_HOST = 'localhost:27017'
    MONGODB_USERNAME = 'root'
    MONGODB_PASSWORD = 'password123'
    MONGODB_DB = 'admin'
    COLLECTION_NAME = 'tech_web'
    MONGO_URI = "mongodb://{username}:{password}@{server}/{database}". \
        format(username=MONGODB_USERNAME, password=MONGODB_PASSWORD, server=MONGODB_HOST, database=MONGODB_DB)


class DevelopmentConfig:
    # 开发环境mongeDB地址
    MONGODB_HOST = 'localhost:27017'
    MONGODB_USERNAME = 'root'
    MONGODB_PASSWORD = 'password123'
    MONGODB_DB = 'admin'
    COLLECTION_NAME = 'tech_web'
    MONGO_URI = "mongodb://{username}:{password}@{server}/{database}". \
        format(username=MONGODB_USERNAME, password=MONGODB_PASSWORD, server=MONGODB_HOST, database=MONGODB_DB)


CONFIG = {
    'local': LocalConfig,
    'dev': DevelopmentConfig,
}

ENV = 'local'

编写完成后运行下面的命令就可以启动 spider:

scrapy crawl tech_web

可以看到数据保存到 mongodb 中了:

image-20220107201532414

六. 部署爬虫项目到 SpiderKeeper

前面介绍了使用命令 scrapy crawl <name> 来运行 spider ,如果我们想要定时运行这些爬虫任务应该怎么做呢?

接下来将介绍部署 scrapy 项目到 SpiderKeeper,部署 scrapy 项目到 SpiderKeeper 需要安装两个 python 库:

1. SpiderKeeper 简介

SpiderKeeper 的源码地址:https://github.com/DormyMo/SpiderKeeper

SpiderKeeperspider 服务的可扩展管理 ui,包括如下功能:

spiderkeeper 目前只支持 scrapyd 方式运行的 scrapy 项目的管理,所以在安装 spiderkeeper 之前需要先安装 scrapyd

2. Scrapyd 简介

Scrapyd 的源码地址:https://github.com/scrapy/scrapyd

Scrapyd 是运行 scrapy 项目的一个守护服务,它允许你部署 scrapy 项目,并且可以使用 http json api 的方式控制 scrapyspider

3. 安装 spiderkeeper

使用下面的命令安装 scrapyd

pip install scrapyd

使用下面的命令启动 scrapydscrapyd 默认运行在 6800 端口,如下所示:

>scrapyd
2022-01-09T10:24:19+0800 [-] Loading e:\anaconda3\lib\site-packages\scrapyd\txapp.py...
2022-01-09T10:24:20+0800 [-] Scrapyd web console available at http://127.0.0.1:6800/
2022-01-09T10:24:20+0800 [-] Loaded.
2022-01-09T10:24:20+0800 [twisted.application.app.AppLogger#info] twistd 21.7.0 (e:\anaconda3\python.exe 3.7.6) starting up.
2022-01-09T10:24:20+0800 [twisted.application.app.AppLogger#info] reactor class: twisted.internet.selectreactor.SelectReactor.
2022-01-09T10:24:20+0800 [-] Site starting on 6800
2022-01-09T10:24:20+0800 [twisted.web.server.Site#info] Starting factory <twisted.web.server.Site object at 0x000001DA664F3C88>
2022-01-09T10:24:20+0800 [Launcher] Scrapyd 1.2.1 started: max_proc=16, runner='scrapyd.runner'

使用下面的命令安装 spiderkeeper

pip install spiderkeeper

使用下面的命令启动 spiderkeeper,默认运行在 5000 端口,如下所示:

>spiderkeeper
--------------------------------------------------------------------------------
INFO in run [e:\anaconda3\lib\site-packages\SpiderKeeper\run.py:22]:
SpiderKeeper startd on 0.0.0.0:5000 username:admin/password:admin with scrapyd servers:http://localhost:6800
--------------------------------------------------------------------------------
2022-01-09 10:27:24,828 - SpiderKeeper.app - INFO - SpiderKeeper startd on 0.0.0.0:5000 username:admin/password:admin with scrapyd servers:http://localhost:6800

spiderkeeper 的其他配置说明如下:


spiderkeeper [options]

Options:

  -h, --help            show this help message and exit
  --host=HOST           host, default:0.0.0.0
  --port=PORT           port, default:5000
  --username=USERNAME   basic auth username ,default: admin
  --password=PASSWORD   basic auth password ,default: admin
  --type=SERVER_TYPE    access spider server type, default: scrapyd
  --server=SERVERS      servers, default: 'http://localhost:6800'
  --database-url=DATABASE_URL
                        SpiderKeeper metadata database default: sqlite:////home/souche/SpiderKeeper.db
  --no-auth             disable basic auth
  -v, --verbose         log level
  

example:

spiderkeeper --server=http://localhost:6800

启动完成 spiderkeeper 后,在浏览器访问:http://localhost:5000,可以看到如下所示页面:

默认用户名和密码都为:admin

image-20220109103152935

登录成功后可以看到如下所示页面:

image-20220109103307504

点击创建一个项目:

image-20220109103406400

可以看到如下部署页面:

image-20220109103452704

scrapy 项目中使用命令 scrapyd-deploy --build-egg output.egg 生成部署文件,并上传,即可完成 scrapy 项目的部署。

使用下面的命令生成部署文件:

>scrapyd-deploy --build-egg output.egg
Writing egg to output.egg

将前面的 scrapy 项目生成的 output.egg 上传到 spiderkeeper 中:

image-20220109103902307

点击 Dashboard --> 点击 Run --> 选择需要运行的 spiderspiderkeeper 会自动识别 spider 中 name 的名称:

image-20220109104314079

如果要创建定时任务,如下图所示:

image-20220109104510161

七. 总结

这篇文章简单记录自己学习 scrapy 的过程,包括从创建项目到部署项目的完整流程,很多细节并没有详细介绍,更多内容可以查看文章中列出的官方文档。

标签:pipeline,demo,self,spider,爬虫,item,Scrapy,从零开始,scrapy
来源: https://www.cnblogs.com/blogwd/p/15780478.html