【问题标题】:Scrapy: extract JSON from within HTML scriptScrapy:从 HTML 脚本中提取 JSON
【发布时间】:2016-11-23 00:58:59
【问题描述】:

我正在尝试从 HTML 脚本中提取(似乎是)JSON 数据。 HTML 脚本在网站上如下所示:

<script>
  $(document).ready(function(){
    var terms    = new Verba.Compare.Collections.Terms([{"id":"6436","name":"SUMMER 16","inquiry":true,"ordering":true},{"id":"6517","name":"FALL 16","inquiry":true,"ordering":true}]);
    var view     = new Verba.Compare.Views.CourseSelector({el: "body", terms: terms});
  });
</script>

我想提取以下内容:

[{"id":"6436","name":"SUMMER 16","inquiry":true,"ordering":true},{"id":"6517","name":"FALL 16","inquiry":true,"ordering":true}]

使用下面的代码,我可以得到完整的脚本。

    def parse(self, response):
        print response.xpath('/html/body/script[2]').extract()

是否有一种简单的方法可以从该脚本中提取“id”、“name”等的值。或者,有没有更直接的修改xpath的方法?我似乎无法使用 firebug 更深入地了解 xpath。

【问题讨论】:

标签: python html json xpath scrapy


【解决方案1】:

您可以为此使用js2xml

为了说明,首先,让我们使用您的示例 HTML 创建一个 Scrapy 选择器,并获取 JavaScript 语句:

>>> import scrapy
>>> sample = '''<script>
...   $(document).ready(function(){
...     var terms    = new Verba.Compare.Collections.Terms([{"id":"6436","name":"SUMMER 16","inquiry":true,"ordering":true},{"id":"6517","name":"FALL 16","inquiry":true,"ordering":true}]);
...     var view     = new Verba.Compare.Views.CourseSelector({el: "body", terms: terms});
...   });
... </script>'''
>>> selector = scrapy.Selector(text=sample, type='html')
>>> selector.xpath('//script//text()').extract_first()
u'\n  $(document).ready(function(){\n    var terms    = new Verba.Compare.Collections.Terms([{"id":"6436","name":"SUMMER 16","inquiry":true,"ordering":true},{"id":"6517","name":"FALL 16","inquiry":true,"ordering":true}]);\n    var view     = new Verba.Compare.Views.CourseSelector({el: "body", terms: terms});\n  });\n'

然后我们就可以用js2xml解析JavaScript代码了。你会得到一个 lxml 树:

>>> import js2xml
>>> jssnippet = selector.xpath('//script//text()').extract_first()
>>> jstree = js2xml.parse(jssnippet)
>>> jstree
<Element program at 0x7fc7c6bae1b8>

这棵树长什么样子?这很冗长:

>>> print(js2xml.pretty_print(jstree))
<program>
  <functioncall>
    <function>
      <dotaccessor>
        <object>
          <functioncall>
            <function>
              <identifier name="$"/>
            </function>
            <arguments>
              <identifier name="document"/>
            </arguments>
          </functioncall>
        </object>
        <property>
          <identifier name="ready"/>
        </property>
      </dotaccessor>
    </function>
    <arguments>
      <funcexpr>
        <identifier/>
        <parameters/>
        <body>
          <var name="terms">
            <new>
              <dotaccessor>
                <object>
                  <dotaccessor>
                    <object>
                      <dotaccessor>
                        <object>
                          <identifier name="Verba"/>
                        </object>
                        <property>
                          <identifier name="Compare"/>
                        </property>
                      </dotaccessor>
                    </object>
                    <property>
                      <identifier name="Collections"/>
                    </property>
                  </dotaccessor>
                </object>
                <property>
                  <identifier name="Terms"/>
                </property>
              </dotaccessor>
              <arguments>
                <array>
                  <object>
                    <property name="id">
                      <string>6436</string>
                    </property>
                    <property name="name">
                      <string>SUMMER 16</string>
                    </property>
                    <property name="inquiry">
                      <boolean>true</boolean>
                    </property>
                    <property name="ordering">
                      <boolean>true</boolean>
                    </property>
                  </object>
                  <object>
                    <property name="id">
                      <string>6517</string>
                    </property>
                    <property name="name">
                      <string>FALL 16</string>
                    </property>
                    <property name="inquiry">
                      <boolean>true</boolean>
                    </property>
                    <property name="ordering">
                      <boolean>true</boolean>
                    </property>
                  </object>
                </array>
              </arguments>
            </new>
          </var>
          <var name="view">
            <new>
              <dotaccessor>
                <object>
                  <dotaccessor>
                    <object>
                      <dotaccessor>
                        <object>
                          <identifier name="Verba"/>
                        </object>
                        <property>
                          <identifier name="Compare"/>
                        </property>
                      </dotaccessor>
                    </object>
                    <property>
                      <identifier name="Views"/>
                    </property>
                  </dotaccessor>
                </object>
                <property>
                  <identifier name="CourseSelector"/>
                </property>
              </dotaccessor>
              <arguments>
                <object>
                  <property name="el">
                    <string>body</string>
                  </property>
                  <property name="terms">
                    <identifier name="terms"/>
                  </property>
                </object>
              </arguments>
            </new>
          </var>
        </body>
      </funcexpr>
    </arguments>
  </functioncall>
