如何使用 Scrapy 和 Python 3 抓取网页

介绍

网页扫描,通常被称为网页扫描或网页蜘蛛,是程序性地跨越一系列网页和提取数据的行为,是处理网页上的数据的强大工具。

使用网页扫描仪,您可以挖掘有关一组产品的数据,获得大量的文本或定量数据来玩耍,从没有官方API的网站中获取数据,或者仅仅满足您自己的个人好奇心。

在本教程中,您将了解扫描和蜘蛛过程的基础知识,因为您探索一个有趣的数据集。我们将使用(http://quotes.toscrape.com),一个网站上托管的报价数据库,旨在测试网页蜘蛛。本教程结束时,您将有一个功能齐全的Python网页扫描程序,通过包含报价的一系列页面行走,并在您的屏幕上显示它们。

扫描器将易于扩展,因此您可以用它来扫描,并将其作为您自己的项目的基石,从网络中扫描数据。

前提条件

要完成本教程,你需要一个Python 3的本地开发环境。你可以遵循 如何安装和设置Python 3的本地编程环境来配置你需要的一切。

步骤1 - 创建一个基本的扫描器

扫描是一个两个步骤的过程:

1、系统地查找和下载网页;2.从下载网页中提取信息。

这两个步骤可以在许多语言中以多种方式实现。

您可以使用您编程语言提供的 [模块] (https://andsky.com/tech/tutorials/how-to-import-modules-in-python-3] 或库从头开始构建刮刮器, 但随着您刮刮器变得更加复杂, 您必须处理一些潜在的头痛 。 例如,你需要处理货币,这样你就可以一次爬出一页以上. 您可能想要找出如何将所刮去的数据转换成不同的格式,例如CSV,XML,或JSON. 有时你必须处理需要特定设置和访问模式的网站.

你将有更好的运气,如果你建立你的扫描仪在一个现有的库上处理这些问题为你. 对于本教程,我们将使用Python和 Scrapy来构建我们的扫描仪。

Scrapy是最流行的和最强大的Python扫描库之一;它采用了包括电池的方法来扫描,这意味着它处理了所有扫描器需要的许多常见功能,因此开发人员不必每次重新发明轮子。

Scrapy,就像大多数Python包一样,是PyPI(也称为)上的,PyPI(Python Package Index)是所有发布的Python软件的社区库。

如果您有像本教程的前提中描述的那种Python安装,您已经在您的机器上安装了pip,因此您可以使用以下命令安装Scrapy:

1pip install scrapy

如果您在安装过程中遇到任何问题,或者您想在不使用)。

安装了 Scrapy,为我们的项目创建一个新的文件夹. 您可以在终端中运行:

1mkdir quote-scraper

现在,导航到您刚刚创建的新目录:

1cd quote-scraper

然后为我们的扫描器创建一个新的Python文件,名为scraper.py。我们将把我们所有的代码放在这个文件中为本教程。

要做到这一点,您需要创建一个 Python 类,该类分类为 scrapy.Spider,这是由 Scrapy 提供的基本蜘蛛类。这个类将具有两个必要的属性:

在文本编辑器中打开scrapy.py文件,并添加此代码来创建基本蜘蛛:

1[label scraper.py]
2import scrapy
3
4class QuoteSpider(scrapy.Spider):
5    name = 'quote-spdier'
6    start_urls = ['https://quotes.toscrape.com']

让我们把这条线路划分为下行:

首先,我们 进口 scrapy 以便我们可以使用该包提供的类。

接下來,我們採取由 Scrapy 提供的「蜘蛛」類別,並從中製作一個名為「BrickSetSpider」的「子類別」。 將子類別視為其母類別的更專門形式。 該「蜘蛛」類別有定義如何追蹤 URL 並從它找到的頁面提取數據的方法和行為,但它不知道要尋找哪裡或要尋找什麼數據。

最后,我们命名类 quote-spider,并给我们的扫描器一个单一的URL,从: https://quotes.toscrape.com开始。 如果您在浏览器中打开该URL,它会带您到搜索结果页面,显示许多著名的引文的第一页。

现在,测试扫描仪. 通常,Python文件使用一个命令,如),以简化启动扫描仪的过程。 使用以下命令启动扫描仪:

