使用 Scrapy 构建可扩展 Web Scraper 的终极指南

已发表: 2022-03-10
快速总结↬ Scrapy 是一个流行的开源 Python 框架,用于编写可扩展的网络爬虫。 在本教程中,我们将逐步引导您使用 Scrapy 从 Wikipedia 收集奥斯卡获奖电影列表。

Web 抓取是一种无需访问 API 或网站数据库即可从网站获取数据的方法。 你只需要访问网站的数据——只要你的浏览器可以访问数据,你就可以抓取它。

实际上,大多数情况下,您可以手动浏览网站并使用复制和粘贴“手动”获取数据,但在很多情况下,这将花费您数小时的手动工作,最终可能会花费您远远超过数据的价值,特别是如果您聘请了某人为您完成任务。 当您可以让程序每隔几秒自动执行一次查询时,为什么还要雇人以每个查询 1 到 2 分钟的时间工作呢?

例如,假设您希望编制一份奥斯卡最佳影片奖得主名单,以及他们的导演、主演、上映日期和放映时间。 使用谷歌,你可以看到有几个网站会按名称列出这些电影,也许还有一些额外的信息,但通常你必须通过链接来获取你想要的所有信息。

显然,遍历从 1927 年到今天的每个链接并手动尝试通过每个页面查找信息是不切实际且耗时的。 通过网络抓取,我们只需要找到一个包含所有这些信息的页面的网站,然后用正确的指令将我们的程序指向正确的方向。

在本教程中,我们将使用 Wikipedia 作为我们的网站,因为它包含我们需要的所有信息,然后使用 Python 上的 Scrapy 作为一种工具来抓取我们的信息。

跳跃后更多! 继续往下看↓

在我们开始之前有几点注意事项:

数据抓取涉及增加您正在抓取的站点的服务器负载,这意味着托管该站点的公司的成本更高,而该站点的其他用户的体验质量较低。 运行网站的服务器的质量、您尝试获取的数据量以及您向服务器发送请求的速率将缓和您对服务器的影响。 牢记这一点,我们需要确保遵守一些规则。

大多数站点的主目录中还有一个名为robots.txt的文件。 该文件规定了网站不希望抓取工具访问哪些目录的规则。 网站的条款和条件页面通常会让您知道他们的数据抓取政策是什么。 例如,IMDB 的条件页面有以下子句:

机器人和屏幕抓取:您不得在本网站上使用数据挖掘、机器人、屏幕抓取或类似的数据收集和提取工具,除非得到我们如下所述的明确书面同意。

在我们尝试获取网站的数据之前,我们应该始终查看网站的条款和robots.txt ,以确保我们获取的是合法数据。 在构建我们的爬虫时,我们还需要确保我们不会用它无法处理的请求来压倒服务器。

幸运的是,许多网站认识到用户需要获取数据,并通过 API 提供数据。 如果这些可用,通过 API 获取数据通常比通过抓取更容易。

维基百科允许数据抓取,只要机器人不会“太快”,如robots.txt中所述。 他们还提供可下载的数据集,以便人们可以在自己的机器上处理数据。 如果我们走得太快,服务器会自动阻止我们的 IP,因此我们将实施计时器以保持在他们的规则范围内。

入门,使用 Pip 安装相关库

首先,首先,让我们安装 Scrapy。

视窗

从 https://www.python.org/downloads/windows/ 安装最新版本的 Python

注意: Windows 用户还需要 Microsoft Visual C++ 14.0,您可以从此处的“Microsoft Visual C++ Build Tools”中获取。

您还需要确保您拥有最新版本的 pip。

cmd.exe中,输入:

 python -m pip install --upgrade pip pip install pypiwin32 pip install scrapy

这将自动安装 Scrapy 和所有依赖项。

Linux

首先,您需要安装所有依赖项:

在终端中,输入:

 sudo apt-get install python3 python3-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev

全部安装完成后,只需输入:

 pip install --upgrade pip

确保 pip 已更新,然后:

 pip install scrapy

这一切都完成了。

苹果

首先,您需要确保您的系统上有一个 c 编译器。 在终端中,输入:

 xcode-select --install

之后,从 https://brew.sh/ 安装自制软件。

更新您的 PATH 变量,以便在系统包之前使用自制包:

 echo "export PATH=/usr/local/bin:/usr/local/sbin:$PATH" >> ~/.bashrc source ~/.bashrc

安装 Python:

 brew install python

然后确保所有内容都已更新:

 brew update; brew upgrade python

