Очень часто при написании скриптов мы сталкиваемся с использованием формата JSON
и регулярно наши клиенты спрашивают как работать с JSON в скриптах
Human Emulator.
В связи с этим мы решили написать вводную статью на эту тему.
Работа с JSON в скриптах XHE полностью аналогична работе с JSON в PHP. Более подробно про
работу с JSON вы можете найти в разделе «литература».
JSON (JavaScript Object Notation) — текстовый формат обмена данных, основанный
на подмножестве JavaScript.
Как и многие другие текстовые форматы, JSON легко читается и пишется вручную людьми.
Несмотря на происхождение от JavaScript (точнее, от подмножества языка стандарта
1999-го г.), формат считается языконезависимым и может использоваться практически
с любым языком программирования (в т.ч., PHP имеет встроенную поддержку JSON)
За счёт своей лаконичности по сравнению с XML, формат JSON может быть более
подходящим для сериализации сложных структур.
Например, если говорить о веб-приложениях, то JSON уместен в задачах обмена данными
между браузером и сервером, а так же, между серверами.
Если говорить о десктопных приложениях, то JSON можно использовать для хранения
конфигурационных файлов программы.
JSON — текст представляет собой (в закодированном виде) одну из двух структур:
* Набор пар «ключ: значение». В PHP это реализовано как ассоциативный массив (array).
Ключом может быть строка, а значением — любой поддерживаемый тип.
{
"key1": "value for key1",
"key2": 2,
"key3": [
"value 1 for key3",
2,
"the 3"
]
}
Упорядоченный набор значений. В PHP это реализовано как простой массив (array).
[
"one",
2,
{
"key1": 1
}
]
Базовые типы JSON:
* «Число» — десятичное число (не делает никакого различия между целыми и числами
с плавающей точкой). Не поддерживается NaN (не-число, Not A Number).
* «Строка» — упорядоченное множество из нуля или более символов юникода, заключённое в
двойные кавычки.
* «Булев» (boolean) — значения либо true, либо false.
* «Массив» — упорядоченнное множество значений, каждое из которых может быть
любого поддерживаемого типа. Массив заключается в квадратные скобки «[]».
Значения разделяются запятыми.
* «Объект» — неупорядоченное множество пар «ключ: значение», заключённое в фигурные
скобки «{}». «Ключ» описывается «строкой», между ним и значением стоит символ «:».
Пары «ключ-значение» отделяются друг от друга запятыми.
* «null» — пустое значение означающее «ничего».
JSON не поддерживает и не разрешает любой вид комментариев, но комментарии
можно эмулировать, например, создав объект с ключом «comment» и в
значении вписать комментарий.
Следующий пример показывает JSON-представление объекта, описывающего человека.
В объекте есть строковые поля имени и фамилии, объект, описывающий адрес,
и массив, содержащий список телефонов:
{
"comment": "пример эмуляции комментария в JSON",
"first_name": "Иван",
"last_name": "Иванов",
"age": 31,
"address": {
"street_address": "Московское ш., 101, кв. 101",
"city": "Москва",
"postal_code": "123456"
},
"phones": [
"+7 978 123 45 67",
"+7 978 123 45 68"
]
}
Предположим, у нас стоит задача вытянуть какие-то данные из веб-сайта и
сохранить их в виде JSON. Для примера, спарсим данные компаний занимающихся
фрахтовкой яхт с сайта
Наш JSON будет следующего формата:
[
{
"name": "название фирмы",
"address": "физический адрес офиса",
"tel": "телефон",
},
{ и т.д. ... },
{ и т.п. ... }
]
Алгоритм работы нашего скрипта будет такой:
1. открываем страницу сайта
2. получаем информацию со страницы (строку)
3. сконвертируем (используя разделитель «\r\n \r\n») полученную строку в массив строк
4. создадим пустой массив, в который будем добавлять массивы с подготовленной информацией.
Затем этот массив будем энкодить в json-строку.
5. в цикле пройдёмся по всем эл-ам массива (который получили в 3-м пункте).
На каждой итерации цикла будем:
5.1. подготавливаем подмассив с нужной информацией (для добавления (но не добавляем) его в массив созданный в 4-м пунке).
5.2. проверяем существует ли файл result.json.
5.2.1. если существует, то считываем его и пытаемся декодировать из json-строки в массив.
5.2.2. если декодировать неудалось, то сохраняем его (на всякий случай) и вместо него создаём новый (пустой, естественно).
5.2.3. если декодировать удалось, то просто добавляем к массиву (п. 4) наш подмассив (п. 5.1).
5.3. энкодим наш, теперь уже, заполненный массив (из п. 4) в json-строку.
5.4. записываем в файл нашу json-строку.
После декодинга из json-строки в массив — кодировка строковых данных — «UTF-8».
Если внутри php-файла кодировка другая, то нужно использовать функцию iconv для перекодировки.
Перед энкодингом массива в json-строку все строковые ел-ты массива нужно перекодировать в
кодировку «UTF-8».
PHP функции json_encode(), json_decode() гарантируют сохранность данных только при использовании
кодировки «UTF-8».
Примеры из скрипта:
Здесь мы формируем ассоциативный массив и все строковые эл-ты перекодируем в UTF-8:
// На каждой итерации сохраняем сюда обработанную информацию, затем будем
// этот массив добавлять в $raw_array[].
$tmp_array = [
"name" => iconv("windows-1251", "utf-8", $tmp[0]),
"address" => iconv("windows-1251", "utf-8", trim(explode(":", $tmp[1])[1])), //подчищаем немножко
"tel" => iconv("windows-1251", "utf-8", trim(explode(":", $tmp[2])[1])) //подчищаем немножко
];
Подготовка для энкодинга (добавляем в существующий массив подготовленную информацию):
$raw_array[] = $tmp_array;
Энкодим массив в json-строку:
Подробнее о константах для функции json_encode см. здесь
$raw_str = json_encode($raw_array, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Записываем полученную строку в файл:
file_put_contents($path_result_file, $raw_str);
Всё, мы закодировали наш массив в json-строку, которую затем записали в файл.
Теперь, чтобы его декодировтаь нужно:
Считать файл:
$raw_str = file_get_contents($path_result_file);
Декодировать из json-строки в массив.
Если у нас был валидный json-файл, то будет возвращён массив, иначе возвращается булев тип false.
Второй эл-нт указывает ф-ции, что декодировать нужно в массив, а не в PHP-объект:
$raw_array = json_decode($raw_str, true);
Исходники скриптов
<?php
// coding: windows-1251
/**
* common.php: общие полезные штуки
*/
/**
* Отправить отладочное сообщение в окно отладки.
*
* Печатает отладочное сообщение в консоль отладки, если задана переменна $dbg=true.
*
* @param string $msg Само отладочное сообщение.
* @param string|bool $print_date Печатать ли перед отладочным сообщением дату и время отправки сообщения.
* + string $print_date: печатать текущее время в отладочном сообщении в заданном формате. Формат
* печтаемой даты и времени для ф-ции date (default: "d.m.y H:i:s").
* + bool $print_date: false - не печатать в отладочном сообщении время.
*/
function dbgmsg($msg, $print_date="d.m.y H:i:s") {
global $dbg;
if ($dbg) {
if (is_string($print_date) && $print_date != false) {
echo "[" . date($print_date) . "]" . " => " . $msg .PHP_EOL;
} else {
echo $msg .PHP_EOL;
}
}
}
/**
* Функция осуществляющая выкусывание строки по префиксу и постфиксу.
*
* @param string $str1 Строковая переменная в которой будет осуществляться поиск
* @param string $pr1 Префикс
* @param string $pr2 Постфикс
* @param int &$ind_st Индекс номера символа в строке, с кот-го необходимо начинать
* поиск (default: 0)
*
* @return string Возвращает обработанную строку.
*/
// получить строку по префиксам
function get_string($str1, $pr1, $pr2, &$ind_st = 0)
{
//echo $str1."
";
$ind1 = strpos($str1, $pr1, $ind_st);
// echo "index 1 ".$ind1."
";
if($ind1 === false)
return "";
$ind2 = strpos($str1, $pr2, $ind1+ strlen($pr1));
//echo "index 2 ".$ind2."
";
if ($ind2 === false)
return "";
// запомним стартовый индекс
$ind_st = $ind2 + 1;
// получим результат
$sres = substr($str1, $ind1 + strlen($pr1), $ind2 - $ind1-strlen($pr1));
return trim($sres);
}
?>
<?php
// coding: windows-1251
/**
* json_example.php: Пример работы с текстовым форматом обменна данных "JSON".
*/
/*****************************************************************************/
// Настройки
/*****************************************************************************/
// Где запущен XHE.
$xhe_host ="127.0.0.1:5753";
// Печатать ли отладочные сообщения?
$dbg=true;
// Путь к файлу с результатом
$path_result_file = "result/result.json";
// Адрес сайта
$site = "http://www.yellowpagesrussia.ru/190/893/";
// Ск-ко времени (в секундах) ждать. Для ф-ции sleep.
$wt = 1;
$wt1 = 1;
$wt2 = 2;
$wt3 = 3;
$wt4 = 4;
$wt5 = 5;
$wt_long = 10;
/*****************************************************************************/
// Подключение необходимых библиотек.
/*****************************************************************************/
// XHE lib
require "C:\XWeb\Human Emulator Advanced\Templates\xweb_human_emulator.php";
// Полезные штуки
require "common.php";
/*****************************************************************************/
// Скрипт
/*****************************************************************************/
dbgmsg("Скрипт начал работу.");
dbgmsg("", false);
// Переходим на сайт
$browser->navigate($site);
sleep($wt);
// Спарсили с сайта содержимое html-тега div с id "content".
// На выходе получили массив (с одним эл-ом) с контентом страницы.
$content = $element->get_all_inner_texts_by_attribute("id", "content");
// Т.к., $content[0] содержит лишние данные, то их необходимо убрать.
// Это можно сделать с помощью функции get_string (обычно находится в
// каждом скрипте в tools/functions.php, lib/common.php и т.п.).
$content = get_string($content[0], "Фрахт", "Страницы");
// Теперь $content это массив строк. Эл-нты массива содержат информацию (разделенную \r\n)
// о предприятиях с сайта.
$content = explode("\r\n \r\n", $content);
// Массив, в который будем добавлять key-value подмассивы и энкодить в JSON-строку.
$raw_array = [];
// Пройдёмся в цикле по массиву $content, чтобы заполнить массив $raw_array
// нужной информацией перед энкодингом в json-строку.
foreach($content as $ck => $cv)
{ // $ck - content key; $cv - content value
// Массив строк. Каждый эл-нт содержит информацию о фирме (название, адрес, тел.)
$tmp = explode("\r\n", $cv);
// На каждой итерации сохраняем сюда обработанную информацию, затем будем
// этот массив добавлять в $raw_array[].
$tmp_array = [
"name" => iconv("windows-1251", "utf-8", $tmp[0]),
"address" => iconv("windows-1251", "utf-8", trim(explode(":", $tmp[1])[1])), //подчищаем немножко
"tel" => iconv("windows-1251", "utf-8", trim(explode(":", $tmp[2])[1])) //подчищаем немножко
];
// проверяем существует ли файл, если существует, то добавляем записи к файлу,
// иначе - создаем новый файл.
if($file_os->is_exist($path_result_file))
{ // файл уже существует
// считываем его
$raw_str = file_get_contents($path_result_file);
$raw_array = json_decode($raw_str, true); // пробуем декодировать в массив
dbgmsg("Файл уже существует.");
if (is_null($raw_array))
{ // json_decode вернул NULL - json-строка не может быть декодирована в массив.
// сохраним этот непонятный файл (на всякий случай) добавив к его названию unix timestamp
$tmp_file_path = date_timestamp_get(date_create());
$file_os->move($path_result_file, $path_result_file . "." . $tmp_file_path);
// т.к. сейчас $raw_array = NULL, то массив надо переинициализировать.
$raw_array = [];
$raw_array[] = $tmp_array; // добавили в массив сформированный с нужной информацией массив.
// формируем из массива валидную json-строку
$raw_str = json_encode($raw_array, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// теперь - файла не существует, создаем новый.
// записываем в файл готовую сформированную json-строку.
//$textfile->write_file($path_result_file, $raw_str);
file_put_contents($path_result_file, $raw_str);
dbgmsg("Существующий файл оказался какой-то не такой. Переместили его в " . $path_result_file . "." . $tmp_file_path);
dbgmsg("Вместо непонятного файла создали новый и записали в него json-строку.");
} else
{ // json-строка нормально декодировалась в массив, значит просто
// добавляем в массив нужные нам данные.
$raw_array[] = $tmp_array;
// формируем из массива валидную json-строку
$raw_str = json_encode($raw_array, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// записываем в файл готовую сформированную json-строку.
//$textfile->write_file($path_result_file, $raw_str);
file_put_contents($path_result_file, $raw_str);
dbgmsg("С файлом всё ок, добавили в массив нужную информацию, заэнкодили в json-строку, записали в файл "
. $path_result_file);
} // end if is null
} else
{ // файл не существует, создаем новый.
dbgmsg("Файл не существует.");
$raw_array[] = $tmp_array;
// формируем из массива валидную json-строку
$raw_str = json_encode($raw_array, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// записываем в файл готовую сформированную json-строку.
//$textfile->write_file($path_result_file, $raw_str);
file_put_contents($path_result_file, $raw_str);
dbgmsg("Создали новый файл " . $path_result_file . " + добавили в него нужный json.");
} // end if is file exist
} // end of foreach
/*****************************************************************************/
// Quit
/*****************************************************************************/
dbgmsg("", false);
dbgmsg("Скрипт закончил работу.");
$app->quit();
?>
Использованная литература и источники:
Скрипты написаны в XHE 4.6.45 MT.