Пауки Scrapy

Рассмотрим парсинг сайтов с использованием Scrapy. Применение такой технологии позволит, например, эффективно находить негативные отзывы клиентов.

Анализ и работа с зонами роста как в уровне сервиса, так и в качестве продуктов повышает лояльность клиентов, а значит, прибыль компании. Вот только как узнать, что именно требует модернизации? Клиенты редко оставляют жалобы напрямую.

Однако в современном мире существуют форумы, сайты, социальные сети. Это действительно огромное количество информации, причем в открытом доступе. Но ее сбор и систематизация требует гигантских временных затрат и ресурсов.

Предлагаем рассмотреть хороший инструмент для автоматизированного сбора негативных отзывов клиентов.

Scrapy — мощный фреймворк, основным принципом которого является гибкая настройка парсеров (пауков, crawlers). Главными особенностями данного фреймворка являются:

  • Запуск всех парсеров в разных процессах (по умолчанию = 16).
  • Автоматическое отслеживание нагрузки на сервер сайта — если в процессе парсинга увеличивается временной интервал между запросом и ответом, то Scrapy для данного паука увеличивает задержку между запросами (delay) до тех пор, пока сервер не будет разгружен и не будет отвечать с ожидаемой задержкой (выставляется в настройках, по умолчанию — 3 секунды).
  • Отслеживание переходов по ссылкам для предотвращения повторных запросов.
  • Удобная структура проекта выделяет слои middleware, pipeline и spider, позволяя гибко добавлять парсеры для различных сайтов и не нарушать принципы DRY.
  • Легко встраивается в такие популярные фреймворки как – Flask, Django. Также разработан специальный фреймворк, выступающий в роли daemon по циклическому запуску парсеров с множеством настроек расписаний – Scrapyd.

Структура проекта выглядит следующим образом:

project/ spiders/ __init__.py spider.py __init__.py items.py middlewares.py pipelines.py settings.py

Перед вами пример парсера, осуществляющий разбор страницы для поиска и сохранения в базе данных информации о сообщении:

1. from project.items import Post 2. from project.common.regexp_template import RegExp 3. from project.settings import SPIDER_URLS 4. from datetime import datetime 5. import scrapy 6. import re 7. 8. 9. class BankiruSpider(scrapy.Spider): 10. # Обязательные переменных класса 11. # Имя парсера, должно быть уникальным по всему проекту 12. name = "bankiru" 13. 14. # Список URL с которых будет начинаться парсинг 15. start_urls = SPIDER_URLS[name] 16. 17. def __init__(self, *args, **kwargs): 18. super().__init__(*args, **kwargs) 19. self.limit_date = kwargs.get("limit_date", None) 20. self.completed = False 21. 22. # Основной метод парсера запускающий разбор страниц 23. def parse(self, response): 24. # Получаем все теги постов (сообщений) на текущей странице сайта 25. main = response.css("main.layout-column-center") 26. posts = main.css("table.resptab") 27. 28. for post in posts: 29. post_link = re.sub(r"#.+$", "", post.css("a.linkNote::attr(href)").get()) 30. # Выполнение парсинга поста в методе self.parse_post 31. yield response.follow(f"{post_link}/", self.parse_post) 32. # Условие для остановки парсинга - достижение поста с определенной датой 33. if self.completed: 34. break 35. 36. # Поиск и формирование ссылки для вледующей страницы сайта 37. current_page = response.url.split("/")[-1] 38. lst_url = current_page.split("=") 39. if len(lst_url) > 2: 40. next_page = f"{lst_url[0]}={lst_url[1]}={int(lst_url[2]) + 1}" 41. else: 42. next_page = f"{current_page}&PAGEN_1=2" 43. 44. # Выполняем парсинг следующей страницы или останавливаем парсинг - возврат пустого словаря 45. yield dict() if self.completed else response.follow(next_page, callback=self.parse) 46. 47. # Метод для парсинга поста 48. def parse_post(self, response): 49. # Флаг остановки парсинга 50. out_of_limit = False 51. post_url = response.url 52. post_id = int(post_url.split("/")[-2]) 53. 54. # Контейнеры 55. post = response.css("table.resptab") 56. author = response.css("table.resptab td.footerline a") 57. 58. # Интересующие нас данные 59. title = RegExp.space.sub(" ", post.css("td.headerline::text").get().strip()) 60. msg = RegExp.space.sub(" ", RegExp.tag.sub(" ", post.css("td.article-text").get() or "").strip()) 61. author_uid = author.attrib["href"].split("=")[-1] 62. author_login = author.css("::text").get() 63. dt = datetime.strptime(response.css("span.color-grey::text").get(), "%d.%m.%Y %H:%M") 64. 65. # Проверка на достижение поста с условной датой 66. if self.limit_date and dt < self.limit_date: 67. self.completed = True 68. out_of_limit = True 69. 70. yield Post() if out_of_limit else Post( 71. post_url=post_url, 72. post_id=post_id, 73. title=title, 74. msg=msg, 75. author_uid=author_uid, 76. author_login=author_login, 77. datetime=dt 78. )

