борется за то, чтобы извлечь из него только json элементов, найденных в переменной "markersData"
На этой странице нет JSON. Существует JavaScript - выглядит аналогично, но это совсем не одно и то же.
Исходный код JavaScript можно получить, прочитав текстовое содержимое элемента <script type="text/javascript">
- поскольку существует несколько сценариев теги на странице, вы должны выбрать тот, который содержит строку "markersData"
.
Это достаточно легко сделать в BeautifulSoup, и я не собираюсь публиковать код для этого.
Более сложная часть задачи состоит в том, чтобы разобраться с исходным кодом JS - парсер JSON здесь не поможет, поэтому мы должны использовать парсер JavaScript и затем извлечь переменную markersData
из ее вывода .
Предположим, что мы использовали строку исходного кода, которую мы извлекли с помощью BeautifulSoup (пример взят дословно из исходного кода вашей целевой страницы):
var markersData = [
{
"id": '0',
"title": 'Al Preston's Garage, Inc.',
"lat": '41.324549',
"lng": '-73.106124',
"addship": '810 Howe Avenue',
"city": 'Shelton',
"state": 'CT',
"zip": '06484',
"phone": '(203) 924-1747',
"fax": '(203) 924-4594',
"machinetype": 'Sales, Service & Parts for:<div data-inline="true" class="dealerExcavator" title="Available at this dealer"></div><div data-inline="true" class="dealerTrackLoader" title="Available at this dealer"></div><div data-inline="true" class="dealerWheelLoader" title="Available at this dealer"></div>',
"website": 'http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp',
"description":
'<div class="popContainer">' +
'<div class="popTitle">Al Preston's Garage, Inc.</div>' +
'<div class="popBody">' +
'<span class="popBodyText">810 Howe Avenue</span><br />' +
'<span class="popBodyText">Shelton, CT 06484</span><br />' +
'<span class="popBodyText">Phone: (203) 924-1747</span><br />' +
'<span class="popBodyText">Fax: (203) 924-4594</span><br />' +
'<span class="popBodyText">Sales, Service & Parts for:<div data-inline="true" class="dealerExcavator" title="Available at this dealer"></div><div data-inline="true" class="dealerTrackLoader" title="Available at this dealer"></div><div data-inline="true" class="dealerWheelLoader" title="Available at this dealer"></div></span>' +
'<a href="http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp" target="_blank" class="popButton">Dealer Website</a>' +
'<a href="javascript:void(0)" onClick="directions(\'41.324549\', \'-73.106124\');" data-lat="41.324549" data-lng="-73.106124" class="popButton cs_ml_5">Dealer Directions</a>' +
'</div>' +
'</div>'
}
];
Один из Python инструментов, которые can parse JavaScript is pyjsparser , основанный на быстродействующем и полнофункциональном JS parser esprima . Он превратит исходный код JS в так называемое «абстрактное синтаксическое дерево» - очень похоже на HTML DOM, но с различными типами узлов.
from pyjsparser import parse
js_code = """ the sample JS code above """
program = parse(js_code)
Полученное синтаксическое дерево составлено вложенных диктов и списков. Чтобы понять, как это выглядит, мы выкидываем дерево в удобочитаемом формате. Модуль json
гораздо лучше справляется с этой задачей, чем собственный симпатичный принтер Python pprint
:
import json
print(json.dumps(program, indent=' '))
Результат:
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "markersData"
},
"init": {
"type": "ArrayExpression",
"elements": [
{
"type": "ObjectExpression",
"properties": [
{
"type": "Property",
"key": {
"type": "Literal",
"value": "id",
"raw": "\"id\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "0",
"raw": "'0'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "title",
"raw": "\"title\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "Al Preston's Garage, Inc.",
"raw": "'Al Preston's Garage, Inc.'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "lat",
"raw": "\"lat\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "41.324549",
"raw": "'41.324549'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "lng",
"raw": "\"lng\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "-73.106124",
"raw": "'-73.106124'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "addship",
"raw": "\"addship\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "810 Howe Avenue",
"raw": "'810 Howe Avenue'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "city",
"raw": "\"city\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "Shelton",
"raw": "'Shelton'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "state",
"raw": "\"state\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "CT",
"raw": "'CT'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "zip",
"raw": "\"zip\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "06484",
"raw": "'06484'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "phone",
"raw": "\"phone\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "(203) 924-1747",
"raw": "'(203) 924-1747'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "fax",
"raw": "\"fax\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "(203) 924-4594",
"raw": "'(203) 924-4594'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "machinetype",
"raw": "\"machinetype\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "Sales, Service & Parts for:<div data-inline=\"true\" class=\"dealerExcavator\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerTrackLoader\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerWheelLoader\" title=\"Available at this dealer\"></div>",
"raw": "'Sales, Service & Parts for:<div data-inline=\"true\" class=\"dealerExcavator\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerTrackLoader\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerWheelLoader\" title=\"Available at this dealer\"></div>'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "website",
"raw": "\"website\""
},
"computed": false,
"value": {
"type": "Literal",
"value": "http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp",
"raw": "'http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp'"
},
"kind": "init",
"method": false,
"shorthand": false
},
{
"type": "Property",
"key": {
"type": "Literal",
"value": "description",
"raw": "\"description\""
},
"computed": false,
"value": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Literal",
"value": "<div class=\"popContainer\">",
"raw": "'<div class=\"popContainer\">'"
},
"right": {
"type": "Literal",
"value": "<div class=\"popTitle\">Al Preston's Garage, Inc.</div>",
"raw": "'<div class=\"popTitle\">Al Preston's Garage, Inc.</div>'"
}
},
"right": {
"type": "Literal",
"value": "<div class=\"popBody\">",
"raw": "'<div class=\"popBody\">'"
}
},
"right": {
"type": "Literal",
"value": "<span class=\"popBodyText\">810 Howe Avenue</span><br />",
"raw": "'<span class=\"popBodyText\">810 Howe Avenue</span><br />'"
}
},
"right": {
"type": "Literal",
"value": "<span class=\"popBodyText\">Shelton, CT 06484</span><br />",
"raw": "'<span class=\"popBodyText\">Shelton, CT 06484</span><br />'"
}
},
"right": {
"type": "Literal",
"value": "<span class=\"popBodyText\">Phone: (203) 924-1747</span><br />",
"raw": "'<span class=\"popBodyText\">Phone: (203) 924-1747</span><br />'"
}
},
"right": {
"type": "Literal",
"value": "<span class=\"popBodyText\">Fax: (203) 924-4594</span><br />",
"raw": "'<span class=\"popBodyText\">Fax: (203) 924-4594</span><br />'"
}
},
"right": {
"type": "Literal",
"value": "<span class=\"popBodyText\">Sales, Service & Parts for:<div data-inline=\"true\" class=\"dealerExcavator\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerTrackLoader\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerWheelLoader\" title=\"Available at this dealer\"></div></span>",
"raw": "'<span class=\"popBodyText\">Sales, Service & Parts for:<div data-inline=\"true\" class=\"dealerExcavator\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerTrackLoader\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerWheelLoader\" title=\"Available at this dealer\"></div></span>'"
}
},
"right": {
"type": "Literal",
"value": "<a href=\"http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp\" target=\"_blank\" class=\"popButton\">Dealer Website</a>",
"raw": "'<a href=\"http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp\" target=\"_blank\" class=\"popButton\">Dealer Website</a>'"
}
},
"right": {
"type": "Literal",
"value": "<a href=\"javascript:void(0)\" onClick=\"directions('41.324549', '-73.106124');\" data-lat=\"41.324549\" data-lng=\"-73.106124\" class=\"popButton cs_ml_5\">Dealer Directions</a>",
"raw": "'<a href=\"javascript:void(0)\" onClick=\"directions(\\'41.324549\\', \\'-73.106124\\');\" data-lat=\"41.324549\" data-lng=\"-73.106124\" class=\"popButton cs_ml_5\">Dealer Directions</a>'"
}
},
"right": {
"type": "Literal",
"value": "</div>",
"raw": "'</div>'"
}
},
"right": {
"type": "Literal",
"value": "</div>",
"raw": "'</div>'"
}
},
"kind": "init",
"method": false,
"shorthand": false
}
]
}
]
}
}
],
"kind": "var"
},
{
"type": "EmptyStatement"
}
]
}
Каждый узел имеет type
и некоторые данные, в зависимости от типа. Узел Program
имеет список body
дочерних узлов, ArrayExpression
имеет elements
, ObjectExpression
имеет properties
, Literal
s имеет value
и т. Д.
Сложный бит здесь - это конкатенация строк HMTL в конце - прокрутка до конца - она состоит из вложенных BinaryExpression
узлов, каждый из которых имеет operator
(+) со стороной left
и right
side.
Чтобы преобразовать все это во что-то, что можно использовать в Python, мы должны сделать то, что называется «обход дерева» (или «обход дерева»). Мы посещаем каждый узел дерева, решаем, что с ним делать, а затем переходим к его дочерним узлам.
Простой обходчик деревьев начинается с какого-то узла, просматривает все свойства этого узла и затем пробует все свойства, которые являются списками. Функция предиката используется для фильтрации интересующих нас узлов:
def walk(node, predicate):
''' traverses the syntax tree and finds matching nodes (recursive) '''
descend = None
if not (isinstance(node, dict) and 'type' in node):
return
if predicate(node):
yield node
for key in node:
if isinstance(node[key], list):
for child in node[key]:
yield from walk(child, predicate)
Чтобы найти узел VariableDeclarator
, идентифицируемый именем markersData
, мы будем использовать его следующим образом:
is_markersData = lambda node: node['type'] == 'VariableDeclarator' and node['id']['name'] == 'markersData'
markersData_node = next(walk(program, is_markersData))
Теперь, когда у нас есть правильный узел, мы должны его оценить. Следующая функция может пройти, например, markersData_node
и вычислить значение результата. Сравните то, с чем функция справилась бы для каждого типа узла в синтаксическом дереве выше:
def evaluate(node):
''' converts a JS literal to a Python data structure (recursive) '''
# JS primitives are returned as their values
if node['type'] == 'Literal':
return node['value']
# JS object literals consist of multiple key-value properties
if node['type'] == 'ObjectExpression':
return { evaluate(p['key']): evaluate(p['value']) for p in node['properties'] }
# JS array literals are lists of elements that we evaluate individually
if node['type'] == 'ArrayExpression':
return [ evaluate(item) for item in node['elements'] ]
# expressions (such as `'string1' + 'string2'`) we calculate
if node['type'] == 'BinaryExpression':
op = node['operator']
left = evaluate(node['left'])
right = evaluate(node['right'])
if op == '+':
return left + right
raise Exception("Unsuported operator %s on %s." % (op, node))
# for variables we are interested in their initializer (the part after the `=`)
if node['type'] == 'VariableDeclarator':
return evaluate(node['init'])
# everything else causes an error, we can implement it when we see it
raise Exception("Don't know what to do with a %s." % node['type'])
Она понимает, что достаточно JavaScript, чтобы понять наш пример. Поддержка других вещей (таких как даты, регулярные выражения, другие операции, кроме +
) может быть добавлена, когда они происходят.
Когда мы называем это:
markersData = evaluate(markersData_node)
print(json.dumps(markersData[0:1], indent=" "))
, мы получаем:
[
{
"id": "0",
"title": "Al Preston's Garage, Inc.",
"lat": "41.324549",
"lng": "-73.106124",
"addship": "810 Howe Avenue",
"city": "Shelton",
"state": "CT",
"zip": "06484",
"phone": "(203) 924-1747",
"fax": "(203) 924-4594",
"machinetype": "Sales, Service & Parts for:<div data-inline=\"true\" class=\"dealerExcavator\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerTrackLoader\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerWheelLoader\" title=\"Available at this dealer\"></div>",
"website": "http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp",
"description": "<div class=\"popContainer\"><div class=\"popTitle\">Al Preston's Garage, Inc.</div><div class=\"popBody\"><span class=\"popBodyText\">810 Howe Avenue</span><br /><span class=\"popBodyText\">Shelton, CT 06484</span><br /><span class=\"popBodyText\">Phone: (203) 924-1747</span><br /><span class=\"popBodyText\">Fax: (203) 924-4594</span><br /><span class=\"popBodyText\">Sales, Service & Parts for:<div data-inline=\"true\" class=\"dealerExcavator\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerTrackLoader\" title=\"Available at this dealer\"></div><div data-inline=\"true\" class=\"dealerWheelLoader\" title=\"Available at this dealer\"></div></span><a href=\"http://takeuchi-us.alprestonsequipment.com/home_takeuchi-us.asp\" target=\"_blank\" class=\"popButton\">Dealer Website</a><a href=\"javascript:void(0)\" onClick=\"directions('41.324549', '-73.106124');\" data-lat=\"41.324549\" data-lng=\"-73.106124\" class=\"popButton cs_ml_5\">Dealer Directions</a></div></div>"
}
]
, что в точности соответствует тому, что код JS выдает в браузере - но здесь это фактическая структура данных Python, которую мы можем использовать. Я проверил его на целом JS источнике с вашей целевой страницы, и он работает для этого.
(Все это было бы намного проще, если бы ввод был на самом деле JSON для начала с.)