完成后,只需使用 pip 安装 Scrapy:

 pip install Scrapy
> ## Scrapy 概述、各部分如何组合、解析器、蜘蛛等

您将编写一个名为“蜘蛛”的脚本供 Scrapy 运行,但不要担心,尽管 Scrapy 蜘蛛有名字,但它一点也不可怕。 Scrapy 蜘蛛和真正的蜘蛛唯一的相似之处是它们喜欢在网上爬行。

蜘蛛内部是一个你定义的class ,它告诉 Scrapy 要做什么。 例如,从哪里开始爬网、它发出的请求类型、如何跟踪页面上的链接以及它如何解析数据。 您甚至可以添加自定义函数来处理数据,然后再输出回文件。

## 编写你的第一个蜘蛛,编写一个简单的蜘蛛以允许动手学习

要启动我们的第一个蜘蛛,我们首先需要创建一个 Scrapy 项目。 为此,请在命令行中输入:

 scrapy startproject oscars

这将为您的项目创建一个文件夹。

我们将从一个基本的蜘蛛开始。 以下代码将被输入到 python 脚本中。 在/oscars/spiders中打开一个新的 python 脚本并将其命名为oscars_spider.py

我们将导入 Scrapy。

 import scrapy

然后我们开始定义我们的 Spider 类。 首先,我们设置名称,然后设置允许蜘蛛抓取的域。 最后,我们告诉蜘蛛从哪里开始抓取。

 class OscarsSpider(scrapy.Spider): name = "oscars" allowed_domains = ["en.wikipedia.org"] start_urls = ['https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture']

接下来,我们需要一个函数来捕获我们想要的信息。 现在,我们只获取页面标题。 我们使用 CSS 找到带有标题文本的标签,然后我们将其提取出来。 最后,我们将信息返回给 Scrapy 以记录或写入文件。

 def parse(self, response): data = {} data['title'] = response.css('title::text').extract() yield data

现在将代码保存在/oscars/spiders/oscars_spider.py

要运行此蜘蛛,只需转到您的命令行并键入:

 scrapy crawl oscars

你应该看到这样的输出:

 2019-05-02 14:39:31 [scrapy.utils.log] INFO: Scrapy 1.6.0 started (bot: oscars) ... 2019-05-02 14:39:32 [scrapy.core.engine] DEBUG: Crawled (200) (referer: None) 2019-05-02 14:39:34 [scrapy.core.engine] DEBUG: Crawled (200) (referer: None) 2019-05-02 14:39:34 [scrapy.core.scraper] DEBUG: Scraped from <200 https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture> {'title': ['Academy Award for Best Picture - Wikipedia']} 2019-05-02 14:39:34 [scrapy.core.engine] INFO: Closing spider (finished) 2019-05-02 14:39:34 [scrapy.statscollectors] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 589, 'downloader/request_count': 2, 'downloader/request_method_count/GET': 2, 'downloader/response_bytes': 74517, 'downloader/response_count': 2, 'downloader/response_status_count/200': 2, 'finish_reason': 'finished', 'finish_time': datetime.datetime(2019, 5, 2, 7, 39, 34, 264319), 'item_scraped_count': 1, 'log_count/DEBUG': 3, 'log_count/INFO': 9, '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(2019, 5, 2, 7, 39, 31, 431535)} 2019-05-02 14:39:34 [scrapy.core.engine] INFO: Spider closed (finished) 2019-05-02 14:39:31 [scrapy.utils.log] INFO: Scrapy 1.6.0 started (bot: oscars) ... 2019-05-02 14:39:32 [scrapy.core.engine] DEBUG: Crawled (200) (referer: None) 2019-05-02 14:39:34 [scrapy.core.engine] DEBUG: Crawled (200) (referer: None) 2019-05-02 14:39:34 [scrapy.core.scraper] DEBUG: Scraped from <200 https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture> {'title': ['Academy Award for Best Picture - Wikipedia']} 2019-05-02 14:39:34 [scrapy.core.engine] INFO: Closing spider (finished) 2019-05-02 14:39:34 [scrapy.statscollectors] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 589, 'downloader/request_count': 2, 'downloader/request_method_count/GET': 2, 'downloader/response_bytes': 74517, 'downloader/response_count': 2, 'downloader/response_status_count/200': 2, 'finish_reason': 'finished', 'finish_time': datetime.datetime(2019, 5, 2, 7, 39, 34, 264319), 'item_scraped_count': 1, 'log_count/DEBUG': 3, 'log_count/INFO': 9, '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(2019, 5, 2, 7, 39, 31, 431535)} 2019-05-02 14:39:34 [scrapy.core.engine] INFO: Spider closed (finished) 2019-05-02 14:39:31 [scrapy.utils.log] INFO: Scrapy 1.6.0 started (bot: oscars) ... 2019-05-02 14:39:32 [scrapy.core.engine] DEBUG: Crawled (200) (referer: None) 2019-05-02 14:39:34 [scrapy.core.engine] DEBUG: Crawled (200) (referer: None) 2019-05-02 14:39:34 [scrapy.core.scraper] DEBUG: Scraped from <200 https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture> {'title': ['Academy Award for Best Picture - Wikipedia']} 2019-05-02 14:39:34 [scrapy.core.engine] INFO: Closing spider (finished) 2019-05-02 14:39:34 [scrapy.statscollectors] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 589, 'downloader/request_count': 2, 'downloader/request_method_count/GET': 2, 'downloader/response_bytes': 74517, 'downloader/response_count': 2, 'downloader/response_status_count/200': 2, 'finish_reason': 'finished', 'finish_time': datetime.datetime(2019, 5, 2, 7, 39, 34, 264319), 'item_scraped_count': 1, 'log_count/DEBUG': 3, 'log_count/INFO': 9, '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(2019, 5, 2, 7, 39, 31, 431535)} 2019-05-02 14:39:34 [scrapy.core.engine] INFO: Spider closed (finished)

