Парсим любой сайт за считанные секунды. Как достать нужную информацию с сайта используя Selenium, XPath и Proxy Sever
Дарова, Хабр! Около года назад я решил заработать на ставках на спорт используя свои знания математики и программирования и тогда я наткнулся на небольшую проблему — как же достать нужную мне информацию с сайта? Как парсить веб-страницы? В этой статье я расскажу простыми словами каким тонкостям я научился.
Парсинг
Что ж такое парсинг? Это собирание и систематизирование информации, которая размещена на веб-сайтах с помощью специальных программ, автоматизирующих процесс.
Парсинг обычно используется для анализа ценовой политики и получения контента.
Начало
Чтобы забрать деньги у букмекеров мне надо было оперативно получать информацию про коэффициенты на определённые события с нескольких сайтов. В математическую часть вдаваться не будем.
Поскольку я изучал С# у себя в шараге, я решил всё писать на нём. Ребята со Stack Overflow посоветовали использовать Selenium WebDriver. Это драйвер браузера(программная библиотека), который позволяет разрабатывать программы, управляющие поведением браузера. То что надо, подумал я.
Установил библиотеку и побежал смотреть гайды в интернете. Спустя некоторое время я написал программу, которая могла открыть браузер и переходить по некоторым ссылкам.
Ура! Хотя стоп, а как на кнопки нажимать, как доставать нужную информацию? Тут нам поможет XPath.
XPath
Если простыми словами, то это язык запросов к элементам XML и XHTML документа.
В этой статье я буду использовать Google Chrome. Однако в других современных браузерах должен быть если не такой же, то очень похожий интерфейс.
Чтобы посмотреть код страницы, на которой вы находитесь надо нажать F12.

Чтобы посмотреть, в каком месте кода находиться элемент на странице(текст, картинка, кнопка) надо нажать на стрелочку в левом верхнем углу и выбрать данный элемент на странице. Теперь перейдем к синтаксису.
Стандартный синтаксис для написания XPath:
//tagname[@attribute=’value’]
//: Выбирает все узлы в html документе начиная от текущего узла
Tagname: Тег текущего узла.
@: Выбирает атрибуты
Attribute: Имя атрибута узла.
Value: Значение атрибута.
Может быть поначалу непонятно, но после примеров всё должно встать на свои места.
Рассмотри несколько простых примеров:
Рассмотрим более сложные примеры для заданного html’я:
habr cool site "text2" habrhabr the same site "text1"
XPath = //div[@class= ‘contentBlock’]//div
Для этого XPath’а будут выбраны следующие элементы:
XPath = //div[@class= ‘contentBlock’]/div
Обратите внимание на разницу между /(выбирает от корневого узла) и //(выбирает узлы от текущего узла независимо от их местонахождения). Если непонятно, то посмотрите на примеры выше ещё раз.
//div[@class= ‘contentBlock’]/div[@class= ‘listItem’]/a[@class= ‘link’]/span[@class= ‘name’]
Этот запрос равносилен этим при таком html’е:
//a[@class= ‘link’]/span[@class= ‘name’]
//a[@class=’link’ and href= ‘habr.com’]/span
//span[text() = ‘habr’ or text() = ‘habrhabr’]
//div[@class= ‘listItem’]//span[@class= ‘name’]
habr habrhabr
parent:: — возвращает предка на один уровень выше.
Есть ещё супер крутая фича, такая как following-sibling:: — возвращает множество элементов на том же уровне, следующих за текущим, аналогично preceding-sibling:: — возвращает множество элементов на том же уровне, предшествующих текущему.
"text1" "text2"
Думаю, теперь стало понятнее. Для закрепления материала советую зайти на этот сайт и написать несколько запросов, чтобы найти некоторые элементы этого html’я.
Pickup and delivery Walmart.com Savings spotlight Walmart.com(Italian) "italian virsion"
Теперь зная, что такое XPath, вернемся к написанию кода. Поскольку модераторам хабра не нравятся букмекерские конторы, то будет парсить цены на кофе в Walmart’е
string pathToFile = AppDomain.CurrentDomain.BaseDirectory + '\\'; IWebDriver driver = new ChromeDriver(pathToFile); driver.Navigate().GoToUrl("https://walmart.com"); Thread.Sleep(5000); IWebElement element = driver.FindElement(By.XPath("//button[@id='header-Header sparkButton']")); element.Click(); Thread.Sleep(2000); element = driver.FindElement(By.XPath("//button[@data-tl-id='GlobalHeaderDepartmentsMenu-deptButtonFlyout-10']")); element.Click(); Thread.Sleep(2000); element = driver.FindElement(By.XPath("//div[text()='Coffee']/parent::a")); driver.Navigate().GoToUrl(element.GetAttribute("href")); Thread.Sleep(10000); List names = new List(), prices = new List(); List listOfElements =driver.FindElements(By.XPath("//div[@class='tile-content']/div[@class='tile-title']/div")).ToList(); foreach (IWebElement a in listOfElements) names.Add(a.Text); listOfElements = driver.FindElements(By.XPath("//div[@class='tile-content']/div[@class='tile-price']/span/span[contains(text(),'$')]")).ToList(); foreach (IWebElement a in listOfElements) prices.Add(a.Text); for (int i = 0; i < prices.Count; i++) Console.WriteLine(names[i] + " " + prices[i]);
Thread.Sleep'ы были написаны, чтобы веб-страничка успевала загрузиться.
Программа откроет сайт магазина Walmart, нажмёт пару кнопок, откроет отдел с кофе и получит название и цены на товары.
Если веб-страничка довольно таки большая и поэтому XPath'ы долго работают или их сложно написать, то надо воспользоваться каким-то другим методом.
HTTP запросы
Для начала рассмотрим как на сайте появляется контент.

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

Теперь осталось найти нужный нам запрос.
Как это делать? – рассмотрите все запросы с типом fetch(третья колонка на картинке выше) и смотрите на вкладку Preview.

Если она не пустая, то она должна быть в формате XML или JSON, если нет – продолжайте поиски. Если да, то посмотрите, есть ли здесь нужная вам информация. Чтобы это проверить советую использовать какой – то JSON Viewer или XML Viewer(загуглите и откройте первую ссылку, скопируйте текст с вкладки Response и вставьте в Viewer). Когда вы найдёте нужный вам запрос, то сохраните у себя где то его название(левая колонка) или хост URL'а(вкладка Headers), чтоб потом не искать. Например если на сайте walmart’а открыть отдел кофе, то будет отправляться запрос, юрл которого начинается с walmart.com/cp/api/wpa. Там будет вся информация про кофе в продаже.

Полпути пройдено, теперь этот запрос можно «подделывать» и отправлять сразу через программу, получая нужную информацию за считанные секунды. Осталось распарсить JSON или XML, а это делается намного проще, чем писание XPath'ов. Но зачастую формирование таких запросов вещь довольно таки неприятная(смотри на URL на картинке выше) и если у вас даже всё получиться, то в некоторых случаях вы будете получать такой ответ.
Сейчас вы узнаете, как можно избежать проблем с подражанием запроса используя альтернативу — прокси сервера.
Прокси сервер
Прокси сервер — устройство, являющееся посредником между компьютером и интернетом.

