4
votes

Comment obtenir un objet JavaScript dans du code JavaScript?

TL; DR

Je veux que parseParameter analyse JSON comme le code suivant. someCrawledJSCode est un code JavaScript exploré.

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

Problème

Je suis en train d'explorer du code JavaScript avec puppeteer et je veux extraire un objet JSON, mais je ne sais pas comment analyser le code JavaScript donné.

Exemple de code JavaScript analysé:

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

2 commentaires

Il existe une solution disponible mais elle est très DÉSÉCURISÉE, d'autant plus que vous récupérez les js d'une source externe en explorant. Vous pouvez essayer d'utiliser eval .


J'ai pensé à eval mais je vais exécuter ce code côté serveur et c'est trop dangereux. Alors je veux une autre solution. Merci d'avoir répondu.


3 Réponses :


0
votes

Le grattage va être moche. Avec quelques hypothèses sur la chaîne que vous essayez d'analyser, vous pouvez:

  1. Extraire la partie qui est poussée vers le tableau
  2. Convertissez cette chaîne en JSON valide:

    • Remplacez les guillemets simples de délimitation des chaînes littérales par des guillemets doubles;
    • Enveloppez les noms de propriétés sans guillemets avec des guillemets doubles;
    • Supprimez la virgule après la dernière propriété

Pour faire cela de manière fiable, vous devez écrire un analyseur qui est tout aussi complexe qu'un analyseur JSON, mais avec certaines hypothèses, il peut probablement être simplifié à ceci:

// 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);

Les choses peuvent encore mal tourner, mais au moins vous n'utilisez pas eval . Par exemple, si vous avez une chaîne littérale dont le contenu correspond à (\ w +): , alors ce qui précède modifierait cette chaîne. On peut bien sûr rendre l'analyse plus fiable ...


1 commentaires

Correction d'un problème avec un groupe de capture.



6
votes

Réponse courte: ne (re) construisez pas un analyseur dans Node.js, utilisez plutôt le navigateur

Je déconseille fortement d'évaluer ou d'analyser les données explorées dans Node.js si vous utilisez de toute façon puppeteer pour l'exploration. Lorsque vous utilisez puppeteer, vous disposez déjà d'un navigateur avec un bon bac à sable pour le code JavaScript exécuté dans un autre processus . Pourquoi risquer ce genre d'isolement et «reconstruire» un analyseur dans votre script Node.js? Si votre script Node.js est interrompu, tout votre script échouera. Dans le pire des cas, vous pourriez même exposer votre machine à de sérieux risques lorsque vous essayez d'exécuter du code non approuvé dans votre thread principal.

Au lieu de cela, essayez de faire autant d'analyse que possible dans le contexte de la page . Vous pouvez même faire un appel evil eval là-bas. Le pire qui puisse arriver? Votre navigateur se bloque ou plante.

Exemple

Imaginez la page HTML suivante (très simplifiée). Vous essayez de lire le texte qui est placé dans un tableau. La seule information dont vous disposez est qu'il existe un attribut supplémentaire id qui est défini sur target-data.

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
});

Code incorrect

Voici un exemple simple à quoi votre code pourrait ressembler actuellement:

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

Dans cet exemple, le script extrait le code JavaScript du page. Maintenant, nous avons le code JavaScript de la page et nous n'avons "que" besoin de l'analyser, non? Eh bien, ce n'est pas la bonne approche. N'essayez pas de reconstruire un analyseur à l'intérieur de Node.js. Utilisez simplement le navigateur. Il existe essentiellement deux approches que vous pouvez adopter pour y parvenir dans votre cas.

  1. Injecter des fonctions proxy dans la page et simuler certaines fonctions intégrées (recommandé)
  2. Analyser les données côté client (!) en utilisant JSON.parse , un regex ou eval (eval uniquement si vraiment nécessaire)

Option 1: Injecter des fonctions proxy dans la page

Dans cette approche, vous remplacez les fonctions natives du navigateur par vos propres «fausses fonctions». Exemple:

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);
}

Ce code remplace la fonction d'origine Array.prototype.push par notre propre fonction. Tout fonctionne normalement, mais quand un élément avec notre id cible est poussé dans un tableau, une condition spéciale est déclenchée. Pour injecter cette fonction dans la page, vous pouvez utiliser page.evaluateOnNewDocument . Pour recevoir les données de Node.js, vous devez exposer une fonction au navigateur via page.exposeFunction :

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

La complexité du code du page est, que cela se produise dans un gestionnaire asynchrone ou si la page change le code environnant. Tant que les données cibles poussent les données dans un tableau, nous les obtiendrons.

Vous pouvez utiliser cette approche pour de nombreuses explorations. Vérifiez comment les données sont traitées et remplacez les fonctions de bas niveau traitant les données par votre propre version proxy.

Option 2: analyser les données

Supposons que la première approche ne fonctionne pas travailler pour une raison quelconque. Les données sont dans une balise de script, mais vous ne pouvez pas les obtenir en utilisant de fausses fonctions.

Ensuite, vous devriez analyser les données, mais pas dans votre environnement Node.js. Faites-le dans le contexte de la page. Vous pouvez exécuter une expression régulière ou utiliser JSON.parse . Mais faites-le avant de renvoyer les données à Node.js. Cette approche présente l'avantage que si votre code plante votre environnement pour une raison quelconque, ce ne sera pas votre script principal, mais juste votre navigateur qui plante.

Pour donner un exemple code. Au lieu d'exécuter le code à partir de l'exemple de "mauvais code" d'origine, nous le changeons comme suit:

<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>

Cela ne renverra que les parties du code dont nous avons besoin, qui peuvent alors Soyez plus fruther traité depuis Node.js.


Quelle que soit l'approche que vous choisissez, les deux méthodes sont bien meilleures et plus sûres que d'exécuter du code inconnu dans votre thread principal. Si vous devez absolument traiter les données dans votre environnement Node.js, utilisez une expression régulière pour cela comme indiqué dans la réponse de trincot. Vous ne devez jamais utiliser eval pour exécuter du code non approuvé.


1 commentaires

Merci pour une nouvelle suggestion de voie. J'ai résolu ce problème en utilisant evaluer et en renvoyant arr .



1
votes

Je pense que l'utilisation d'un générateur AST comme Esprima ou d'autres outils AST est le moyen le plus simple de lire et de travailler avec le code source.

Honnêtement, si vous trouvez comment exécuter Esprima et générez un «arbre de syntaxe abstraite» à partir du code source, vous trouverez étonnamment facile et simple de lire l'arborescence générée qui représente le code que vous venez d'analyser, et vous trouverez étonnamment facile de lire les informations et de les convertir en tout ce que vous voulez.

Cela peut sembler intimidant au début, mais honnêtement, ce n'est pas le cas. Vous serez surpris: les outils AST comme Esprima ont été conçus exactement pour des objectifs similaires à ceux que vous essayez de faire, afin de vous faciliter la tâche.

Les outils AST sont nés de plusieurs années de recherche sur la façon de lire et de manipuler le code source, je les recommande donc vivement.

Essayez-le!

Pour vous aider à comprendre à quoi ressemblent les différents AST, vous pouvez consulter https://astexplorer.net . C'est très utile pour savoir à quoi ressemblent les arborescences AST de divers outils.

Oh, une dernière chose! Pour parcourir un arbre AST, vous pouvez utiliser quelque chose comme https://github.com/estools/estraverse. Cela vous facilitera la vie.


0 commentaires