Краткий ответ: не перестраивайте анализатор в 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. Просто используйте браузер. Есть два основных подхода, которые вы можете использовать для этого в вашем случае.
- Добавление прокси-функций на страницу и подделка некоторых встроенных функций (рекомендуется)
- Разобрать данные на стороне клиента (!), Используя
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 для запуска ненадежного кода.