Вытащить все местоположения из локатора магазина, используя python и HTML - PullRequest
0 голосов
/ 28 апреля 2020

Я хочу получить все магазины с веб-сайта компании, используя python для проекта класса. Я могу получить данные HTML, но изо всех сил пытаюсь извлечь из него только те элементы json, которые находятся в переменной "markersData"

import urllib.request, urllib.parse, urllib.error
from bs4 import BeautifulSoup

url = "https://locator.takeuchi-us.com/"
html = urllib.request.urlopen(url)
soup = BeautifulSoup(html.read())

data = html.read().decode()

print(data)

Нужно извлечь все данные, найденные в переменная "markersData" (идентификатор, заголовок, широта, lng, сложение, город, штат, почтовый индекс, телефон)


Сокращенный фрагмент исходного кода с целевой страницы:

<body data-theme="a" data-content-theme="a">
    <!-- ... -->
    <script type="text/javascript">
        var markersData = [                
                    {
                        "id": '0',
                        "title": 'Al Preston&#39;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&#39;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>'
                    }
            ];
    </script>
</body>

1 Ответ

2 голосов
/ 28 апреля 2020

борется за то, чтобы извлечь из него только json элементов, найденных в переменной "markersData"

На этой странице нет JSON. Существует JavaScript - выглядит аналогично, но это совсем не одно и то же.

Исходный код JavaScript можно получить, прочитав текстовое содержимое элемента <script type="text/javascript"> - поскольку существует несколько сценариев теги на странице, вы должны выбрать тот, который содержит строку "markersData".

Это достаточно легко сделать в BeautifulSoup, и я не собираюсь публиковать код для этого.

Более сложная часть задачи состоит в том, чтобы разобраться с исходным кодом JS - парсер JSON здесь не поможет, поэтому мы должны использовать парсер JavaScript и затем извлечь переменную markersData из ее вывода .

Предположим, что мы использовали строку исходного кода, которую мы извлекли с помощью BeautifulSoup (пример взят дословно из исходного кода вашей целевой страницы):

        var markersData = [

                    {
                        "id": '0',
                        "title": 'Al Preston&#39;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&#39;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&#39;s Garage, Inc.",
                      "raw": "'Al Preston&#39;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&#39;s Garage, Inc.</div>",
                                            "raw": "'<div class=\"popTitle\">Al Preston&#39;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&#39;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&#39;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 для начала с.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...