{
    "version": "https:\/\/jsonfeed.org\/version\/1",
    "title": "Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: github",
    "home_page_url": "http:\/\/test.leftjoin.ru\/tags\/github\/",
    "feed_url": "http:\/\/test.leftjoin.ru\/tags\/github\/json\/",
    "icon": "http:\/\/test.leftjoin.ru\/user\/userpic@2x.jpg",
    "author": {
        "name": "Николай Валиотти",
        "url": "http:\/\/test.leftjoin.ru\/",
        "avatar": "http:\/\/test.leftjoin.ru\/user\/userpic@2x.jpg"
    },
    "items": [
        {
            "id": "110",
            "url": "http:\/\/test.leftjoin.ru\/all\/parser-indeed-with-python\/",
            "title": "Парсим вакансии для аналитиков из Indeed",
            "content_html": "<p>В этом материале мы расскажем, как парсить вакансии с сайта Indeed. Indeed — это крупнейший в мире поисковик вакансий. Этим текстом мы начинаем большой проект по анализу и визуализации показателей оплаты труда в области Data Science в разных странах.<br \/>\nПодобный анализ рынка вакансий, но только в России, мы проводили в материале <a href=\"http:\/\/test.leftjoin.ru\/all\/hh-dashboard-bi-and-analysts-market\/\">Анализ рынка вакансий аналитики и BI: дашборд в Tableau<\/a>, когда парсили данные с сайта HeadHunter.<\/p>\n<p class=\"note\">А еще у нас можно почитать материал  <a href=\"http:\/\/test.leftjoin.ru\/all\/parsim-dannye-kataloga-sayta-ispolzuya-beautiful-soup-i-selenium\/\">Парсим данные каталога сайта, используя Beautiful Soup и Selenium<\/a><\/p>\n<p><b>Импорт библиотек<\/b><br \/>\nБиблиотека <span class=\"inline-code\">fake_useragent<\/span> имитирует реальный User-Agent, чтобы преодолеть защиту сайта от парсинга. Таким образом мы сможем пройти проверку HTTP заголовка User-Agent.<br \/>\nМодуль <span class=\"inline-code\">urllib.parse<\/span> разбирает URL-адрес на компоненты и записывает его как кортеж. Он пригодится для перехода на карточки вакансий. BeautifulSoup поможет разобраться в структуре html-страницы и добыть нужную нам информацию.<\/p>\n<pre class=\"e2-text-code\"><code>import requests\r\nfrom datetime import timedelta, datetime\r\nimport urllib.parse\r\nfrom fake_useragent import UserAgent\r\nfrom bs4 import BeautifulSoup\r\nimport pandas as pd\r\nimport time\r\nfrom lxml.html import fromstring\r\nfrom clickhouse_driver import Client\r\nfrom clickhouse_driver import errors\r\nimport numpy as np\r\nfrom funcs import check_title, get_skills_row, parse_salary, get_sheetname, create_table<\/code><\/pre><p><b>Создадим таблицу в Clickhouse<\/b><br \/>\nДанные, которые мы собираемся собрать, будем хранить в базе Clickhouse.<\/p>\n<pre class=\"e2-text-code\"><code>create_table = '''CREATE TABLE if not exists indeed.vacancies (\r\n    row_idx UInt16,\r\n    query_string String,\r\n    country String,\r\n    title String,\r\n    company String,\r\n    city String,\r\n    job_added Date,\r\n    easy_apply UInt8,\r\n    company_rating Nullable(Float32),\r\n    remote UInt8,\r\n    job_id String,\r\n    job_link String,\r\n    sheet String,\r\n    skills String,\r\n    added_date Date,\r\n    month_salary_from_USD Float64,\r\n    month_salary_to_USD Float64,\r\n    year_salary_from_USD Float64,\r\n    year_salary_to_USD Float64,\r\n)\r\nENGINE = ReplacingMergeTree\r\nSETTINGS index_granularity = 8192'''<\/code><\/pre><p><b>Обход блокировок<\/b><br \/>\nНам нужно обойти защиту Indeed и избежать блокировки по IP. Для этого используем анонимные прокси адреса на сайте free-proxy-list.net. Как собрать свежие прокси, мы писали в нашем предыдущем тексте <a href=\"http:\/\/test.leftjoin.ru\/all\/selenium-proxy\/\" class=\"nu\">«<u>Пишем парсер свежих прокси на Python для Selenium<\/u>»<\/a>. Прокси адреса мы запишем в массив, который понадобится в момент обращения к Indeed, когда запрос будет проверять User-Agent.<\/p>\n<p>Данный метод удаляет IP из списка с прокси в том случае, если ответ от Indeed через него так и не пришел.<\/p>\n<pre class=\"e2-text-code\"><code>def remove_proxy_from_list_and_update_if_required(proxy):\r\n    global _proxies\r\n    _proxies.remove(proxy)\r\n    if len(_proxies) == 0:\r\n        update_proxy_list()<\/code><\/pre><p>Функция, используя прокси, возвращает нам страницу Indeed, из которой мы впоследствии спарсим данные.<\/p>\n<pre class=\"e2-text-code\"><code>def get_page(updated_url, session):\r\n    proxy = get_proxy()\r\n    proxy_dict = {&quot;http&quot;: proxy, &quot;https&quot;: proxy}\r\n    logger.info(f'try with proxy: {proxy}')\r\n    try:\r\n        session.proxies = proxy_dict\r\n        return session.get(updated_url, timeout=15)\r\n    except (requests.exceptions.RequestException, requests.exceptions.ProxyError, requests.exceptions.ConnectTimeout,\r\n            requests.exceptions.ReadTimeout, requests.exceptions.SSLError,\r\n            requests.exceptions.ConnectionError, url_ex.MaxRetryError, ConnectionResetError,\r\n            socket.timeout, url_ex.ReadTimeoutError):\r\n        remove_proxy_from_list_and_update_if_required(proxy)\r\n        logger.info(f'try with proxy {proxy}')\r\n        return get_page(updated_url, session)<\/code><\/pre><p><b>Методы для парсера<\/b><br \/>\nИскомые данные нужно будет искать по тегам и атрибутам верстки с помощью BeautifulSoup. Мы заранее собрали ключевые слова, которые нас будут интересовать в вакансиях, и подготовили с ними отдельный датасет.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/--2021-05-26--10.28.08.png\" width=\"1078\" height=\"686\" alt=\"\" \/>\n<\/div>\n<p>В карточках вакансий нет точной даты публикации, указано лишь сколько дней назад она была опубликована. Сохраним точную дату публикации в традиционном формате с помощью <span class=\"inline-code\">timedelta<\/span>.<\/p>\n<pre class=\"e2-text-code\"><code>def raw_date_to_str(raw_date):\r\n    raw_date = raw_date.lower()\r\n    if '+' in raw_date or &quot;более&quot; in raw_date:\r\n        delta = timedelta(days=32)\r\n        return (datetime.now() - delta).strftime(&quot;%Y-%m-%d&quot;)\r\n    else:\r\n        parts = raw_date.split()\r\n        for part in parts:\r\n            if part.isdigit():\r\n                delta = timedelta(days=part.isdigit())\r\n                return (datetime.now() - delta).strftime(&quot;%Y-%m-%d&quot;)\r\n    return &quot;&quot;<\/code><\/pre><p>Сохраним id вакансии в системе Indeed. Подставляя id в URL страницы, мы сможем получить доступ к полному описанию вакансий.<\/p>\n<pre class=\"e2-text-code\"><code>def get_job_id_from_card(card):\r\n    try:\r\n        return card['id'].split('_')[1]\r\n    except:\r\n        return &quot;&quot;<\/code><\/pre><p>Данный метод соберет названия вакансий.<\/p>\n<pre class=\"e2-text-code\"><code>def get_title_from_card(card):\r\n    try:\r\n        job_title = card.find('a', {'class': 'jobtitle'}).text\r\n        return job_title.replace('\\n', '')\r\n    except:\r\n        return ''<\/code><\/pre><p>Аналогичным образом напишем методы, которые будут собирать данные о названии компании, времени публикации объявления, местоположении работодателя и рейтинге работодателя на портале.<\/p>\n<p>URL сайта Indeed пишется для разных стран по-разному. Для США это будет просто indeed.com, а локализации для других стран получают префиксом xx.indeed.com. Список с префиксами мы собрали в массив заранее из <a href=\"\"><a href=\"https:\/\/opensource.indeedeng.io\/api-documentation\/docs\/supported-countries\/\">https:\/\/opensource.indeedeng.io\/api-documentation\/docs\/supported-countries\/<\/a> списка<\/a> Indeed.<\/p>\n<pre class=\"e2-text-code\"><code>def get_link_from_card(card, card_country):\r\n    try:\r\n        if card_country == 'us':\r\n            return f&quot;https:\/\/indeed.com{card.find('a', {'class': 'jobtitle'})['href']}&quot;\r\n        else:\r\n            return f&quot;https:\/\/{card_country}.indeed.com{card.find('a', {'class': 'jobtitle'})['href']}&quot;\r\n    except:\r\n        return &quot;&quot;<\/code><\/pre><p>Спарсим описание вакансии, которое можно найти по тегу ’summary’. Именно там содержатся требования, которые предъявляют к кандидату.<\/p>\n<pre class=\"e2-text-code\"><code>def get_summary_from_card_and_transform_to_skills(card):\r\n    try:\r\n        smr = card.find('div', {'class': 'summary'}).text\r\n        return get_skills_row(smr)\r\n    except:\r\n        return &quot;&quot;\r\nНеобходимые hard-skills из описания вакансий будем сверять со списком 'skills'. \r\nskills = [&quot;python&quot;, &quot;tableau&quot;, &quot;etl&quot;, &quot;power bi&quot;, &quot;d3.js&quot;, &quot;qlik&quot;, &quot;qlikview&quot;, &quot;qliksense&quot;,\r\n          &quot;redash&quot;, &quot;metabase&quot;, &quot;numpy&quot;, &quot;pandas&quot;, &quot;congos&quot;, &quot;superset&quot;, &quot;matplotlib&quot;, &quot;plotly&quot;,\r\n          &quot;airflow&quot;, &quot;spark&quot;, &quot;luigi&quot;, &quot;machine learning&quot;, &quot;amplitude&quot;, &quot;sql&quot;, &quot;nosql&quot;, &quot;clickhouse&quot;,\r\n          'sas', &quot;hadoop&quot;, &quot;pytorch&quot;, &quot;tensorflow&quot;, &quot;bash&quot;, &quot;scala&quot;, &quot;git&quot;, &quot;aws&quot;, &quot;docker&quot;,\r\n          &quot;linux&quot;, &quot;kafka&quot;, &quot;nifi&quot;, &quot;ozzie&quot;, &quot;ssas&quot;, &quot;ssis&quot;, &quot;redis&quot;, 'olap', ' r ', 'bigquery', 'api', 'excel']<\/code><\/pre><p>Эта функция разобьет ’summary’ на слова пробелом и проверит их на соответствие нашему списку. В датасет будут возвращаться совпадения с нашим списком hard-skills.<\/p>\n<pre class=\"e2-text-code\"><code>def get_skills_row(summary):\r\n    summary = summary.lower()\r\n    row = []\r\n    for sk in skills:\r\n        if sk in summary:\r\n            row.append(sk)\r\n    return ','.join(row)<\/code><\/pre><p>На выходе мы получим таблицу с примерно 30 тысячами строк.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/--2021-05-21--17.29.19.png\" width=\"1105\" height=\"658\" alt=\"\" \/>\n<\/div>\n<p>Полный код проекта можно посмотреть в нашем <a href=\"https:\/\/github.com\/valiotti\/leftjoin\/tree\/master\/indeed\"> репозитории<\/a> на GitHub.<\/p>\n",
            "date_published": "2021-05-27T10:10:46+03:00",
            "date_modified": "2021-05-26T12:43:15+03:00",
            "image": "http:\/\/test.leftjoin.ru\/pictures\/--2021-05-26--10.28.08.png",
            "_date_published_rfc2822": "Thu, 27 May 2021 10:10:46 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "110",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "http:\/\/test.leftjoin.ru\/pictures\/--2021-05-26--10.28.08.png",
                    "http:\/\/test.leftjoin.ru\/pictures\/--2021-05-21--17.29.19.png"
                ]
            }
        },
        {
            "id": "106",
            "url": "http:\/\/test.leftjoin.ru\/all\/github-api\/",
            "title": "Пишем скрипт для автоматизации коммитов GitHub",
            "content_html": "<p>У GitHub есть API, позволяющий делать всё, что можно сделать руками: создавать репозитории, делать коммиты файлов и переключаться между ветками. Не все используют GUI при работе с системой контроля версий, и для коммита файлов приходится вводить не одну команду в терминал, а смена веток нередко приводит к запутанности действий. Сегодня мы поэкспериментируем с GitHub API и напишем скрипт, который сам собирает все файлы текущей директории раз в час и отправляет в отдельную ветку на GitHub при помощи get, post и put-запросов к методу \/repos.<\/p>\n<h2>Получение access token<\/h2>\n<p>Для работы с GitHub API нужно получить токен, который выдаётся каждому пользователю в <a href=\"https:\/\/github.com\/settings\/tokens\">настройках<\/a>. Для создания нового токена нажимаем на «Generate new token»:<\/p>\n<p class=\"note\">В процессе работы с GitHub API токен может просрочиться и в ответ придёт ошибка «Bad credentials». Для решения проблемы можно создать новый токен или <a href=\"https:\/\/docs.github.com\/en\/developers\/apps\/about-apps\">получить токен приложения<\/a><\/p>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/1-29.png\" width=\"1035\" height=\"345\" alt=\"\" \/>\n<\/div>\n<p>Следом нужно оставить название для токена и отметить области доступа ключу. Для наших целей достаточно дать доступ только к разделу repo.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/2-28.png\" width=\"763\" height=\"454\" alt=\"\" \/>\n<\/div>\n<p>После в зелёном окошке появится токен:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/3-24.png\" width=\"781\" height=\"288\" alt=\"\" \/>\n<\/div>\n<h2>Отправляем запросы<\/h2>\n<p>Подключим нужные библиотеки и введём некоторые константные значения: логин, токен, название для репозитория и ветки, в которую в автоматическом режиме будут отправляться файлы:<\/p>\n<pre class=\"e2-text-code\"><code>import requests\r\nimport base64\r\nimport json\r\nimport os\r\n\r\nusername = 'leftjoin'\r\nrepo = 'leftjoin'\r\ntoken = &quot;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&quot;\r\nnew_branch_name = 'automatic'<\/code><\/pre><p>Создадим новый репозиторий post-запросом. В поле data передаем название репозитория, в auth — логин и токен:<\/p>\n<pre class=\"e2-text-code\"><code>r = requests.post('https:\/\/api.github.com\/user\/repos',\r\n                  data=json.dumps({'name':repo}),\r\n                  auth=(username, token),\r\n                  headers={&quot;Content-Type&quot;: &quot;application\/json&quot;})<\/code><\/pre><p>Теперь загрузим файл README.md, в котором будет строка “Hello, world!”. Для этого строку нужно перекодировать в base64 — GitHub принимает данные в такой кодировке:<\/p>\n<pre class=\"e2-text-code\"><code>content = &quot;Hello, world!&quot;\r\n\r\nb_content = content.encode('utf-8')\r\nbase64_content = base64.b64encode(b_content)\r\nbase64_content_str = base64_content.decode('utf-8')<\/code><\/pre><p>Теперь создадим коммит в формате словаря: он состоит из пути (пока его оставим пустым), сообщения коммита и содержания:<\/p>\n<pre class=\"e2-text-code\"><code>f = {'path':'',\r\n     'message': 'Automatic update',\r\n     'content': base64_content_str}<\/code><\/pre><p>Отправим файл в репозиторий put-запросом, передав путь до README.md прямо в ссылке запроса:<\/p>\n<pre class=\"e2-text-code\"><code>f_resp = requests.put(f'https:\/\/api.github.com\/repos\/{username}\/{repo}\/contents\/README.md',\r\n                auth=(username, token),\r\n                headers={ &quot;Content-Type&quot;: &quot;application\/json&quot; },\r\n                data=json.dumps(f))<\/code><\/pre><p>Теперь в репозитории в ветке main есть один файл README.md, который содержит строку «Hello, world!»:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/4-16.png\" width=\"927\" height=\"157\" alt=\"\" \/>\n<\/div>\n<h2>Работа с ветками<\/h2>\n<p>Так как наш скрипт подразумевает работу в фоновом режиме, не всегда будет возможность контролировать, какие версии файлов он отправляет. Чтобы не засорять основную ветку будем отправлять все файлы в специальную, из которой при надобности можно будет достать обновления. Логика такая: проверяем, есть ли в репозитории такая ветка и, если нет, создаём её. Затем получаем все файлы текущей директории, построчно считываем, перекодируем в base64, коммитим и отправляем в ветку для автоматических обновлений.<\/p>\n<p>Начнём с функции, которая определяет, существует ли ветка с таким названием. Она отправит запрос на получение всех веток репозитория, и вернёт False, если ветки не существует:<\/p>\n<pre class=\"e2-text-code\"><code>def branch_exist(branch_name):\r\n    branches = requests.get(f'https:\/\/api.github.com\/repos\/{username}\/{repo}\/git\/refs').json()\r\n    try:\r\n        for branch in branches:\r\n            if branch['ref'] == 'refs\/heads\/' + branch_name:\r\n                return True\r\n        return False\r\n    except Exception:\r\n        return False<\/code><\/pre><p>У каждой ветки, файлов и репозиториев на GitHub есть уникальный идентификатор, получаемый путём хэш-суммы алгоритмом SHA. Он необходим как для модификации файлов, так и для смены веток — опишем функцию, которая по названию ветки получает SHA для неё:<\/p>\n<pre class=\"e2-text-code\"><code>def get_branch_sha(branch_name):\r\n    try:\r\n        branches = requests.get(f'https:\/\/api.github.com\/repos\/{username}\/{repo}\/git\/refs').json()\r\n        for branch in branches:\r\n            sha = None\r\n            if branch['ref'] == 'refs\/heads\/' + branch_name:\r\n                sha = branch['object']['sha']\r\n            return sha\r\n    except Exception:\r\n        return None<\/code><\/pre><p>Следом опишем функцию создания новой ветки с названием из переменной new_branch_name:<\/p>\n<pre class=\"e2-text-code\"><code>def create_branch():\r\n    main_branch_sha = get_branch_sha('main')\r\n    requests.post(f'https:\/\/api.github.com\/repos\/{username}\/{repo}\/git\/refs',\r\n             auth=(username, token),\r\n             data=json.dumps({\r\n                 'ref':f'refs\/heads\/{new_branch_name}',\r\n                 'sha':main_branch_sha\r\n             })).json()<\/code><\/pre><p>Для модификации файлов тоже необходимо знать его идентификатор:<\/p>\n<pre class=\"e2-text-code\"><code>def get_sha(path):\r\n    r = requests.get(f'https:\/\/api.github.com\/repos\/{username}\/{repo}\/contents\/{path}',\r\n                    auth=(username, token),\r\n                    data=json.dumps({\r\n                        'branch': new_branch_name\r\n                    }))\r\n    sha = r.json()['sha']\r\n    return sha<\/code><\/pre><p>Наконец, опишем ряд главных функций — первая по аналогии с примером из начала материала принимает содержимое файла и формирует коммит в отдельную ветку, а вторая отправляет файл в указанный репозиторий:<\/p>\n<pre class=\"e2-text-code\"><code>def make_file_and_commit(content, sha=None):\r\n    b_content = content.encode('utf-8')\r\n    base64_content = base64.b64encode(b_content)\r\n    base64_content_str = base64_content.decode('utf-8')\r\n    f = {'path':'',\r\n     'message': 'Automatic update',\r\n     'content': base64_content_str,\r\n     'sha':sha,\r\n     'branch': new_branch_name}\r\n    return f\r\n\r\ndef send_file(path, data):\r\n    f_resp = requests.put(f'https:\/\/api.github.com\/repos\/{username}\/{repo}\/contents\/{path}',\r\n                    auth=(username, token),\r\n                    headers={ &quot;Content-Type&quot;: &quot;application\/json&quot; },\r\n                    data=json.dumps(data))\r\n    print(f_resp.json())\r\n    return f_resp<\/code><\/pre><p>И ещё две функции напоследок: первая нужна, потому что мы должны знать, модифицируем мы уже существующий файл или новый. В случае с новым нет необходимости получать SHA для файла — его просто не существует. Вторая функция считывает всё содержимое файла в строку.<\/p>\n<pre class=\"e2-text-code\"><code>def file_exist(path):\r\n    r = requests.get(f'https:\/\/api.github.com\/repos\/{username}\/{repo}\/contents\/{path}',\r\n                    auth=(username, token))\r\n    return r.ok\r\n\r\ndef file_reader(path):\r\n    lines = &quot;&quot;\r\n    with open(path, 'r') as f:\r\n        for line in f:\r\n            lines += line\r\n    return lines<\/code><\/pre><p>Соберём всё вместе в функцию main(). Проверяем, существует ли ветка для автоматических коммитов, затем получаем все файлы директории, проверяем их существование в репозитории и отправляем:<\/p>\n<pre class=\"e2-text-code\"><code>def main():\r\n    if not branch_exist(new_branch_name):\r\n        create_branch()\r\n    files = [os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser(&quot;.&quot;)) for f in fn]\r\n    for path in files:\r\n        print(path)\r\n        sha = None\r\n        if file_exist(path):\r\n            sha = get_sha(path)\r\n        lines = file_reader(path)\r\n        f = make_file_and_commit(lines, sha)\r\n        r = send_file(path, f)<\/code><\/pre><p>Перевести скрипт в автоматический режим может помочь библиотека schedule, которую мы уже использовали в материале <a href=\"http:\/\/test.leftjoin.ru\/all\/hh-dashboard-bi-and-analysts-market\/\" class=\"nu\">«<u>Анализ рынка вакансий аналитики и BI: дашборд в Tableau<\/u>»<\/a><\/p>\n<pre class=\"e2-text-code\"><code>import schedule\r\n\r\nschedule.every().hour.do(main)\r\n\r\nwhile True:\r\n    schedule.run_pending()<\/code><\/pre><p>Вот и всё: мы написали скрипт, который самостоятельно каждый час отправляет все файлы текущей директории в ветку на GitHub.<\/p>\n",
            "date_published": "2021-04-07T16:13:41+03:00",
            "date_modified": "2021-04-07T16:13:20+03:00",
            "image": "http:\/\/test.leftjoin.ru\/pictures\/4-15.png",
            "_date_published_rfc2822": "Wed, 07 Apr 2021 16:13:41 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "106",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "http:\/\/test.leftjoin.ru\/pictures\/4-15.png",
                    "http:\/\/test.leftjoin.ru\/pictures\/1-29.png",
                    "http:\/\/test.leftjoin.ru\/pictures\/2-28.png",
                    "http:\/\/test.leftjoin.ru\/pictures\/3-24.png",
                    "http:\/\/test.leftjoin.ru\/pictures\/4-16.png"
                ]
            }
        }
    ],
    "_e2_version": 3365,
    "_e2_ua_string": "E2 (v3365; Aegea)"
}