petrinh1988
Light Weight
- Депозит
- $0
Автор petrinh1988
Источник https://xss.is
Для атак на веб-приложения, нужна база сайтов, вот мой вариант массового сбора по доркам. Кроме того, считаю, что Google Apps Script сильно недооценен в сообществе, поэтому кроме основной темы, разберу пару интересных примеров.
Есть куча инструментов, которые позволяют собирать сайты, но не всегда ими удобно пользоваться. Ну или не выгодно. Тот же A-Parser или Zenno стоит денег. Плюс нагрузка на комп и сеть. GAS же позволяет парсить параллельно другим процессам, не требуя дополнительных ресурсов. Поэтому, я решил использовать возможности Google Sheets и Google Apps Script.
Стратегия работы такая: пишу парсер на GAS для Google-таблицы, делаю кучу копий и получаю результат. Все это без прокси, танцев с бубном и на мощностях Google.
Что такое GAS?
Начнем с базы. Google Apps Script - это, как понятно из названия, скриптовый язык Google. Как Visual Basic for Application в продуктах MS Office. Он также охватывает большую часть продуктов и сервисов Google.
Sheets, Docs, Drive, Gmail, Calendar — этим всем можно спокойно оперировать при помощи скриптов. Сам по себе язык, это одна из реализаций Javascript. Поэтому, если есть базовые знания JS, никаких проблем не возникнет. Чтобы писать полноценные решения, нужно будет просто посмотреть, какие объекты (интерфейсы) представляет GAS. Ну и разобраться с некоторым несложным устройством, а также с замороченными правами доступа.
Сами проекты, которые используются в ваших Google-аккаунтах, можно посмотреть по адресу https://script.google.com/home Скрипт может быть привязан к той же таблице (создаваться из нее), тогда при копировании таблицы будет копироваться и скрипт.
Важные детали перед началом
GAS имеет ограничения на количество исходящих запросов. Раньше было 100 000 запросов в сутки с аккаунта, сейчас 20 000. Т.е., если потребуется большое количество парсингов, потребуется большое количество аккаунтов. Повторюсь — ограничение для аккаунта, а не таблицы или чего-то другого. Суммируются все исходящие запросы, запросы к опубликованному приложению не считаются. По крайней мере я не видел таких квот.
Для парсинга использую сервис. Почему? Потому что так отпадает множество вопросов. Не нужно париться по поводу распарсивания самой страницы. Подобные сервисы берут данные из Google через XML API и нет возни с подозрениями Google, гаданием каптчей и т.п. Просто сделали запрос и получили результат от 0 до 100 записей. Если пихать дорки в поиск Google, он быстро задастся вопросом - а чего это ты так активно пользуешься дорками? Очередной плюс парсинга через XML API Google в том, что прокси не нужны.
Сервис можете выбрать любой, а не тот которым пользуюсь я. Не рекламирую, реферальных ссылок не распространяю, каких-то других плюшек не имею, к сервису отношения не имею, только пользуюсь. Возможно, что это самый хреновый из сервисов и я делаю большую глупость, работая с ним. Честно сказать, особо не задавался вопросом выбора, возможно есть более быстрые и более дешевые. Если знаете такой, поделитесь в комментариях.
В моем случае, стоимость 1000 запросов 20 руб. Т.е. за 20 рублей можно получить до 100 000 сайтов. Хотя на практике, будут дубли, как ты с ними не борись Будут “пролазить” крупные порталы разработчиков и всякие вопросы-ответы.. Ну и не всегда можно получить сотню сайтов… бывает и ноль. Виноват, заголовок кликбейтный... Не лишним, перед запуском парсинга, глазками пробежаться по базе дорков, пройтись руками и посмотреть, какие сайты заминусить. Типа github, youtube, stackoverflow и т.п. Для каждого отдельного дорка сайты будут разные.
Можно заморочиться и написать свой код, который будет точно так же парсить Google используя XML API. Но я в этом моменте не разбирался. Единственное, нашел справку и попробовал выполнить запрос из примера, результат работы:
Пошаговая инструкция
Как ни странно, начинаю с создания таблицы. Вбиваю в браузере sheets.new и получаю готовую табличку. Да, если кто не в курсе, Google купил домены sheets.new для создания таблиц и doc.new для быстрого создания документов. Документ назову “Parser”
Назову лист “dorks” для дорков и создам еще один с именем “results”. Вам захотеться добавить дополнительные листы для сращивания. Например, лист содержащий регионы. Таким образом, можно было бы обойти все дорки для разных регионов поиска. Но тогда нужно дописывать кучу циклов, обходящих дополнительные листы и код становится неудобным для поддержки и оптимизации. Да и время парсинга увеличится, так как будет запущена одна очередь. Все же, рекомендую разделять и властвовать. Только для примера приведу кусок кода со сращиванием.
Иду в верхнее меню Extensions -> Apps Script и попадаю в проект GAS Переименовываю, кликнув по названию, чтобы было понятно к какой таблице относится скрипт. Когда их становится под сотню, названия очень помогают.
GAS-проект, созданный таким способом, будет привязан к самой таблице, а значит будет вместе с ней копироваться!
Прежде, чем идти дальше, обращу внимание на еще одно важное ограничение Google: время выполнения скрипта ограничено шестью минутами. Парсинг большого количества запросов явно превысит предел в 360 секунд. Особенно, учитывая неторопливое добавление данных в таблицу. Я выработал следующую стратегию:
Мне удобнее, когда есть хоть какое-то разделение кода. Жму плюсик вверху слева, выбираю “Script” и переименовываю gs-файл в const. Здесь будут лежать все необходимые глобальные константы.
Потребуется константа для хранения ключа апи сервиса, константа для айди пользователя. Добавлю константу с идентификатором региона и самим адресом для запросов. Сами значения беру из сервиса.
Если будете пользоваться тем же сервисом, потребуются константы, которые я тщательно замазал… тщательно, т.к. Местные по обрубкам пикселей цифр смогут user id восстановить))))
JavaScript: Скопировать в буфер обмена
SHEET_DORKS и SHEET_RESULT сюда же, чтобы дальше было удобнее и управляемее.
Теперь, если надо поменять регион парсинга, можно это сделать в полтора клика, заменив константу, а не копать код в поисках нужной строчки.
Создаю запускающую функцию startParsing:
JavaScript: Скопировать в буфер обмена
Скрипт получает параметр из ScriptProperties, если он пустой, считает это первым сканированием и инициализирует переменные. После переходит к самому парсингу.
parseInt() используется потому как параметры текстовые, более того, при сохранении приводятся к виду “1.0”. По идее, JS должен прекрасно пониматЬ, что речь идет о единице, но в данном случае нет.
Обращаю внимание на то, что первая строка задается как 1. Дело в том, что мы будем работать с таблицей гугла, а там нумерация начинается с 1, не с 0! Видмо, гугл сделал для удобства, чтобы проблемную строку таблицы было удобно искать.
Переменная startTime нужна для отслеживания времени выполнения и инициализируется при запуске скрипта.
Основная функция всего скрипта
Первым делом, получаю объект таблицы. Так как скрипт создавался из самой таблицы, он к таблице привязан и для него активный Spreadsheet будет нужной таблицей.
JavaScript: Скопировать в буфер обмена
Следующим шагом, получаю интересующие листы по их названию. Как писал вначале, это
JavaScript: Скопировать в буфер обмена
Результаты будут парситься и добавляться в отдельной функции, но чтобы каждый раз не пинать Google Apps Script на предмет получения ссылки на лист, будем передавать его параметром.
Для запуска главного цикла не хватает номера последней заполненной строки. Получить ее можно используя метод lastRow(). Но чтобы цикл прошел до конца, прибавлю единичку.
JavaScript: Скопировать в буфер обмена
Внутри цикла, первым делом, скрипт проверяет текущее время выполнения. Если мы близки к порогу, прекращаем выполнение. Далее скрипт берет нужный ключ с листа дорков. Для этого надо получить нужный диапазон, указав строку и ячейку (getRange). После вытащить из него значение через getValue().
JavaScript: Скопировать в буфер обмена
Запрос к API и сохранение результатов реализую отдельными методами. Чуть позже пригодится такой подход. Да и как-то профессиональнее что ли…
JavaScript: Скопировать в буфер обмена
В самом конце цикла, нужно увеличить номер строки с ключом на единичку, чтобы при следующем запуске скрипт стартовал с правильной строки. Я же не знаю, может уже время работы скрипта подходит к концу и пора бы свернуться.
JavaScript: Скопировать в буфер обмена
Итоговая главная функция выглядит так:
JavaScript: Скопировать в буфер обмена
Функция сохранения:
Для сохранения информации предпочитаю appendRow(). Есть другой вариант, получать последний range и писать данные в него через setValues(). Но тогда придется самому контролировать с каким диапазоном работать, не перезаписываю ли какие-то данные и не надо ли в лист добавить строк. Второй подход, по ощущениям, работает чуть быстрее, но как-то лениво его использовать…
JavaScript: Скопировать в буфер обмена
Функция проста, как банка огурцов. Из всего json забираем только results После проходим циклом, выгружая значения объекта в табличку. Первые две ячейки оставляю пустыми. К ним вернемся позже, при нормализации данных.
Последней реализую функцию запроса к API. В ней нет ничего сложного. Для выполнения запросов в GAS есть объект URLFetch. Просто вызываю его метод fetch() с нужными параметрами. Из функции возвращаю тело, преобразованное в JSON.
JavaScript: Скопировать в буфер обмена
Для тех, кто не знаком или плохо знаком с JS — в url помещается строка, которая зажата в литеральные кавычки (буква ё с английской раскладкой). Эти кавычки позволяют использовать подстановки ${...} прям как в BASH. Ну или как f”{...}” в Python. Внутри может быть переменная, вызов функции и т.п. Все значения, которые могут подставляться параметрам, взяты из сервиса. В данном случае мы получаем максимум 100 значений (максимум сервиса), 37 это домен google.com, lr — регион США.
И последний параметр, но не последний по важности, это filter=1 - в моем случае, этот параметр отвечает за отображение скрытых результатов. Сами понимаете, что там гугл может спрятать крайне полезную информацию.
Первый запуск
Итак, у нас получилось четыре функции, которые полностью реализу нужный нам парсинг. Можно жать на кнопу “Run” и…. не спешим радоваться и не отчаиваемся. Google хочет убедиться, что мы действительно понимаем, что запускаем скрипт и для этого просит подтверждения.
Жмем “Review permissions” и выбираем нужный аккаунт для авторизации. После жмем на слабо заметную ссылку Advanced.
Да, Google постарался максимально усложнить процесс запуска скрипта, чтобы усложнить жизнь честным хацкерам и скамерам. Жмем на Go … (unsafe)
После нажатия Allow, на почту прилетит письмо о предоставленном доступе и скрипт, наконец-то, выполнится. Должен выполниться без ошибок, если все написано правильно и есть баланс. Если что-то пошло не так, внизу появится ошибка.
Что делать в случае ошибки?
Триггер для автозапуска
Чтобы все свелось к добавлению новых дорков и сбросу счетчиков на 1, осталось добавить автозапуск. Жмем на часики слева и попадаем в список триггеров. Добавляем новый. Параметры, как на картинке:
Все, каждые 5 минут будет запускаться функция startParsing. Версия Head - это исходники. Time-driven, соответственно, запуск по времени. Запуск по минутам, каждые пять минут. Отчет о запусках ежедневно.
К слову об отчетах, в левой панели, прямо по часиками есть пункт “Execute” (запуски). Это полноценный лог всех запусков проекта. Там есть время запуска, время выполнения, тип запуска и куча полезной информации. Чтобы посмотреть ошибки, жмем на нужный запуск. Но главное, что все консоль логи попадают сюда...
Логирование проекта
В большинстве случаев, достаточно выводить данные в консоль, через console.log() или Logger.log(). Но бывают ситуации, когда таким образом данные не удастся получить, а распечатка данных нужна. Или, например, нужен быстрый доступ к списку запросов полученных через doGet() или doPost(). В этом случае, можно сделать отдельный лист “log” и добавлять на него данные через appendRow[]
JavaScript: Скопировать в буфер обмена
Ускоряем парсинг
Если надо парсить большое количество дорков, лучше разбить их на разные файлы. Создали основную таблицу, сделали хоть 100 копий, сбросили переменные и добавили триггеры. При копировании в рамках одного аккаунта, скрипты нормально копируются. Если копировать между аккаунтами, могут возникнуть коллизии. Лучше создавать таблицы заново.
Получение данных из таблицы GET-запросом без заморочек
Отлично, сайты парсятся и можно руками что-то с ними делать. Но разве ради этого мы всю эту историю с автоматизацией придумали? Чтобы парсить, а потом руками куда-то переносить? Нет, поэтому напишем простой код для получения данных. В этом нам помогут быстрые триггеры doGet() или doPost(). Чем они занимаются, понятно из названий — обрабатывают GET и POST запросы к нашему веб-приложению.
Вэб приложени? Да! Фишка скриптов Google в том, что их можно опубликовать, как полноценное приложение. Более того, можно даже веб-морду прикрутить, но это не является темой нашего урока, поэтому не отвлекаемся.
Для начала напишем простую функцию получения данных:
JavaScript: Скопировать в буфер обмена
Чтобы приложение GAS дало нам ответ, нам нужно сделать правильные return из doGet(). В этом нам поможет интерфейс ContentService. Функция createTextOutput сформирует правильный HTTP-ответ. Не важно, возвращаем мы просто текст, CSV или JSON, нужна именно текстовая функция. Ну и, как видно из кода, чтобы задать конкретный Content-Type, добавляем его через setMimeType используя константы хранящиеся в ContentService.MimeType.
Следующим шагом нужно задеплоить приложение. Важная оговорка — в конце деплоя будет предоставлен адрес для доступа к приложениею. По этому адресу будет открываться последняя версия опубликованного приложения. Если после публикации были внесены изменения в код, они не будут работать, так как версия исходников и деплоя будет отличаться. Поэтому, важно следить, чтобы была задеплоена актуальная версия, иначе можно часами искать ошибку и не понимать, почему код не работает.
Справа вверху жмем Deploy > New Deployment и видим такое окошко.
Кликаем на шестеренку слева, выбираем “Web app”. Указываем от чьего имени будет выполняться приложение. В нашем случае выбираем Me. В “Who has access” указываем “Anyone”. Именно такие параметры, так как нам нужен простой прямой доступ к данным. В ином случае, надо заморачиваться с авторизациями и правами. А так, делаем из Python обычный get и как хотим оперируем данными.
На последнем шаге, Google дает нам идентификатор веб-приложения и ссылку для доступа. Копируем ссылку и жмем Done. Переходим по ссылке и видим надпись “XSS.is”. Ура, веб-приложение как-то но работает.
Заставим функцию делать то, что нужно нам:
Параметры получаю следующим образом:
JavaScript: Скопировать в буфер обмена
Объект, который получаем на входе содержит в себе ряд важных свойств. При работе с GET-параметрами, чаще всего нужен parameters. Из него, методом десириализации, получаю две нужных переменных. Если бы мы работали с doPost() и входными данными POST-запроса, мы бы брали данные из e.postData.contents. Эта информация для тех, кто хочет углубиться, добавив функционала.
Следующим шагом, получаю ссылку на таблицу уже известным способом:
JavaScript: Скопировать в буфер обмена
Далее делаю пару проверок. Во-первых, если у нас offset больше или равен количеству строк, можно сразу вернуть пустой объект. Во-вторых, проверю, чтобы значение офсета было больше 0 (помните, что в таблицах данные с единички?). Ну и ограничу максимальное количество в ответе тысячей строк и сделаю проверку на забывчивость:
JavaScript: Скопировать в буфер обмена
Остается только получить данные из таблицы и вернуть их:
JavaScript: Скопировать в буфер обмена
Как и раньше, используем getRange(). Отличие только в третьем параметре, им мы указываем количество нужных строк. Если бы и ячеек надо было несколько, дописали бы четвертый параметр. Дальше работает функция getValues(), которая возвращает массив массивов. В нашем случае это выглядит так:
Соответственно, нам нужно сделать массив плоским, для чего и нужна функция map(el => el[0]), которая в сущности просто возвращает, вместо массива значений, одно значение, которые упаковываются в обычный массив строк.
Возврат ContentService уже знаком, разве что передаем объект, который конвертируется в строку через JSON.stringify(). Сам объект выглядит так:
JSON: Скопировать в буфер обмена
Можно не париться и возвращать просто строками. Все зависит от ваших задач и предпочтений. Мне удобнее получить полноценный объект, который можно удобно построчно обрабатывать. Но если, например, предполагается дальнейшая загрузка в тот же Acunetix через CSV-файлы можно сделать так:
JavaScript: Скопировать в буфер обмена
А в принимающем скрипте на Python просто напрямую писать в нужный файл. Разве что, ограничить количество строк в 500, т.к. из csv окунь больше не принимает.
Скрипт готов, осталось только сделать новый деплой: Deploy > Manage Deployments, в появившемся окне жмем на карандашик, в версиях выбираем New Version и жмем Deploy. После этого, скрипт будет полноценно работать.
Спойлер: Вся функция doGet(e)
JavaScript: Скопировать в буфер обмена
Пример результата:
JSON: Скопировать в буфер обмена
Останется дописать скрипт, например, на Python, который будет перегружать данные из таблицы в тот же Acunetix. Подробнее о том, как создавать таргеты в окуне и генерить новые сканирования, читайте в этой статье. Здесь просто приведу короткий скрипт на питоне, без детальных пояснений, так как они излишни. Единственное, обращу внимание на то, где взять ID приложения. Помните мы делали деплой? Там в окне был нужный нам ID. Для его получения можно зайти в Deploy -> Manage Deployment
Python: Скопировать в буфер обмена
Нормализация данных
Парсить научились, отдавать данные тоже. Но есть нюанс — URL’ы являются полноценными ссылками, с указанием путей и GET-параметров. Много где это может мешать. Например, sqlmap полезно дать полный урл, Acunetix надо бы домен, а какому-нибудь DNS-дамперу хост. Нужно все это дело нормализовать и привести к удобному виду.
В оригинале, предпочитаю чтобы у меня были следующие данные:
JavaScript: Скопировать в буфер обмена
Все, что делает getClearURLData():
1. Выделяет из урла протокол
2. Разбивает оставшийся после первой операции хвост, и берет оттуда первый элемент - это хост
3. Собирает все обратно в удобный объект.
Парсинг без использования сервисов
Вероятно, у вас возникнет желание парсить что либо еще, без использования сервисов и API, а в лоб через DOM. Тогда на помощь нам придет возможность подключать сторонние библиотеки, а именно Cheerio. Вот ссылка на проект Cheerio для GAS https://github.com/tani/cheeriogs
Чтобы подключить его, в проекте GAS слева жмем плюсик возле надписи Libraries. В появившееся окно вбиваем идентификатор библиотеки 1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0 Это точно такой же ID, который нам выдает Deploy приложения.
После нажатия на Look up, окно приобретает такой вид. Оставляем последнюю версию и жмем Add. Если потребуется, всегда можно будет кликнуть на библиотеку и заменить версию.
Теперь доступен объект Cheerio со всем его функционалом. Вот пример использования из справки:
JavaScript: Скопировать в буфер обмена
После обработки контента через Cheerio, становится доступна работа с контентом, практически как с обычным DOM через jQuery. Для примера приведу парсер прокси с одного из тонны сайтов-листингов бесплатных прокси. Пример намеренно выстроен таким образом, чтобы максимально просто показать работу с библиотекой:
JavaScript: Скопировать в буфер обмена
Сначала находим таблицу, вернее сразу ее тело. Следом берем все tr, приводим к массиву и обходим их, вытаскивая нужные ячейки таблицы. На выходе у нас массив проксей и портов:
Итоги
В этой статье, на реальном примере, разобрал как можно использовать возможности Google Apps Script для парсинга целей. Хоть это и реальный рабочий пример, но по сути только верхушка айсберга возможностей. GAS позволяет творить очень много интересного. Вот некоторые мысли:
Можно прикрутить не только парсинг сайтов, но и наполнение базы нужными данными: статистика посещаемости, ДНС-реверс, фаззинг и т.д. Можно прикрутить различные сервисы для сбора данных так же по API или варварски через Cheerio, Можно внешними скриптами наполнять данные по результатам работы инструментов (например, все тот же окунь). У вас есть механизм, который может полноценно работать сам по себе, не требуя ваших ресурсов и вмешательства.
Никто не мешает в контент сервисе указать MIME-type “JAVASCRIPT” и через вебприложение гугла отдавать полноценный скрипт. Да, видимо в борясь с хацкерами, которые использовали подобное для XSS атак для обхода политик безопасности, Google перенес приложения на домен script.googleusercontent.com но в любом случае, подобное хранилище скриптов может оказаться полезным. Как минимум, не нужны сервера, не нужен отдельный домен.
Те же телеграм-боты спокойно цепляются к GAS вебхуком. А все остальная инфраструктура Google? Мы ведь даже не коснулись ее. Между тем, мне в смартфон до сих пор ежедневно сыплются десятками уведомления от календаря по типу “Аня отправила вам видео” или “Сбербанк: поступил перевод”. Не лазил в этим темы, но скорее всего, реализованы они именно через GAS.
Я постарался максимально понятно и подробно донести свои мысли. Если интересно развитие темы применения GAS в нашей сфере, дайте знать любым удобным способом и я обязательно выдам что-то очень интересное.
Спойлер: Полный код code.gs
JavaScript: Скопировать в буфер обмена
Спойлер: Код const.gs
JavaScript: Скопировать в буфер обмена
Источник https://xss.is
Для атак на веб-приложения, нужна база сайтов, вот мой вариант массового сбора по доркам. Кроме того, считаю, что Google Apps Script сильно недооценен в сообществе, поэтому кроме основной темы, разберу пару интересных примеров.
Есть куча инструментов, которые позволяют собирать сайты, но не всегда ими удобно пользоваться. Ну или не выгодно. Тот же A-Parser или Zenno стоит денег. Плюс нагрузка на комп и сеть. GAS же позволяет парсить параллельно другим процессам, не требуя дополнительных ресурсов. Поэтому, я решил использовать возможности Google Sheets и Google Apps Script.
Стратегия работы такая: пишу парсер на GAS для Google-таблицы, делаю кучу копий и получаю результат. Все это без прокси, танцев с бубном и на мощностях Google.
Что такое GAS?
Начнем с базы. Google Apps Script - это, как понятно из названия, скриптовый язык Google. Как Visual Basic for Application в продуктах MS Office. Он также охватывает большую часть продуктов и сервисов Google.
Sheets, Docs, Drive, Gmail, Calendar — этим всем можно спокойно оперировать при помощи скриптов. Сам по себе язык, это одна из реализаций Javascript. Поэтому, если есть базовые знания JS, никаких проблем не возникнет. Чтобы писать полноценные решения, нужно будет просто посмотреть, какие объекты (интерфейсы) представляет GAS. Ну и разобраться с некоторым несложным устройством, а также с замороченными правами доступа.
Сами проекты, которые используются в ваших Google-аккаунтах, можно посмотреть по адресу https://script.google.com/home Скрипт может быть привязан к той же таблице (создаваться из нее), тогда при копировании таблицы будет копироваться и скрипт.
Важные детали перед началом
GAS имеет ограничения на количество исходящих запросов. Раньше было 100 000 запросов в сутки с аккаунта, сейчас 20 000. Т.е., если потребуется большое количество парсингов, потребуется большое количество аккаунтов. Повторюсь — ограничение для аккаунта, а не таблицы или чего-то другого. Суммируются все исходящие запросы, запросы к опубликованному приложению не считаются. По крайней мере я не видел таких квот.
Для парсинга использую сервис. Почему? Потому что так отпадает множество вопросов. Не нужно париться по поводу распарсивания самой страницы. Подобные сервисы берут данные из Google через XML API и нет возни с подозрениями Google, гаданием каптчей и т.п. Просто сделали запрос и получили результат от 0 до 100 записей. Если пихать дорки в поиск Google, он быстро задастся вопросом - а чего это ты так активно пользуешься дорками? Очередной плюс парсинга через XML API Google в том, что прокси не нужны.
Сервис можете выбрать любой, а не тот которым пользуюсь я. Не рекламирую, реферальных ссылок не распространяю, каких-то других плюшек не имею, к сервису отношения не имею, только пользуюсь. Возможно, что это самый хреновый из сервисов и я делаю большую глупость, работая с ним. Честно сказать, особо не задавался вопросом выбора, возможно есть более быстрые и более дешевые. Если знаете такой, поделитесь в комментариях.
В моем случае, стоимость 1000 запросов 20 руб. Т.е. за 20 рублей можно получить до 100 000 сайтов. Хотя на практике, будут дубли, как ты с ними не борись Будут “пролазить” крупные порталы разработчиков и всякие вопросы-ответы.. Ну и не всегда можно получить сотню сайтов… бывает и ноль. Виноват, заголовок кликбейтный... Не лишним, перед запуском парсинга, глазками пробежаться по базе дорков, пройтись руками и посмотреть, какие сайты заминусить. Типа github, youtube, stackoverflow и т.п. Для каждого отдельного дорка сайты будут разные.
Можно заморочиться и написать свой код, который будет точно так же парсить Google используя XML API. Но я в этом моменте не разбирался. Единственное, нашел справку и попробовал выполнить запрос из примера, результат работы:
Прямой парсинг Google через GAS не получится. Парсер сразу отлетает на сообщение о роботизированном трафике. Проблем приводящих к этому несколько:
1. IP давно известны "шалостями", т.к. запросы идут с определенных серверов Гугла, которыми пользовались другие люди...
2. На текущий момент, нет способа прикрутить прокси к Google. Только костыли, а в этом случае нет смысла в представленной схеме... тогда уж проще взять любую связьку, где будет использоваться какой-то вебдрайвер
3. Даже если бы прокси проходили, в официальной документации нет ничего про юзер-агенты. Народ пытается пихать, но насколько в этом есть смысл, не проверял.
Нажмите, чтобы раскрыть...
Пошаговая инструкция
Как ни странно, начинаю с создания таблицы. Вбиваю в браузере sheets.new и получаю готовую табличку. Да, если кто не в курсе, Google купил домены sheets.new для создания таблиц и doc.new для быстрого создания документов. Документ назову “Parser”
Назову лист “dorks” для дорков и создам еще один с именем “results”. Вам захотеться добавить дополнительные листы для сращивания. Например, лист содержащий регионы. Таким образом, можно было бы обойти все дорки для разных регионов поиска. Но тогда нужно дописывать кучу циклов, обходящих дополнительные листы и код становится неудобным для поддержки и оптимизации. Да и время парсинга увеличится, так как будет запущена одна очередь. Все же, рекомендую разделять и властвовать. Только для примера приведу кусок кода со сращиванием.
Иду в верхнее меню Extensions -> Apps Script и попадаю в проект GAS Переименовываю, кликнув по названию, чтобы было понятно к какой таблице относится скрипт. Когда их становится под сотню, названия очень помогают.
GAS-проект, созданный таким способом, будет привязан к самой таблице, а значит будет вместе с ней копироваться!
Прежде, чем идти дальше, обращу внимание на еще одно важное ограничение Google: время выполнения скрипта ограничено шестью минутами. Парсинг большого количества запросов явно превысит предел в 360 секунд. Особенно, учитывая неторопливое добавление данных в таблицу. Я выработал следующую стратегию:
- Скрипт запускается по триггеру. Триггер основан на времени, запуск каждые 5 минут. Триггер запускает Head версию, хотя можно заморочиться с версионностью, но у нас скрипт на 10 строчек…
- Скрипт будет обрабатывать лист с дорками, проходя по каждому.Номер последней строки по которой были получены данные, надо где-то хранить. Иначе будем ходить по кругу по первым строчкам. Для хранения таких вещей отлично подходят параметры скрипта.
- Так как триггер запускает код каждые 5 минут, чтобы один и тот же скрипт не запускался в параллели, добавляю контроль времени выполнения. Можно, конечно, повесить распараллеливание на Google, запуская скрипт на выполнение хоть каждую минуту и перед каждой итерацией запрашивать последнюю взятую в работу строку. Но может начаться хаос.
Мне удобнее, когда есть хоть какое-то разделение кода. Жму плюсик вверху слева, выбираю “Script” и переименовываю gs-файл в const. Здесь будут лежать все необходимые глобальные константы.
Потребуется константа для хранения ключа апи сервиса, константа для айди пользователя. Добавлю константу с идентификатором региона и самим адресом для запросов. Сами значения беру из сервиса.
Если будете пользоваться тем же сервисом, потребуются константы, которые я тщательно замазал… тщательно, т.к. Местные по обрубкам пикселей цифр смогут user id восстановить))))
JavaScript: Скопировать в буфер обмена
Code:
const API_URL = `https://xmlstock.com/google/xml/?`;
const API_KEY = `PUT_YOUR_API_KEY_HERE`;
const API_USER = 00000;
const SHEET_DORKS = `dorks`;
const SHEET_RESULTS = `results`;
const MAX_TIME_SEC = 280;
SHEET_DORKS и SHEET_RESULT сюда же, чтобы дальше было удобнее и управляемее.
Теперь, если надо поменять регион парсинга, можно это сделать в полтора клика, заменив константу, а не копать код в поисках нужной строчки.
Создаю запускающую функцию startParsing:
JavaScript: Скопировать в буфер обмена
Code:
let startTime = new Date()
function startParsing() {
let currentDork = parseInt(ScriptProperties.getProperty('currentDork'));
if (!currentDork) {
ScriptProperties.setProperty('currentDork', 1);
currentDork = 1;
}
startParsinп(currentDork)
}
Скрипт получает параметр из ScriptProperties, если он пустой, считает это первым сканированием и инициализирует переменные. После переходит к самому парсингу.
parseInt() используется потому как параметры текстовые, более того, при сохранении приводятся к виду “1.0”. По идее, JS должен прекрасно пониматЬ, что речь идет о единице, но в данном случае нет.
Обращаю внимание на то, что первая строка задается как 1. Дело в том, что мы будем работать с таблицей гугла, а там нумерация начинается с 1, не с 0! Видмо, гугл сделал для удобства, чтобы проблемную строку таблицы было удобно искать.
Переменная startTime нужна для отслеживания времени выполнения и инициализируется при запуске скрипта.
Основная функция всего скрипта
Первым делом, получаю объект таблицы. Так как скрипт создавался из самой таблицы, он к таблице привязан и для него активный Spreadsheet будет нужной таблицей.
JavaScript: Скопировать в буфер обмена
const xss = SpreadsheetApp.getActiveSpreadsheet();
Следующим шагом, получаю интересующие листы по их названию. Как писал вначале, это
JavaScript: Скопировать в буфер обмена
Code:
const sheetDorks = xss.getSheetByName(SHEET_DORKS);
const sheetResults = xss.getSheetByName(SHEET_RESULTS);
Результаты будут парситься и добавляться в отдельной функции, но чтобы каждый раз не пинать Google Apps Script на предмет получения ссылки на лист, будем передавать его параметром.
Для запуска главного цикла не хватает номера последней заполненной строки. Получить ее можно используя метод lastRow(). Но чтобы цикл прошел до конца, прибавлю единичку.
JavaScript: Скопировать в буфер обмена
const lastDork = sheetDorks.getLastRow() + 1;
Внутри цикла, первым делом, скрипт проверяет текущее время выполнения. Если мы близки к порогу, прекращаем выполнение. Далее скрипт берет нужный ключ с листа дорков. Для этого надо получить нужный диапазон, указав строку и ячейку (getRange). После вытащить из него значение через getValue().
JavaScript: Скопировать в буфер обмена
Code:
for(let i = currentDork; i < lastDork; i++) {
let currentTime = new Date().getTime()
let seconds = (currentTime - startTime) / 1000
if (seconds > MAX_TIME_SEC) {
console.log('Time end');
return;
}
const dorkValue = sheetDorks.getRange(i, 1).getValue()
// ...
}
Запрос к API и сохранение результатов реализую отдельными методами. Чуть позже пригодится такой подход. Да и как-то профессиональнее что ли…
JavaScript: Скопировать в буфер обмена
Code:
// ...
const result = getDataFromAPI(dorkValue);
parseJSONToSheet_(result , sheetResults, dorkValue);
В самом конце цикла, нужно увеличить номер строки с ключом на единичку, чтобы при следующем запуске скрипт стартовал с правильной строки. Я же не знаю, может уже время работы скрипта подходит к концу и пора бы свернуться.
JavaScript: Скопировать в буфер обмена
Code:
currentDork++;
ScriptProperties.setProperty('currentDork', currentDork);
Итоговая главная функция выглядит так:
JavaScript: Скопировать в буфер обмена
Code:
function startParsing(currentDork) {
const xss = SpreadsheetApp.getActiveSpreadsheet();
const sheetDorks = xss.getSheetByName(SHEET_DORKS);
const sheetResults = xss.getSheetByName(SHEET_RESULTS);
const lastDork = sheetDorks.getLastRow() + 1;
for(let i = currentDork; i < lastDork; i++) {
let currentTime = new Date().getTime();
let seconds = (currentTime - startTime) / 1000;
if (seconds > MAX_TIME_SEC) {
console.log('Time end');
return;
}
const dorkValue = sheetDorks.getRange(i, 1).getValue();
const result = getDataFromAPI(dorkValue);
parseJSONToSheet_(result , sheetResults, dorkValue);
currentDork++;
ScriptProperties.setProperty('currentDork', currentDork);
}
}
Функция сохранения:
Для сохранения информации предпочитаю appendRow(). Есть другой вариант, получать последний range и писать данные в него через setValues(). Но тогда придется самому контролировать с каким диапазоном работать, не перезаписываю ли какие-то данные и не надо ли в лист добавить строк. Второй подход, по ощущениям, работает чуть быстрее, но как-то лениво его использовать…
JavaScript: Скопировать в буфер обмена
Code:
function parseJSONToSheet_(json, sheet, dork) {
const {results} = json
for(let i = json.first; i <= json.last; i++) {
sheet.appendRow(['', '', results[i].url, results[i].title, results[i].passage, ,dork]);
}
}
Функция проста, как банка огурцов. Из всего json забираем только results После проходим циклом, выгружая значения объекта в табличку. Первые две ячейки оставляю пустыми. К ним вернемся позже, при нормализации данных.
Последней реализую функцию запроса к API. В ней нет ничего сложного. Для выполнения запросов в GAS есть объект URLFetch. Просто вызываю его метод fetch() с нужными параметрами. Из функции возвращаю тело, преобразованное в JSON.
JavaScript: Скопировать в буфер обмена
Code:
function getDataFromAPI(dork) {
const url = `${API_URL}?user=${API_USER}&key=${API_KEY}&groupby=100&domain=37&device=desktop&hl=en&lr=2840&filter=1&query=${encodeURIComponent(dork)}`;
const response = UrlFetchApp.fetch(url);
const content = response.getContentText();
const json = JSON.parse(content);
return json;
}
Для тех, кто не знаком или плохо знаком с JS — в url помещается строка, которая зажата в литеральные кавычки (буква ё с английской раскладкой). Эти кавычки позволяют использовать подстановки ${...} прям как в BASH. Ну или как f”{...}” в Python. Внутри может быть переменная, вызов функции и т.п. Все значения, которые могут подставляться параметрам, взяты из сервиса. В данном случае мы получаем максимум 100 значений (максимум сервиса), 37 это домен google.com, lr — регион США.
И последний параметр, но не последний по важности, это filter=1 - в моем случае, этот параметр отвечает за отображение скрытых результатов. Сами понимаете, что там гугл может спрятать крайне полезную информацию.
Первый запуск
Итак, у нас получилось четыре функции, которые полностью реализу нужный нам парсинг. Можно жать на кнопу “Run” и…. не спешим радоваться и не отчаиваемся. Google хочет убедиться, что мы действительно понимаем, что запускаем скрипт и для этого просит подтверждения.
Жмем “Review permissions” и выбираем нужный аккаунт для авторизации. После жмем на слабо заметную ссылку Advanced.
Да, Google постарался максимально усложнить процесс запуска скрипта, чтобы усложнить жизнь честным хацкерам и скамерам. Жмем на Go … (unsafe)
После нажатия Allow, на почту прилетит письмо о предоставленном доступе и скрипт, наконец-то, выполнится. Должен выполниться без ошибок, если все написано правильно и есть баланс. Если что-то пошло не так, внизу появится ошибка.
Что делать в случае ошибки?
- Самый действенный совет олдовых админов —перезагрузи. Да, бывает такая фигня, что Google тупит и не может запустить скрипт. Закрываем скрипт, закрываем табличку, после открываем снова.
- Что-то с вашим сервисом парсинга. Проверьте, что все параметры прописаны верно. Добавьте в функцию запроса, сразу после инициализации url строчку console.log(url)и посмотрите верна ли ссылка. Протестируйте полученную ссылку руками.
- Если проблемы на этапе сохранения, проверьте правильно ли скрипт обрабатывает ответ вашего сервиса.
- Ничего не помогло? Пишите здесь, по возможности отвечу.
Триггер для автозапуска
Чтобы все свелось к добавлению новых дорков и сбросу счетчиков на 1, осталось добавить автозапуск. Жмем на часики слева и попадаем в список триггеров. Добавляем новый. Параметры, как на картинке:
Все, каждые 5 минут будет запускаться функция startParsing. Версия Head - это исходники. Time-driven, соответственно, запуск по времени. Запуск по минутам, каждые пять минут. Отчет о запусках ежедневно.
К слову об отчетах, в левой панели, прямо по часиками есть пункт “Execute” (запуски). Это полноценный лог всех запусков проекта. Там есть время запуска, время выполнения, тип запуска и куча полезной информации. Чтобы посмотреть ошибки, жмем на нужный запуск. Но главное, что все консоль логи попадают сюда...
Логирование проекта
В большинстве случаев, достаточно выводить данные в консоль, через console.log() или Logger.log(). Но бывают ситуации, когда таким образом данные не удастся получить, а распечатка данных нужна. Или, например, нужен быстрый доступ к списку запросов полученных через doGet() или doPost(). В этом случае, можно сделать отдельный лист “log” и добавлять на него данные через appendRow[]
JavaScript: Скопировать в буфер обмена
Code:
const xss = SpreadsheetApp.getActiveSpreadsheet();
const sheetLog = xss.getSheetByName(`log`);
sheetLog.appendRow([new Date(),’logLevel’ , ‘Data to log’]);
Ускоряем парсинг
Если надо парсить большое количество дорков, лучше разбить их на разные файлы. Создали основную таблицу, сделали хоть 100 копий, сбросили переменные и добавили триггеры. При копировании в рамках одного аккаунта, скрипты нормально копируются. Если копировать между аккаунтами, могут возникнуть коллизии. Лучше создавать таблицы заново.
Получение данных из таблицы GET-запросом без заморочек
Отлично, сайты парсятся и можно руками что-то с ними делать. Но разве ради этого мы всю эту историю с автоматизацией придумали? Чтобы парсить, а потом руками куда-то переносить? Нет, поэтому напишем простой код для получения данных. В этом нам помогут быстрые триггеры doGet() или doPost(). Чем они занимаются, понятно из названий — обрабатывают GET и POST запросы к нашему веб-приложению.
Вэб приложени? Да! Фишка скриптов Google в том, что их можно опубликовать, как полноценное приложение. Более того, можно даже веб-морду прикрутить, но это не является темой нашего урока, поэтому не отвлекаемся.
Для начала напишем простую функцию получения данных:
JavaScript: Скопировать в буфер обмена
Code:
function doGet(e) {
return ContentService.createTextOutput('XSS.is').setMimeType(ContentService.MimeType.TEXT)
}
Чтобы приложение GAS дало нам ответ, нам нужно сделать правильные return из doGet(). В этом нам поможет интерфейс ContentService. Функция createTextOutput сформирует правильный HTTP-ответ. Не важно, возвращаем мы просто текст, CSV или JSON, нужна именно текстовая функция. Ну и, как видно из кода, чтобы задать конкретный Content-Type, добавляем его через setMimeType используя константы хранящиеся в ContentService.MimeType.
Следующим шагом нужно задеплоить приложение. Важная оговорка — в конце деплоя будет предоставлен адрес для доступа к приложениею. По этому адресу будет открываться последняя версия опубликованного приложения. Если после публикации были внесены изменения в код, они не будут работать, так как версия исходников и деплоя будет отличаться. Поэтому, важно следить, чтобы была задеплоена актуальная версия, иначе можно часами искать ошибку и не понимать, почему код не работает.
Справа вверху жмем Deploy > New Deployment и видим такое окошко.
Кликаем на шестеренку слева, выбираем “Web app”. Указываем от чьего имени будет выполняться приложение. В нашем случае выбираем Me. В “Who has access” указываем “Anyone”. Именно такие параметры, так как нам нужен простой прямой доступ к данным. В ином случае, надо заморачиваться с авторизациями и правами. А так, делаем из Python обычный get и как хотим оперируем данными.
На последнем шаге, Google дает нам идентификатор веб-приложения и ссылку для доступа. Копируем ссылку и жмем Done. Переходим по ссылке и видим надпись “XSS.is”. Ура, веб-приложение как-то но работает.
Заставим функцию делать то, что нужно нам:
- Получать get-параметры offset и count, чтобы можно было получать данные по частям. Все же, перекидывать десятки тысяч строк — это моветон.
- Формировать осмысленный JSON, с которым потом будет удобно работать в других скриптах
- Возвращать осмысленные данные из таблицы
Параметры получаю следующим образом:
JavaScript: Скопировать в буфер обмена
let {offset,count} = e.parameters;
Объект, который получаем на входе содержит в себе ряд важных свойств. При работе с GET-параметрами, чаще всего нужен parameters. Из него, методом десириализации, получаю две нужных переменных. Если бы мы работали с doPost() и входными данными POST-запроса, мы бы брали данные из e.postData.contents. Эта информация для тех, кто хочет углубиться, добавив функционала.
Следующим шагом, получаю ссылку на таблицу уже известным способом:
JavaScript: Скопировать в буфер обмена
Code:
const xss = SpreadsheetApp.getActiveSpreadsheet();
const sheetResults = xss.getSheetByName(SHEET_RESULTS);
Далее делаю пару проверок. Во-первых, если у нас offset больше или равен количеству строк, можно сразу вернуть пустой объект. Во-вторых, проверю, чтобы значение офсета было больше 0 (помните, что в таблицах данные с единички?). Ну и ограничу максимальное количество в ответе тысячей строк и сделаю проверку на забывчивость:
JavaScript: Скопировать в буфер обмена
Code:
if (offset >= sheetResults.getLastRow()) {
return ContentService.createTextOutput({success:true, count: 0, results:[]}).setMimeType(ContentService.MimeType.JSON)
}
if (!offset || offset < 1) offset = 1
if (!count || count > 1000) count = 1000;
Остается только получить данные из таблицы и вернуть их:
JavaScript: Скопировать в буфер обмена
Code:
const results = sheetResults.getRange(offset, 1, count).getValues().map(el => el[0]).filter(Boolean)
return ContentService.createTextOutput(JSON.stringify({success:true, count: results.length, results})).setMimeType(ContentService.MimeType.TEXT);
Как и раньше, используем getRange(). Отличие только в третьем параметре, им мы указываем количество нужных строк. Если бы и ячеек надо было несколько, дописали бы четвертый параметр. Дальше работает функция getValues(), которая возвращает массив массивов. В нашем случае это выглядит так:
Соответственно, нам нужно сделать массив плоским, для чего и нужна функция map(el => el[0]), которая в сущности просто возвращает, вместо массива значений, одно значение, которые упаковываются в обычный массив строк.
Возврат ContentService уже знаком, разве что передаем объект, который конвертируется в строку через JSON.stringify(). Сам объект выглядит так:
JSON: Скопировать в буфер обмена
Code:
{
success: true,
count: results.length,
results
}
Можно не париться и возвращать просто строками. Все зависит от ваших задач и предпочтений. Мне удобнее получить полноценный объект, который можно удобно построчно обрабатывать. Но если, например, предполагается дальнейшая загрузка в тот же Acunetix через CSV-файлы можно сделать так:
JavaScript: Скопировать в буфер обмена
return ContentService.createTextOutput(results.join(‘,\n’)).setMimeType(ContentService.MimeType.CSV);
А в принимающем скрипте на Python просто напрямую писать в нужный файл. Разве что, ограничить количество строк в 500, т.к. из csv окунь больше не принимает.
Скрипт готов, осталось только сделать новый деплой: Deploy > Manage Deployments, в появившемся окне жмем на карандашик, в версиях выбираем New Version и жмем Deploy. После этого, скрипт будет полноценно работать.
Спойлер: Вся функция doGet(e)
JavaScript: Скопировать в буфер обмена
Code:
function doGet(e) {
let {offset,count} = e.parameters;
const xss = SpreadsheetApp.getActiveSpreadsheet();
const sheetResults = xss.getSheetByName(SHEET_RESULTS);
if (!offset || offset < 1) offset = 1
if (!count || count > 1000) count = 1000;
if (offset >= sheetResults.getLastRow()) {
return ContentService.createTextOutput({success:true, count: 0, results:[]}).setMimeType(ContentService.MimeType.JSON)
}
const results = sheetResults.getRange(offset, 1, count).getValues().map(el => el[0]).filter(Boolean)
return ContentService.createTextOutput(JSON.stringify({success:true, count: results.length, results})).setMimeType(ContentService.MimeType.TEXT);
}
Пример результата:
JSON: Скопировать в буфер обмена
Code:
{
"success": true,
"count": 3,
"results": [
"wipach.si","flutacious.com","naveenautomationlabs.com"
]
}
Останется дописать скрипт, например, на Python, который будет перегружать данные из таблицы в тот же Acunetix. Подробнее о том, как создавать таргеты в окуне и генерить новые сканирования, читайте в этой статье. Здесь просто приведу короткий скрипт на питоне, без детальных пояснений, так как они излишни. Единственное, обращу внимание на то, где взять ID приложения. Помните мы делали деплой? Там в окне был нужный нам ID. Для его получения можно зайти в Deploy -> Manage Deployment
Python: Скопировать в буфер обмена
Code:
import requests
deployment_id = 'your_deployment_id'
offset = 0
count = 100
url = f'https://script.google.com/macros/s/{deployment_id}/exec?offset={offset}&count={count}'
response = requests.get(url=url)
if response.status_code ==200:
print(response.text)
Нормализация данных
Парсить научились, отдавать данные тоже. Но есть нюанс — URL’ы являются полноценными ссылками, с указанием путей и GET-параметров. Много где это может мешать. Например, sqlmap полезно дать полный урл, Acunetix надо бы домен, а какому-нибудь DNS-дамперу хост. Нужно все это дело нормализовать и привести к удобному виду.
В оригинале, предпочитаю чтобы у меня были следующие данные:
- Хост
- Полный домен
- Полный URL страницы
- Title
- Description
- Другие полезные данные, например, статистика посещаемости.
JavaScript: Скопировать в буфер обмена
Code:
function getClearURLData(url) {
const [protocol, tail] = url.split(':');
const host = tail.replace('//','').split('/')[0];
return {
protocol, host, domain: protocol.concat('://', host)
}
}
function parseJSONToSheet_(json, sheet, dork) {
const {results} = json;
for(let i = json.first; i <= json.last; i++) {
const clearURLData = getClearURLData(results[i].url)
console.log('Append data: ', [results[i].url, results[i].title, results[i].passage, ,dork])
sheet.appendRow([clearURLData.host, clearURLData.domain, results[i].url, results[i].title, results[i].passage, ,dork]);
}
}
Все, что делает getClearURLData():
1. Выделяет из урла протокол
2. Разбивает оставшийся после первой операции хвост, и берет оттуда первый элемент - это хост
3. Собирает все обратно в удобный объект.
Парсинг без использования сервисов
Вероятно, у вас возникнет желание парсить что либо еще, без использования сервисов и API, а в лоб через DOM. Тогда на помощь нам придет возможность подключать сторонние библиотеки, а именно Cheerio. Вот ссылка на проект Cheerio для GAS https://github.com/tani/cheeriogs
Чтобы подключить его, в проекте GAS слева жмем плюсик возле надписи Libraries. В появившееся окно вбиваем идентификатор библиотеки 1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0 Это точно такой же ID, который нам выдает Deploy приложения.
После нажатия на Look up, окно приобретает такой вид. Оставляем последнюю версию и жмем Add. Если потребуется, всегда можно будет кликнуть на библиотеку и заменить версию.
Теперь доступен объект Cheerio со всем его функционалом. Вот пример использования из справки:
JavaScript: Скопировать в буфер обмена
Code:
const content = UrlFetchApp.fetch('https://en.wikipedia.org').getContentText()
const $ = Cheerio.load(content);
Logger.log($('p').first().text());
После обработки контента через Cheerio, становится доступна работа с контентом, практически как с обычным DOM через jQuery. Для примера приведу парсер прокси с одного из тонны сайтов-листингов бесплатных прокси. Пример намеренно выстроен таким образом, чтобы максимально просто показать работу с библиотекой:
JavaScript: Скопировать в буфер обмена
Code:
function parseProxy() {
const url = `https://freeproxyupdate.com/fast-response-proxy`;
const html = UrlFetchApp.fetch(url).getContentText();
console.log(html)
const $ = Cheerio.load(html);
const table = $('.list-proxy > tbody').first();
const rows = $(table).find('tr').toArray();
const proxyData = rows.map(el => $(el).find('td').toArray())
.map(cells => [$(cells[0]).text(), $(cells[1]).text()])
console.log(proxyData)
}
Сначала находим таблицу, вернее сразу ее тело. Следом берем все tr, приводим к массиву и обходим их, вытаскивая нужные ячейки таблицы. На выходе у нас массив проксей и портов:
[ [ '167.114.222.149', '27182' ], [ '167.114.222.144', '27182' ], [ '\n\n\n\n', '' ], [ '64.201.163.133', '80' ], [ '138.199.48.1', '8443' ], [ '138.199.48.4', '8443' ], [ '51.124.209.11', '80' ], [ '201.174.239.31', '4153' ], [ '195.189.62.5', '80' ]]
Нажмите, чтобы раскрыть...
Итоги
В этой статье, на реальном примере, разобрал как можно использовать возможности Google Apps Script для парсинга целей. Хоть это и реальный рабочий пример, но по сути только верхушка айсберга возможностей. GAS позволяет творить очень много интересного. Вот некоторые мысли:
Можно прикрутить не только парсинг сайтов, но и наполнение базы нужными данными: статистика посещаемости, ДНС-реверс, фаззинг и т.д. Можно прикрутить различные сервисы для сбора данных так же по API или варварски через Cheerio, Можно внешними скриптами наполнять данные по результатам работы инструментов (например, все тот же окунь). У вас есть механизм, который может полноценно работать сам по себе, не требуя ваших ресурсов и вмешательства.
Никто не мешает в контент сервисе указать MIME-type “JAVASCRIPT” и через вебприложение гугла отдавать полноценный скрипт. Да, видимо в борясь с хацкерами, которые использовали подобное для XSS атак для обхода политик безопасности, Google перенес приложения на домен script.googleusercontent.com но в любом случае, подобное хранилище скриптов может оказаться полезным. Как минимум, не нужны сервера, не нужен отдельный домен.
Те же телеграм-боты спокойно цепляются к GAS вебхуком. А все остальная инфраструктура Google? Мы ведь даже не коснулись ее. Между тем, мне в смартфон до сих пор ежедневно сыплются десятками уведомления от календаря по типу “Аня отправила вам видео” или “Сбербанк: поступил перевод”. Не лазил в этим темы, но скорее всего, реализованы они именно через GAS.
Я постарался максимально понятно и подробно донести свои мысли. Если интересно развитие темы применения GAS в нашей сфере, дайте знать любым удобным способом и я обязательно выдам что-то очень интересное.
Спойлер: Полный код code.gs
JavaScript: Скопировать в буфер обмена
Code:
let startTime = new Date().getTime();
function doGet(e) {
let {offset,count} = e.parameters;
const xss = SpreadsheetApp.getActiveSpreadsheet();
const sheetResults = xss.getSheetByName(SHEET_RESULTS);
if (!offset || offset < 1) offset = 1
if (!count || count > 1000) count = 1000;
if (offset >= sheetResults.getLastRow()) {
return ContentService.createTextOutput({success:true, count: 0, results:[]}).setMimeType(ContentService.MimeType.JSON)
}
const results = sheetResults.getRange(offset, 1, count).getValues().map(el => el[0]).filter(Boolean)
return ContentService.createTextOutput(JSON.stringify({success:true, count: results.length, results})).setMimeType(ContentService.MimeType.TEXT);
}
function resetDorkRow() {
ScriptProperties.setProperty('currentDork', 1);
}
function startParsing() {
let currentDork = parseInt(ScriptProperties.getProperty('currentDork'));
if (!currentDork) {
currentDork = 1;
ScriptProperties.setProperty('currentDork', currentDork);
}
startParsing(currentDork);
}
function getDataFromAPI(dork) {
const url = `${API_URL}?user=${API_USER}&key=${API_KEY}&groupby=100&domain=37&device=desktop&hl=en&lr=${API_REGION}&filter=1&query=${encodeURIComponent(dork)}`;
console.log('Start fetching by url: ', url);
const response = UrlFetchApp.fetch(url);
const content = response.getContentText();
console.log('Response:');
console.log(content);
const json = JSON.parse(content);
return json;
}
function getClearURLData(url) {
const [protocol, tail] = url.split(':');
const host = tail.replace('//','').split('/')[0];
return {
protocol, host, domain: protocol.concat('://', host)
}
}
function parseJSONToSheet_(json, sheet, dork) {
const {results} = json;
for(let i = json.first; i <= json.last; i++) {
const clearURLData = getClearURLData(results[i].url)
console.log('Append data: ', [results[i].url, results[i].title, results[i].passage, ,dork])
sheet.appendRow([clearURLData.host, clearURLData.domain, results[i].url, results[i].title, results[i].passage, ,dork]);
}
}
function startParsing(currentDork) {
const xss = SpreadsheetApp.getActiveSpreadsheet();
const sheetDorks = xss.getSheetByName(SHEET_DORKS);
const sheetResults = xss.getSheetByName(SHEET_RESULTS);
const lastDork = sheetDorks.getLastRow() + 1;
for(let i = currentDork; i < lastDork; i++) {
let currentTime = new Date().getTime();
let seconds = (currentTime - startTime) / 1000;
if (seconds > MAX_TIME_SEC) {
console.log('Time end');
return;
}
const dorkValue = sheetDorks.getRange(i, 1).getValue();
const result = getDataFromAPI(dorkValue);
parseJSONToSheet_(result , sheetResults, dorkValue);
currentDork++;
ScriptProperties.setProperty('currentDork', currentDork);
}
}
Спойлер: Код const.gs
JavaScript: Скопировать в буфер обмена
Code:
const API_URL = `h_ttps://xmlstock.com/google/json/`;
const API_KEY = `your_api_key`;
const API_USER = your_user_id;
const API_REGION = 2840;
const SHEET_DORKS = `dorks`;
const SHEET_RESULTS = `results`;
const MAX_TIME_SEC = 280;