Очень часто при написании скриптов мы сталкиваемся с использованием формата 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).
Ключом может быть строка, а значением — любой поддерживаемый тип.
1 2 3 4 5 6 7 8 9 |
{ "key1": "value for key1", "key2": 2, "key3": [ "value 1 for key3", 2, "the 3" ] } |
Упорядоченный набор значений. В PHP это реализовано как простой массив (array).
1 2 3 4 5 6 7 |
[ "one", 2, { "key1": 1 } ] |
Базовые типы JSON:
* «Число» — десятичное число (не делает никакого различия между целыми и числами
с плавающей точкой). Не поддерживается NaN (не-число, Not A Number).
* «Строка» — упорядоченное множество из нуля или более символов юникода, заключённое в
двойные кавычки.
* «Булев» (boolean) — значения либо true, либо false.
* «Массив» — упорядоченнное множество значений, каждое из которых может быть
любого поддерживаемого типа. Массив заключается в квадратные скобки «[]».
Значения разделяются запятыми.
* «Объект» — неупорядоченное множество пар «ключ: значение», заключённое в фигурные
скобки «{}». «Ключ» описывается «строкой», между ним и значением стоит символ «:».
Пары «ключ-значение» отделяются друг от друга запятыми.
* «null» — пустое значение означающее «ничего».
JSON не поддерживает и не разрешает любой вид комментариев, но комментарии
можно эмулировать, например, создав объект с ключом «comment» и в
значении вписать комментарий.
Следующий пример показывает JSON-представление объекта, описывающего человека.
В объекте есть строковые поля имени и фамилии, объект, описывающий адрес,
и массив, содержащий список телефонов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "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 будет следующего формата:
1 2 3 4 5 6 7 8 9 |
[ { "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:
1 2 3 4 5 6 7 |
// На каждой итерации сохраняем сюда обработанную информацию, затем будем // этот массив добавлять в $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])) //подчищаем немножко ]; |
Подготовка для энкодинга (добавляем в существующий массив подготовленную информацию):
1 |
$raw_array[] = $tmp_array; |
Энкодим массив в json-строку:
Подробнее о константах для функции json_encode см. здесь
1 |
$raw_str = json_encode($raw_array, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); |
Записываем полученную строку в файл:
1 |
file_put_contents($path_result_file, $raw_str); |
Всё, мы закодировали наш массив в json-строку, которую затем записали в файл.
Теперь, чтобы его декодировтаь нужно:
Считать файл:
1 |
$raw_str = file_get_contents($path_result_file); |
Декодировать из json-строки в массив.
Если у нас был валидный json-файл, то будет возвращён массив, иначе возвращается булев тип false.
Второй эл-нт указывает ф-ции, что декодировать нужно в массив, а не в PHP-объект:
1 |
$raw_array = json_decode($raw_str, true); |
Исходники скриптов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
<?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."<br>"; $ind1 = strpos($str1, $pr1, $ind_st); // echo "index 1 ".$ind1."<br>"; if($ind1 === false) return ""; $ind2 = strpos($str1, $pr2, $ind1+ strlen($pr1)); //echo "index 2 ".$ind2."<br>"; 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.