恭喜,你已经构建了你的第一个基本的 Scrapy 刮板!

完整代码:

 import scrapy class OscarsSpider(scrapy.Spider): name = "oscars" allowed_domains = ["en.wikipedia.org"] start_urls = ["https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture"] def parse(self, response): data = {} data['title'] = response.css('title::text').extract() yield data

显然,我们希望它做更多的事情,所以让我们看看如何使用 Scrapy 来解析数据。

首先,让我们熟悉一下 Scrapy shell。 Scrapy shell 可以帮助您测试您的代码,以确保 Scrapy 正在抓取您想要的数据。

要访问 shell,请在命令行中输入:

 scrapy shell “https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture”

这基本上会打开您将其定向到的页面,并允许您运行单行代码。 例如,您可以通过键入以下内容来查看页面的原始 HTML:

 print(response.text)

或者在默认浏览器中输入以下内容打开页面:

 view(response)

我们的目标是找到包含我们想要的信息的代码。 现在,让我们尝试只获取电影名称。

找到我们需要的代码的最简单方法是在浏览器中打开页面并检查代码。 在此示例中,我使用的是 Chrome DevTools。 只需右键单击任何电影标题并选择“检查”:

使用 Chrome DevTools 检查 HTML 和 CSS
Chrome 开发工具窗口。 (大预览)

如您所见,奥斯卡获奖者的背景为黄色,而提名者的背景则为纯色。 还有一个关于电影标题的文章的链接,电影的链接以电影结尾film) 。 现在我们知道了这一点,我们可以使用 CSS 选择器来获取数据。 在 Scrapy shell 中,输入:

 response.css(r"tr[] a[href*='film)']").extract()

如您所见,您现在拥有所有奥斯卡最佳影片获奖者的名单!

 > response.css(r"tr[] a[href*='film']").extract() ['<a href="/wiki/Wings_(1927_film)" title="Wings (1927 film)">Wings</a>', ... '<a href="/wiki/Green_Book_(film)" title="Green Book (film)">Green Book</a>', '<a href="/wiki/Jim_Burke_(film_producer)" title="Jim Burke (film producer)">Jim Burke</a>']

回到我们的主要目标,我们想要一份奥斯卡最佳影片奖得主名单,以及他们的导演、主演、上映日期和放映时间。 为此,我们需要 Scrapy 从每个电影页面中获取数据。

我们将不得不重写一些东西并添加一个新函数,但别担心,这很简单。

我们将首先以与以前相同的方式启动刮板。

 import scrapy, time class OscarsSpider(scrapy.Spider): name = "oscars" allowed_domains = ["en.wikipedia.org"] start_urls = ["https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture"]

但这一次,有两件事会改变。 首先,我们将与scrapy一起导入time ,因为我们想创建一个计时器来限制机器人抓取的速度。 此外,当我们第一次解析页面时,我们只想获取每个标题的链接列表,因此我们可以从这些页面中获取信息。

 def parse(self, response): for href in response.css(r"tr[] a[href*='film)']::attr(href)").extract(): url = response.urljoin(href) print(url) req = scrapy.Request(url, callback=self.parse_titles) time.sleep(5) yield req