1scrapy runspider scraper.py

命令将输出这样的东西:

 1[secondary_label Output]
 22022-12-02 10:30:08 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor
 32022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet Password: b4d94e3a8d22ede1
 42022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled extensions:
 5['scrapy.extensions.corestats.CoreStats',
 6 ...
 7 'scrapy.extensions.logstats.LogStats']
 82022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled downloader middlewares:
 9['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
10 ...
11 'scrapy.downloadermiddlewares.stats.DownloaderStats']
122022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled spider middlewares:
13['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
14 ...
15 'scrapy.spidermiddlewares.depth.DepthMiddleware']
162022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled item pipelines:
17[]
182022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider opened
192022-12-02 10:30:08 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
202022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
212022-12-02 10:49:32 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com> (referer: None)
222022-12-02 10:30:08 [scrapy.core.engine] INFO: Closing spider (finished)
232022-12-02 10:30:08 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
24{'downloader/request_bytes': 226,
25 ...
26 'start_time': datetime.datetime(2022, 12, 2, 18, 30, 8, 492403)}
272022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider closed (finished)

这是很多的输出,所以让我们打破它。

  • 扫描器初始化并加载了需要处理从 URL 读取数据的额外组件和扩展。 * 它使用了我们在start_urls列表中提供的 URL,并抓住了HTML,就像您的Web浏览器一样。

现在让我们从页面中提取一些数据。

步骤 2 — 从页面提取数据

我们创建了一个非常基本的程序,它将页面拖下来,但它还没有进行任何扫描或蜘蛛。

如果你看 我们想要扫描的页面,你会看到它有以下结构:

  • 每个页面都有一个标题。 * 有一个链接可以登录。 * 然后有引文本身,显示在看起来像表或排序列表中。

当写一个扫描器时,你需要看看HTML文件的来源并熟悉结构,所以这里是它,我们的目标不相关的标签被删除,以便可读性:

 1[secondary_label quotes.toscrape.com]
 2<body>
 3...
 4    <div class="quote" itemscope itemtype="http://schema.org/CreativeWork">
 5        <span class="text" itemprop="text">“I have not failed. I&#39;ve just found 10,000 ways that won&#39;t work.”</span>
 6        <span>by <small class="author" itemprop="author">Thomas A. Edison</small>
 7        <a href="/author/Thomas-A-Edison">(about)</a>
 8        </span>
 9        <div class="tags">
10            Tags:
11            <meta class="keywords" itemprop="keywords" content="edison,failure,inspirational,paraphrased" /    > 
12
13            <a class="tag" href="/tag/edison/page/1/">edison</a>
14
15            <a class="tag" href="/tag/failure/page/1/">failure</a>
16
17            <a class="tag" href="/tag/inspirational/page/1/">inspirational</a>
18
19            <a class="tag" href="/tag/paraphrased/page/1/">paraphrased</a>
20
21        </div>
22    </div>
23...    
24</body>

扫描此页面是一个两个步骤的过程:

  1. 首先,通过寻找页面中有我们想要的数据的部分来捕捉每个引用; 2. 然后,对于每个引用,通过从HTML标签中拉出数据来捕捉我们想要的数据。

选择器是我们可以用来在页面上找到一个或多个元素的模式,这样我们就可以用元素中的数据来工作。

现在我们将使用CSS选择器,因为CSS是找到页面上的所有集合的完美匹配。如果你看HTML,你会看到每个引文都被指定为引文。因为我们正在寻找一个类,我们将使用.quote作为我们的CSS选择器。选择器的.部分在元素上搜索class属性。我们只需要在我们的类中创建一个名为parse的新方法,然后将该选择器传入response对象,如下:

 1[label scraper.py]
 2class QuoteSpider(scrapy.Spider):
 3    name = 'quote-spdier'
 4    start_urls = ['https://quotes.toscrape.com']
 5
 6    def parse(self, response):
 7        QUOTE_SELECTOR = '.quote'
 8        TEXT_SELECTOR = '.text::text'
 9        AUTHOR_SELECTOR = '.author::text'
