in

如何使用Python中的JMESPath解析JSON

如何使用Python中的JMESPath解析JSON

JMESPath是一种流行的 JSON 查询语言,用于解析 JSON 数据集。随着 JSON 成为该媒体中最流行的数据结构,它在网络爬取中越来越受欢迎。 许多流行的网络爬取目标包含可以直接提取的隐藏 JSON 数据。不幸的是,这些数据集非常庞大并且包含大量无用数据。这使得 JSON 解析成为现代网络爬取过程的重要组成部分。 在本 Jmespath 教程中,我们将快速概述网络爬取和 Python 中的这种路径语言。我们将介绍设置、最常用的功能,并通过爬取Realtor.com做一个快速的真实示例项目。

什么是 JMESPath?

JMESPath 是一种用于解析 JSON 数据集的路径语言。简而言之,它允许编写用于选择 JSON 中特定数据字段的路径规则。 当网络爬取时,JMESPath 类似于我们用来解析 HTML 的 XPath 或 CSS 选择器——但对于 JSON。这使得 JMESPath 成为我们网络爬取工具集的出色补充,因为 HTML 和 JSON 是该领域中最常见的数据格式。

JMESPath 设置

JMESPath 以许多不同的语言实现:

在本教程中,我们将使用 Python,但其他语言应该非常相似。 要在 Python 中安装 jmespath,我们可以使用pip install终端命令:

$ pip install jmespath

Jmespath 使用教程

您可能熟悉基于字典/哈希表点的路径选择器,例如data.address.zipcode– 这种点符号是 JMESPath 的基础,但它可以做更多的事情! 就像 Python 的列表一样,我们也可以对jmespath 数组进行切片和索引:

import jmespath
data = {
    "people": [
        {"address": ["123 Main St", "California" ,"US"]},
    ]
}
jmespath.search("people[0].address[:2]", data)
['123 Main St', 'California']

此外,我们可以应用为每个列表元素应用规则的投影。这是通过语法完成的[]

data = {
  "people": [
    {"address": ["123 Main St", "California" ,"US"]},
    {"address": ["345 Alt St", "Alaska" ,"US"]},
  ]
}
jmespath.search("people[].address[:2]", data)
[
  ['123 Main St', 'California'], 
  ['345 Alt St', 'Alaska']
]

就像列表一样,我们也可以将类似的投影应用于对象(字典)。为此*使用:

data = {
  "people": {
    "foo": {"email": "[email protected]"},
    "bar": {"email": "[email protected]"},
  }
}
jmespath.search("people.*.email", data)
[
  "[email protected]",
  "[email protected]",
]

用于 Web 爬取的 JMESPath 最有趣的特性必须是数据重塑。使用.[]and.{}语法我们可以完全重塑列表和对象:

data = {
  "people": [
    {
      "name": "foo", 
      "age": 33, 
      "addresses": [
        "123 Main St", "California", "US"
      ],
      "primary_email": "[email protected]",
      "secondary_email": "[email protected]",
    }
  ]
}
jmespath.search("""
  people[].{
    first_name: name, 
    age_in_years: age,
    address: addresses[0],
    state: addresses[1],
    country: addresses[2],
    emails: [primary_email, secondary_email]
  }
""", data)
[
  {
    'address': '123 Main St',
    'age_in_years': 33,
    'country': 'US',
    'emails': ['[email protected]', '[email protected]'],
    'first_name': 'foo',
    'state': 'California'
  }
]

如您所见,使用 JMESPath 我们可以轻松地将复杂的数据集解析为更易于消化的内容,这在网络爬取 JSON 数据集时特别有用。

Web 爬取工具示例

让我们通过查看如何在网络爬取中使用它来探索一个真实的 JMESPath python 示例。 在这个示例项目中,我们将在realtor.com上爬取房地产数据,realtor.com 是美国流行的房地产出租和销售门户网站。 我们将使用一些 Python 包:

  • httpx – HTTP 客户端库,可以让我们与 Realtor.com 的服务器进行通信
  • parsel – HTML 解析库,它将帮助我们解析网络爬取的 HTML 文件。

当然jmespath还有解析 JSON。pip install所有这些都可以使用命令安装:

$ pip install jmespath httpx parsel

Realtor.com 使用隐藏的网络数据来呈现其属性页面,这意味着我们无需解析 HTML,就可以找到隐藏在 HTML 代码中的整个 JSON 数据集。 让我们看一下像这样的任何随机示例属性 如果我们查看页面源代码,我们可以看到隐藏在标记中的 JSON 数据集<script>

realtor.com 上的页面源隐藏数据插图
我们可以看到隐藏在脚本元素中的整个属性数据集

我们可以使用 HTML 解析器提取它,尽管它很大并且包含一堆乱码的计算机数据。所以我们可以使用 JMESPath 解析出有用的位:

import json
import httpx
import jmespath
from parsel import Selector

# establish HTTP client and to prevent being instantly banned lets set some browser-like headers
session = httpx.Client(
    headers={
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Accept-Language": "en-US,en;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
    },
)

