Scrapy 初学者系列第 1 部分:如何构建你的第一个生产级 Scrapy

第一部分:Scrapy

Spider
在本教程的第 1 部分:基本 Scrapy Spider中,我们将介绍:

在本系列中,我们将从Chocolate.co.uk抓取产品,因为它将是一个很好的示例,说明如何抓取电子商务商店的数据。另外,谁不喜欢巧克力呢!


什么是 Scrapy


Scrapy由Zyte的联合创始人Pablo Hoffman 和 Shane Evans 开发,是一个专为网络抓取而设计的 Python 框架。

使用 Scrapy,您可以轻松构建高度可扩展的抓取工具,它将检索页面 HTML、解析和处理数据,并将其存储为您选择的文件格式和位置。

为什么以及何时应该使用 Scrapy


不过,还有其他 Python 库也可用于网页抓取:

  • Python 请求/ BeautifulSoup:适用于小规模网页抓取,其中数据在 HTML 响应中返回。需要构建自己的蜘蛛管理功能来管理并发、重试、数据清理、数据存储。
  • Python Request-HTML:将 Python 请求与解析库相结合,Request-HTML 是 Python Requests/BeautifulSoup 组合和 Scrapy 之间的中间立场。
  • Python Selenium:如果您正在抓取网站,并且它仅在 Javascript 呈现后返回目标数据,或者您需要与页面元素交互以获取数据,请使用它。

Python Scrapy具有更多功能,非常适合开箱即用的大规模抓取:

  • CSS 选择器和 XPath 表达式解析
  • 数据格式(CSV、JSON、XML)和存储(FTP、S3、本地文件系统)
  • 强大的编码支持
  • 并发管理
  • 自动重试
  • Cookie 和会话处理
  • 爬行蜘蛛和内置分页支持

您只需在您的设置文件中进行自定义,或者添加开发人员开源的众多 Scrapy 扩展和中间件之一。

学习曲线最初比使用 Python Requests/BeautifulSoup 组合更陡峭,但是,从长远来看,在部署生产抓取工具和大规模抓取时,它将为您节省大量时间。


Scrapy 初学者

教程
介绍完毕,让我们开始开发 Spider。首先,我们需要设置 Python 环境。

步骤 1 - 设置 Python

环境
为了避免以后出现版本冲突,最佳做法是为每个 Python 项目创建一个单独的虚拟环境。这意味着您为某个项目安装的任何软件包都与其他项目分开,因此您不会无意中破坏其他项目。

根据您机器的操作系统,这些命令会略有不同。

MacOS 或

Linux
在 MacOS 或任何 Linux 发行版上设置虚拟环境。

首先,我们要确保安装了最新版本的软件包。

$ sudo apt-get update
$ apt install tree

<font style="color:rgb(28, 30, 33);">python3-venv</font>如果你还没有安装,那么请安装

$ sudo apt install -y python3-venv

接下来,我们将创建我们的 Python 虚拟环境。

$ cd /scrapy_tutorials
$ python3 -m venv venv
$ source venv/bin/activate

最后,我们将在虚拟环境中安装 Scrapy。

$ apt-get install python3-pip
$ sudo pip3 install scrapy

窗户

在 Windows 上设置虚拟环境。

<font style="color:rgb(28, 30, 33);">virtualenv</font>在您正在使用的 Windows 命令 shell、Powershell 或其他终端中安装。

pip install virtualenv

导航到您想要创建虚拟环境的文件夹,然后启动 virtualenv。

cd /scrapy_tutorials
virtualenv venv

激活虚拟环境。

source venv\Scripts\activate

最后,我们将在虚拟环境中安装 Scrapy。

pip install scrapy
测试 Scrapy 已

安装
为了确保一切正常,如果你<font style="color:rgb(28, 30, 33);">scrapy</font>在命令行中输入命令,你应该得到如下输出:

$ scrapy

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

第 2 步 - 设置我们的 Scrapy

项目
现在我们已经设置好了环境,可以开始做有趣的事情了。构建我们的第一个 Scrapy 蜘蛛!

创建我们的 Scrapy

项目
我们要做的第一件事是创建我们的 Scrapy 项目。该项目将保存我们抓取工具的所有代码。

执行此操作的命令行语法是:

scrapy startproject <project_name>

因此,在这种情况下,由于我们要抓取一个巧克力网站,因此我们将项目命名为<font style="color:rgb(28, 30, 33);">chocolatescraper</font>。但您可以使用任何您想要的项目名称。