Было б замечательно, если б наша программа была прокси сервером, тогда можно быстро и удобно обрабатывать нужные респонсы с сервера. Тогда была б такая цепочка Браузер – Программа – Интернет(сервер сайта, который парсим).
Благо для си шарпа есть замечательная библиотека для таких нужд – Titanium Web Proxy.
Создадим класс PServer
class PServer < private static ProxyServer proxyServer; public PServer() < proxyServer = new ProxyServer(); proxyServer.BeforeResponse += OnResponse; var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Loopback, 8000, true); explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest; proxyServer.AddEndPoint(explicitEndPoint); proxyServer.Start(); >private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e) < if (!e.HttpClient.Request.Url.Contains("www.walmart.com"))< e.DecryptSsl = false; >> public async Task OnResponse(object sender, SessionEventArgs e) < if (e.HttpClient.Response.StatusCode == 200 && (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST")) < string url = e.HttpClient.Request.Url; if (url.Contains("walmart.com/cp/api/wpa"))< Console.WriteLine(await e.GetResponseBodyAsString()); >> > >
Теперь пройдемся по каждому методу отдельно.
public PServer()
proxyServer.BeforeRepsone += OnRespone – добавляем метод обработки ответа с сервера. Он будет вызываться автоматически, когда будет приходить респонс.
explicitEndPoint — Конфигурация прокси сервера,
ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true)
IPAddress и port, на котором работает прокси сервер.
decryptSsl – стоит ли расшифровывать SSL. Иначе говоря, если decrtyptSsl = true, то прокси сервер будет обрабатывать все запросы и ответы.
explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest — добавляем метод обработки запроса перед его отправкой на сервер. Он также будет вызываться автоматически перед отправкой запроса.
proxyServer.Start() — «запуск» прокси-сервера, с этого момента он начинает обрабатывать запросы и ответы.
private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e) < if (!e.HttpClient.Request.Url.Contains("www.walmart.com"))< e.DecryptSsl = false; >>
e.DecryptSsl = false — текущий запрос и ответ на него не будут обрабатываться.
Если нас не интересует запрос или ответ на него(например картинка или какой – то скрипт), то зачем его расшифровывать? На это тратиться довольно много ресурсов, и если будут расшифровываться все запросы и ответы, то программа будет долго работать. Поэтому если текущий запрос не содержит хост интересующего нас запроса, то расшифровывать его нет смысла.
public async Task OnResponse(object sender, SessionEventArgs e) < if (e.HttpClient.Response.StatusCode == 200 && (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST")) < string url = e.HttpClient.Request.Url; if (url.Contains("walmart.com/cp/api/wpa")) Console.WriteLine(await e.GetResponseBodyAsString()); >> >
await e.GetResponseBodyAsString() – возвращает респонс в виде строки.
Чтобы WebDriver подключился к прокси серверу, то надо написать следующие:
string pathToFile = AppDomain.CurrentDomain.BaseDirectory + '\\'; ChromeOptions options = new ChromeOptions(); options.AddArguments("--proxy-server=" + IPAddress.Loopback + ":8000"); IWebDriver driver = new ChromeDriver(pathToFile, options);
Теперь можно обрабатывать нужные вам запросы.
Заключение
С помощью WebDriver'а можно переходить по страницам, нажимать на кнопки и подражать поведение обычного юзера. С помощью XPath'ов можно извлекать нужную информацию с веб-страниц. Если XPath'ы не работают, то всегда может помочь прокси сервер, который может перехватывать запросы между браузером и сайтом.
- Google Chrome
- C#
- Тестирование веб-сервисов
Скрапинг веб-сайтов на Python — пошаговое руководство
Научитесь парсить веб-страницы с помощью Python, чтобы быстро собирать данные с нескольких сайтов с экономией времени и усилий.
3 min read
Antonello Zanini

Узнайте, как выполнять веб-скрапинг с помощью Python, чтобы быстро собирать данные с различных сайтов, экономя время и силы.
Веб-скрапинг — извлечение данных с сайтов в интернете. В частности, парсер —инструмент, позволяющий осуществлять веб-скрапинг. Python является одним из самых простых языков программирования и содержит большое количество библиотек для веб-скрапинга. Это делает его идеальным вариантом для парсинга сайтов. Скрипт для веб-скрапинга на Python требует относительно небольшого количества кода!
В этом пошаговом руководстве вы узнаете, как создать простой парсер на языке Python. Это приложение будет обходить весь сайт, извлекать данные с каждой его страницы и экспортировать их в CSV-файл. Данное руководство поможет вам понять, какие библиотеки для сбора данных на языке Python являются лучшими, какие из них следует взять на вооружение и как именно их использовать. Следуйте этому пошаговому руководству и узнайте, как создать Python-скрипт для веб-скрапинга.
Требования
Чтобы создать парсер Python, вам необходимы:
Обратите внимание, что pip по умолчанию включен в состав Python версии 3.4 и выше. Поэтому устанавливать его вручную не требуется. Если на вашем компьютере нет Python, следуйте приведенному ниже руководству для вашей операционной системы
macOS
Раньше компьютеры Mac поставлялись с предустановленным Python 2.7, но теперь это уже не так, поскольку эта версия устарела.
Если вам нужна последняя версия Python, ее необходимо установить вручную. Для этого загрузите программу установки, дважды щелкните по ней, чтобы запустить, и следуйте указаниям мастера установки.
Windows
Загрузите программу установки Python и запустите ее. Во время работы мастера установки убедитесь, что отмечен флажок «Добавить python.exe в PATH», как показано ниже:

Таким образом, Windows будет автоматически распознавать команды python и pip в терминале. Напомним, что pip — менеджер пакетов для Python.
Linux
В большинстве дистрибутивов Linux Python уже предустановлен, однако речь может идти не о самой последней версии. Команда для установки или обновления Python в Linux зависит от менеджера пакетов. В дистрибутивах Linux на базе Debian выполните команду:
sudo apt-get install python3
Независимо от используемой ОС, откройте терминал и убедитесь, что Python успешно установлен:
python --version
В результате должно получиться что-то вроде этого:
Python 3.11.0
Теперь все готово для создания вашего первого парсера Python. Но сначала вам нужна библиотека веб-скрапинга Python!
Лучшие библиотеки веб-скрапинга Python
Можно создать скрипт веб-парсера с нуля с помощью ванильного Python, но это не идеальное решение. В конце концов, Python известен своим обширным выбором пакетов и, в частности, есть масса библиотек для веб-скрапинга. Пришло время рассмотреть наиболее важные из них!
Requests
Библиотека Requests позволяет выполнять HTTP-запросы на языке Python. Она упрощает отправку HTTP-запросов, особенно по сравнению со стандартной библиотекой Python HTTP. Requests играет ключевую роль в проекте для веб-скрапинга на Python. Это связано с тем, что для сбора данных, содержащихся на странице, необходимо сначала получить их с помощью HTTP-запроса GET. Кроме того, возможно, придется выполнить и другие HTTP-запросы к серверу целевого сайта.
Установить Requests можно с помощью следующей команды pip:
pip install requests
Beautiful Soup
Python-библиотека Beautiful Soup упрощает сбор информации со страниц. В частности, Beautiful Soup работает с любым HTML- или XML-парсером и предоставляет все необходимое для итерации, поиска и модификации абстрактного синтаксического дерева. Обратите внимание, что Beautiful Soup можно использовать вместе с html.parser — парсером, входящим в стандартную библиотеку Python и позволяющим парсить текстовые HTM-файлы. В частности, Beautiful Soup помогает обходить DOM и извлекать из него нужные данные.
Установить Beautiful Soup с помощью программы pip можно следующим образом:
pip install beautifulsoup4
Selenium
Selenium — современная система автоматизированного тестирования с открытым исходным кодом, позволяющая выполнять операции на странице в браузере. Другими словами, с его помощью можно поручить браузеру выполнение определенных задач. Обратите внимание, что Selenium также можно использовать в качестве библиотеки для веб-скрапинга благодаря его возможностям «безголового» браузера. Если вы не знакомы с этим понятием, то речь идет о веб-браузере, работающем без графического интерфейса пользователя (GUI). Если Selenium настроен в безголовом режиме, он будет запускать управляемый браузер, образно говоря «закулисно».
Интернет-страницы, посещаемые в Selenium, отображаются в реальном браузере. Таким образом, Selenium поддерживает веб-парсинг страниц, которые используют JavaScript для рендеринга или получения данных. Selenium предоставляет все необходимое для создания парсера, не требуя использования других библиотек. Установить его можно с помощью следующей команды pip:
pip install selenium
Создание парсера на Python
Теперь давайте узнаем, как создать веб-парсер на языке Python. Вот как выглядит целевой сайт:
Это сайт Quotes to Scrape — «песочница» для веб-скрапинга, содержащая постраничный список цитат.
Цель этого урока — научиться извлекать из него данные: текст, автора и список тегов для каждой цитаты. Затем полученная информация будет преобразована в CSV-формат.
Как видите, Quotes to Scrape — это не что иное, как песочница для парсинга веб-страниц. Сайт содержит разбитый на страницы список цитат. Парсер Python, который вы собираетесь создать, извлечет все цитаты на каждой странице, и предоставит их в виде данных CSV.
Шаг 1: Выбор правильных библиотек для веб-скрапинга на языке Python
Прежде всего, необходимо понять, какие библиотеки для веб-скрапинга на языке Python лучше всего подходят для достижения поставленной цели. Для этого посетите целевой сайт в браузере. Щелкните правой кнопкой мыши по странице и выберите «Просмотреть код». Откроется окно браузера DevTools. Перейдите на вкладку «Сеть» и перезагрузите страницу.
Как вы заметили, целевой сайт не выполняет никаких Fetch/XHR-запросов.

Это означает, что Quotes to Scrape не использует JavaScript для динамического извлечения данных. Другими словами, страницы, возвращаемые сервером, уже содержат интересующие данные. Именно так происходит на сайтах со статическим контентом.
Поскольку целевой сайт не использует JavaScript для вывода страницы или получения данных, Selenium для его сканирования не нужен. Вы можете использовать его, но это не обязательно. Причина в том, что Selenium открывает страницы в браузере. Поскольку это требует времени и ресурсов, Selenium снижает производительность. Этого можно избежать, используя Beautiful Soup вместе с Requests.
Теперь, когда вы поняли, какие библиотеки для веб-скрапинга на Python следует использовать, узнайте, как создать простой парсер с помощью Beautiful Soup!
Шаг 2: Инициализация проекта Python
Прежде чем написать первую строку кода, необходимо настроить проект для веб-скрапинга на языке Python. Технически для этого достаточно одного файла .py. Однако использование продвинутой IDE (Integrated Development Environment) облегчит вам работу c кодом. Здесь мы рассмотрим настройку Python-проекта в PyCharm, но подойдет и любая другая IDE для Python.
Откройте PyCharm и выберите «File > New Project… ». Во всплывающем окне «Новый проект» выберите «Чистый Python» и создайте новый проект.

Например, вы можете назвать свой проект python-web-scraper. Нажмите кнопку «Создать», и вы получите доступ к своему пустому Python-проекту. По умолчанию PyCharm инициализирует файл main.py. Для наглядности переименуйте его в scraper.py. Вот как теперь будет выглядеть ваш проект:

PyCharm автоматически инициализирует Python-файл со стандартным кодом. Удалите его, чтобы начать писать код с нуля.
Далее необходимо установить зависимости проекта. Установить Requests и Beautiful Soup можно, выполнив в терминале следующую команду:
pip install requests beautifulsoup4
Это приведет к одновременной установке двух библиотек. Дождитесь завершения процесса установки. Теперь вы готовы использовать Beautiful Soup и Requests для создания веб-краулера и парсера на Python. Убедитесь в импорте двух библиотек, добавив следующие строки в верхнюю часть файла сценария scraper.py:
import requests from bs4 import BeautifulSoup
PyCharm подсветит эти две строки серым цветом, потому что библиотеки не используются в коде. Если он подчеркнет их красным цветом, значит, что-то пошло не так в процессе установки. В этом случае попробуйте установить их снова.

Теперь вы готовы приступить к написанию логики веб-скрапинга на языке Python.
Шаг 2: Подключение к целевому URL
Первое, что необходимо сделать в веб-парсере — подключиться к целевому сайту. Для этого необходимо получить полный URL-адрес страницы из браузера. Обязательно скопируйте также секцию http:// или https:// HTTP-протокола. В данном случае это полный URL целевого сайта:
https://quotes.toscrape.com
Теперь вы можете использовать запросы для загрузки страницы с помощью следующей строки кода:
page = requests.get('https://quotes.toscrape.com')
Эта строка просто присваивает результат работы метода request.get() переменной page. Закулисно request.get() выполняет GET-запрос с использованием URL, переданного в качестве параметра. Затем он возвращает объект Response, содержащий ответ сервера на HTTP-запрос.
Если HTTP-запрос выполнен успешно, page.status_code будет содержать статус 200. Код ответа HTTP 200 OK означает, что HTTP-запрос был выполнен успешно. Статус HTTP 4xx или 5xx означает ошибку. Она может произойти по целому ряду причин, но следует помнить, что большинство сайтов блокируют запросы, не содержащие корректного User-Agent. Этот специальный заголовок представляет собой строку, характеризующую версию приложения и операционной системы, с которой поступил запрос. Подробнее о User-Agent для веб-скрапинга.
Задать допустимый заголовок User-Agent в запросах можно следующим образом:
headers = < 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' >page = requests.get('https://quotes.toscrape.com', headers=headers
Requests теперь будет выполнять HTTP-запрос с заголовками, переданными в качестве параметра.
На что следует обратить внимание, так это на свойство page.text . В нем будет содержаться HTML-документ, возвращенный сервером в строковом формате. Передайте свойство text в программу Beautiful Soup, чтобы извлечь данные со страницы. Давайте узнаем, как это сделать.
На что следует обратить внимание, так это на свойство page.text. В нем будет содержаться HTML-документ, возвращенный сервером в строковом формате. Передайте свойство text в программу Beautiful Soup для парсинга страницы. Как это сделать, узнаем на следующем шаге!
Шаг 3: Парсинг содержимого HTML-документа
Чтобы выполнить парсинг HTML-документа, возвращенного сервером после GET-запроса, передайте конструктору BeautifulSoup() файл page.text:
soup = BeautifulSoup(page.text, 'html.parser')
Второй параметр задает парсер, который будет использовать Beautiful Soup.
Теперь переменная soup содержит объект BeautifulSoup. Это древовидная структура, созданная в результате разбора HTML-документа, содержащегося в page.text, с помощью встроенного в Python парсера html.parser.
Теперь вы можете использовать его для выбора нужного HTML-элемента на странице. Смотрите, как!
Шаг 4: Выделение HTML-элементов с помощью Beautiful Soup
Beautiful Soup предлагает несколько подходов к выбору элементов в DOM. Отправными точками являются:
- find(): Возвращает первый HTML-элемент, соответствующий введенному селектору, если таковой вообще имеется.
- find_all(): Возвращает список HTML-элементов, удовлетворяющих условию селектора, переданному в качестве параметра.
В зависимости от параметров, передаваемых этим двум методам, они будут искать элементы на странице по-разному. В частности, можно выбрать элементы HTML:
# get all elements # on the page h1_elements = soup.find_all('h1')
# get the element with main_title_element = soup.find(id='main-title')
# find the footer element # based on the text it contains footer_element = soup.find(text=)
# find the email input element # through its "name" attribute email_element = soup.find(attrs=)
# find all the centered elements # on the page centered_element = soup.find_all(class_='text-center')
Объединение этих методов позволяет извлекать данные из любого HTML-элемента на странице. Обратите внимание на приведенный ниже пример:
# get all "li" elements # in the ".navbar" element soup.find(class_='navbar').find_all('li')
Чтобы упростить работу, в Beautiful Soup можно также использовать метод select(). Он позволяет применять CSS-селектор напрямую:
# get all "li" elements # in the ".navbar" element soup.select('.navbar > li')
Обратите внимание, что на момент написания статьи селекторы XPath не поддерживаются.
Важно усвоить, что для извлечения данных с интернет-страницы необходимо сначала определить интересующие вас HTML-элементы. В частности, важно определить стратегию выбора тех, которые содержат требующие извлечения данные.
Для этого можно воспользоваться предлагаемыми браузером инструментами для разработчиков. В браузере Chrome щелкните правой кнопкой мыши на интересующем вас HTML-элементе и выберите опцию «Просмотреть код». В данном случае это нужно сделать на элементе цитаты.

- Текст цитаты в HTML-элементе
- Автор цитаты в HTML-элементе
- Список тегов в элементе , каждый из которых содержится в HTML-элементе .
В частности, вы можете извлечь эти данные, используя следующие селекторы CSS в .quote:
Шаг 5: Извлечение данных из элементов
Во-первых, необходима структура данных, в которой будет храниться полученная благодаря парсингу информация. Для этого инициализируем переменную массива.
quotes = []
Затем с помощью Soup извлекаем из DOM элементы цитаты, применив определенный ранее CSS-селектор .quote:
quote_elements = soup.find_all('div', class_='quote')
Метод find_all() вернет список всех HTML-элементов
, идентифицированных классом quote. Итерация по списку цитат для получения данных о них приведена ниже:
for quote_element in quote_elements: # extract the text of the quote text = quote_element.find('span', class_='text').text # extract the author of the quote author = quote_element.find('small', class_='author').text # extract the tag HTML elements related to the quote tag_elements = quote_element.select('.tags .tag') # store the list of tag strings in a list tags = [] for tag_element in tag_elements: tags.append(tag_element.text)
Метод Beautiful Soup find() получит единственный интересующий элемент HTML. Поскольку строк тегов, которые связанны с цитатой, несколько, их следует хранить в списке.
Затем можно преобразовать полученные данные в словарь и добавить его в список цитат следующим образом:
quotes.append( < 'text': text, 'author': author, 'tags': ', '.join(tags) # merge the tags into a "A, B, . Z" string >)
Хранение данных в структурированном словаре облегчает доступ к ним и их понимание.
Отлично! Вы только что увидели, как извлечь все данные с одной страницы! Однако не забывайте, что у целевого сайта их гораздо больше. Пришло время научиться обходить весь сайт!
Шаг 6: Реализация логики поиска данных

Переходя по ссылке, содержащейся в этом HTML-элементе, можно легко перемещаться по всему сайту. Итак, начните с главной страницы и посмотрите, как перейти на каждую страницу, из которых состоит целевой сайт. Для этого достаточно найти HTML-элемент .next и извлечь из него относительную ссылку на следующую страницу.
Реализовать логику по обходу всего сайта можно следующим образом:
# the URL of the home page of the target website base_url = 'https://quotes.toscrape.com' # retrieve the page and initializing soup. # get the "Next →" HTML element next_li_element = soup.find('li', class_='next') # if there is a next page to scrape while next_li_element is not None: next_page_relative_url = next_li_element.find('a', href=True)['href'] # get the new page page = requests.get(base_url + next_page_relative_url, headers=headers) # parse the new page soup = BeautifulSoup(page.text, 'html.parser') # scraping logic. # look for the "Next →" HTML element in the new page next_li_element = soup.find('li', class_='next')
Цикл where выполняет итерацию по каждой странице до тех пор, пока не окажется на последней странице. Он извлекает относительный URL следующей страницы и использует его для создания полноценного URL-адреса для дальнейшего парсинга. Новая страница загружается, выполняется ее веб-скрапинг и та же логика повторяется снова.
Теперь вы знаете, как парсить многостраничный сайт. Осталось научиться преобразовывать извлеченные данные в более удобный формат, например CSV.
Шаг 7: Извлечение полученных данных в CSV-файл
Рассмотрим, как экспортировать список словарей, содержащих извлеченные данные, в CSV-файл. Для этого понадобится следующий код:
import csv # scraping logic. # reading the "quotes.csv" file and creating it # if not present csv_file = open('quotes.csv', 'w', encoding='utf-8', newline='') # initializing the writer object to insert data # in the CSV file writer = csv.writer(csv_file) # writing the header of the CSV file writer.writerow(['Text', 'Author', 'Tags']) # writing each row of the CSV for quote in quotes: writer.writerow(quote.values()) # terminating the operation and releasing the resources csv_file.close()
Этот фрагмент записывает данные о цитатах, содержащиеся в списке словарей, в файл quotes.csv . Обратите внимание, что csv является частью стандартной библиотеки Python. Поэтому его можно импортировать и использовать без установки каких-либо дополнительных зависимостей.
По сути, необходимо просто создать CSV-файл с помощью функции open(). Затем заполнить его функцией writerow() из объекта Writer библиотеки csv. В результате каждый словарь цитат будет записан в виде строки в CSV-формате.
Вы перешли от необработанных данных, содержащихся на сайте, к полуструктурированным данным, хранящимся в CSV-файле. Процесс извлечения данных завершен, и теперь вы можете взглянуть на свой полноценный Python-парсер.
Шаг 8: Собираем все вместе
Вот как выглядит готовый скрипт на Python для сбора данных:
import requests from bs4 import BeautifulSoup import csv def scrape_page(soup, quotes): # retrieving all the quote HTML element on the page quote_elements = soup.find_all('div', class_='quote') # iterating over the list of quote elements # to extract the data of interest and store it # in quotes for quote_element in quote_elements: # extracting the text of the quote text = quote_element.find('span', class_='text').text # extracting the author of the quote author = quote_element.find('small', class_='author').text # extracting the tag HTML elements related to the quote tag_elements = quote_element.find('div', class_='tags').find_all('a', class_='tag') # storing the list of tag strings in a list tags = [] for tag_element in tag_elements: tags.append(tag_element.text) # appending a dictionary containing the quote data # in a new format in the quote list quotes.append( < 'text': text, 'author': author, 'tags': ', '.join(tags) # merging the tags into a "A, B, . Z" string >) # the url of the home page of the target website base_url = 'https://quotes.toscrape.com' # defining the User-Agent header to use in the GET request below headers = < 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' ># retrieving the target web page page = requests.get(base_url, headers=headers) # parsing the target web page with Beautiful Soup soup = BeautifulSoup(page.text, 'html.parser') # initializing the variable that will contain # the list of all quote data quotes = [] # scraping the home page scrape_page(soup, quotes) # getting the "Next →" HTML element next_li_element = soup.find('li', class_='next') # if there is a next page to scrape while next_li_element is not None: next_page_relative_url = next_li_element.find('a', href=True)['href'] # getting the new page page = requests.get(base_url + next_page_relative_url, headers=headers) # parsing the new page soup = BeautifulSoup(page.text, 'html.parser') # scraping the new page scrape_page(soup, quotes) # looking for the "Next →" HTML element in the new page next_li_element = soup.find('li', class_='next') # reading the "quotes.csv" file and creating it # if not present csv_file = open('quotes.csv', 'w', encoding='utf-8', newline='') # initializing the writer object to insert data # in the CSV file writer = csv.writer(csv_file) # writing the header of the CSV file writer.writerow(['Text', 'Author', 'Tags']) # writing each row of the CSV for quote in quotes: writer.writerow(quote.values()) # terminating the operation and releasing the resources csv_file.close()
Как здесь показано, веб-парсер можно создать менее чем за 100 строк кода. Этот скрипт Python способен обойти весь сайт, автоматически извлечь его данные и экспортировать их в CSV-файл.
Поздравляем! Вы только что узнали, как создать веб-парсер на Python с помощью Requests и Beautiful Soup!
Шаг 9: Запуск скрипта для веб-скрапинга на языке Python
Если вы являетесь пользователем PyCharm, запустите скрипт, нажав кнопку ниже:

Или запустите следующую команду Python в терминале внутри каталога проекта:
python scraper.py
Дождитесь окончания процесса, и вы получите доступ к файлу quotes.csv. Откройте его. Он должен содержать следующие данные:

Теперь у вас есть все 100 цитат, содержащихся на целевом сайте, в одном файле и в удобном для чтения формате!
Вывод
Из этого руководства вы узнали, как выполнять веб-скрапинг с помощью Python, что в этом случае необходимо для начала работы и какие библиотеки Python для парсинга являются лучшими. Затем на реальном примере мы показали, как использовать Beautiful Soup и Requests для создания приложения для веб-скрапинга. Как вы могли убедиться, для создания скрипта для парсинга на языке Python нужно не так уж и много кода.
Однако веб-скрапинг сопряжен с целым рядом проблем. В частности, все большую популярность приобретают технологии защиты от ботов и парсинга. Именно поэтому вам необходим современный инструмент для веб-скрапинга, который предлагает компания Bright Data.
Чтобы избежать проблем с блокировками, мы также рекомендуем выбрать прокси с учетом ваших потребностей из большого ассортимента прокси-сервисов Bright Data.
FAQ
Является ли Python подходящим языком для веб-скрапинга? Python не только отлично подходит для веб-скрапинга, но и считается одним из лучших для этого языков. Это объясняется его удобочитаемостью и относительной простотой. Кроме того, он имеет одно из самых больших сообществ в мире IT и предлагает широкий выбор библиотек и инструментов, предназначенных для парсинга. Является ли веб-скрапинг и краулинг частью науки о данных? Да, веб-скрапинг и краулинг являются частью большой области науки о данных. Скрапинг/кроулинг служит основой для всех других побочных продуктов, которые могут быть получены из структурированных и неструктурированных данных. К ним относятся аналитика, алгоритмические модели/результаты, инсайты и «применимые знания». Как с помощью Python получить с сайта конкретные данные? Веб-скрапинг данных с сайта с помощью Python подразумевает изучение страницы целевого URL, определение данных, которые необходимо извлечь, написание и выполнение кода извлечения данных, а также сохранение полученной информации в нужном формате. Как создать веб-парсер на Python? Первым шагом в создании веб-парсера с помощью Python является использование строковых методов для анализа сайта, затем сбор данных с него с использованием HTML-парсера и, наконец, взаимодействие с необходимыми формами и компонентами интернет-ресурса.
Создание python-пакетов (aka setup scripts)
Одна из действительно полезных вещей в python — это система скриптов установки. Любой, серьезно увлекающийся python-программированием разработчик рано или поздно сталкивается с ней. Но из-за гибкости инструментария скриптов установки, их документация весьма раздута. На текущий момент имеется набор утилит (setuptools, distutils, distribute) выполняющих одинаковые задачи.

В данной статье я на конкретных примерах покажу как создать и настроить простой python-пакет.
Наш проект будет иметь следующую функциональность:
- Метод возвращающий строку: "Hello World!";
- Команда helloworld печатающая эту строку в стандартный вывод.
Создаем структуру проекта
Для начала создадим директорию для пакета. Ее минимальный набор файлов состоит из: файла дистрибьюции ( setup.py ) описывающего метаданные и python кода проекта (в нашем случае модуля helloworld).
Также, xорошим тоном считается создание в корне директории файла с описанием проекта: README.txt .
Получаем следующую структуру:
helloworld-project ├── helloworld │ ├── __init__.py │ └── core.py ├── setup.py └── README.txt
Наша корневая директория helloworld-project будет содержать мета-данные пакета и вспомогательные файлы (тесты, лицензию, документацию и т.д.), а поддиректория helloworld непосредственно сам модуль helloworld .
Теперь отредактируем файл: helloworld/core.py и добавим логику нашего приложения (получение и вывод строки "Hello World!"):
def get_message(): return "Hello World!" def print_message(): print get_message()
Редактируем мета-информацию (setup.py)
Заполним файл описания README.rst :
Description =========== An example Hello World project.
Теперь отредактируем файл setup.py :
from setuptools import setup, find_packages from os.path import join, dirname setup( name='helloworld', version='1.0', packages=find_packages(), long_description=open(join(dirname(__file__), 'README.txt')).read(), )
Убедитесь, что в вашей системе доступны setuptools, в противном случае установите python-пакет distribute
Этих операций достаточно, чтобы собрать пакет дистрибьюции. Выполните команду сборки:
$ python setup.py sdist
В случае успеха вы получите файл: dist/helloworld-1.0.tar.gz . Это полноценный, архивированный python-пакет и вы можете распространять его среди прочих разработчиков.
Виртуальное окружение
Virtualenv — пакет применяемый для создания изолированного python-окружения. Используем его для тестирования нашего проекта.
Создадим окружение env:
$ virtualenv env
Команда создаст директорию env внутри нашего проекта и установит туда python, pip и distribute. Произведем в него установку нашего проекта.
$ ./env/bin/python setup.py install running install running bdist_egg running egg_info [. ] Processing dependencies for helloworld==1.0 Finished processing dependencies for helloworld==1.0
И протестируем его работоспособность:
$ ./env/bin/python >>> import helloworld.core as hw >>> hw.get_message() 'Hello World!' >>> hw.print_message() Hello World!
Все работает. Осталось добавить поддержку команды helloworld в консоли.
Создание команд
Для создания команды helloworld изменим файл setup.py :
setup( . entry_points= 'console_scripts': ['helloworld = helloworld.core:print_message'] > )
В параметре entry_points мы задаем словарь с "точками вызова" нашего приложения. Ключ console_scripts задает список создаваемых исполняемых скриптов (в Windows это будут exe-файлы). В данном случае мы указали создание исполняемого скрипта helloworld при вызове которого будет запускаться метод print_message из модуля helloworld.core.
Переустановим модуль в наше окружение и проверим работу созданного скрипта (для этого прийдется активировать наше окружение):
$ ./env/bin/python setup.py install $ source ./env/bin/activate (env) $ helloworld Hello World! (env)
Похоже все работает.
Работа с версиями
Номер версии важная часть любого проекта. От него зависит обновление пакетов и разрешение зависимостей. В примере выше мы указали номер версии 1.0 в файле setup.py . Более правильное решение перенести его в файл helloworld/__init__.py чтобы сделать доступным в python-коде. По существующим соглашения для хранения номера версии в модуле, используется переменная __version__.
__version__ = '1.0'
Изменим файл setup.py , чтобы нам не приходилось редактировать номер версии в двух местах:
. import helloworld setup( name='helloworld', version=helloworld.__version__, .
Существует множество систем наименования версий в python обычно рекомендуется использовать PEP386. Можно представить, что обозначение версии состоит из номера мажорного, минорного релизов (номера багфикса при необходимости), разделенных точками. В последней части версии разрешается использовать буквы латинского алфавита. Примеры из официальной документации:
0.4 0.4.0 (these two are equivalent) 0.4.1 0.5a1 0.5b3 0.5 0.9.6 1.0 1.0.4a3 1.0.4b1 1.0.4
Управление зависимостями
Добавим функциональности нашему проекту. Создадим команду serve которая будет запускать вебсервер отдающий страницу со строкой "Hello world!" генерируемой нашим модулем. Для этого воспользуемся пакетом Flask.
Добавляем файл helloworld/web.py :
from flask import Flask, render_template from helloworld.core import get_message app = Flask(__name__) @app.route("/") def hello(): return render_template('index.html', message=get_message()) def run_server(): app.run()
И файл helloworld/templates/index.html :
body>>body>
И опишем команду serve в файле setup.py :
. entry_points= 'console_scripts': [ 'helloworld = helloworld.core:print_message', 'serve = helloworld.web:run_server', ] >, .
Теперь в нашем проекте появилась зависимость от пакета Flask. Без его установки наше приложение не будет правильно работать. За описание зависимостей в файле setup.py отвечает параметр install_requires:
. install_requires=[ 'Flask==0.8' ]
Проверим установку зависимостей обновив наш пакет и работу команды serve:
$ ./env/bin/python setup.py develop . Processing dependencies for helloworld==0.1 Searching for Flask==0.8 . $ serve * Running on http://127.0.0.1:5000/
Открыв браузер по адресу http://127.0.0.1:5000 вы должны увидеть нашу страницу.
Управление файлами проекта (MANIFEST.in)
На текущий момент при сборке нашего пакета distutils включает в него только python-файлы. Необходимо включить в него файл helloworld/templates/index.html без которого проект работать не будет.
Чтобы сделать это мы должны сообщить distutils какие еще файлы надо включать в наш проект. Один из способов — это создание файла MANIFEST.in :
recursive-include helloworld/templates *.html
Данная команда указывает distutils на включение в проект всех html файлов в директории helloworld/templates .
Также придется обновить setup.py :
. setup( . include_package_data=True, . )
Теперь шаблоны будут включены в наш проект.
Создание и запуск тестов
Хорошей практикой считается создание тестов для вашего проекта. Добавим простейшую реализацию, файл tests.py :
from unittest import TestCase from helloworld.core import get_message class HelloworldTestCase(TestCase): def test_helloworld(self): self.assertEqual(get_message(), 'Hello World!')
И обновим setup.py :
. setup( . test_suite='tests', . )
Теперь мы можем произвести предварительное тестирование нашего проекта:
$ python setup.py test running test running egg_info writing requirements to helloworld.egg-info/requires.txt writing helloworld.egg-info/PKG-INFO writing top-level names to helloworld.egg-info/top_level.txt writing dependency_links to helloworld.egg-info/dependency_links.txt writing entry points to helloworld.egg-info/entry_points.txt reading manifest file 'helloworld.egg-info/SOURCES.txt' reading manifest template 'MANIFEST.in' writing manifest file 'helloworld.egg-info/SOURCES.txt' running build_ext test_helloworld (tests.HelloworldTestCase) . ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Обратите внимание, что для запуска тестов даже не нужно создание виртуального окружения. Необходимые зависимости будут скачаны в директорию проекта в виде egg пакетов.
Публикация пакета на pypi.python.org
Прежде чем вы сможете опубликовать свой проект вам необходимо зарегистрироваться на PyPi. Запишите ваши реквизиты в файле ~/.pypirc :
[distutils] index-servers = pypi [pypi] username: password:
Все ваш проект готов к публикации. Достаточно ввести соответствующую команду:
$ python setup.py register sdist upload
Вы не сможете опубликовать пакет helloworld , тк данное имя проекта уже занято.
Как парсить страницы сайтов с автоподгрузкой на примере Instagram

Статья обновлена 19 января 2020 в связи с изменениями структуры JS необходимой для извлечения query_hash в парсере по тэгам. Механика автоподгрузки на страницах сайтов осуществляется с помощью Javascript. Поэтому, для того, чтобы определить на какой URL нам нужно обращаться и какие параметры использовать, нам нужно либо досконально изучить JS код который работает на странице, либо, и что предпочтительней, изучить запросы, которые делает браузер при прокрутке страницы вниз. Изучить запросы мы можем с помощью Инструментов для разработчика, которые встроены во все современные браузеры. В нашей статье мы будем использовать Google Chrome, но вы можете использовать любой другой браузер, приняв во внимание, что инструменты разработчика могут выглядеть по разному в разных браузерах.
Изучать нашу задачу мы будем на примере Instagram, а именно, используя официальный канал Instagram. Откроем эту страницу в браузере, и запустим Chrome Dev Tools — инструменты для разработчика, которые встроены в Google Chrome. Для этого кликнем правой кнопкой мыши в любом месте страницы и выберем опцию «Просмотреть код» или нажмите «Ctrl+Shift+I»:

У нас откроется окно инструментов, где мы перейдем во вкладку Network и в фильтрах выберем показ только XHR запросов. Мы это делаем для того, чтобы отфильтровать ненужные нам запросы. После этого перезагрузим страницу в браузере с помощью кнопки Reload в интерфейсе браузера или клавиши «F5» на клавиатуре.

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

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

Убедившись, что это нужные нам запросы, рассмотрим один из них более внимательно. Для этого перейдем во вкладку Headers. Там мы можем найти информацию о том, на какой именно URL производится запрос, какой тип запроса (POST или GET) используется, а также какие параметры передаются с запросом.

Параметры запроса лучше изучать в секции Query String Parameters, прокрутив рабочее окно в панели инструментов вниз до конца:

Результатом нашего анализа станут следующие факты:
URL запроса: https://www.instagram.com/graphql/query/
Тип запроса: GET
Передаваемые параметры: query_hash и variables
Очевидно, что в query_hash передается статичный id, который генерируется, скорее всего, когда вы заходите на страницу. В variables же передаются некие параметры в JSON формате, влияющие на выборку данных.
Давайте проведем небольшой эксперимент, возьмем URL с параметрами, который использовался для загрузки данных:
Если бы до последнего апдейта API мы бы взяли и вставили его в адресную строку браузера и нажали Enter, то мы бы увидели как загрузится страница в JSON формате:

Однако, теперь просто так API Инстаграма не отдает данные, для этого необходимо рассчитать подпись для запроса и передать ее в заголовке запроса. Этот вопрос более подробно рассматривается ниже. Без корректного заголовка все что мы получим сейчас — это ошибку 403.
Теперь нам нужно понять, откуда берется query_hash. Если мы перейдем во вкладку Elements и попытаемся найти (CTRL+F) наш query_hash f2405b236d85e8296cf30347c9f08c2a, то мы узнаем что на самой странице его нет, а значит он подгружается или генерируется где-то в коде Javascript. Поэтому, перейдем опять во вкладку Network и поставим фильтр на JS. Таким образом мы увидим только запросы на JS файлы. Последовательно перебирая запрос за запросом, будем искать наш id в загруженных файлах: просто выбираем запрос, затем открываем в открывшейся панели вкладку Response чтобы увидеть содержимое JS и делаем поиск нашего id (CTRL+F). После нескольких неудачных попыток, мы обнаружим, что наш id находится в следующем JS файле:
а фрагмент кода, который обрамляет id, выглядит так:
s.pagination>,queryId:"f2405b236d85e8296cf30347c9f08c2a"
Соответственно, для получения query_hash нам надо найти на первой странице URL на ProfilePageContainer.js файл, извлечь этот URL, забрать JS файл по этому URL, распарсить место с нужным нам id и записать его в переменную для дальнейшего использования.
Теперь давайте посмотрим, что за переменные передаются в variables:
Если мы проанализируем все XHR запросы с догружаемыми данными, что мы обнаружим, что меняется только параметр after. Поэтому id скорее всего есть id канала, который мы парсим, first — количество записей, которые сервер должен отдать по запросу, а after — очевидно id последней показанной записи.
Нам нужно найти место, из которого мы можем извлечь id канала, для этого первым делом мы поищем текст 25025320 в исходном коде начальной страницы. Перейдем во вкладку Elements и сделаем поиск (CTRL+F) нашего id. Мы обнаружим, что он есть в JSON структуре на самой странице, именно оттуда мы и можем его извлечь:

Вроде все понятно, но где нам брать этот самый after для каждой последующей подгрузки? Все очень просто. Если мы загрузим в браузере следующий URL:
мы увидим, что там есть следующая структура:
data: < user: < edge_owner_to_timeline_media: < count: 5014, page_info: < has_next_page: true, end_cursor: "AQCCoEpYvQtj0-NgbaQUg9g4ffOJf8drV2RieFJw1RA3E9lDoc8euxXjeuwlUEtXB6CRS9Zs2ZGJcNKseKF9f6b0cN0VC3ck8rnTfOw5q8nlJw" >> > >
То есть, в нашей логике мы сможем использовать значение поля has_next_page чтобы знать переходить ли на следующую страницу или нет и end_cursor как значение параметра after.
Сейчас мы напишем заготовку нашего парсера, загрузим первую страницу и попытаемся загрузить JS файл с query_id. Создайте диггер в вашем аккаунте Diggernaut и добавьте в него следующую конфигурацию:
--- config: agent: Firefox debug: 2 do: # Загружаем начальную страницу - walk: to: https://www.instagram.com/instagram/ do: # Ищем элементы script которые подгружают JS - find: path: script[type="text/javascript"] do: # Парсим значение атрибута src - parse: attr: src # Проверяем, нужный ли это Javascript, нам нужен тот, у которого в URL есть строка ProfilePageContainer.js - if: match: ProfilePageContainer\.js do: # Переходим по URL скрипта - walk: to: value do:
Установите диггер в режим Отладка. Теперь нам нужно запустить наш парсер и после того как он отработает посмотреть лог. В конце лога мы увидим как диггернаут работает с JS файлами. Он преобразовывает их в следующую структуру:
А значит селектор для забора всего JS будет script. Давайте допишем функцию парсинга query_id из JS:
--- config: agent: Firefox debug: 2 do: # Загружаем начальную страницу - walk: to: https://www.instagram.com/instagram/ do: # Ищем элементы script которые подгружают JS - find: path: script[type="text/javascript"] do: # Парсим значение атрибута src - parse: attr: src # Проверяем, нужный ли это Javascript, нам нужен тот, у которого в URL есть строка ProfilePageContainer.js - if: match: ProfilePageContainer\.js do: # Переходим по URL скрипта - walk: to: value do: # Ищем элемент, содержащий искомый JS - find: path: script do: # Парсим контент элемента, используя фильтр с регулярным выражением - parse: filter: profilePosts\.byUserId\.get[^,]+,queryId\:\&\s*quot\;([^&]+)\&\s*quot\; # Сохраняем полученное значение в переменной - variable_set: queryid
Сохраним наш парсер и снова запустим. Подождем когда он закончит работу и посмотрим в лог. В логе мы увидим следующую строчку:
Set variable queryid to register value: 42323d64886122307be10013ad2dcc44
Это значит, что query_hash был успешно извлечен и записан в переменную с именем queryid.
Теперь мы извлечем id канала. Как вы помните, он есть в JSON объекте на самой странице. Поэтому нам нужно взять содержимое определенного элемента script, вытащить оттуда JSON, конвертировать его в XML и забрать нужное нам значение, используя CSS селектор.
--- config: agent: Firefox debug: 2 do: # Загружаем начальную страницу - walk: to: https://www.instagram.com/instagram/ do: # Ищем элементы script которые подгружают JS - find: path: script[type="text/javascript"] do: # Парсим значение атрибута src - parse: attr: src # Проверяем, нужный ли это Javascript, нам нужен тот, у которого в URL есть строка ProfilePageContainer.js - if: match: ProfilePageContainer\.js do: # Переходим по URL скрипта - walk: to: value do: # Ищем элемент, содержащий искомый JS - find: path: script do: # Парсим контент элемента, используя фильтр с регулярным выражением - parse: filter: profilePosts\.byUserId\.get[^,]+,queryId\:\&\s*quot\;([^&]+)\&\s*quot\; # Сохраняем полученное значение в переменной - variable_set: queryid # находим элемент script, который содержит текст window._sharedData - find: path: script:contains("window._sharedData") do: - parse - space_dedupe - trim # извлекаем JSON - filter: args: window\._sharedData\s+\=\s+(.+)\s*;\s*$ # конвертим JSON в XML - normalize: routine: json2xml # превращаем XML строку в DOM блок - to_block - find: path: body_safe do: # Находим элемент в котором хранится id канала - find: path: entry_data > profilepage > graphql > user > id do: # Парсим содержимое элемента - parse # Сохраняем полученное значение в переменной - variable_set: chid
Если вы внимательно посмотрите в лог, то увидите, что JSON структура трансформируется в DOM следующим образом:
qNVodzmebd0ZnAEOYxFCPpMV1XWGEaDz US 1.5 480 360 profilePage_25025320 Discovering — and telling — stories from around the world. Curated by Instagram’s community team. false false http://blog.instagram.com/ http://l.instagram.com/?u=http%3A%2F%2Fblog.instagram.com%2F&e=ATM_VrrL-_PjBU0WJ0OT_xPSlo-70w2PtE177ZsbPuLY9tmVs8JmIXfYgban04z423i2IL8M 230937095 false 197 false Instagram false false 25025320 false true 5014 GraphVideo false 607 1080 https://scontent-iad3-1.cdninstagram.com/vp/9cdd0906e30590eed4ad793888595629/5A5F5679/t51.2885-15/s1080x1080/e15/fr/26158234_2061044554178629_8867446855789707264_n.jpg 573448 Video by @yanndixon Spontaneous by nature, a flock of starlings swarm as one at sunset in England. #WHPspontaneous 4709 1688175842423510712 true 25025320 Bdtmvv-DJa4 1515466361 150 150 https://scontent-iad3-1.cdninstagram.com/vp/1ec5640a0a97e98127a1a04f1be62b6b/5A5F436E/t51.2885-15/s150x150/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 240 240 https://scontent-iad3-1.cdninstagram.com/vp/8c972cdacf536ea7bc6764279f3801b3/5A5EF038/t51.2885-15/s240x240/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 320 320 https://scontent-iad3-1.cdninstagram.com/vp/a74e8d0f933bffe75b28af3092f12769/5A5EFC3E/t51.2885-15/s320x320/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 480 480 https://scontent-iad3-1.cdninstagram.com/vp/59790fbcf0a358521f5eb81ec48de4a6/5A5F4F4D/t51.2885-15/s480x480/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 640 640 https://scontent-iad3-1.cdninstagram.com/vp/556243558c189f5dfff4081ecfdf06cc/5A5F43E1/t51.2885-15/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg https://scontent-iad3-1.cdninstagram.com/vp/556243558c189f5dfff4081ecfdf06cc/5A5F43E1/t51.2885-15/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 2516274 . AQAchf_lNcgUmnCZ0JTwqV_p3J0f-N21HeHzR2xplwxalNZDXg9tNmrBCzkegX1lN53ROI_HVoUZBPtdxZLuDyvUsYdNoLRb2-z6HMtJoTXRYQ true 45ca3dc3d5fd true
Это поможет нам построить CSS селекторы для забора первых 12 записей и маркера последней записи, который нужен нам для забора следующих 12 записей. Напишем логику для извлечения данных, а также начем формировать пул (pool) линков со ссылками на фиды (feeds) с подгружаемыми данными. Далее начнем итерацию по пулу линков и посмотрим как преобразует Diggernaut полученный JSON, так, чтобы мы смогли построить корректные CSS селекторы для логики парсера.
Совсем недавно Instagram сделал изменения в публичном API, теперь для авторизация делается не по CSRF токену, а по специальной сигнатуре, которая рассчитывается используя новый параметр rhx_gis, передаваемый в sharedData странице канала и передаваемые в запросе переменные. Алгоритм можно узнать при разборе JS. Этот алгоритм мы используем и будем автоматически подписывать запросы. Для этого нам нужно извлечь rhx_gis параметр.
--- config: agent: Firefox debug: 2 do: # Загружаем начальную страницу - walk: to: https://www.instagram.com/instagram/ do: # Ищем элементы script которые подгружают JS - find: path: script[type="text/javascript"] do: # Парсим значение атрибута src - parse: attr: src # Проверяем, нужный ли это Javascript, нам нужен тот, у которого в URL есть строка ProfilePageContainer.js - if: match: ProfilePageContainer\.js do: # Переходим по URL скрипта - walk: to: value do: # Ищем элемент, содержащий искомый JS - find: path: script do: # Парсим контент элемента, используя фильтр с регулярным выражением - parse: filter: profilePosts\.byUserId\.get[^,]+,queryId\:\&\s*quot\;([^&]+)\&\s*quot\; # Сохраняем полученное значение в переменной - variable_set: queryid # находим элемент script, который содержит текст window._sharedData - find: path: script:contains("window._sharedData") do: - parse - space_dedupe - trim # извлекаем JSON - filter: args: window\._sharedData\s+\=\s+(.+)\s*;\s*$ # конвертим JSON в XML - normalize: routine: json2xml # превращаем XML строку в DOM блок - to_block - find: path: body_safe do: # Находим элемент в котором хранится id канала - find: path: entry_data > profilepage > graphql > user > id do: # Парсим содержимое элемента - parse # Сохраняем полученное значение в переменной - variable_set: chid # Находим элемент в котором хранится rhx_gis - find: path: rhx_gis do: # Парсим содержимое элемента - parse # Сохраняем полученное значение в переменной - variable_set: rhxgis # Находим элементы записей и итерируем по ним - find: path: entry_data > profilepage > graphql > user > edge_owner_to_timeline_media > edges > node do: # Создаем новый объект с именем item - object_new: item # Находим элемент с URL изображения - find: path: display_url do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: url # Находим элемент с описанием записи - find: path: edge_media_to_caption > edges > node > text do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: caption # Находим элемент с флагом видео это или нет - find: path: is_video do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: video # Находим элемент с количеством комментариев - find: path: edge_media_to_comment > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: comments # Находим элемент с количеством лайков - find: path: edge_media_preview_like > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: likes # записываем объект в базу - object_save: name: item # Находим элемент, в котором хранятся данные для подгрузки - find: path: entry_data > profilepage > graphql > user > edge_owner_to_timeline_media > page_info do: # Находим элемент, в котором хранятся данные о наличии следующей страницы - find: path: has_next_page do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: hnp # Читаем содержимое переменной в регистр - variable_get: hnp # Проверяем равно ли значение 'true' - if: match: 'true' do: # Если да, то находим элемент с курсором - find: path: end_cursor do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: cursor # URL-энкодим параметр - eval: routine: js body: '(function () ")>)();' # Сохраняем значение в переменную - variable_set: cursor_encoded # Формируем пул линков и добавляем в него URL на первую подгрузку - link_add: url: https://www.instagram.com/graphql/query/?query_hash=&variables=%7B%22id%22%3A%22%22%2C%22first%22%3A12%2C%22after%22%3A%22%22%7D # Формируем подпись и записываем ее в переменную signature - register_set: ':<"id":"","first":12,"after":"">' - normalize: routine: md5 - variable_set: signature # Устанавливаем счетчик подгрузок в 0 - counter_set: name: pages value: 0 # Итерируем по пулу и загружаем текущий линк и используем подпись в заголовках запроса - walk: to: links headers: x-instagram-gis: x-requested-with: XMLHttpRequest do:
После запуска в логе мы можем увидеть вот такую структуру, с которой нам нужно работать:
qNVodzmebd0ZnAEOYxFCPpMV1XWGEaDz US 1.5 480 360 profilePage_25025320 Discovering — and telling — stories from around the world. Curated by Instagram’s community team. false false http://blog.instagram.com/ http://l.instagram.com/?u=http%3A%2F%2Fblog.instagram.com%2F&e=ATM_VrrL-_PjBU0WJ0OT_xPSlo-70w2PtE177ZsbPuLY9tmVs8JmIXfYgban04z423i2IL8M 230937095 false 197 false Instagram false false 25025320 false true 5014 GraphVideo false 607 1080 https://scontent-iad3-1.cdninstagram.com/vp/9cdd0906e30590eed4ad793888595629/5A5F5679/t51.2885-15/s1080x1080/e15/fr/26158234_2061044554178629_8867446855789707264_n.jpg 573448 Video by @yanndixon Spontaneous by nature, a flock of starlings swarm as one at sunset in England. #WHPspontaneous 4709 1688175842423510712 true 25025320 Bdtmvv-DJa4 1515466361 150 150 https://scontent-iad3-1.cdninstagram.com/vp/1ec5640a0a97e98127a1a04f1be62b6b/5A5F436E/t51.2885-15/s150x150/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 240 240 https://scontent-iad3-1.cdninstagram.com/vp/8c972cdacf536ea7bc6764279f3801b3/5A5EF038/t51.2885-15/s240x240/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 320 320 https://scontent-iad3-1.cdninstagram.com/vp/a74e8d0f933bffe75b28af3092f12769/5A5EFC3E/t51.2885-15/s320x320/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 480 480 https://scontent-iad3-1.cdninstagram.com/vp/59790fbcf0a358521f5eb81ec48de4a6/5A5F4F4D/t51.2885-15/s480x480/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 640 640 https://scontent-iad3-1.cdninstagram.com/vp/556243558c189f5dfff4081ecfdf06cc/5A5F43E1/t51.2885-15/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg https://scontent-iad3-1.cdninstagram.com/vp/556243558c189f5dfff4081ecfdf06cc/5A5F43E1/t51.2885-15/e15/c236.0.607.607/26158234_2061044554178629_8867446855789707264_n.jpg 2516274 . AQAchf_lNcgUmnCZ0JTwqV_p3J0f-N21HeHzR2xplwxalNZDXg9tNmrBCzkegX1lN53ROI_HVoUZBPtdxZLuDyvUsYdNoLRb2-z6HMtJoTXRYQ true 45ca3dc3d5fd true
Мы намеренно укоротили исходный код, убрав повторяющиеся элементы. Теперь мы можем описать логику парсинга всех нужных нам полей, а также добавить ограничитель на количество подгрузок, скажем, 10. Также мы добавим паузу, для менее агрессивного парсинга. В результате мы получим финальную версию нашего парсера Instagram.
--- config: agent: Firefox debug: 2 do: # Загружаем начальную страницу - walk: to: https://www.instagram.com/instagram/ do: # Ищем элементы script которые подгружают JS - find: path: script[type="text/javascript"] do: # Парсим значение атрибута src - parse: attr: src # Проверяем, нужный ли это Javascript, нам нужен тот, у которого в URL есть строка ProfilePageContainer.js - if: match: ProfilePageContainer\.js do: # Переходим по URL скрипта - walk: to: value do: # Ищем элемент, содержащий искомый JS - find: path: script do: # Парсим контент элемента, используя фильтр с регулярным выражением - parse: filter: profilePosts\.byUserId\.get[^,]+,queryId\:\&\s*quot\;([^&]+)\&\s*quot\; # Сохраняем полученное значение в переменной - variable_set: queryid # находим элемент script, который содержит текст window._sharedData - find: path: script:contains("window._sharedData") do: - parse - space_dedupe - trim # извлекаем JSON - filter: args: window\._sharedData\s+\=\s+(.+)\s*;\s*$ # конвертим JSON в XML - normalize: routine: json2xml # превращаем XML строку в DOM блок - to_block - find: path: body_safe do: # Находим элемент в котором хранится id канала - find: path: entry_data > profilepage > graphql > user > id do: # Парсим содержимое элемента - parse # Сохраняем полученное значение в переменной - variable_set: chid # Находим элемент в котором хранится rhx_gis - find: path: rhx_gis do: # Парсим содержимое элемента - parse # Сохраняем полученное значение в переменной - variable_set: rhxgis # Находим элементы записей и итерируем по ним - find: path: entry_data > profilepage > graphql > user > edge_owner_to_timeline_media > edges > node do: # Создаем новый объект с именем item - object_new: item # Находим элемент с URL изображения - find: path: display_url do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: url # Находим элемент с описанием записи - find: path: edge_media_to_caption > edges > node > text do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: caption # Находим элемент с флагом видео это или нет - find: path: is_video do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: video # Находим элемент с количеством комментариев - find: path: edge_media_to_comment > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: comments # Находим элемент с количеством лайков - find: path: edge_media_preview_like > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: likes # записываем объект в базу - object_save: name: item # Находим элемент, в котором хранятся данные для подгрузки - find: path: entry_data > profilepage > graphql > user > edge_owner_to_timeline_media > page_info do: # Находим элемент, в котором хранятся данные о наличии следующей страницы - find: path: has_next_page do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: hnp # Читаем содержимое переменной в регистр - variable_get: hnp # Проверяем равно ли значение 'true' - if: match: 'true' do: # Если да, то находим элемент с курсором - find: path: end_cursor do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: cursor # URL-энкодим параметр - eval: routine: js body: '(function () ")>)();' # Сохраняем значение в переменную - variable_set: cursor_encoded # Формируем пул линков и добавляем в него URL на первую подгрузку - link_add: url: https://www.instagram.com/graphql/query/?query_hash=&variables=%7B%22id%22%3A%22%22%2C%22first%22%3A12%2C%22after%22%3A%22%22%7D # Формируем подпись и записываем ее в переменную signature - register_set: ':<"id":"","first":12,"after":"">' - normalize: routine: md5 - variable_set: signature # Устанавливаем счетчик подгрузок в 0 - counter_set: name: pages value: 0 # Итерируем по пулу и загружаем текущий линк и используем подпись в заголовках запроса - walk: to: links headers: x-instagram-gis: x-requested-with: XMLHttpRequest do: - sleep: 3 # Находим элемент, в котором хранятся данные для подгрузки - find: path: edge_owner_to_timeline_media > page_info do: # Находим элемент, в котором хранятся данные о наличии следующей страницы - find: path: has_next_page do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: hnp # Читаем содержимое переменной в регистр - variable_get: hnp # Проверяем равно ли значение 'true' - if: match: 'true' do: # Если да, то проверяем счетчик подгрузок, больше ли он 10 - counter_get: pages - if: type: int gt: 10 else: # Если нет, то находим элемент с курсором - find: path: end_cursor do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: cursor # URL-энкодим параметр - eval: routine: js body: '(function () ")>)();' # Сохраняем значение в переменную - variable_set: cursor_encoded # Формируем пул линков и добавляем в него URL следующей подгрузки - link_add: url: https://www.instagram.com/graphql/query/?query_hash=&variables=%7B%22id%22%3A%22%22%2C%22first%22%3A12%2C%22after%22%3A%22%22%7D # Формируем подпись и записываем ее в переменную signature - register_set: ':<"id":"","first":12,"after":"">' - normalize: routine: md5 - variable_set: signature # Находим элементы записей и итерируем по ним - find: path: edge_owner_to_timeline_media > edges > node do: # Создаем новый объект с именем item - object_new: item # Находим элемент с URL изображения - find: path: display_url do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: url # Находим элемент с описанием записи - find: path: edge_media_to_caption > edges > node > text do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: caption # Находим элемент с флагом видео это или нет - find: path: is_video do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: video # Находим элемент с количеством комментариев - find: path: edge_media_to_comment > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: comments # Находим элемент с количеством лайков - find: path: edge_media_preview_like > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: likes # записываем объект в базу - object_save: name: item # Увеличим счетчик подгрузок на 1 - counter_increment: name: pages by: 1
Теперь мы пожем перевести наш диггер в Активный режим и запустить его. Как результат в вашем наборе данных будут подобные записи.
Надеемся что данная статья поможет вам в изучении мета-языка и теперь вы сможете решать задачи по парсингу страниц с подгрузкой без затруднений. В качестве домашнего задания, попробуйте разобраться с тем как работает версия парсера для поиска по хэштегам. Ниже приведим код парсера:
--- config: agent: Firefox debug: 2 do: # Инициальзируем переменную в которую записываем hashtag - variable_set: field: tag value: beard # Загружаем начальную страницу - walk: to: https://www.instagram.com/explore/tags/ do: # Ищем элементы script которые подгружают JS - find: path: script[type="text/javascript"] do: # Парсим значение атрибута src - parse: attr: src # Проверяем, нужный ли это Javascript, нам нужен тот, у которого в URL есть строка Consumer.js - if: match: Consumer\.js do: # Переходим по URL скрипта - walk: to: value do: # Ищем элемент, содержащий искомый JS - find: path: script do: # Парсим контент элемента, используя фильтр с регулярным выражением - parse: filter: T\.pagination\>,queryId\:\&\s*quot\;([^&]+)\&\s*quot\; # Сохраняем полученное значение в переменной - variable_set: queryid # находим элемент script, который содержит текст window._sharedData - find: path: script:contains("window._sharedData") do: - parse - space_dedupe - trim # извлекаем JSON - filter: args: window\._sharedData\s+\=\s+(.+)\s*;\s*$ # конвертим JSON в XML - normalize: routine: json2xml # превращаем XML строку в DOM блок - to_block - find: path: body_safe do: # Находим элемент в котором хранится rhx_gis - find: path: rhx_gis do: # Парсим содержимое элемента - parse # Сохраняем полученное значение в переменной - variable_set: rhxgis # Находим элементы записей и итерируем по ним - find: path: entry_data > tagpage > graphql > hashtag > edge_hashtag_to_media > edges > node do: # Создаем новый объект с именем item - object_new: item # Находим элемент с URL изображения - find: path: display_url do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: url # Находим элемент с описанием записи - find: path: edge_media_to_caption > edges > node > text do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: caption # Находим элемент с флагом видео это или нет - find: path: is_video do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: video # Находим элемент с количеством комментариев - find: path: edge_media_to_comment > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: comments # Находим элемент с количеством лайков - find: path: edge_media_preview_like > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: likes # записываем объект в базу - object_save: name: item # Находим элемент, в котором хранятся данные для подгрузки - find: path: entry_data > tagpage > graphql > hashtag > edge_hashtag_to_media > page_info do: # Находим элемент, в котором хранятся данные о наличии следующей страницы - find: path: has_next_page do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: hnp # Читаем содержимое переменной в регистр - variable_get: hnp # Проверяем равно ли значение 'true' - if: match: 'true' do: # Если да, то находим элемент с курсором - find: path: end_cursor do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: cursor # URL-энкодим параметр - eval: routine: js body: '(function () ")>)();' # Сохраняем значение в переменную - variable_set: cursor_encoded # Формируем пул линков и добавляем в него URL на первую подгрузку - link_add: url: https://www.instagram.com/graphql/query/?query_hash=&variables=%7B%22tag_name%22%3A%22%22%2C%22first%22%3A12%2C%22after%22%3A%22%22%7D # Формируем подпись и записываем ее в переменную signature - register_set: ':<"tag_name":"","first":12,"after":"">' - normalize: routine: md5 - variable_set: signature # Устанавливаем счетчик подгрузок в 0 - counter_set: name: pages value: 0 # Итерируем по пулу и загружаем текущий линк и используем подпись в заголовках запроса - walk: to: links headers: x-instagram-gis: x-requested-with: XMLHttpRequest do: - sleep: 3 # Находим элемент, в котором хранятся данные для подгрузки - find: path: edge_hashtag_to_media > page_info do: # Находим элемент, в котором хранятся данные о наличии следующей страницы - find: path: has_next_page do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: hnp # Читаем содержимое переменной в регистр - variable_get: hnp # Проверяем равно ли значение 'true' - if: match: 'true' do: # Если да, то проверяем счетчик подгрузок, больше ли он 10 - counter_get: pages - if: type: int gt: 10 else: # Если нет, то находим элемент с курсором - find: path: end_cursor do: # Парсим содержимое элемента - parse # Сохраняем значение в переменную - variable_set: cursor # URL-энкодим параметр - eval: routine: js body: '(function () ")>)();' # Сохраняем значение в переменную - variable_set: cursor_encoded # Формируем пул линков и добавляем в него URL следующей подгрузки - link_add: url: https://www.instagram.com/graphql/query/?query_hash=&variables=%7B%22tag_name%22%3A%22%22%2C%22first%22%3A12%2C%22after%22%3A%22%22%7D # Формируем подпись и записываем ее в переменную signature - register_set: ':<"tag_name":"","first":12,"after":"">' - normalize: routine: md5 - variable_set: signature # Находим элементы записей и итерируем по ним - find: path: edge_hashtag_to_media > edges > node do: # Создаем новый объект с именем item - object_new: item # Находим элемент с URL изображения - find: path: display_url do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: url # Находим элемент с описанием записи - find: path: edge_media_to_caption > edges > node > text do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: caption # Находим элемент с флагом видео это или нет - find: path: is_video do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: video # Находим элемент с количеством комментариев - find: path: edge_media_to_comment > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: comments # Находим элемент с количеством лайков - find: path: edge_media_preview_like > count do: # Парсим содержимое элемента - parse # Записываем значение в поле объекта item - object_field_set: object: item field: likes # записываем объект в базу - object_save: name: item # Увеличим счетчик подгрузок на 1 - counter_increment: name: pages by: 1
