Поиск по сайту:

Как сканировать веб-страницу с помощью Scrapy и Python 3


Введение

Веб-скрапинг, часто называемый веб-сканированием или веб-пауками, представляет собой акт программного просмотра набора веб-страниц и извлечения данных и представляет собой мощный инструмент для работы с данными в Интернете.

С помощью парсера вы можете собирать данные о наборе продуктов, получать большой набор текстовых или количественных данных для экспериментов, извлекать данные с сайта без официального API или просто удовлетворять свое личное любопытство.

В этом учебном пособии вы узнаете об основах процесса парсинга и сканирования, изучая набор данных в игровой форме. Мы будем использовать Quotes to Scrape, базу данных котировок, размещенную на сайте, предназначенном для тестирования веб-пауков. К концу этого руководства у вас будет полнофункциональный веб-парсер Python, который проходит через серию страниц, содержащих цитаты, и отображает их на вашем экране.

Парсер будет легко расширяться, так что вы сможете повозиться с ним и использовать его в качестве основы для ваших собственных проектов, собирающих данные из Интернета.

Предпосылки

Для выполнения этого руководства вам понадобится локальная среда разработки для Python 3. Вы можете следовать инструкциям по установке и настройке локальной среды программирования для Python 3, чтобы настроить все, что вам нужно.

Шаг 1 — Создание базового скребка

Скрапинг представляет собой двухэтапный процесс:

  1. Систематический поиск и загрузка веб-страниц.
  2. Извлечение информации из загруженных страниц.

Оба эти шага могут быть реализованы различными способами на многих языках.

Вы можете создать парсер с нуля, используя модули или библиотеки, предоставляемые вашим языком программирования, но тогда вам придется столкнуться с некоторыми потенциальными головными болями по мере того, как ваш парсер станет более сложным. Например, вам нужно будет обрабатывать параллелизм, чтобы вы могли сканировать более одной страницы за раз. Вы, вероятно, захотите выяснить, как преобразовать ваши очищенные данные в различные форматы, такие как CSV, XML или JSON. И вам иногда придется иметь дело с сайтами, которые требуют определенных настроек и шаблонов доступа.

Вам повезет больше, если вы создадите парсер на основе существующей библиотеки, которая решит эти проблемы за вас. В этом уроке мы собираемся использовать Python и Scrapy для создания нашего парсера.

Scrapy — одна из самых популярных и мощных библиотек парсинга Python; он использует подход «включенных батарей» к парсингу, а это означает, что он обрабатывает множество общих функций, которые нужны всем парсерам, поэтому разработчикам не нужно каждый раз изобретать велосипед.

Scrapy, как и большинство пакетов Python, использует PyPI (также известный как pip). PyPI, индекс пакетов Python, представляет собой репозиторий всего опубликованного программного обеспечения Python, принадлежащий сообществу.

Если у вас есть установка Python, подобная той, что описана в предварительных требованиях к этому руководству, на вашем компьютере уже установлен pip, поэтому вы можете установить Scrapy с помощью следующей команды:

  1. pip install scrapy

Если у вас возникнут проблемы с установкой или вы хотите установить Scrapy без использования pip, ознакомьтесь с официальной документацией по установке.

Установив Scrapy, создайте новую папку для нашего проекта. Вы можете сделать это в терминале, запустив:

  1. mkdir quote-scraper

Теперь перейдите в новый каталог, который вы только что создали:

  1. cd quote-scraper

Затем создайте новый файл Python для нашего парсера с именем scraper.py. Мы поместим весь наш код в этот файл для этого урока. Вы можете создать этот файл, используя программное обеспечение для редактирования по вашему выбору.

Начните проект с создания очень простого парсера, который использует Scrapy в качестве основы. Для этого вам нужно создать класс Python, который является подклассом scrapy.Spider, базового класса паука, предоставляемого Scrapy. Этот класс будет иметь два обязательных атрибута:

  • name — просто имя паука.
  • start_urls — список URL-адресов, с которых вы начинаете сканирование. Начнем с одного URL.

Откройте файл scrapy.py в текстовом редакторе и добавьте этот код, чтобы создать базового паука:

import scrapy


class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

Разберем это построчно:

Во-первых, мы импортируем scrapy, чтобы использовать классы, предоставляемые пакетом.

Затем мы берем класс Spider, предоставленный Scrapy, и делаем из него подкласс с именем BrickSetSpider. Думайте о подклассе как о более специализированной форме его родительского класса. Класс Spider имеет методы и поведение, которые определяют, как переходить по URL-адресам и извлекать данные из найденных страниц, но он не знает, где искать или какие данные искать. Создав подкласс, мы можем дать ему эту информацию.

Наконец, мы назовем класс quote-spider и дадим нашему парсеру единственный URL-адрес, с которого нужно начинать: https://quotes.toscrape.com. Если вы откроете этот URL-адрес в своем браузере, вы попадете на страницу результатов поиска, показывающую первую из многих страниц известных цитат.

Теперь проверьте скребок. Обычно файлы Python запускаются с помощью такой команды, как python path/to/file.py. Однако Scrapy поставляется с собственным интерфейсом командной строки для упрощения процесса запуска парсера. Запустите парсер с помощью следующей команды:

  1. scrapy runspider scraper.py

