in

如何使用 Python 爬取 GraphQL 数据

如何使用 Python 爬取 GraphQL 数据

GraphQL 正在成为处理动态网站中主要数据集的一种越来越流行的方式。数据量大的网站通常会使用 graphql 作为 javascript 驱动的前端的后端,它会在用户浏览网页时动态加载数据。这样做的主要缺点是抓取 graphql 网站更加困难,因为它需要浏览器模拟或逆向工程网站的后端功能。 在本文中,我们将了解什么是 Graphql 以及我们如何在网络抓取中处理它。我们将了解查询构建基础知识、如何检查网络目标和 graphql 查询以进行网络抓取。

什么是 GraphQL

Graphql 是一种用于基于图形的数据集的查询语言。图形在数据繁重的网站中越来越受欢迎,因为它是一种更高效、更灵活的数据传输方式。与 REST 服务相比,graphql 客户端可以显式请求数据集的特定部分,这是 Web 开发人员所期望的功能,因为它允许更高效的数据传输和更快、更灵活的开发。 当谈到网络抓取时,我们也可以分享同样的好处——通过减少查询,我们可以只抓取我们需要的细节,这将节省我们抓取的资源和带宽。 由 graphql 支持的网站的一些示例是: https : //wallmart.com/、https ://www.airbnb.com/、https : //www.coursera.org/、https : //opensea.io/ 等 抓取 Graphql 支持的网站通常有点困难,因为数据协议更复杂并且难以理解和复制。那么让我们来看看是什么让 graphql 协议如此特别,以及我们如何在网络抓取中利用它。

理解 Graphql

为了成功地抓取 GraphQL 支持的网站,我们需要了解两件事:一点 graphql 查询语言和一点网站逆向工程。让我们从 graphql 基础知识开始,以及我们如何在 Python 中应用它们。 Graphql 客户端通常通过包含两到三个关键字的 JSON 文档传输查询:强制query和可选operationName以及variables

{
    "operationName": "QueryName",
    "query": "query QueryName($variable: VariableType) {
        field1
        field2
        filedObject() {
            field3
            field4
        }
    }",
    "variables": {
        "variable": "europe"
    }
}

query 关键字必须包含一个有效的 graphql 查询,它是相当简单的函数,如语法。括号中的值表示可选的变量(例如MyQuery($some_number: Float)),大括号中的值表示我们希望返回的字段。 让我们用一个包含一些虚构产品的示例 graphql 演示服务器来尝试一下。为此,让我们启动docker并启动一个简单的演示服务器:

$ docker run -p 8000:8000 scrapecrow/graphql-products-example:latest
Running strawberry on `http://0.0.0.0:8000/graphql`

