Привет, Хабр! Сегодня мы поговорим о хорошем инструменте для веб-скрапинга, который зарекомендовал себя, библиотеке Scrapy для Python.

Установим:

pip install Scrapy

Начнем

Scrapy использует спец. классы, называемые пауками, в коде - spiders, для сбора информации с веб-страниц:

import scrapy

class ExampleSpider(scrapy.Spider):
    name = 'otus'
    allowed_domains = ['example.com']
    start_urls = ['http://example.com/']

    def parse(self, response):
        title = response.css('title::text').get()
        yield {'title': title}
  • name — имя паучка.

  • allowed_domains — список доменов, с которых паук может собирать данные.

  • start_urls — список URL-адресов, с которых паук начинает сбор данных.

  • parse вызывается для каждого ответа, который паук получает в ответ на запросы URL из start_urls.

Запуск паука:

scrapy crawl myspider

Для работы с пагинацией можно использовать несколько подходов. Один из них - это прямое следование по ссылкам на следующие страницы. Можно реализовать, извлекая URL след. страницы и передавая его в метод follow Scrapy. Например:

def parse(self, response):
    # обработка данных страницы
    ...
    next_page = response.css("li.next a::attr(href)").get()
    if next_page is not None:
        yield response.follow(next_page, self.parse)

Для автоматизации входа на сайты можно использовать формы входа, автоматом заполняя и отправляя данные форм. Scrapy поддерживает отправку форм с помощью класса FormRequest:

from scrapy.http import FormRequest

def start_requests(self):
    return [FormRequest("http://example.com/login", formdata={'user': 'john', 'pass': 'secret'}, callback=self.logged_in)]

def logged_in(self, response):
    # продолжение скрапинга после входа
    pass

Для страниц, где контент загружается с помощью JS, можно использовать Splash или Selenium, которые позволяют исполнять JS перед извлечением данных. Например, тот же Splash интегрируется с Scrapy через scrapy-splash, позволяя выполнять JavaScript и получать актуальный HTML. Пример:

from scrapy_splash import SplashRequest

def start_requests(self):
    yield SplashRequest(url='http://example.com', callback=self.parse, endpoint='render.html', args={'wait': 0.5})

def parse(self, response):
    # извлечение данных
    pass

Scrapy поддерживает экспорт данных в форматы JSON, CSV и XML с помощью встроенных экспортеров. Экспорт данных может быть настроен через settings.py проекта с помощью параметра FEEDS, который позволяет указать различные настройки экспорта для каждого типа файла:

# настройка экспорта данных в JSON и CSV
FEEDS = {
    'items.json': {
        'format': 'json',
        'encoding': 'utf8',
        'store_empty': False,
        'fields': None,
        'indent': 4,
    },
    'items.csv': {
        'format': 'csv',
        'fields': ['field1', 'field2', 'field3'],  # спецификация полей для экспорта
    }
}

Item Pipelines в Scrapy позволяют проводить послепроцессинг собранных данных, включая их валидацию, очистку и сохранение в БД. Например, для сохранения данных в БД MySQL или PostgreSQL, можно настроить pipeline, которая будет обрабатывать каждый собранный элемент:

import psycopg2

class PostgresPipeline(object):
    def open_spider(self, spider):
        hostname = 'localhost'
        username = 'postgres'
        password = 'yourpassword'
        database = 'scrapy_db'
        self.connection = psycopg2.connect(host=hostname, user=username, password=password, dbname=database)
        self.cur = self.connection.cursor()

    def process_item(self, item, spider):
        self.cur.execute("INSERT INTO table_name (field1, field2) VALUES (%s, %s)", 
                         (item['field1'], item['field2']))
        self.connection.commit()
        return item

    def close_spider(self, spider):
        self.cur.close()
        self.connection.close()

Этот pipeline устанавливает соединение с БД при открытии паука, обрабатывает каждый элемент при его сборе, вставляя данные в таблицу, и закрывает соединение после завершения работы паука.

Пример использования

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

Создадим новый проект Scrapy:

scrapy startproject books_scraper

Переходим в директорию проекта:

cd books_scraper

В директории spiders создадим нового паука book_spider.py:

import scrapy
from scrapy_splash import SplashRequest
from scrapy.http import FormRequest
from books_scraper.items import BookItem  # нужен класс Item в items.py

class BookSpider(scrapy.Spider):
    name = 'book_spider'
    login_url = 'http://books.example.com/login'
    start_urls = ['http://books.example.com']

    def start_requests(self):
        #переход на страницу входа
        yield scrapy.Request(self.login_url, self.parse_login)

    def parse_login(self, response):
        # отправка данных для входа
        yield FormRequest.from_response(response,
                                        formdata={'username': 'user', 'password': 'pass'},
                                        callback=self.after_login)

    def after_login(self, response):
        # проверка успешного входа и продолжение скрапинга
        if "authentication failed" in response.body:
            self.logger.error("Login failed")
            return
        else:
            # запуск скрапинга с использованием Splash для обработки JavaScript
            for url in self.start_urls:
                yield SplashRequest(url, self.parse, endpoint='execute', args={'lua_source': self.lua_script()})

    def parse(self, response):
        # обработка данных страницы
        books = response.css('div.book')
        for book in books:
            item = BookItem()
            item['title'] = book.css('h3.title::text').get()
            item['price'] = book.css('p.price::text').get()
            yield item

        # пагинация
        next_page = response.css('li.next a::attr(href)').get()
        if next_page:
            yield SplashRequest(next_page, self.parse, endpoint='execute', args={'lua_source': self.lua_script()})

    def lua_script(self):
        # Lua скрипт для Splash, скроллинг страницы или другие действия
        return """
        function main(splash)
            splash:set_user_agent(splash.args.ua)
            assert(splash:go(splash.args.url))
            assert(splash:wait(1))
            return {html = splash:html()}
        end
        """

В файле items.py создадим структуру данных для книг:

import scrapy

class BookItem(scrapy.Item):
    title = scrapy.Field()
    price = scrapy.Field()

В pipelines.py добавим код для сохранения данных в PostgreSQL:

import psycopg2

class PostgresPipeline(object):
    def open_spider(self, spider):
        self.connection = psycopg2.connect(host='localhost', dbname='scrapy_books', user='postgres', password='password')
        self.cur = self.connection.cursor()

    def close_spider(self, spider):
        self.connection.close()

    def process_item(self, item, spider):
        self.cur.execute("INSERT INTO books (title, price) VALUES (%s, %s)", (item['title'], item['price']))
        self.connection.commit()
        return item

Активируем PostgresPipeline в settings.py и настраивем Splash в случае работы с JS:

ITEM_PIPELINES = {
   'books_scraper.pipelines.PostgresPipeline': 300,
}

SPLASH_URL = 'http://localhost:8050'

Запускаем:

scrapy crawl book_spider

Подробней с Scrapy можно ознакомиться в документации.

В заключение расскажу об открытых уроках:

  • 13 мая: Генераторы и очереди в Python: рассмотрим концепции генераторных функций и очередей, поговорим о сходствах, различиях, и применимости в решении реальных задач. Записаться

  • 28 мая: Самые эффективные однострочники в Python: поделимся секретами и мастерством создания мощных однострочников в Python. Записаться

Комментарии (0)