IT-инфраструктура для бизнеса и творчества
Разработка
NTA

Пауки 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.

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

{ "author_name": "NTA", "author_type": "editor", "tags": ["\u043f\u0430\u0440\u0441\u0438\u043d\u0433","scrapy"], "comments": 2, "likes": 5, "favorites": 28, "is_advertisement": false, "subsite_label": "dev", "id": 138132, "is_wide": true, "is_ugc": false, "date": "Tue, 30 Jun 2020 07:09:25 +0300", "is_special": false }
(function () { let cdnUrl = `https://specialsf378ef5-a.akamaihd.net/SelectelBranding/images/` let previousArticleNumber = null let currentArticleNumber = 0 let platform = 'Desktop' let articles = [ // { // name: 'camera', // url: `${cdnUrl}CameraCat`, // text: 'умную камеру для\u00A0наблюдения за\u00A0котиками', // link: '1', // }, { name: 'chill', url: `${cdnUrl}ChillCat`, text: 'трекер, который подскажет, когда пора отдохнуть', link: 'https://vc.ru/promo/288561-eye-tracker', }, // { // name: 'cloud', // url: `${cdnUrl}CloudCat`, // text: 'котика: даёшь ему «пять», а\u00A0он делает бэкап в облако', // link: '3', // } ] let buttonCycle = document.querySelector('.button--cycle') let textField = document.querySelector('.selectel-footer-subtitle') let imageAgent = document.querySelector('.image--agent') let banner = document.querySelector('.selectel-footer') buttonCycle.addEventListener('click', cycleClick) let media = window.matchMedia("(max-width: 570px)") media.addEventListener('change', matchMedia) function matchMedia() { if (media.matches) { platform = 'Mobile' } else { platform = 'Desktop' } update() } matchMedia() function cycleClick(event) { if (event) { event.preventDefault() event.stopPropagation() } window.open('https://vc.ru/tag/selectelDIY', '_blank') //cycle(event) } function cycle(event) { // incrementArticleNumber() textField.innerHTML = generatedText() imageAgent.src = articles[currentArticleNumber].url + platform + '.svg?5' imageAgent.setAttribute("class", "") imageAgent.classList.add('image--agent', articles[currentArticleNumber].name) banner.href = articles[currentArticleNumber].link } function update() { banner.href = articles[currentArticleNumber].link imageAgent.src = articles[currentArticleNumber].url + platform + '.svg?5' textField.innerHTML = generatedText() } function incrementArticleNumber() { previousArticleNumber = currentArticleNumber if (currentArticleNumber >= articles.length - 1) { currentArticleNumber = 0 } else { currentArticleNumber++ } } function generatedText() { let defaultText if (platform === 'Desktop') { defaultText = `Мы тут собрали %text%. Хотите почитать?` } else { defaultText = `Мы тут собрали %text%.` } return defaultText.replace('%text%', articles[currentArticleNumber].text) } function getRandom(min, max) { min = Math.ceil(min) max = Math.floor(max) return Math.floor(Math.random() * (max - min + 1)) + min } (function create() { currentArticleNumber = getRandom(0, articles.length - 1) cycle() let page = document.querySelector('.page--entry') if (page) { function insertAfter() { let parents = page.querySelectorAll('[data-id="7"]') let referenceNode = parents[0] referenceNode.parentNode.insertBefore(banner, referenceNode.nextSibling); loaded() } setTimeout(() => insertAfter(), 0) } }()) function loaded() { banner.classList.add('loaded') } loadImages([ `${cdnUrl}CameraCatDesktop.svg`, `${cdnUrl}ChillCatDesktop.svg`, `${cdnUrl}CloudCatDesktop.svg`, `${cdnUrl}CameraCatMobile.svg`, `${cdnUrl}ChillCatMobile.svg`, `${cdnUrl}CloudCatMobile.svg`, ]) function loadImages(urls) { return Promise.all(urls.map(function (url) { return new Promise(function (resolve) { var img = document.createElement('img'); img.onload = resolve; img.onerror = resolve; img.src = url; }); })); } }())
0
2 комментария
Популярные
По порядку

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

1

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

0
Читать все 2 комментария
Готовы выбрать победителя премии «Экспортер года eBay — 2021»?
Кейс: как мы арендовали гостиницу на юге и ничего не заработали

На днях я поговорила с предпринимателями, которые в 2018 году арендовали гостевой дом в Геленджике, и выяснила, почему нужно чётко фиксировать все принципиальные вопросы в договоре заранее и что важно просчитать на старте, чтобы бизнес был прибыльным.

Набережная Геленджика  Фото является объектом авторского права
Управление репутацией на Otzovik.Как убить негативный рейтинг/удалить негативные отзывы и выдвинуть хорошие отзывы вверх
Нужны ли в России сити-фермы

И появятся ли грядки на крышах пятиэтажек.

re-thinkingthefuture.com
Как мы проводили командную ретроспективу в Minecraft

Рассказываем историю, на что стоит обратить внимание при проведении командного мероприятия в игре Minecraft, какие грабли могут быть, как организовать онлайн- и офлайн-участие.

Как традиционному малому бизнесу превратиться в стартап: план действий

Сейчас в России предприниматели переходят из традиционного малого бизнеса в стартапы очень редко — меньше чем в 0,02% случаев. Это не больше 1 000 стартапов из около 6 млн предприятий малого бизнеса. Поговорим о том, что мешает предпринимателям и как действовать, если есть желание создать стартап.

Нью-Йорк первым из крупных городов США принял указы об условиях труда курьеров: разрешил отказываться от заказа и другое Статьи редакции

Сервисы должны будут сообщать, куда везти заказ, до его принятия и платить не реже раза в неделю.

«Любая клавиатура может быть эргономичной — просто купите вторую» Статьи редакции

Программист Disney Джефф Пэрриш несколько лет страдал от боли в спине при наборе текста— смена клавиатуры и упражнения не помогали. Решение оказалось изящнее.

Эксперты Httpool выступят на конференции по глобальному маркетингу Globalize! 2021

Событие соберет самых продвинутых специалистов в сфере трафика и аналитики из Google, Httpool, AppsFlyer, Aitarget, Angle Connect, TikTok, OWOX, SHAREit, Tribuna, SportQuake, LCFC, FBS.

Сергей Галицкий о деньгах: «В гробу карманов нет. Два завтрака съесть не сможешь» Статьи редакции

В сентябре бизнесмен сообщил о своей болезни, которая мешает ему работать.

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

Ты не можешь употребить больше двух-трёх тысяч калорий в день. Тебе не нужно столько. Не нужно двадцать машин, тридцать самолетов.

Сергей Галицкий
цитата по Sports.ru
В интернете появилась база клиентов «Совкомбанка», которые подавали заявки на кредит — банк подтвердил утечку Статьи редакции

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

null