¿Cómo extraer un objeto JSON que se definió en un bloque javascript de una página HTML usando Python?

Estoy descargando páginas HTML que tienen datos definidos de la siguiente manera:

...  window.blog.data = {"activity":{"type":"read"}};  ... 

Me gustaría extraer el objeto JSON definido en ‘window.blog.data’. ¿Hay una forma más sencilla de analizarlo manualmente? (Estoy buscando en Beautiful Soap pero parece que no puedo encontrar un método que devuelva el objeto exacto sin analizarlo)

Gracias

Edición: ¿Sería posible y más correcto hacer esto con un navegador sin cabeza de python (por ejemplo, Ghost.py)?

BeautifulSoup es un analizador html; También necesita un analizador de JavaScript aquí. por cierto, algunos literales de objetos javascript no son válidos json (aunque en su ejemplo, el literal también es un objeto json válido).

En casos simples usted podría:

  1. extraiga el texto de usando un analizador html
  2. supongamos que window.blog... es una sola línea o no hay ';' dentro del objeto y extraiga el literal del objeto javascript utilizando manipulaciones de cadena simples o una expresión regular
  3. asume que la cadena es un json válido y analízalo usando el módulo json

Ejemplo:

 #!/usr/bin/env python html = """ extract javascript object as json  

some other html here """ import json import re from bs4 import BeautifulSoup # $ pip install beautifulsoup4 soup = BeautifulSoup(html) script = soup.find('script', text=re.compile('window\.blog\.data')) json_text = re.search(r'^\s*window\.blog\.data\s*=\s*({.*?})\s*;\s*$', script.string, flags=re.DOTALL | re.MULTILINE).group(1) data = json.loads(json_text) assert data['activity']['type'] == 'read'

Si las suposiciones son incorrectas, entonces el código falla.

Para relajar la segunda suposición, se podría usar un analizador javascript en lugar de una expresión regular, por ejemplo, slimit ( sugerido por @approximatenumber ):

 from slimit import ast # $ pip install slimit from slimit.parser import Parser as JavascriptParser from slimit.visitors import nodevisitor soup = BeautifulSoup(html, 'html.parser') tree = JavascriptParser().parse(soup.script.string) obj = next(node.right for node in nodevisitor.visit(tree) if (isinstance(node, ast.Assign) and node.left.to_ecma() == 'window.blog.data')) # HACK: easy way to parse the javascript object literal data = json.loads(obj.to_ecma()) # NOTE: json format may be slightly different assert data['activity']['type'] == 'read' 

No es necesario tratar el objeto literal ( obj ) como un objeto json. Para obtener la información necesaria, los obj pueden ser visitados recursivamente como otros nodos ast. Permitiría soportar código javascript arbitrario (que puede ser analizado por slimit ).

Algo como esto puede funcionar:

 import re HTML = """   ...    ...   """ JSON = re.compile('window.blog.data = ({.*?});', re.DOTALL) matches = JSON.search(HTML) print matches.group(1) 

Tuve un problema similar y terminé usando selenium con phantomjs. Es un poco intrincado y no pude descifrar la espera correcta hasta el método, pero la espera implícita parece funcionar bien hasta el momento.

 from selenium import webdriver import json import re url = "http..." driver = webdriver.PhantomJS(service_args=['--load-images=no']) driver.set_window_size(1120, 550) driver.get(url) driver.implicitly_wait(1) script_text = re.search(r'window\.blog\.data\s*=.*<\/script>', driver.page_source).group(0) # split text based on first equal sign and remove trailing script tag and semicolon json_text = script_text.split('=',1)[1].rstrip('').strip().rstrip(';').strip() # only care about first piece of json json_text = json_text.split("};")[0] + "}" data = json.loads(json_text) driver.quit() 

“ `