Работа парсера начинается с вызова метода «parse», с передачей единственного параметра response, который содержит структурированный экземпляр страницы.

В процессе разбора страницы можно выделить два основных метода:

  • response.css(<tag>) — выполняет поиск указанного тега или последовательности вложенности тегов с параметрами.
  • response.follow(,) — выполняет загрузку следующей страницы, разобранную структуру которого передает в качестве параметра функции «parse»..

Данный рекурсивный процесс выполняется до тех пор, пока функция «parse» не вернет пустую структуру данных (или пустой словарь). Для парсеров, запускаемых на регулярной основе необходимо предусмотреть условие прерывания.

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

1. from scrapy.utils.project import get_project_settings 2. from scrapy.crawler import CrawlerProcess 3. from project.spiders.bankiru import BankiruSpider 4. 5. 6. limit_date = datetime.strptime("29.05.2020", "%d.%m.%Y") 7. process = CrawlerProcess(get_project_settings()) 8. process.crawl(BankiruSpider, limit_date=limit_date) 9. process.start()

Код сохранения полученных данных со страниц следует выносить в pipeline.py, а именно в класс ParserPipeline(object).

Каждый вызов «yield response.follow(f»{post_link}/», self.parse_post)» парсера передает результаты обратным вызовом функции ParserPipeline.process_item, внутри которой следует описывать работу с сохранением данных.

Подключение к БД следует описывать в ParserPipeline.from_crawler, который вызывается при создании экземпляра класса фреймворком Scrapy.

1. from project.common.db_manager import DB, DBParams 2. 3. 4. class NewsParserPipeline(object): 5. def __init__(self, params: DBParams): 6. self._params = params 7. self.db = None 8. 9. @classmethod 10. def from_crawler(cls, crawler): 11. db_params = crawler.settings.getdict("DB_PARAMS") 12. return cls(DBParams( 13. host=db_params["host"], 14. port=db_params["port"], 15. name=db_params["name"], 16. user=db_params["user"], 17. password=db_params["password"] 18. )) 19. 20. def open_spider(self, spider): 21. self.db = DB(self._params) 22. self._source_id = int(self._get_source_id(spider.name)) 23. self._post_types = self._get_types_id() 24. spider.set_last_post_id(self._get_last_post_id()) 25. 26. def close_spider(self, spider): 27. self.db.disconnect() 28. 29. def process_item(self, item, spider): 30. self.db.insert.posts({ 31. "p_post_url": item.post_url, 32. "p_post_id": item.post_id, 33. "p_title": item.title, 34. "p_msg": item.msg, 35. "p_author_uid": item.author_uid, 36. "p_datetime": item.datetime 37. }) 38. self.db.commit()

Отметим также, что если использовать Docker-контейнер, то полученную систему можно быстро и легко развернуть на любой машине.

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

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

0
2 комментария
Сергей Тюрин

Познавательно. Спасибо

Ответить
Развернуть ветку
Vlad Samusenko

а не проще программным роботом парсить? Или это другая тема? https://vc.ru/marketing/149696-parsing-eto-polezno-i-ne-stydno-osobenno-esli-dannye-sobiraet-robot-rpa-a-otchety-pokazyvaet-bi

Ответить
Развернуть ветку
-1 комментариев
Раскрывать всегда