Команда выведет что-то вроде этого:

Output
2022-12-02 10:30:08 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor 2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet Password: b4d94e3a8d22ede1 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled extensions: ['scrapy.extensions.corestats.CoreStats', ... 'scrapy.extensions.logstats.LogStats'] 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled downloader middlewares: ['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware', ... 'scrapy.downloadermiddlewares.stats.DownloaderStats'] 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled spider middlewares: ['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware', ... 'scrapy.spidermiddlewares.depth.DepthMiddleware'] 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled item pipelines: [] 2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider opened 2022-12-02 10:30:08 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min) 2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023 2022-12-02 10:49:32 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com> (referer: None) 2022-12-02 10:30:08 [scrapy.core.engine] INFO: Closing spider (finished) 2022-12-02 10:30:08 [scrapy.statscollectors] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 226, ... 'start_time': datetime.datetime(2022, 12, 2, 18, 30, 8, 492403)} 2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider closed (finished)

Результата много, так что давайте разберем его.

  • Парсер инициализировал и загрузил дополнительные компоненты и расширения, необходимые для обработки данных чтения с URL-адресов.
  • Он использовал URL-адрес, который мы указали в списке start_urls, и захватил HTML-код, как это сделал бы ваш веб-браузер.
  • Он передал этот HTML-код методу parse, который по умолчанию ничего не делает. Поскольку мы никогда не писали собственный метод parse, паук просто завершает работу, не выполняя никакой работы.

Теперь давайте вытащим некоторые данные со страницы.

Шаг 2 — Извлечение данных со страницы

Мы создали очень простую программу, которая извлекает страницу, но еще не выполняет парсинг или сканирование. Давайте дадим ему некоторые данные для извлечения.

Если вы посмотрите на страницу, которую мы хотим очистить, вы увидите, что она имеет следующую структуру:

  • На каждой странице есть заголовок.
  • Ссылка для входа.
  • Далее сами котировки отображаются в виде таблицы или упорядоченного списка. Каждая цитата имеет аналогичный формат.

При написании парсера вам нужно будет посмотреть исходник HTML-файла и ознакомиться со структурой. Итак, вот он, с удаленными тегами, которые не имеют отношения к нашей цели для удобства чтения:

quotes.toscrape.com
<body> ... <div class="quote" itemscope itemtype="http://schema.org/CreativeWork"> <span class="text" itemprop="text">“I have not failed. I&#39;ve just found 10,000 ways that won&#39;t work.”</span> <span>by <small class="author" itemprop="author">Thomas A. Edison <a href="/author/Thomas-A-Edison">(about)</a> </span> <div class="tags"> Tags: <meta class="keywords" itemprop="keywords" content="edison,failure,inspirational,paraphrased" / > <a class="tag" href="/tag/edison/page/1/">edison</a> <a class="tag" href="/tag/failure/page/1/">failure</a> <a class="tag" href="/tag/inspirational/page/1/">inspirational</a> <a class="tag" href="/tag/paraphrased/page/1/">paraphrased</a> </div> </div> ... </body>

Очистка этой страницы — это двухэтапный процесс:

  1. Сначала извлеките каждую цитату, выполнив поиск частей страницы, содержащих нужные нам данные.
  2. Затем для каждой цитаты извлеките из нее нужные данные, извлекая данные из тегов HTML.

scrapy собирает данные на основе предоставленных вами селекторов. Селекторы — это шаблоны, которые мы можем использовать для поиска одного или нескольких элементов на странице, чтобы затем работать с данными внутри элемента. scrapy поддерживает либо селекторы CSS, либо селекторы XPath.

Сейчас мы будем использовать селекторы CSS, так как CSS идеально подходит для поиска всех наборов на странице. Если вы посмотрите на HTML, вы увидите, что каждая цитата указана с классом quote. Поскольку мы ищем класс, мы будем использовать .quote в качестве нашего селектора CSS. Часть . селектора ищет атрибут class в элементах. Все, что нам нужно сделать, это создать в нашем классе новый метод с именем parse и передать этот селектор в объект response, например:

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        
        for quote in response.css(QUOTE_SELECTOR):
            pass

Этот код захватывает все наборы на странице и перебирает их, чтобы извлечь данные. Теперь давайте извлечем данные из этих котировок, чтобы мы могли их отобразить.

Еще один взгляд на исходный код страницы, которую мы анализируем, показывает, что текст каждой цитаты хранится в span с классом text и автором цитаты в тег <small> с классом author:

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

Объект quote, который мы зацикливаем, имеет собственный метод css, поэтому мы можем передать селектор для поиска дочерних элементов. Измените свой код следующим образом, чтобы найти имя набора и отобразить его:

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        
        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
            }

Примечание. Запятая после extract_first() не является опечаткой. В Python конечная запятая в объектах dict является допустимым синтаксисом и хорошим способом оставить место для добавления дополнительных элементов, которые мы рассмотрим здесь позже.