10
11        for quote in response.css(QUOTE_SELECTOR):
12            pass

这个代码抓住页面上的所有集,并绕过它们提取数据,现在让我们从这些引用中提取数据,以便我们可以显示它。

我们正在审查的页面的另一个观点(https://quotes.toscrape.com)告诉我们,每个引文的文本被存储在一个 spantext 类和引文的作者在一个 <small> 标签与 author 类内:

1[secondary_label quotes.toscrape.com]
2        ...
3        <span class="text" itemprop="text">“I have not failed. I&#39;ve just found 10,000 ways that won&#39;t work.”</span>
4        <span>by <small class="author" itemprop="author">Thomas A. Edison</small>
5        ...

我们正在循环的引用对象有自己的css方法,所以我们可以通过选择器来找到儿童元素。

 1[label scraper.py]
 2class QuoteSpider(scrapy.Spider):
 3    name = 'quote-spdier'
 4    start_urls = ['https://quotes.toscrape.com']
 5
 6    def parse(self, response):
 7        QUOTE_SELECTOR = '.quote'
 8        TEXT_SELECTOR = '.text::text'
 9        AUTHOR_SELECTOR = '.author::text'
10
11        for quote in response.css(QUOTE_SELECTOR):
12            yield {
13                'text': quote.css(TEXT_SELECTOR).extract_first(),
14                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
15            }

注:在Python中,在dict对象中的trailing comma是有效的语法,并且是一个很好的方法,可以为更多添加更多项目留下空间。

你会注意到这个代码中发生的两件事:

  • 我们添加 ::text 到我们的选项引用和作者. 这是一个 CSS pseudo-selector 捕捉标签的文本 inside 而不是标签本身. * 我们呼叫 extract_first() 对象返回由 quote.css(TEXT_SELECTOR) 因为我们只想要第一个元素匹配选项. 这给了我们一个 string,而不是一个元素列表。

保存檔案並重新執行掃描器:

1scrapy runspider scraper.py

这次的输出将包含引用和他们的作者:

1[secondary_label Output]
2...
32022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
4{'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein'}
52022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
6{'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen'}
72022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
8{'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe'}
9...

让我们通过添加有关作者页面的链接和引用标签的新选项来继续扩展这一点。

  • 链接到作者的 about 页面被存储在他们的名字后面的链接中。 * 标签被存储为a标签的集合,每个标签都被分类为tag,存储在一个div元素中与tags类别。

所以,让我们修改扫描器以获取这些新信息:

 1[label scraper.py]
 2class QuoteSpider(scrapy.Spider):
 3    name = 'quote-spdier'
 4    start_urls = ['https://quotes.toscrape.com']
 5
 6    def parse(self, response):
 7        QUOTE_SELECTOR = '.quote'
 8        TEXT_SELECTOR = '.text::text'
 9        AUTHOR_SELECTOR = '.author::text'
10        ABOUT_SELECTOR = '.author + a::attr("href")'
11        TAGS_SELECTOR = '.tags > .tag::text'
12
13        for quote in response.css(QUOTE_SELECTOR):
14            yield {
15                'text': quote.css(TEXT_SELECTOR).extract_first(),
16                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
17                'about': 'https://quotes.toscrape.com' + 
18                        quote.css(ABOUT_SELECTOR).extract_first(),
19                'tags': quote.css(TAGS_SELECTOR).extract(),
20            }

保存您的更改,并再次运行扫描器:

1scrapy runspider scraper.py

现在输出将包含新的数据:

1[secondary_label Output]
22022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
3{'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein', 'about': 'https://quotes.toscrape.com/author/Albert-Einstein', 'tags': ['inspirational', 'life', 'live', 'miracle', 'miracles']}
42022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
5{'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen', 'about': 'https://quotes.toscrape.com/author/Jane-Austen', 'tags': ['aliteracy', 'books', 'classic', 'humor']}
62022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
7{'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe', 'about': 'https://quotes.toscrape.com/author/Marilyn-Monroe', 'tags': ['be-yourself', 'inspirational']}

现在让我们把这个扫描器变成一个跟随链接的蜘蛛。

步骤3 - 扫描多个页面

您已成功从该初始页提取数据,但我们没有通过它来看到其余的结果. 蜘蛛的整个点是检测和穿越链接到其他页面,并从这些页面获取数据。

你会注意到每个页面的顶部和底部都有一个小小的右卡拉(>)链接到下一个结果页面。

 1[secondary_label quotes.toscrape.com]
 2...
 3    <nav>
 4        <ul class="pager">
 5
 6            <li class="next">
 7                <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a>
 8            </li>
 9
10        </ul>
11    </nav>
12...

在源头中,你会发现一个li标签与下一类,在该标签中,有一个a标签,链接到下一页。

更改您的代码如下:

 1[label scraper.py]
 2class QuoteSpider(scrapy.Spider):
 3    name = 'quote-spdier'
 4    start_urls = ['https://quotes.toscrape.com']
 5
 6    def parse(self, response):
 7        QUOTE_SELECTOR = '.quote'
 8        TEXT_SELECTOR = '.text::text'
 9        AUTHOR_SELECTOR = '.author::text'
10        ABOUT_SELECTOR = '.author + a::attr("href")'
11        TAGS_SELECTOR = '.tags > .tag::text'
12        NEXT_SELECTOR = '.next a::attr("href")'
13
14        for quote in response.css(QUOTE_SELECTOR):
15            yield {
16                'text': quote.css(TEXT_SELECTOR).extract_first(),
17                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
18                'about': 'https://quotes.toscrape.com' + 
19                        quote.css(ABOUT_SELECTOR).extract_first(),
20                'tags': quote.css(TAGS_SELECTOR).extract(),
21            }
22
23        next_page = response.css(NEXT_SELECTOR).extract_first()
24        if next_page:
25            yield scrapy.Request(response.urljoin(next_page))

首先,我们为下一页链接定义一个选择器,提取第一个匹配,并检查是否存在。

这意味着一旦我们去到下一个页面,我们会寻找一个链接到下一个页面,在那个页面上我们会寻找一个链接到下一个页面,等等,直到我们找到一个链接到下一个页面。

现在,如果你保存你的代码并再次运行蜘蛛,你会看到它不会停止,一旦它重复通过第一页的集合. 它继续通过所有10页的100个引用。

以下是我们完成的这个教程的代码:

 1[label scraper.py]
 2import scrapy
 3
 4class QuoteSpider(scrapy.Spider):
 5    name = 'quote-spdier'
 6    start_urls = ['https://quotes.toscrape.com']
 7
 8    def parse(self, response):
 9        QUOTE_SELECTOR = '.quote'
10        TEXT_SELECTOR = '.text::text'
11        AUTHOR_SELECTOR = '.author::text'
12        ABOUT_SELECTOR = '.author + a::attr("href")'
13        TAGS_SELECTOR = '.tags > .tag::text'
14        NEXT_SELECTOR = '.next a::attr("href")'
15
16        for quote in response.css(QUOTE_SELECTOR):
17            yield {
18                'text': quote.css(TEXT_SELECTOR).extract_first(),
19                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
20                'about': 'https://quotes.toscrape.com' + 
21                        quote.css(ABOUT_SELECTOR).extract_first(),
22                'tags': quote.css(TAGS_SELECTOR).extract(),
23            }
24
25        next_page = response.css(NEXT_SELECTOR).extract_first()
26        if next_page:
27            yield scrapy.Request(
28                response.urljoin(next_page),
29            )

结论

在这个教程中,你建造了一个功能完备的蜘蛛 从网页中提取不到30行代码的数据。 这是一个伟大的开始,但有很多有趣的事情 你可以做与这个蜘蛛。 这应该足以让你思考和实验。 如果您需要有关Scrapy的更多信息,请查看[Scrapy的官方docs] (https://scrapy.org/doc/). 关于使用网络数据工作的更多信息,请参见我们的教程How To scrape Web pages with Beautiful Soup and Python 3.

Published At
Categories with 技术
comments powered by Disqus