Как получить объект JavaScript в коде JavaScript?

TL;DR

Я хочу parseParameter анализировать JSON, как следующий код. someCrawledJSCode — это просканированный код JavaScript.

const data = parseParameter(someCrawledJSCode);
console.info(data);  // data1: {...}

Проблема

Я просматриваю некоторый код JavaScript с помощью puppeteer и хочу извлечь из него объект JSON, но я не знаю, как анализировать данный код JavaScript.

Пример просканированного кода JavaScript:

const somecode = 'somevalue';
arr.push({
  data1: {
    prices: [{
      prop1: 'hi',
      prop2: 'hello',
    },
    {
      prop1: 'foo',
      prop2: 'bar',
    }]
  }
});

В этом коде я хочу получить массив prices (или data1).

Что я сделал

Я попытался разобрать код в JSON, но он не работает. Поэтому я искал инструменты для синтаксического анализа и получил Эсприма. Но я думаю, что это не поможет для решения этой проблемы.

Существует доступное решение, но оно очень НЕБЕЗОПАСНО, тем более что вы получаете js из внешнего источника путем сканирования. Вы можете попробовать использовать eval.

varun agarwal 08.04.2019 09:13

Я думал о eval, но я собираюсь запустить этот код на стороне сервера, а это слишком опасно. Поэтому я хочу получить другое решение. Спасибо за ответ.

Wonjun Kim 08.04.2019 09:19
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
4
2
453
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Стирать будет некрасиво. С некоторыми предположениями о строке, которую вы пытаетесь проанализировать, вы можете:

  1. Извлеките часть, которая помещается в массив
  2. Преобразуйте эту строку в действительный JSON:

    • Замените разделяющие одинарные кавычки строковых литералов двойными кавычками;
    • Заключайте имена свойств без кавычек в двойные кавычки;
    • Удалите запятую после последнего свойства

Чтобы сделать это надежно, вам нужно написать анализатор, который так же сложен, как и анализатор JSON, но с некоторыми предположениями его, вероятно, можно упростить до этого:

// Sample data
var someCrawledJSCode = `
const somecode = 'somevalue';
arr.push({
  data1: {
    prices: [{
      prop1: 'hi',
      prop2: 'hello',
    },
    {
      prop1: 'foo',
      prop2: 'bar',
    }]
  }
});`;


var obj;
var notJson = someCrawledJSCode.replace(/\.push\(([^]*?)\)/, (_, notJson) => {
    // Try to turn the string into valid JSON:
    // 1. string literals should not be enclosed in single, but double quotes
    // 2. property names should be enclosed in double quotes
    // 3. there should be no trailing comma after the last property
    var json = notJson.replace(/'((\\.|[^\\'])*)'/g, '"$1"')
                      .replace(/(\w+):/g, '"$1":')
                      .replace(/,\s*}/g, "}");
    obj = JSON.parse(json);
});
console.info(obj);

Все еще может пойти не так, но, по крайней мере, вы не используете eval. Например, если у вас есть строковый литерал, содержимое которого соответствует (\w+):, то приведенное выше действие изменит эту строку. Можно, конечно, сделать синтаксический анализ более надежным...

Исправлена ​​проблема с группой захвата.

trincot 08.04.2019 10:02
Ответ принят как подходящий

Краткий ответ: не создавайте (пере) парсер в Node.js, вместо этого используйте браузер

Я настоятельно не рекомендую оценивать или анализировать просканированные данные в Node.js, если вы все равно используете puppeteer для сканирования. Когда вы используете puppeteer, у вас уже есть браузер с отличной песочницей для кода JavaScript, работающего в другом процессе. Зачем рисковать такой изоляцией и «перестраивать» синтаксический анализатор в сценарии Node.js? Если ваш скрипт Node.js сломается, весь ваш скрипт выйдет из строя. В худшем случае вы можете даже подвергнуть свою машину серьезному риску, когда попытаетесь запустить ненадежный код внутри основного потока.

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

Пример

Представьте себе следующую HTML-страницу (очень упрощенную). Вы пытаетесь прочитать текст, который помещается в массив. Единственная информация, которая у вас есть, это то, что есть дополнительный атрибут id, который установлен на target-data.

<html>
<body>
  <!--- ... -->
  <script>
    var arr = [];
    // some complex code...
    arr.push({
      id: 'not-interesting-data',
      data: 'some data you do not want to crawl',
    });
    // more complex code here...
    arr.push({
      id: 'target-data',
      data: 'THIS IS THE DATA YOU WANT TO CRAWL', // <---- You want to get this text
    });
    // more code...
    arr.push({
      id: 'some-irrelevant-data',
      data: 'again, you do not want to crawl this',
    });
  </script>
  <!--- ... -->
