Привет, Хабр! Сегодня мы поговорим о хорошем инструменте для веб-скрапинга, который зарекомендовал себя, библиотеке 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. Записаться