在 Scrapy 中使用 XPath 提取 HTML 结果失败,因为内容是动态加载的

Posted

技术标签:

【中文标题】在 Scrapy 中使用 XPath 提取 HTML 结果失败,因为内容是动态加载的【英文标题】:Extracting HTML results using XPath fail in Scrapy because content is loaded dynamically 【发布时间】:2017-12-04 23:51:01 【问题描述】:

与我之前的问题Extracting p within h1 with Python/Scrapy 相关但不同,我遇到了一种情况,即 Scrapy(用于 Python)不会在 h4 标记中提取跨度标记。

示例 html 是:

<div class="event-specifics">
 <div class="event-location">
  <h3>   Gourmet Matinee </h3>
  <h4>
   <span id="spanEventDetailPerformanceLocation">Knight Grove</span>
  </h4>
</div>
</div>

我正在尝试在 span 标签中获取文本“Knight Grove”。在命令行使用scrapy shell时,

response.xpath('.//div[@class="event-location"]//span//text()').extract()

返回:

['Knight Grove']

response.xpath('.//div[@class="event-location"]/node()')

返回整个节点,即:

['\n                    ', '<h3>\n                        Gourmet Matinee</h3>', '\n                    ', '<h4><span id="spanEventDetailPerformanceLocation"><p>Knight Grove</p></span></h4>', '\n                ']

但是,当在蜘蛛中运行相同的 Xpath 时,不会返回任何内容。以下面的蜘蛛代码为例,它是为了抓取上面的示例 HTML 所在的页面,https://www.clevelandorchestra.com/17-blossom--summer/1718-gourmet-matinees/2017-07-11-gourmet-matinee/。 (部分代码与问题无关,已删除):

# -*- coding: utf-8 -*-
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.loader import ItemLoader
from concertscraper.items import Concert
from scrapy.contrib.loader import XPathItemLoader
from scrapy import Selector
from scrapy.http import XmlResponse

class ClevelandOrchestra(CrawlSpider):
    name = 'clev2'
    allowed_domains = ['clevelandorchestra.com']

    start_urls = ['https://www.clevelandorchestra.com/']

    rules = (
         Rule(LinkExtractor(allow=''), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
     thisconcert = ItemLoader(item=Concert(), response=response)
     for concert in response.xpath('.//div[@class="event-wrap"]'): 

        thisconcert.add_xpath('location','.//div[@class="event-location"]//span//text()')

     return thisconcert.load_item()

这不会返回任何项目['location']。我也试过:

thisconcert.add_xpath('location','.//div[@class="event-location"]/node()')

与上述问题中关于 h 中的 p 不同,HTML 中的 h 标签中允许使用 span 标签,除非我弄错了?

为清楚起见,“位置”字段在 Concert() 对象中定义,我已禁用所有管道以进行故障排除。

h4 中的 span 可能在某种程度上是无效的 HTML;如果不是,是什么原因造成的?

有趣的是,使用 add_css() 执行相同的任务,如下所示:

thisconcert.add_css('location','.event-location')

产生一个存在 span 标签但缺少内部文本的节点:

['<div class="event-location">\r\n'
          '                    <h3>\r\n'
          '                        BLOSSOM MUSIC FESTIVAL </h3>\r\n'
          '                    <h4><span '
          'id="spanEventDetailPerformanceLocation"></span></h4>\r\n'
          '                </div>']

为了确认这不是重复的:在这个特定的例子中确实有一个p标签在h4标签内的span标签内;但是,当不涉及 p 标签时,也会发生相同的行为,例如:https://www.clevelandorchestra.com/1718-concerts-pdps/1718-rental-concerts/1718-rentals-other/2017-07-21-cooper-competition/?performanceNumber=16195。

【问题讨论】:

您引用的跨度在您的示例 URL 中似乎是空的。因此文本节点不存在,因此它什么也不返回。 Scrapy 不是 Web 浏览器,因此它不会像 Web 浏览器那样执行 javascript 等来更改页面并呈现它。当您在 Web 浏览器中加载它时,页面上的脚本似乎必须填充该跨度的值(这就是为什么您的 xpath 浏览器扩展工作的原因),但 scrapy 不会运行脚本并加载它(因此,它不会t 在范围内找到文本节点,因此失败)。 当页面运行到站点以填充日期选择器时,似乎有一个 XHR 请求,您可以找到/计算出它调用的 URL 以获取作为 JSON 对象的一部分的位置 - 可能是最好的解决方案. 为了在未来测试爬虫,在终端中运行scrapy shell,因为它会模拟爬虫的行为 如果您使用请求正文"startDate":"2017-06-30T21:00:00.000Z","endDate":"2017-12-31T21:00:00.000Z"https://www.clevelandorchestra.com/Services/PerformanceService.asmx/GetToolTipPerformancesForCalendar 发出POST 请求,您可以获得所有表演日期,如名称、数据、程序、url 等。此请求以 Json 格式向您返回包含所有数据的响应。 【参考方案1】:

此内容通过 Ajax 调用加载。为了获取数据,您需要发出类似的POST 请求,并且不要忘记添加内容类型为:headers = 'content-type': "application/json" 的标头,然后您将获得 Json 文件作为响应。

import requests

url = "https://www.clevelandorchestra.com/Services/PerformanceService.asmx/GetToolTipPerformancesForCalendar"
payload = "startDate": "2017-06-30T21:00:00.000Z", "endDate": "2017-12-31T21:00:00.000Z"
headers = 'content-type': "application/json"

json_response = requests.post(url, json=payload, headers=headers).json()
for performance in json_response['d']:
    print(performance["performanceName"], performance["dateString"])

# Star-Spangled Spectacular Friday, June 30, 2017
# Blossom: Tchaikovskys Spectacular 1812 Overture Saturday, July 1, 2017
# Blossom: Tchaikovskys Spectacular 1812 Overture Sunday, July 2, 2017
# Blossom: A Salute to America Monday, July 3, 2017
# Blossom: A Salute to America Tuesday, July 4, 2017

【讨论】:

这很好用,谢谢。我已更改问题的标题以反映结果和答案。 没问题,很高兴能为您提供帮助。我用requests 做了我的例子,如果你想使用Scrapy,你可以使用this question 的代码。

以上是关于在 Scrapy 中使用 XPath 提取 HTML 结果失败,因为内容是动态加载的的主要内容,如果未能解决你的问题,请参考以下文章

在Scrapy中如何利用Xpath选择器从HTML中提取目标信息(两种方式)

如何使用scrapy提取表内的项目

scrapy xpath选择器多级选择错误

Xpath 使用 Scrapy 在下一个兄弟标签中获取信息

scrapy框架Selector提取数据

Scrapy模块