Как получить объект JavaScript в коде JavaScript? - PullRequest
4 голосов
/ 08 апреля 2019

TL; DR

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

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

Проблема

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

Пример кода JavaScript для обхода:

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

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

Что я сделал

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

Ответы [ 3 ]

5 голосов
/ 08 апреля 2019

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

Я настоятельно не рекомендую оценивать или анализировать сканируемые данные в Node.js, если вы все равно используете кукловод для сканирования. Когда вы используете 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. Внедрение прокси-функций на страницу

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

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 для запуска ненадежного кода.

1 голос
/ 08 апреля 2019

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

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

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

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

Дай попробовать!

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

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

0 голосов
/ 08 апреля 2019

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

  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.log(obj);

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

...