tag - scrape with selenium python
¿Cómo extraer un objeto JSON que se definió en una página HTML javascript block usando Python? (3)
Algo como esto puede funcionar:
import re
HTML = """
<html>
<head>
...
<script type= "text/javascript">
window.blog.data = {"activity":
{"type":"read"}
};
...
</script>
</head>
<body>
...
</body>
</html>
"""
JSON = re.compile(''window.blog.data = ({.*?});'', re.DOTALL)
matches = JSON.search(HTML)
print matches.group(1)
Estoy descargando páginas HTML que tienen datos definidos en ellas de la siguiente manera:
... <script type= "text/javascript"> window.blog.data = {"activity":{"type":"read"}}; </script> ...
Me gustaría extraer el objeto JSON definido en ''window.blog.data''. ¿Hay una manera más simple que analizarla manualmente? (Estoy buscando en Beautiful Soap, pero no puedo encontrar un método que devuelva el objeto exacto sin analizar)
Gracias
Editar: ¿Sería posible y más correcto hacer esto con un navegador sin cabeza de python (por ejemplo, Ghost.py)?
Tuve un problema similar y terminé usando selenio con phantomjs. Es un poco hacky y no pude entender el método correcto de esperar hasta el método, pero la espera implícita parece funcionar bien hasta ahora para mí.
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(''</script>'').strip().rstrip('';'').strip()
# only care about first piece of json
json_text = json_text.split("};")[0] + "}"
data = json.loads(json_text)
driver.quit()
`` `
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:
- Extrae el texto de
<script>
usando un analizador html - asume que
window.blog...
es una sola línea o que no hay'';''
dentro del objeto y extrae el literal del objeto javascript usando manipulaciones simples de cadenas o una expresión regular - asumir que la cadena es un JSON válido y analizarlo utilizando el módulo json
Ejemplo:
#!/usr/bin/env python
html = """<!doctype html>
<title>extract javascript object as json</title>
<script>
// ..
window.blog.data = {"activity":{"type":"read"}};
// ..
</script>
<p>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, el código falla.
Para relajar la segunda suposición, se podría usar un analizador de 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, obj
se puede visitar recursivamente como otros nodos AS. Permitiría admitir código javascript arbitrario (que puede ser analizado por slimit
).