如果我们在浏览器中打开调试工作室链接 ( http://0.0.0.0:8000/graphql),我们可以使用前端帮助我们为这个演示数据集构建一些查询。让我们从返回价格大于100 的所有产品的产品查询开始:

# define query
query MyQuery($priceGt: Float) {
  # we can only query queries server supports, in this example server contains
  # "products" query which can take many different filter parameters
  products(priceGt: $priceGt) {
    # here we define which fields we want returned
    name
    price
    # take note that "object" fields need to be further expanded to include which sub-fields to retrieve
    locations {
      name
    }
  }
}

在这里,我们构建了一个非常简单的查询来获取产品名称、价格和位置详细信息。如果我们启动浏览器的网络检查器(F12在 chrome 中并选择Network选项卡)并查看当我们单击发送查询时发生的情况,我们可以看到正在发出一个简单的 json 请求:

graphql 检查器的插图

浏览器客户端将带有我们的查询和变量的请求作为 JSON 文档发送到服务器,服务器返回带有查询结果或错误的 JSON 文档 – 非常简单! 让我们在 Python 中复制这个请求以完全掌握 graphql 客户端行为(我们将使用requests包)

import json
import requests

query = """
query MyQuery($priceGt: Float) {
  products(priceGt: $priceGt) {
    name
    price
    locations {
      name
    }
  }
}
"""
json_data = {
    'query': query,
    'variables': {
        'priceGt': 100,
    },
    'operationName': 'MyQuery',
}

response = requests.post('http://0.0.0.0:8000/graphql', json=json_data)
print(json.dumps(response.json(), indent=2))

如果我们运行这个脚本,它会打印出我们在调试前端看到的相同 JSON 结果:

{
  "data": {
    "products": [
      {
        "name": "Nintendo gaming console [mark 1]",
        "price": 111.22,
        "locations": [
          {
            "name": "London"
          }
        ]
      },
      {
        "name": "Sony microwave [limited edition]",
        "price": 277.52,
        "locations": [
          {
            "name": "Bangkok"
          }
        ]
      }
      ...
  }
}

所以总而言之,我们可以将 graphql 查询作为 JSON POST 请求发送,query它的有效负载中必须包含关键字。

抓取 Graphql

现在我们了解了 graphql 以及如何发出简单的 graphql 请求,让我们来看看它在网络抓取的上下文中是如何工作的。 在我们可以将 graphql 查询发送到我们的抓取目标之前,我们首先需要反省可用的查询键和变量。有两种方法可以做到这一点:内省和逆向工程。

深入理解 Graphql

Introspection 是一个漂亮的 graphql 特性,它告诉 graphql 客户端有关可用图形数据集的信息:支持哪些查询,数据集对象包含哪些字段以及可以为数据过滤提供哪些变量。 不幸的是,许多公共服务器禁用了 graphql 自省以防止数据抓取或数据库攻击——但是,我们仍然可以尝试! 有几种简单的方法可以执行 graphql 自省。第一个是我们可以运行一个简单的 graphql 查询,它引用特殊__schema变量:

import json
import requests

query = """
query IntrospectionQuery {
  __schema {
    queryType { name }
    mutationType { name }
    subscriptionType { name }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}

fragment InputValue on __InputValue {
  name
  description
  type { ...TypeRef }
  defaultValue
}

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}
"""
json_data = {
    'query': query,
}
response = requests.post('http://0.0.0.0:8000/graphql', json=json_data)
print(json.dumps(response.json(), indent=2))

此查询将以 JSON 格式返回数据集的架构,其中包含有关可用查询、对象和字段的信息。 然而,人类阅读 JSON 模式可能是一项非常艰巨的任务,因此我们可以使用一些内省 GUI 工具来大大简化这个过程。让我们来看看我们如何使用 graphql 开发 GUI 来编写用于网络抓取的代码。

使用 Apollo Studio 进行自省

流行的内省工具由主要的 graphql 平台Apollo提供。这个平台在网络抓取中经常遇到,所以我们可以使用他们自己的工作室工具来逆向工程 graphql 查询。 在这个例子中,我们将使用 SpaceX 开放的 graphql 数据库:https://spacex.land。让我们转到此页面,看看我们的 Web 检查器中发生了什么:

示例 graphql 数据 SpaceX 项目

在这里,我们看到正在向 SpaceX 的 graphql 服务器发出 graphql 请求: https: //api.spacex.land/graphql 让我们尝试通过访问 https://studio.apollographql.com/并创建一个新图表项目:

阿波罗工作室界面图片
Apollo Studio 允许与公共 graphql 服务连接——在浏览器中试验和构建 graphql 查询!
Apollo Studio 仅适用于启用了内省的 graphql 服务器,并且通常可能需要添加一些额外的标头,例如 API 令牌或密钥(使用“添加标头”按钮)。

如果自省是可能的,Apollo Studio 将为我们提供一个方便的 graphql 调试环境,我们可以在其中构建和测试我们的查询:

graphql GUI界面图像
我们可以使用直观的 GUI 创建和测试我们的查询,然后在我们的爬虫代码中重新创建它们

一旦我们对我们的查询感到满意,我们就可以在您的网络抓取工具中复制它们,就像我们在上一节中所做的那样:

# scrape all SpaceX launch details
import json
import requests

query = """
query Launches {
  launches {
    launch_date_utc
    mission_name
    mission_id
    rocket {
      rocket {
        id
        name
        company
      }
    }
    details
    links {
      article_link
    }
  }
}
"""
json_data = {
    'query': query,
}

response = requests.post('https://api.spacex.land/graphql', json=json_data)
print(json.dumps(response.json(), indent=2))

在上面的代码中,我们可以在单个请求中检索所有启动数据!GraphQL 服务器非常强大,通常允许在不分页的情况下检索大块数据集,从而使网络爬虫逻辑更加简单和易于维护。

挑战

不幸的是,graphql 自省很少公开访问,因此要构建用于网络抓取的 graphql 查询,我们需要运用一些逆向工程知识。这通常涉及一些常见的挑战:

  • 锁定查询。 为了保护自己免受网络抓取,网站通常只响应已知查询。换句话说,有时查询哈希是在请求标头/正文中提供的,或者在评估之前由服务器检查。这意味着我们只能查询列入白名单的值,因此在我们的网络抓取工具代码中分析网站时,复制我们在网络检查器中看到的查询非常重要。
  • 标头很重要。 另一种保护形式是标题锁定。网站通常会使用各种秘密令牌(应该首先查看诸如 之类的标头Authorization,带前缀的标头)来验证该查询来自网站 javascript 客户端,而不是某些网络抓取工具。X-幸运的是,这些密钥中的大多数通常驻留在网页的 HTML 正文中,可以轻松找到并添加到网络抓取程序代码中。

总而言之,当网络抓取 graphql 时,最好的做法是复制网络浏览器正在执行的确切行为,然后可以仔细调整查询以优化网络抓取。

概括

在本介绍教程中,我们熟悉了网络抓取 graphql。我们深入了解了查询语言本身以及如何在 Python 中执行它。我们还介绍了如何使用各种内省工具来分析网站的 graphql 功能,以及 graphql 爬虫面临的一些挑战。 Graphql 正在成为一种越来越流行的动态加载数据的选择,并且通过一些努力,它就像任何其他动态数据技术一样可抓取!

Written by 河小马

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