</program>

您可以使用您的 XPath 技能来指向 JavaScript 数组(您希望将 new 结构的“点”访问器的第一个参数分配给 var terms):

>>> jstree.xpath('//var[@name="terms"]')
[<Element var at 0x7fc7c565e638>]
>>> jstree.xpath('//var[@name="terms"]/new/arguments/*')
[<Element array at 0x7fc7c565e5a8>]
>>> jstree.xpath('//var[@name="terms"]/new/arguments/*')[0]
<Element array at 0x7fc7c565e5a8>

最后,现在你有了&lt;array&gt; 元素,你可以将它传递给js2xml.jsonlike.make_dict() 以获得一个很好的Python 对象来使用(make_dict 的命名有点错误):

>>> js2xml.jsonlike.make_dict(jstree.xpath('//var[@name="terms"]/new/arguments/*')[0])
[{'ordering': True, 'inquiry': True, 'id': '6436', 'name': 'SUMMER 16'}, {'ordering': True, 'inquiry': True, 'id': '6517', 'name': 'FALL 16'}]
>>> 

注意:您还可以使用快捷方式 js2xml.jsonlike.getall() 来获取所有看起来像 Python 字典或列表的内容(您会得到 2 个列表,您对第一个感兴趣):

>>> js2xml.jsonlike.getall(jstree)
[[{'ordering': True, 'inquiry': True, 'id': '6436', 'name': 'SUMMER 16'}, {'ordering': True, 'inquiry': True, 'id': '6517', 'name': 'FALL 16'}], {'el': 'body', 'terms': 'terms'}]

【讨论】:

    【解决方案2】:

    我会使用正则表达式提取它,例如:

    response.xpath('/html/body/script[2]').re_first('\((\[.*\])\)')
    

    【讨论】:

    • 有效!经过更多研究,我想出了与您刚刚发布的方法类似的方法。但是,你的更干净。谢谢。
    • 不客气。如果它解决了您的问题,请随时将此标记为答案。
    【解决方案3】:

    chompjs 提供了将 JavaScript 对象解析为 dict 的 API。

    例如,如果 JavaScript 代码包含var data = {field: "value", secondField: "second value"};您可以按如下方式提取该数据:

    import chompjs
    javascript = response.css('script::text').get()
    data = chompjs.parse_js_object(javascript)
    

    最终结果是 {'field': 'value', 'secondField': 'second value'} 一个

    【讨论】:

      【解决方案4】:

      您不能“深入”,因为该元素的内容只是文本。从 JavaScript 中读取 JSON 并不难:

      line = javascript.strip().splitlines()[1]
      the_json = line.split('(', 1)[1].split(')', 1)[0]
      

      【讨论】:

        猜你喜欢
        • 2018-05-23
        • 2019-07-27
        • 2021-01-09
        • 2016-04-12
        • 2020-02-19
        • 1970-01-01
        • 2017-09-30
        • 2020-02-23
        • 2019-07-26
        相关资源
        最近更新 更多