[Урок 12] . Как сделать скрипт, работающий с момента прошлой остановки на примере avito. CRUD-операции с JSON в iMacros+JS.

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

CRUD-операции с JSON в iMacros+JS

CRUD(Create, Read, Update, Delete) — так называются четыре базовые функции(создание, чтение, редактирование, удаление), которые используются для работы с данными. Подобная концепция используется повсеместно в разработке сайтов, различных веб-приложений и программ. В нашем случае, мы будем использовать массив объектов в формате JSON и выполнять над ним эти операции.

Разберем простой пример:

Нам нужно сделать скрипт, который заходит на 10 сайтов и в случае, если мы закроем браузер после 5 сайта, то при следующем запуске скрипта, он начнет свою работу с 6 сайта, а не с первого.

Модель данных будет выглядеть так:

var data = [
	{		
		url: "Ссылка на страницу города",
		status: "not started",
		id: 1
	}
];

Это массив data, внутри которого будут объекты. В каждом объекте будет ссылка на страницу, которую нужно посетить, а в статусе мы будем указывать была ли посещена страница или если возникла ошибка.

Функция Create

Для начала нам понадобиться зайти на главную страницу avito.ru, получить оттуда ссылки на все города и области и загнать их в массив, после чего сохранить на диск в формате JSON. Тут мы как раз и будем использовать функцию Create

// Наша, пока что еще пустая модель
var data = [];

// определяем функцию Create
function create (params) {
	data.push(params);
}

// Вызов функции Create
create({
	url: "Название города",
	status: "not started",
	id: 1
});

Вам может показаться бессмысленным, что ради одной строки кода мы определили целую функцию. Но, это очень простой пример, на практике вам будет необходимо совершать больше различных манипуляций. Поэтому данная абстракция здесь к месту. Помимо ссылки и статуса, мы добавим поле id. Оно нам понадобиться, для того, чтобы мы смогли корректно обновлять и удалять объекты.
id должно быть уникальным, чтобы мы случайно не изменили не тот объект.

Функция Update

Давайте рассмотрим функцию Update. Ее логика в том, чтобы найти объект по каким-либо признакам и изменить в нем данные. Мы будем искать по id и это будет необходимым входным параметром, но никто нам не мешает искать по адресу или статусу.

// определяем функцию Update
function update (params) {
	for (var i = 0; i < data.length; i++) {
		// Ищем совпадение по Id
		if (data[i].id == params.id) {
			// Обновляем все ключи, присутствующие в params
			for (var item in params) {
				data[i][item] = params[item];
			}
		}
	}	
}

// Вызов функции Update
update({
	status: "completed",
	id: 1
});

В этом примере мы находим в массиве объект с id=1, и меняем его статус на «completed», что будет означать, что по этой ссылке бот уже прошел. Я частенько люблю совмещать Create и Update, код от этого измениться не очень сильно.

Функция Delete

Данная функция в этой задаче нам не понадобиться, но точно понадобиться в какой-нибудь другой. Поэтому о ней я тоже расскажу. По логике, нам необходимо найти элемент массива по параметру id и удалить весь объект из массива, выглядеть это будет так:

// определяем функцию Delete
function remove (params) {
	for (var i = 0; i < data.length; i++) {
		// Ищем совпадение по Id
		if (data[i].id == params.id) {
			data.splice(i, 1);
		}
	}	
}

// Вызов функции Delete
remove({id: 1});

Обратите внимание, в названии функции я использовал remove, потому что delete — это ключевое зарезервированное слово в javascript и его нельзя использовать в названиях функций и переменных. Метод для удаления элемента массива splice() принимает в качестве первого  аргумента индекс элемента в массиве, а в качестве второго аргумента — количество удаляемых элементов.

Функция Read

Иногда, нам требуется прочитать какие-либо данные из объекта. Тут похожая логика, мы ищем элемент массива по параметру id и возвращаем объект с данными:

// определяем функцию Read
function read (params) {
	for (var i = 0; i < data.length; i++) {
		// Ищем совпадение по Id
		if (data[i].id == params.id) {
			return data[i];
		}
	}	
}

// Вызов функции Read
window.console.log( read({id: 1}) );

В результате работы функции, мы получаем объект со всеми-всеми параметрами.
Теперь у нас есть все необходимое, чтобы написать скрипт, который будет сохранять свое состояние даже после аварийной остановки Firefox.

Финальный скрипт для перехода по страницам avito, запоминающий свое состояние

