Как вытащить данные со страницы сайта, если они передаются в js коде? Python3

У меня есть задача спарсить один сайт. Проблема в том, что там используется что-то типа vue/react/angular и серверный рендеринг. Но очень странный серверный рендеринг. Данные отправляются в самом документе, но они не сгенерированы в html. Они в js коде. То есть возвращается примерно такой документ:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="content"></div>

    <script>
        let data = {...}
    </script>
</body>
</html>

Где data обычный js объект. Далее уже у клиента данные из переменной data преобразуются в html. Как такое парсить с помощью python?


Ответы (2 шт):

Автор решения: Arty

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

Я оформил пример извлечения из HTML в следующем коде. Для его работы необходимо установить один regex PIP модуль (один раз) python -m pip install regex.

Суть работы моего алгоритма следующая. Берётся регулярное выражение для полной валидации JSON (его можно увидеть в коде функции extract_jsons(...). Далее из HTML извлекаются все подстроки которые являются валидным JSON, кроме основного дампа данных могут быть ещё много мелких (одиночные числа, строки и т.п.). Сортируем по убыванию длины полученных подстрок, в надежде что наши данные будут самой длинной строкой. Ну и для вывода данны в консоль или сохранения куда либо мы красиво их форматируем используя json модуль.

Проверьте, если для вас сработает то очень здорово, если нет, значит там не валидный JSON, а просто валидный JavaScript объект (т.к. он от JSON может отличаться послаблением правил), тогда надо регулярное выражение наше дорабатывать именно под JavaScript синтаксис объектов, это не очень сложно, но нужен актуальный пример. Сообщайте о ваших результатах, если надо регулярное выражение доработаем!

Попробовать код ниже онлайн!

def extract_jsons(text):
    import regex as re
    json_re = re.compile(r"""
      (?(DEFINE)
         (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )    
         (?<boolean>   true | false | null )
         (?<string>    " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " )
         (?<array>     \[  (?:  (?&json)  (?: , (?&json)  )*  )?  \s* \] )
         (?<pair>      \s* (?&string) \s* : (?&json)  )
         (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*  )?  \s* \} )
         (?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* )
      )
       (?&json) 
    """, re.S | re.I | re.X)
    return [m.group(0) for m in re.finditer(json_re, text)]
    
def extract_jsobjs(text):
    import regex as re
    jsobj_re = re.compile(r"""
      (?(DEFINE)
         (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )    
         (?<boolean>   true | false | null )
         (?<identifier>  [0-9a-zA-Z_]+ )
         (?<sq_string>    ' ([^'\\\\]* | \\\\ ['\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* ' )
         (?<string>    (?: (?:" ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* ") | (?&sq_string)) )
         (?<array>     \[  (?:  (?&json)  (?: , (?&json)  )*  )?  \s* ,? \s* \] )
         (?<pair>      \s* (?:(?&string)|(?&identifier)) \s* : (?&json)  )
         (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*  )?  \s* ,? \s* \} )
         (?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* )
      )
       (?&json) 
    """, re.S | re.I | re.X)
    return [m.group(0) for m in re.finditer(jsobj_re, text)]
        
######## TEST #############

import json
    
html = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="content"></div>

    <script>
        let data = {"a": 123, "b":
                [1, null, 3.0, [{
                    key: 'value',
                    'qkey': 123.456,
                    key_2: "val2",
                },false,true,]]};
    </script>
</body>
</html>
"""

#print(json.dumps(json.loads(sorted(extract_jsobjs(html),
#    key = lambda e: len(e), reverse = True)[0]), indent = 4))

print(sorted(extract_jsobjs(html), key = lambda e: len(e), reverse = True)[0])

Вывод:

 {"a": 123, "b":
                [1, null, 3.0, [{
                    key: 'value',
                    'qkey': 123.456,
                    key_2: "val2",
                },false,true,]]}
→ Ссылка
Автор решения: Inventor

Моим решением стал пост на англоязычном stackoverflow: https://stackoverflow.com/questions/24027589/how-to-convert-raw-javascript-object-to-python-dictionary

Я использовал сначала demjson, эта библиотека делает именно то, что мне нужно. Далее я узнал, что существует JSON5 стандарт, синтаксис которого 1 в 1 совпадает с объектами JS. Для работы с JSON5 в python есть библиотека pyjson5. Я использовал её. Теперь мой скрипт выполняется не 10.5 секунд, а 0.004 секунды

→ Ссылка