在这里,我们创建一个循环来查找页面上以film)结尾的每个链接,其中包含黄色背景,然后我们将这些链接连接到一个 URL 列表中,我们将发送到函数parse_titles以进一步传递。 我们还设置了一个计时器,使其仅每 5 秒请求一次页面。 请记住,我们可以使用 Scrapy shell 来测试我们的response.css字段,以确保我们得到正确的数据!

 def parse_titles(self, response): for sel in response.css('html').extract(): data = {} data['title'] = response.css(r"h1[id='firstHeading'] i::text").extract() data['director'] = response.css(r"tr:contains('Directed by') a[href*='/wiki/']::text").extract() data['starring'] = response.css(r"tr:contains('Starring') a[href*='/wiki/']::text").extract() data['releasedate'] = response.css(r"tr:contains('Release date') li::text").extract() data['runtime'] = response.css(r"tr:contains('Running time') td::text").extract() yield data

真正的工作在我们的parse_data函数中完成,我们在其中创建一个名为data的字典,然后用我们想要的信息填充每个键。 同样,所有这些选择器都是使用 Chrome DevTools 找到的,如前所述,然后使用 Scrapy shell 进行测试。

最后一行将数据字典返回给 Scrapy 进行存储。

完整代码:

 import scrapy, time class OscarsSpider(scrapy.Spider): name = "oscars" allowed_domains = ["en.wikipedia.org"] start_urls = ["https://en.wikipedia.org/wiki/Academy_Award_for_Best_Picture"] def parse(self, response): for href in response.css(r"tr[] a[href*='film)']::attr(href)").extract(): url = response.urljoin(href) print(url) req = scrapy.Request(url, callback=self.parse_titles) time.sleep(5) yield req def parse_titles(self, response): for sel in response.css('html').extract(): data = {} data['title'] = response.css(r"h1[id='firstHeading'] i::text").extract() data['director'] = response.css(r"tr:contains('Directed by') a[href*='/wiki/']::text").extract() data['starring'] = response.css(r"tr:contains('Starring') a[href*='/wiki/']::text").extract() data['releasedate'] = response.css(r"tr:contains('Release date') li::text").extract() data['runtime'] = response.css(r"tr:contains('Running time') td::text").extract() yield data

有时我们会想要使用代理,因为网站会试图阻止我们的抓取尝试。

为此,我们只需要更改一些内容。 使用我们的示例,在我们的def parse()中,我们需要将其更改为以下内容:

 def parse(self, response): for href in (r"tr[] a[href*='film)']::attr(href)").extract() : url = response.urljoin(href) print(url) req = scrapy.Request(url, callback=self.parse_titles) req.meta['proxy'] = "https://yourproxy.com:80" yield req

这将通过您的代理服务器路由请求。

部署和日志记录,展示如何在生产环境中实际管理蜘蛛

现在是时候运行我们的蜘蛛了。 要让 Scrapy 开始抓取然后输出到 CSV 文件,请在命令提示符中输入以下内容:

 scrapy crawl oscars -o oscars.csv

你会看到一个很大的输出,几分钟后,它会完成,你的项目文件夹中会有一个 CSV 文件。

编译结果,展示如何使用前面步骤中编译的结果

当您打开 CSV 文件时,您将看到我们想要的所有信息(按带有标题的列排序)。 真的就是这么简单。

奥斯卡获奖电影和相关信息的 CSV
奥斯卡获奖电影名单和信息。 (大预览)

通过数据抓取,我们可以获得几乎任何我们想要的自定义数据集,只要信息是公开可用的。 你想用这些数据做什么取决于你。 这项技能对于进行市场研究、更新网站上的信息以及许多其他事情非常有用。

设置您自己的网络抓取工具以自行获取自定义数据集相当容易,但是,请始终记住,可能还有其他方法可以获取您需要的数据。 企业在提供您想要的数据方面投入了大量资金,因此我们尊重他们的条款和条件是公平的。

了解更多关于 Scrapy 和 Web Scraping 的其他资源

  • Scrapy 官方网站
  • Scrapy 的 GitHub 页面
  • “10 种最佳数据抓取工具和 Web 抓取工具”,Scraper API
  • “在不被阻止或列入黑名单的情况下进行 Web 抓取的 5 个技巧”,Scraper API
  • Parsel,一个使用正则表达式从 HTML 中提取数据的 Python 库。