scrapy startproject chocolatescraper
理解 Scrapy 项目

结构
为了帮助我们理解我们刚刚做了什么,以及 Scrapy 如何构建它的项目,我们将暂停一会儿。

首先,我们来看看<font style="color:rgb(28, 30, 33);">scrapy startproject chocolatescraper</font>刚刚做了什么。在命令行中输入以下命令:

$ cd /chocolatescraper
$ tree

你应该看到类似这样的内容:

├── scrapy.cfg
└── chocolatescraper
    ├── __init__.py
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        └── __init__.py

当我们运行该<font style="color:rgb(28, 30, 33);">scrapy startproject chocolatescraper</font>命令时,Scrapy会自动生成一个模板项目供我们使用。

我们不会在这个初学者项目中使用大多数这些文件,但我们会对每个文件进行简要解释,因为每个文件都有特殊的用途:

  • settings.py包含所有项目设置,例如激活管道、中间件等。在这里,您可以更改延迟、并发性以及更多内容。
  • items.py是提取数据的模型。您可以定义一个自定义模型(如 ProductItem),该模型将继承 Scrapy Item 类并包含您抓取的数据。
  • pipelines.py是蜘蛛传递所产生的项目的地方,它主要用于清理文本并连接到文件输出或数据库(CSV、JSON SQL 等)。
  • 当您想要修改请求的方式以及 scrapy 处理响应的方式时,middlewares.py很有用。
  • scrapy.cfg是一个配置文件,用于更改一些部署设置等。

步骤3-创建我们的

蜘蛛
好的,我们已经创建了一般的项目结构。现在,我们将创建用于抓取数据的爬虫。

Scrapy 提供了许多不同类型的蜘蛛,但在本教程中,我们将介绍最常见的蜘蛛,即通用蜘蛛。以下是一些最常见的蜘蛛:

  • Spider -获取 start_urls 列表并使用<font style="color:rgb(28, 30, 33);">parse</font>方法抓取每一个。
  • CrawlSpider -旨在通过跟踪其找到的任何链接来抓取整个网站。
  • SitemapSpider -旨在从站点地图中提取 URL

要创建一个新的通用蜘蛛,只需运行genspider命令:

# syntax is --> scrapy genspider <name_of_spider> <website> 
$ scrapy genspider chocolatespider chocolate.co.uk

现在一个新的蜘蛛将被添加到您的<font style="color:rgb(28, 30, 33);">spiders</font>文件夹中,它应该看起来像这样:

import scrapy

class ChocolatespiderSpider(scrapy.Spider):
    name = 'chocolatespider'
    allowed_domains = ['chocolate.co.uk']
    start_urls = ['http://chocolate.co.uk/']

    def parse(self, response):
        pass

这里我们看到命令<font style="color:rgb(28, 30, 33);">genspider</font>以类的形式创建了一个模板蜘蛛供我们使用<font style="color:rgb(28, 30, 33);">Spider</font>。这个蜘蛛类包含:

  • name - 为蜘蛛命名的类属性。稍后运行蜘蛛时我们会用到它<font style="color:rgb(28, 30, 33);">scrapy crawl <spider_name></font>
  • allowed_domains - 一个类属性,告诉 Scrapy 只应抓取该<font style="color:rgb(28, 30, 33);">chocolate.co.uk</font>域的页面。这可防止蜘蛛程序出差错并抓取大量网站。这是可选的。
  • start_urls - 一个类属性,告诉 Scrapy 应该抓取的第一个 URL。我们稍后会对此进行一些更改。
  • 解析——<font style="color:rgb(28, 30, 33);">parse</font>从目标网站收到响应后调用该函数。

要开始使用这个 Spider,我们必须做两件事:

  1. 将 更改<font style="color:rgb(28, 30, 33);">start_urls</font>为我们要抓取的 URL https://www.chocolate.co.uk/collections/all
  2. 将我们的解析代码插入到<font style="color:rgb(28, 30, 33);">parse</font>函数中。

步骤 4 - 更新起始

网址
这很简单,我们只需要替换数组中的 url <font style="color:rgb(28, 30, 33);">start_urls</font>

import scrapy

class ChocolatespiderSpider(scrapy.Spider):
    name = 'chocolatespider'
    allowed_domains = ['chocolate.co.uk']
    start_urls = ['https://www.chocolate.co.uk/collections/all']

    def parse(self, response):
        pass

接下来,我们需要创建 CSS 选择器来解析页面中所需的数据。为此,我们将使用 Scrapy Shell。