# 1. Scrape the page and parse hidden web data
response = session.get(
    "https://www.realtor.com/realestateandhomes-detail/335-30th-Ave_San-Francisco_CA_94121_M17833-49194"
)
assert response.status_code == 200, "response is banned"
selector = Selector(text=response.text)
# find <script id="__NEXT_DATA__"> node and select it's text:
data = selector.css("script#__NEXT_DATA__::text").get()
# load JSON as python dictionary and select property value:
data = json.loads(data)["props"]["pageProps"]["initialProps"]["propertyData"]

# 2. Reduce web dataset to important data fields:
result = jmespath.search(
    """{
    id: listing_id,
    url: href,
    status: status,
    price: list_price,
    price_per_sqft: price_per_sqft,
    date: list_date,
    details: description,
    features: details[].text[],
    photos: photos[].{url: href, tags: tags[].label}
}
""", data)
print(result)

示例输出

{
  "id": "2950457253",
  "url": "https://www.realtor.com/realestateandhomes-detail/335-30th-Ave_San-Francisco_CA_94121_M17833-49194",
  "status": "for_sale",
  "price": 2995000,
  "price_per_sqft": 977,
  "date": "2022-12-04T23:43:42Z",
  "details": {
    "baths": 4,
    "baths_consolidated": "3.5",
    "baths_full": 3,
    "baths_3qtr": null,
    "baths_half": 1,
    "baths_1qtr": null,
    "baths_min": null,
    "baths_max": null,
    "beds_min": null,
    "beds_max": null,
    "beds": 4,
    "garage": null,
    "pool": null,
    "sqft": 3066,
    "sqft_min": null,
    "sqft_max": null,
    "lot_sqft": 3000,
    "rooms": null,
    "stories": null,
    "sub_type": null,
    "text": "With four bedrooms, three and one-half baths, and over 3, 000 square feet of living space, 335 30th avenue offers a fantastic modern floor plan with classic finishes in the best family-friendly neighborhood in San Francisco. Originally constructed in 1908, the house underwent a total gut renovation and expansion in 2014, with an upgraded foundation, all new plumbing and electrical, double-pane windows and all new energy efficient appliances. Interior walls were removed on the main level to create a large flowing space. The home is detached on three sides (East, South, and West) and enjoys an abundance of natural light. The top floor includes the primary bedroom with two gorgeous skylights and an en-suite bath; two kids bedrooms and a shared hall bath. The main floor offers soaring ten foot ceilings and a modern, open floor plan perfect for entertaining. The combined family room - kitchen space is the heart of the home and keeps everyone together in one space. Just outside the breakfast den, the back deck overlooks the spacious yard and offers indoor/outdoor living. The ground floor encompasses the garage, a laundry room, and a suite of rooms that could serve as work-from-home space, AirBnB, or in-law unit.",
    "type": "single_family",
    "units": null,
    "unit_type": null,
    "year_built": 1908,
    "name": null
  },
  "features": [
    "Bedrooms: 4",
    "...etc, reduced for blog"
  ],
  "photos": [
    {
      "url": "https://ap.rdcpix.com/f707c59fa49468fde4999bbd9e2d433bl-m872089375s.jpg",
      "tags": [
        "garage",
        "house_view",
        "porch",
        "complete",
        "front"
      ]
    },
    "...etc, reduced for blog"
  ]
}

使用 JMESpath,我们仅用几行 Python 代码和一个 JMESPath 查询就设法将数千行 JSON 减少为基本数据字段 – 非常棒!

常问问题

为了总结 JMESPath 教程,让我们看一下一些常见问题:

JMESPath 中的 [] 和 [*] 有什么区别?

[]平所有结果,同时[*]保持原始数据集中的结构。在 Python 中查看此示例:

data = {
  "employees": [
    {
      "people": [
        {"address": ["123 Main St", "California", "US"]},
        {"address": ["456 Sec St", "Nevada", "US"]},
      ],
    },
    {
      "people": [
        {"address": ["789 Main St", "Washington", "US"]},
        {"address": ["a12 Sec St", "Alaska", "US"]},
      ],
    },
  ]
}

jmespath.search("employees[*].people[*].address", data)
[
  # fromt he first group:
  [['123 Main St', 'California', 'US'], ['456 Sec St', 'Nevada', 'US']],
  # from the second group:
  [['789 Main St', 'Washington', 'US'], ['a12 Sec St', 'Alaska', 'US']]
]

jmespath.search("employees[].people[].address", data)
[
  # all groups merged:
  ['123 Main St', 'California', 'US'],
  ['456 Sec St', 'Nevada', 'US'],
  ['789 Main St', 'Washington', 'US'],
  ['a12 Sec St', 'Alaska', 'US']
]

JMESPath 可以在 HTML 上使用吗?

不,因为那指的是非常相似的 HTML 路径语言,如CSS SelectorsXpath Selectors

JMESPath 有哪些替代方案?

其他一些流行的 JSON 查询语言是JsonPathJQpyquery

Web 爬取中的 Jmespath 总结

在本 JMESPath 教程中,我们快速概述了这种路径语言在解析 JSON 时的功能。 我们已经通过使用jmespath python 库的Python 示例介绍了 JMESPath 的多个过滤器和选择器。最后,为了总结所有内容,我们了解了 JMESPath 如何用于通过现实生活中的爬取器进行 Web 爬取。

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。