Алгоритм:

  1. Заходим на avito.ru
  2. Парсим все ссылки на города
  3. Забиваем ссылки в массив
  4. Сохраняем массив ссылок на диск в JSON-формате
  5. Проходим по каждой ссылке
  6. Проверяем, что у ссылки status="Not started" и тогда переходим по ссылке
  7. Обновляем статус на "Completed"
  8. Снова сохраняем обновленный массив на диск

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

Полностью код с комментариями:

// Default reset
iimPlayCode('SET !EXTRACT_TEST_POPUP NO');
iimPlayCode('SET !ERRORIGNORE YES');
iimPlayCode('SET !TIMEOUT_STEP 0');
iimPlayCode('TAB T=1');

var APP = {

	_fileName: "C:\\BOTS\\avito.json",


	// Read file and return our model or empty array
	_data: function () {	
		window.console.log(this);
		try {	 
			var fileDescriptor = imns.FIO.openNode(this._fileName);			
			return JSON.parse(imns.FIO.readTextFile(fileDescriptor));		 
		} catch (e) {			
			return [];
		}
	},


	// Create/Update
	write: function (params) {

		var isUpdate = false;

		//  Update
		for (var i = 0; i < this._data.length; i++) {
			// Ищем совпадение по Id
			if (this._data[i].id == params.id) {
				// Обновляем все ключи, присутствующие в params
				for (var item in params) {
					this._data[i][item] = params[item];
				}
				isUpdate = true;
			}
		}	

		// Create
		if (!isUpdate) {
			this._data.push(params);
		}

		// Write file on disk
		var fileDescriptor = imns.FIO.openNode(this._fileName);
		imns.FIO.writeTextFile( fileDescriptor, JSON.stringify(this._data, null, 4) );
	},

	// Get city list and fill array
	getCityList: function () {

		iimPlayCode('URL GOTO=https://avito.ru/');

		var cities = window.document.querySelectorAll('.js-index-cities-item');

		for (var i = 0; i < cities.length; i++) {			

			this.write({
				url: cities[i].getAttribute('href'),
				status: "not started",
				id: cities[i].getAttribute('id')
			});

		}
	},


	//  Main scenario
	run: function () {

		// Load data from file if exsists
		this._data = this._data();

		if (this._data.length == 0) {
			this.getCityList();			
		} 

		for (var i = 0; i < this._data.length; i++) {

			// Check if url was opened
			if (this._data[i].status == "not started") {

				var resultCode = iimPlayCode('SET !TIMEOUT 5\nURL GOTO=' + this._data[i].url); // set timeout for loading page 5 seconds
			
				iimPlayCode('WAIT SECONDS=1');
				// Any action with page can placed here
				// ...

				//  Update status
				if (resultCode >= 0 || resultCode ==-802) { // -802 = stop loading by timeout(5 sec.)
					this.write({
						id: this._data[i].id,
						status: "completed"
					});
				}
				

			}				

		}		

	}


}


// Start script
APP.run();

Как вы можете видеть, мы создали достаточно удобную структуру с данными и функциями, которая запускается одной командой APP.run(); Здесь мы используем Create\Update, но я заменил их на одну функцию write(). Это базовая заготовка для iMacros, которая демонстрирует принцип сохранения и обновления статусов. Этот пример можно бесконечно усложнять, добавлять вложенность, любые дополнительные действия и обработку данных. Очень важно уметь сохранять текущее состояние бота, это может сэкономить кучу времени и избавит вас от необходимости выполнять двойную работу.

Пишите ваши вопросы и комментарии.