Вы заметите, что в этом коде происходят две вещи:

  • Мы добавляем ::text к нашим селекторам цитаты и автора. Это псевдоселектор CSS, который извлекает текст внутри тега, а не сам тег.
  • Мы вызываем extract_first() для объекта, возвращаемого quote.css(TEXT_SELECTOR), потому что нам просто нужен первый элемент, соответствующий селектору. Это дает нам строку, а не список элементов.

Сохраните файл и снова запустите парсер:

  1. scrapy runspider scraper.py

На этот раз вывод будет содержать цитаты и их авторов:

Output
... 2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'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'} 2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen'} 2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe'} ...

Продолжаем расширять это, добавляя новые селекторы для ссылок на страницы об авторе и теги для цитаты. Исследуя HTML для каждой цитаты, мы находим:

  • Ссылка на страницу об авторе автора хранится в ссылке, следующей сразу за его именем.
  • Теги хранятся в виде набора тегов a, каждый из которых имеет класс tag, хранящихся в элементе div с тегами класс.

Итак, давайте изменим парсер, чтобы получить эту новую информацию:

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        ABOUT_SELECTOR = '.author + a::attr("href")'
        TAGS_SELECTOR = '.tags > .tag::text'

        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
                'about': 'https://quotes.toscrape.com' + 
                        quote.css(ABOUT_SELECTOR).extract_first(),
                'tags': quote.css(TAGS_SELECTOR).extract(),
            }

Сохраните изменения и снова запустите парсер:

  1. scrapy runspider scraper.py

Теперь вывод будет содержать новые данные:

Output
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'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']} 2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'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']} 2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'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 — Сканирование нескольких страниц

Вы успешно извлекли данные с этой начальной страницы, но мы не продвигаемся дальше, чтобы увидеть остальные результаты. Весь смысл паука заключается в том, чтобы обнаруживать и перемещаться по ссылкам на другие страницы, а также собирать данные с этих страниц.

Вы заметите, что вверху и внизу каждой страницы есть маленький значок справа (>), который ссылается на следующую страницу результатов. Вот HTML для этого:

quotes.toscrape.com
... <nav> <ul class="pager"> <li class="next"> <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a> </li> </ul> </nav> ...

В исходном коде вы найдете тег li с классом next, а внутри этого тега есть тег a со ссылкой на следующая страница. Все, что нам нужно сделать, это указать парсеру перейти по этой ссылке, если она существует.

Измените код следующим образом:

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        ABOUT_SELECTOR = '.author + a::attr("href")'
        TAGS_SELECTOR = '.tags > .tag::text'
        NEXT_SELECTOR = '.next a::attr("href")'

        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
                'about': 'https://quotes.toscrape.com' + 
                        quote.css(ABOUT_SELECTOR).extract_first(),
                'tags': quote.css(TAGS_SELECTOR).extract(),
            }

        next_page = response.css(NEXT_SELECTOR).extract_first()
        if next_page:
            yield scrapy.Request(response.urljoin(next_page))

Во-первых, мы определяем селектор для ссылки «следующая страница», извлекаем первое совпадение и проверяем, существует ли оно. scrapy.Request — это новый объект запроса, который, как известно Scrapy, означает, что он должен получить и далее разбирать.

Это значит, что как только мы перейдем на следующую страницу, мы будем искать там ссылку на следующую страницу, а на этой странице будем искать ссылку на следующую страницу, и так далее, пока не найдем ссылка на следующую страницу. Это ключевой элемент парсинга веб-страниц: поиск и переход по ссылкам. В этом примере это очень линейно; одна страница имеет ссылку на следующую страницу, пока мы не перейдем к последней странице, но вы можете перейти по ссылкам на теги или другие результаты поиска или любой другой URL-адрес, который вы хотите.

Теперь, если вы сохраните свой код и снова запустите паука, вы увидите, что он не просто останавливается после того, как перебирает первую страницу наборов. Он продолжает перебирать все 100 цитат на всех 10 страницах. По большому счету, это небольшой объем данных, но теперь вы знаете, как автоматически находить новые страницы для парсинга.

Вот наш завершенный код для этого урока:

import scrapy


class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        ABOUT_SELECTOR = '.author + a::attr("href")'
        TAGS_SELECTOR = '.tags > .tag::text'
        NEXT_SELECTOR = '.next a::attr("href")'

        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
                'about': 'https://quotes.toscrape.com' + 
                        quote.css(ABOUT_SELECTOR).extract_first(),
                'tags': quote.css(TAGS_SELECTOR).extract(),
            }

        next_page = response.css(NEXT_SELECTOR).extract_first()
        if next_page:
            yield scrapy.Request(
                response.urljoin(next_page),
            )

Заключение

В этом руководстве вы создали полнофункциональный паук, который извлекает данные с веб-страниц менее чем за тридцать строк кода. Это отличное начало, но есть много забавных вещей, которые вы можете сделать с этим пауком. Этого должно быть достаточно, чтобы заставить вас думать и экспериментировать. Если вам нужна дополнительная информация о Scrapy, ознакомьтесь с разделом «Как очищать веб-страницы с помощью Beautiful Soup и Python 3».