步骤 5 - Scrapy Shell:查找我们的 CSS

选择器</font
要从 HTML 页面中提取数据,我们需要使用XPathCSS 选择器来告诉 Scrapy 数据在页面中的哪个位置。XPath 和 CSS 选择器就像 Scrapy 的小地图,用于导航 DOM 树并找到我们需要的数据的位置。在本指南中,我们将使用 CSS 选择器来解析页面中的数据。为了帮助我们创建这些 CSS 选择器,我们将使用Scrapy Shell

Scrapy 的一大优点是它带有内置 shell,可让您快速测试和调试 XPath 和 CSS 选择器。您无需运行整个抓取程序来查看 XPath 或 CSS 选择器是否正确,而是可以直接将它们输入到终端并查看结果。

要打开 Scrapy shell,请使用以下命令:

scrapy shell

注意:如果您想使用 IPython 作为您的 Scrapy shell(功能更强大并提供智能自动完成和彩色输出),那么请确保您已安装 IPython:

pip3 install ipython

然后<font style="color:rgb(28, 30, 33);">scrapy.cfg</font>像这样编辑你的文件:

## scrapy.cfg
[settings]
default = chocolatescraper.settings
shell = ipython

打开我们的 Scrapy shell 后,你应该会看到类似这样的内容:

[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x0000025111C47948>
[s]   item       {}
[s]   settings   <scrapy.settings.Settings object at 0x0000025111D17408>
[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]:

获取

页面
为了创建我们的 CSS 选择器,我们将在以下页面上测试它们:

https://www.chocolate.co.uk/collections/all

我们要做的第一件事是在我们的 Scrapy shell 中获取巧克力网站的主要产品页面。

fetch('https://www.chocolate.co.uk/collections/all')

我们应该看到如下响应:

In [1]: fetch('https://www.chocolate.co.uk/collections/all')
2021-12-22 13:28:56 [scrapy.core.engine] INFO: Spider opened
2021-12-22 13:28:57 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.chocolate.co.uk/robots.txt> (referer: None)
2021-12-22 13:28:57 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.chocolate.co.uk/collections/all> (referer: None)

我们可以看到,我们成功的从 检索到了页面<font style="color:rgb(28, 30, 33);">chocolate.co.uk</font>,并且 Scrapy shell 已经自动将 HTML 响应保存在响应变量中。

In [2]: response
Out[2]: <200 https://www.chocolate.co.uk/collections/all>

查找产品 CSS

选择器
为了找到正确的 CSS 选择器来解析产品详细信息,我们首先在浏览器的 DevTools 中打开该页面。

打开网站,然后打开开发者工具控制台(右键单击页面并单击检查)。

使用检查元素,将鼠标悬停在项目上并查看各个产品的 ID 和类别。

在这种情况下,我们可以看到每盒巧克力都有自己的特殊组件,称为<font style="color:rgb(28, 30, 33);">product-item</font>。我们可以使用它来引用我们的产品(见上图)。

现在使用我们的 Scrapy shell 我们可以看看是否可以使用此类提取产品信息。

response.css('product-item')

我们可以看到它找到了所有与该选择器匹配的元素。

In [3]: response.css('product-item')
Out[3]:
[<Selector xpath='descendant-or-self::product-item' data='<product-item class="product-item pro...'>,
 <Selector xpath='descendant-or-self::product-item' data='<product-item class="product-item " r...'>,
 <Selector xpath='descendant-or-self::product-item' data='<product-item class="product-item " r...'>,
 <Selector xpath='descendant-or-self::product-item' data='<product-item class="product-item " r...'>,
 <Selector xpath='descendant-or-self::product-item' data='<product-item class="product-item " r...'>,
 <Selector xpath='descendant-or-self::product-item' data='<product-item class="product-item pro...'>,
 ...

获取第一个

产品
为了获得我们使用的第一个产品,<font style="color:rgb(28, 30, 33);">.get()</font>将其附加到命令的末尾。

response.css('product-item').get()

这将返回 DOM 树中此节点的所有 HTML。

In [4]: response.css('product-item').get()
Out[4]: '<product-item class="product-item product-item--sold-out" reveal><div class="product-item__image-wrapper product-item__image-wrapper--multiple"><div class="product-item__label-list label-list"><span class="label label--custom">New</span><span class="label label--subdued">Sold out</span></div><a href="/products/100-dark-hot-chocolate-flakes" class="product-item__aspect-ratio aspect-ratio " style="padding-bottom: 100.0%; --aspect-ratio: 1.0">\n 
...

获取所有

产品
现在我们已经找到了包含产品项目的 DOM 节点,我们将获取所有产品项目并将这些数据保存到响应变量中,然后循环遍历这些项目并提取我们需要的数据。

因此可以使用以下命令执行此操作。

products = response.css('product-item')

产品变量现在是页面上所有产品的列表。

要检查产品变量的长度,我们可以看看有多少个产品。

len(products)

输出如下:

In [6]: len(products)
Out[6]: 24

提取产品

详细信息
现在让我们从产品列表中提取每个产品的名称价格网址。

products 变量是产品列表。当我们更新蜘蛛代码时,我们将循环遍历此列表,但是,为了找到正确的选择器,我们将在列表的第一个元素上测试 CSS 选择器<font style="color:rgb(28, 30, 33);">products[0]</font>

单一产品 –获取单一产品。

product = products[0]

名称 -可以通过以下方式找到产品名称:

product.css('a.product-item-meta__title::text').get()
In [5]: product.css('a.product-item-meta__title::text').get()
Out[5]: '100% Dark Hot Chocolate Flakes'

价格 -可以通过以下方式查看产品价格:

product.css('span.price').get()

您可以看到返回的价格数据包含大量多余的 HTML。我们将在下一步中将其删除。

In [6]: product.css('span.price').get()
Out[6]: '<span class="price">\n              <span class="visually-hidden">Sale price</span>▒8.50</span>'

要从价格中删除多余的 span 标签,我们可以使用该<font style="color:rgb(28, 30, 33);">.replace()</font>方法。当我们需要清理数据时,replace 方法很有用。

在这里我们将<font style="color:rgb(28, 30, 33);"><span></font>用空引号替换部分<font style="color:rgb(28, 30, 33);">''</font>

product.css('span.price').get().replace('<span class="price">\n              <span class="visually-hidden">Sale price</span>','').replace('</span>','')
In [7]: product.css('span.price').get().replace('<span class="price">\n              <span class="visually-hidden">Sale price</span>','').replace('</span>','')
Out[7]: '8.50'

产品 URL -接下来让我们看看如何提取每个产品的产品 URL。为此,我们可以使用<font style="color:rgb(28, 30, 33);">products.css('div.product-item-meta a')</font>

product.css('div.product-item-meta a').attrib['href']
In [8]: product.css('div.product-item-meta a').attrib['href']
Out[8]: '/products/100-dark-hot-chocolate-flakes'

更新的[Spider](https://scrapeops.io/python-scrapy-playbook/scrapy-beginners-guide/#updated-spider)
现在,我们已经找到了正确的 CSS 选择器,让我们更新我们的蜘蛛。使用` exit()` 命令退出 Scrapy shell。

我们更新的 Spider 代码应如下所示:

import scrapy 

class ChocolatespiderSpider(scrapy.Spider)

    #the name of the spider
    name = 'chocolatespider'

    #the url of the first page that we will start scraping
    start_urls = ['https://www.chocolate.co.uk/collections/all']

    def parse(self, response):

        #here we are looping through the products and extracting the name, price & url
        products = response.css('product-item')
        for product in products:
            #here we put the data returned into the format we want to output for our csv or json file
            yield{
                'name' : product.css('a.product-item-meta__title::text').get(),
                'price' : product.css('span.price').get().replace('<span class="price">\n              <span class="visually-hidden">Sale price</span>','').replace('</span>',''),
                'url' : product.css('div.product-item-meta a').attrib['href'],
            }

在这里,我们的蜘蛛执行以下步骤:

  1. 向 发出请求<font style="color:rgb(28, 30, 33);">'https://www.chocolate.co.uk/collections/all'</font>
  2. 当它得到响应时,它会使用从页面中提取所有产品<font style="color:rgb(28, 30, 33);">products = response.css('product-item')</font>
  3. 循环遍历每个产品,并使用我们创建的 CSS 选择器提取名称价格url 。
  4. 产生这些项目以便它们可以存储在 CSV、JSON、DB 等中。

步骤 5 - 运行我们的

Spider
现在我们有了一个蜘蛛,我们可以通过进入 scrapy 项目的顶层并运行以下命令来运行它。

scrapy crawl chocolatespider

它将运行,您应该会在屏幕上看到日志。以下是最终统计数据:

2021-12-22 14:43:54 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 707,
 'downloader/request_count': 2,
 'downloader/request_method_count/GET': 2,
 'downloader/response_bytes': 64657,
 'downloader/response_count': 2,
 'downloader/response_status_count/200': 2,
 'elapsed_time_seconds': 0.794875,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2021, 12, 22, 13, 43, 54, 937791),
 'httpcompression/response_bytes': 268118,
 'httpcompression/response_count': 2,
 'item_scraped_count': 24,
 'log_count/DEBUG': 26,
 'log_count/INFO': 10,
 'response_received_count': 2,
 'robotstxt/request_count': 1,
 'robotstxt/response_count': 1,
 'robotstxt/response_status_count/200': 1,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 1,
 'scheduler/enqueued/memory': 1,
 'start_time': datetime.datetime(2021, 12, 22, 13, 43, 54, 142916)}
2021-12-22 14:43:54 [scrapy.core.engine] INFO: Spider closed (finished)

从上面的统计数据我们可以看出,我们的蜘蛛抓取了 24 个项目:<font style="color:rgb(28, 30, 33);">'item_scraped_count': 24</font>

如果我们想将数据保存到 JSON 文件,我们可以使用该<font style="color:rgb(28, 30, 33);">-O</font>选项,后跟文件名。

scrapy crawl chocolatespider -O myscrapeddata.json

如果我们想将数据保存到 CSV 文件,我们也可以这样做。

scrapy crawl chocolatespider -O myscrapeddata.csv

步骤 6 - 导航至“下一页


到目前为止,代码运行良好,但我们只从网站的第一页获取产品,即我们在 start_url 变量中列出的 url。

因此,下一个合乎逻辑的步骤是转到下一页(如果有的话),并从中抓取商品数据!以下是我们的实现方法。

首先,让我们再次打开 Scrapy shell,获取页面并找到正确的选择器以获取下一页按钮。

scrapy shell

然后再次获取该页面。

fetch('https://www.chocolate.co.uk/collections/all')

然后获取包含下一页 url 的 href 属性。

response.css('[rel="next"] ::attr(href)').get()
In [2]: response.css('[rel="next"] ::attr(href)').get()
Out[2]: '/collections/all?page=2'

现在,我们只需要更新我们的蜘蛛,让它在解析完页面上的所有项目后请求此页面。

import scrapy
 
class ChocolateSpider(scrapy.Spider):

   #the name of the spider
   name = 'chocolatespider'

   #these are the urls that we will start scraping
   start_urls = ['https://www.chocolate.co.uk/collections/all']

   def parse(self, response):

       products = response.css('product-item')
       for product in products:
           #here we put the data returned into the format we want to output for our csv or json file
           yield{
               'name' : product.css('a.product-item-meta__title::text').get(),
               'price' : product.css('span.price').get().replace('<span class="price">\n              <span class="visually-hidden">Sale price</span>','').replace('</span>',''),
               'url' : product.css('div.product-item-meta a').attrib['href'],
           }

       next_page = response.css('[rel="next"] ::attr(href)').get()

       if next_page is not None:
           next_page_url = 'https://www.chocolate.co.uk' + next_page
           yield response.follow(next_page_url, callback=self.parse)

这里我们看到我们的蜘蛛现在找到了下一页的 URL,如果不是,它会将其附加到基本 URL 并发出另一个请求。

现在我们在 Scrapy 统计信息中看到我们已经抓取了 5 个页面并提取了 73 个项目:

2021-12-22 15:10:45 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 2497,
 'downloader/request_count': 5,
 'downloader/request_method_count/GET': 5,
 'downloader/response_bytes': 245935,
 'downloader/response_count': 5,
 'downloader/response_status_count/200': 5,
 'elapsed_time_seconds': 2.441196,
 'feedexport/success_count/FileFeedStorage': 1,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2021, 12, 22, 14, 10, 45, 62280),
 'httpcompression/response_bytes': 986800,
 'httpcompression/response_count': 5,
 'item_scraped_count': 73,
 'log_count/DEBUG': 78,
 'log_count/INFO': 11,
 'request_depth_max': 3,
 'response_received_count': 5,
 'robotstxt/request_count': 1,
 'robotstxt/response_count': 1,
 'robotstxt/response_status_count/200': 1,
 'scheduler/dequeued': 4,
 'scheduler/dequeued/memory': 4,
 'scheduler/enqueued': 4,
 'scheduler/enqueued/memory': 4,
 'start_time': datetime.datetime(2021, 12, 22, 14, 10, 42, 621084)}
2021-12-22 15:10:45 [scrapy.core.engine] INFO: Spider closed (finished)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值