14 комментариев

  1. Ищу работу))) А щас много предлагают работы постить объявления на авито. Правда, их еще составить надо, а это уже не автоматом.

    Ответить
  2. добрый день. помогите пожалуйста. как прописать чтобы выбирал просто первого человека списке здесь https://m.facebook.com/friends/center/requests/outgoing/#friends_center_main. а то при записи получается только имя конкретного человека.

    вот так:

    VERSION BUILD=844 RECORDER=CR
    URL GOTO=https://m.facebook.com/friends/center/requests/outgoing/#friends_center_main
    TAG POS=1 TYPE=A ATTR=TXT:MariaBekasova
    TAG POS=1 TYPE=A ATTR=TXT:Еще
    TAG POS=1 TYPE=A ATTR=TXT:Заблокировать
    TAG POS=1 TYPE=BUTTON FORM=ACTION:/privacy/touch/block/id/?bid=606607564&ret_cancel&source=profile&

    Ответить
  3. У меня вопрос к блоку // Default reset
    В предыдущих уроках вы говорили, что такой вариант записи не будет работать целостно, то есть ни одна из этих команд не будет влиять на выполнения макроса в дальнейшем. Зачем тогда их писать?

    Ответить
    • Алексей, в блоке Default reset, в-основном, мы устанавливаем глобальные переменные, которые будут актуальны и в дальнейшем. В прошлых уроках я писал, что некоторые команды в совокупности с другими командами не будут корректно выполняться если их разделить. В данном блоке команды могут спокойно работать независимо друг от друга.

      Ответить
  4. FILTER TYPE=IMAGES STATUS=OFF
    TAB CLOSEALLOTHERS
    SET !VAR1 API-CAPTCHA-KEY
    SET !VAR2 адрес криптовалюты
    SET !VAR3 image.jpg
    SET !VAR4 http://imacros2.rucaptcha.com/new/
    SET !VAR5 getcapcha.php

    URL GOTO=http://topfan.info/
    SET !EXTRACT NULL
    WAIT SECONDS=1
    ONDOWNLOAD FOLDER=C:\CAPCH\ FILE=image.jpg
    WAIT SECONDS=1
    TAG POS=1 TYPE=DIV ATTR=ID:adcopy-puzzle-image CONTENT=EVENT:SAVE_ELEMENT_SCREENSHOT
    WAIT SECONDS=1
    TAB OPEN
    TAB T=2
    URL GOTO={{!VAR4}}
    TAG POS=1 TYPE=INPUT:TEXT FORM=ACTION:{{!VAR5}} ATTR=NAME:key CONTENT={{!VAR1}}
    TAG POS=1 TYPE=INPUT:FILE FORM=ACTION:{{!VAR5}} ATTR=NAME:file CONTENT=C:\CAPCH\{{!VAR3}}
    TAG POS=1 TYPE=INPUT:CHECKBOX FORM=ACTION:{{!VAR5}} ATTR=NAME:calc CONTENT=NO
    TAG POS=1 TYPE=INPUT:TEXT FORM=ACTION:{{!VAR5}} ATTR=NAME:soft_id CONTENT=677
    TAG POS=1 TYPE=INPUT:SUBMIT FORM=ACTION:{{!VAR5}} ATTR=*
    TAG POS=1 TYPE=* ATTR=TXT:* EXTRACT=TXT
    WAIT SECONDS=1
    FRAME F=0
    TAB CLOSE
    TAB T=1
    WAIT SECONDS=1
    TAG POS=1 TYPE=INPUT:TEXT FORM=ACTION:#about ATTR=ID:adcopy_response CONTENT={{!EXTRACT}}
    WAIT SECONDS=1
    TAG POS=1 TYPE=BUTTON FORM=ACTION:#about ATTR=TXT:Getareward
    WAIT SECONDS=3600
    Подскажите что делать…вроде норма работает но иногда 1)решенную капчу не ставляет в окно для отпрвки …или 2)капча ставить но не кликает на GET A REVARD///

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

      Ответить
  5. Спасибо огромное, за урок! С нетерпением жду продолжения. Очень помогли в учебе. За пару недель, благодаря Вам начал что-то писать сам. Но, у меня загвоздка. В моем случае сбор // Get city list and fill array должен происходить по нескольким страницам поочередно. То есть, собрал на одной занес в массив и перешел на другую, по коду iimPlayCode(‘TAG POS=1 TYPE=SPAN ATTR=TXT:Ещерезультаты’);. Или, собрал на одной странице, выполнил блок команд // Any action with page can placed here далее выполнил код перехода iimPlayCode(‘TAG POS=1 TYPE=SPAN ATTR=TXT:Ещерезультаты’); и повтор блока // Get city list and fill array…

    Ответить
  6. Добился, что код перехода выполняет iimPlayCode(‘TAG POS=1 TYPE=SPAN ATTR=TXT:Ещерезультаты’), но после, сразу выбрасывает…

    Ответить
  7. так себе пост
    для меня вообще 0 смысловой нагурзки
    пример скрипта на авито пфф
    кому сдался авито твой

    Ответить
    • Спасибо за комментарий) В тебе очень много негатива — это верный путь стать неудачником по жизни. Ноль смысловой нагрузки — в твоих предложениях без запятых.

      Ответить

Оставить комментарий