</body>
</html>

Плохой код

Вот простой пример того, как может выглядеть ваш код прямо сейчас:

await page.goto('http://...');
const crawledJsCode = await page.evaluate(() => document.querySelector('script').innerHTML);

В этом примере скрипт извлекает код JavaScript со страницы. Теперь у нас есть код JavaScript со страницы, и нам нужно «всего лишь» разобрать его, верно? Что ж, это неправильный подход. Не пытайтесь перестроить парсер внутри Node.js. Просто используйте браузер. Есть в основном два подхода, которые вы можете использовать, чтобы сделать это в вашем случае.

  1. Внедрить прокси-функции на страницу и подделать некоторые встроенные функции (рекомендуется)
  2. Разобрать данные на стороне клиента (!) с помощью JSON.parse, регулярного выражения или eval (eval, только если это действительно необходимо)

Вариант 1. Внедрить прокси-функции на страницу

В этом подходе вы заменяете собственные функции браузера своими «фальшивыми функциями». Пример:

const originalPush = Array.prototype.push;
Array.prototype.push = function (item) {
    if (item && item.id === 'target-data') {
        const data = item.data; // This is the data we are trying to crawl
        window.exposedDataFoundFunction(data); // send this data back to Node.js
    }
    originalPush.apply(this, arguments);
}

Этот код заменяет исходную функцию Array.prototype.push нашей собственной функцией. Все работает как обычно, но когда элемент с нашим целевым идентификатором помещается в массив, срабатывает особое условие. Чтобы внедрить эту функцию на страницу, вы можете использовать page.evaluateOnNewDocument. Чтобы получить данные из Node.js, вам нужно будет предоставить браузеру функцию через page.exposeFunction:

// called via window.dataFound from within the fake Array.prototype.push function
await page.exposeFunction('exposedDataFoundFunction', data => {
    // handle the data in Node.js
});

Теперь уже не так важно, насколько сложен код страницы, происходит ли это внутри какого-то асинхронного обработчика или страница изменяет окружающий код. Пока целевые данные помещают данные в массив, мы их получим.

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

Вариант 2. Проанализируйте данные

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

Затем вы должны проанализировать данные, но не в вашей среде Node.js. Сделайте это внутри контекста страницы. Вы можете запустить регулярное выражение или использовать JSON.parse. Но сделайте это перед возвратом данных обратно для Node.js. Преимущество этого подхода в том, что если ваш код по какой-то причине приведет к сбою вашей среды, это будет не ваш основной скрипт, а только что ваш браузер, который выйдет из строя.

Чтобы привести пример кода. Вместо того, чтобы запускать код из исходного примера «плохого кода», мы меняем его на это:

const crawledJsCode = await page.evaluate(() => {
    const code = document.querySelector('script').innerHTML; // instead of returning this
    const match = code.match(/some tricky regex which extracts the data you want/); // we run our regex in the browser
    return match; // and only return the results
});

Это вернет только те части кода, которые нам нужны, которые затем могут быть обработаны внутри Node.js.


Независимо от того, какой подход вы выберете, оба способа намного лучше и безопаснее, чем выполнение неизвестного кода внутри основного потока. Если вам абсолютно необходимо обрабатывать данные в среде Node.js, используйте для них регулярное выражение, как показано в ответе от trincot. Вы должны никогда использовать eval для запуска ненадежного кода.

Спасибо за предложение нового способа. Я решил эту проблему с помощью evaluate и возврата arr.

Wonjun Kim 09.04.2019 04:52

Я думаю, что использование генератора AST, такого как Esprima или других инструментов AST, — это самый простой способ читать исходный код и работать с ним.

Честно говоря, если вы разберетесь, как запустить Esprima и сгенерируете «Абстрактное синтаксическое дерево» из исходного кода, вы обнаружите, что на удивление легко и просто читать сгенерированную древовидную структуру, представляющую код, который вы только что проанализировали, и вы обнаружите, что это удивительно легко читать информацию и конвертировать ее во что угодно.

Сначала это может показаться пугающим, но, честно говоря, это не так. Вы будете удивлены: инструменты AST, такие как Esprima, были созданы именно для целей, аналогичных тем, которые вы пытаетесь сделать, чтобы упростить работу.

Инструменты AST созданы в результате многолетних исследований того, как читать исходный код и манипулировать им, поэтому я настоятельно рекомендую их.

Попробуйте!

Чтобы помочь вам понять, как выглядят различные AST, вы можете посмотреть https://astexplorer.net. Очень полезно знать, как выглядят древовидные структуры AST из различных инструментов.

О, последнее! Чтобы обойти дерево AST, вы можете использовать что-то вроде https://github.com/estools/estraverse. Это облегчит жизнь.

Другие вопросы по теме