scrapy 可以用来从使用 AJAX 的网站抓取动态内容吗?

2024-11-29 08:42:00
admin
原创
113
摘要:问题描述:我最近在学习 Python,并尝试着开发一个网络爬虫。它没什么特别的;它的唯一目的是从博彩网站获取数据并将这些数据放入 Excel 中。大多数问题都是可以解决的,我正忙得不可开交。然而,有一个问题让我遇到了巨大的障碍。如果一个网站加载了一张赛马表并列出了当前的投注价格,则此信息不在任何源文件中。线索...

问题描述:

我最近在学习 Python,并尝试着开发一个网络爬虫。它没什么特别的;它的唯一目的是从博彩网站获取数据并将这些数据放入 Excel 中。

大多数问题都是可以解决的,我正忙得不可开交。然而,有一个问题让我遇到了巨大的障碍。如果一个网站加载了一张赛马表并列出了当前的投注价格,则此信息不在任何源文件中。线索是这些数据有时是实时的,数字显然是从某个远程服务器更新的。我电脑上的 HTML 只是有一个漏洞,他们的服务器正在推送我需要的所有有趣数据。

现在我对动态网络内容的经验很少,所以这件事是我很难理解的。

我认为 Java 或 Javascript 是关键,这个经常出现。

抓取工具只是一个赔率比较引擎。有些网站有 API,但我需要这个来处理没有 API 的网站。我使用的是 Python 2.7 的 scrapy 库

如果这个问题太开放,我深表歉意。简而言之,我的问题是:如何使用 scrapy 来抓取这些动态数据,以便我可以使用?以便我可以实时抓取这些投注赔率数据?


另请参阅:如何使用 Python 抓取包含动态内容(由 JavaScript 创建)的页面?了解一般情况。


解决方案 1:

这是一个 scrapy使用 AJAX 请求的简单示例。让我们看看网站rubin-kazan.ru。

所有消息都通过 AJAX 请求加载。我的目标是获取这些消息及其所有属性(作者、日期等):

在此处输入图片描述

当我分析页面源代码时,我看不到所有这些消息,因为该网页使用了 AJAX 技术。但我可以使用 Mozilla Firefox 中的 Firebug(或其他浏览器中的等效工具)来分析在网页上生成消息的 HTTP 请求:

在此处输入图片描述

它不会重新加载整个页面,而只会重新加载包含消息的页面部分。为此,我点击底部的任意数量的页面:

在此处输入图片描述

我观察了负责消息正文的 HTTP 请求:

在此处输入图片描述

完成后,我分析了请求的标头(我必须引用这个 URL,我将从 var 部分的源页面中提取,请参阅下面的代码):

在此处输入图片描述

以及请求的表单数据内容(HTTP 方法是“Post”):

在此处输入图片描述

响应的内容(JSON 文件)如下:

在此处输入图片描述

它提供了我正在寻找的所有信息。

从现在起,我必须在 scrapy 中实现所有这些知识。为此,我们来定义一下 spider:

class spider(BaseSpider):
    name = 'RubiGuesst'
    start_urls = ['http://www.rubin-kazan.ru/guestbook.html']

    def parse(self, response):
        url_list_gb_messages = re.search(r'url_list_gb_messages="(.*)"', response.body).group(1)
        yield FormRequest('http://www.rubin-kazan.ru' + url_list_gb_messages, callback=self.RubiGuessItem,
                          formdata={'page': str(page + 1), 'uid': ''})

    def RubiGuessItem(self, response):
        json_file = response.body

在函数中parse,我有第一个请求的响应。RubiGuessItem我有包含所有信息的 JSON 文件。

解决方案 2:

基于 Webkit 的浏览器(如 Google Chrome 或 Safari)具有内置的开发者工具。您可以在 Chrome 中打开它Menu->Tools->Developer Tools。该Network选项卡允许您查看有关每个请求和响应的所有信息:

在此处输入图片描述

在图片的底部,您可以看到我已经将请求过滤为XHR- 这些是由 javascript 代码发出的请求。

提示:每次加载页面时都会清除日志,图片下方的黑点按钮会保存日志。

分析完请求和响应后,您可以模拟来自网络爬虫的这些请求并提取有价值的数据。在许多情况下,获取数据比解析 HTML 更容易,因为该数据不包含表示逻辑,并且已格式化为可通过 javascript 代码访问。

Firefox 有类似的扩展,称为firebug。有些人会认为 firebug 更强大,但我喜欢 webkit 的简单性。

解决方案 3:

很多时候,我们在抓取时会遇到问题,页面上呈现的内容是用 Javascript 生成的,因此 scrapy 无法对其进行抓取(例如 ajax 请求、jQuery 疯狂)。

但是,如果您将 Scrapy 与 Web 测试框架 Selenium 一起使用,那么我们就可以抓取普通 Web 浏览器中显示的任何内容。

需要注意的事项:

  • 您必须安装 Python 版本的 Selenium RC 才能使其工作,并且必须正确设置 Selenium。此外,这只是一个模板爬虫。您可以更疯狂、更高级地使用这些东西,但我只想展示基本思想。按照现在的代码,您将对任何给定的 URL 执行两个请求。一个请求由 Scrapy 发出,另一个请求由 Selenium 发出。我相信有办法解决这个问题,这样您就可以让 Selenium 执行唯一的请求,但我没有费心去实现它,通过执行两个请求,您也可以使用 Scrapy 爬取页面。

  • 这非常强大,因为现在您可以抓取整个渲染的 DOM,并且仍然可以使用 Scrapy 中所有不错的抓取功能。当然,这会使抓取速度变慢,但根据您对渲染的 DOM 的需求程度,等待可能是值得的。

from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import HtmlXPathSelector
from scrapy.http import Request

from selenium import selenium

class SeleniumSpider(CrawlSpider):
    name = "SeleniumSpider"
    start_urls = ["http://www.domain.com"]

    rules = (
        Rule(SgmlLinkExtractor(allow=('.html', )), callback='parse_page',follow=True),
    )

    def __init__(self):
        CrawlSpider.__init__(self)
        self.verificationErrors = []
        self.selenium = selenium("localhost", 4444, "*chrome", "http://www.domain.com")
        self.selenium.start()

    def __del__(self):
        self.selenium.stop()
        print self.verificationErrors
        CrawlSpider.__del__(self)

    def parse_page(self, response):
        item = Item()

        hxs = HtmlXPathSelector(response)
        #Do some XPath selection with Scrapy
        hxs.select('//div').extract()

        sel = self.selenium
        sel.open(response.url)

        #Wait for javscript to load in Selenium
        time.sleep(2.5)

        #Do some crawling of javascript created content with Selenium
        sel.get_text("//div")
        yield item

# Snippet imported from snippets.scrapy.org (which no longer works)
# author: wynbennett
# date  : Jun 21, 2011

参考:http://snipplr.com/view/66998/

解决方案 4:

另一个解决方案是实现下载处理程序或下载处理程序中间件。(有关下载器中间件的更多信息,请参阅scrapy 文档)以下是使用 selenium 和 headless phantomjs webdriver 的示例类:

1)在脚本中定义类middlewares.py

from selenium import webdriver
from scrapy.http import HtmlResponse

class JsDownload(object):

    @check_spider_middleware
    def process_request(self, request, spider):
        driver = webdriver.PhantomJS(executable_path='D:phantomjs.exe')
        driver.get(request.url)
        return HtmlResponse(request.url, encoding='utf-8', body=driver.page_source.encode('utf-8'))

2)JsDownload()将类添加到变量DOWNLOADER_MIDDLEWAREsettings.py

DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}

3)整合HTMLResponsewithin your_spider.py。解码响应主体将获得所需的输出。

class Spider(CrawlSpider):
    # define unique name of spider
    name = "spider"

    start_urls = ["https://www.url.de"] 

    def parse(self, response):
        # initialize items
        item = CrawlerItem()

        # store data as items
        item["js_enabled"] = response.body.decode("utf-8") 

可选插件:

我希望能够告诉不同的蜘蛛使用哪个中间件,因此我实现了这个包装器:

def check_spider_middleware(method):
@functools.wraps(method)
def wrapper(self, request, spider):
    msg = '%%s %s middleware step' % (self.__class__.__name__,)
    if self.__class__ in spider.middleware:
        spider.log(msg % 'executing', level=log.DEBUG)
        return method(self, request, spider)
    else:
        spider.log(msg % 'skipping', level=log.DEBUG)
        return None

return wrapper

为了使包装器正常工作,所有蜘蛛必须至少具备:

middleware = set([])

包含中间件:

middleware = set([MyProj.middleware.ModuleName.ClassName])

优点:

以这种方式实现而不是在蜘蛛程序中实现它的主要优点是,您最终只需发出一个请求。例如,在 A T 的解决方案中:下载处理程序处理请求,然后将响应交给蜘蛛程序。然后,蜘蛛程序在其 parse_page 函数中发出一个全新的请求——这是对相同内容的两个请求。

解决方案 5:

我正在使用自定义下载器中间件,但对它不太满意,因为我无法使缓存与其一起工作。

更好的方法是实现自定义下载处理程序。

这里有一个可行的示例。它看起来像这样:

# encoding: utf-8
from __future__ import unicode_literals

from scrapy import signals
from scrapy.signalmanager import SignalManager
from scrapy.responsetypes import responsetypes
from scrapy.xlib.pydispatch import dispatcher
from selenium import webdriver
from six.moves import queue
from twisted.internet import defer, threads
from twisted.python.failure import Failure


class PhantomJSDownloadHandler(object):

    def __init__(self, settings):
        self.options = settings.get('PHANTOMJS_OPTIONS', {})

        max_run = settings.get('PHANTOMJS_MAXRUN', 10)
        self.sem = defer.DeferredSemaphore(max_run)
        self.queue = queue.LifoQueue(max_run)

        SignalManager(dispatcher.Any).connect(self._close, signal=signals.spider_closed)

    def download_request(self, request, spider):
        """use semaphore to guard a phantomjs pool"""
        return self.sem.run(self._wait_request, request, spider)

    def _wait_request(self, request, spider):
        try:
            driver = self.queue.get_nowait()
        except queue.Empty:
            driver = webdriver.PhantomJS(**self.options)

        driver.get(request.url)
        # ghostdriver won't response when switch window until page is loaded
        dfd = threads.deferToThread(lambda: driver.switch_to.window(driver.current_window_handle))
        dfd.addCallback(self._response, driver, spider)
        return dfd

    def _response(self, _, driver, spider):
        body = driver.execute_script("return document.documentElement.innerHTML")
        if body.startswith("<head></head>"):  # cannot access response header in Selenium
            body = driver.execute_script("return document.documentElement.textContent")
        url = driver.current_url
        respcls = responsetypes.from_args(url=url, body=body[:100].encode('utf8'))
        resp = respcls(url=url, body=body, encoding="utf-8")

        response_failed = getattr(spider, "response_failed", None)
        if response_failed and callable(response_failed) and response_failed(resp, driver):
            driver.close()
            return defer.fail(Failure())
        else:
            self.queue.put(driver)
            return defer.succeed(resp)

    def _close(self):
        while not self.queue.empty():
            driver = self.queue.get_nowait()
            driver.close()

假设您的抓取工具名为“scraper”。如果您将上述代码放在“scraper”文件夹根目录下的 handlers.py 文件中,则可以将其添加到您的 settings.py 中:

DOWNLOAD_HANDLERS = {
    'http': 'scraper.handlers.PhantomJSDownloadHandler',
    'https': 'scraper.handlers.PhantomJSDownloadHandler',
}

瞧,JS 解析了 DOM,带有 scrapy 缓存、重试等。

解决方案 6:

如何使用 scrapy 来抓取这些动态数据以便我可以使用它?

我很奇怪为什么没有人发布仅使用 Scrapy 的解决方案。

查看 Scrapy 团队的博客文章SCRAPING INFINITE SCROLLING PAGES。示例抓取了使用无限滚动的http://spidyquotes.herokuapp.com/scroll网站。

我们的想法是使用浏览器的开发人员工具并注意 AJAX 请求,然后根据该信息创建 Scrapy 请求

import json
import scrapy


class SpidyQuotesSpider(scrapy.Spider):
    name = 'spidyquotes'
    quotes_base_url = 'http://spidyquotes.herokuapp.com/api/quotes?page=%s'
    start_urls = [quotes_base_url % 1]
    download_delay = 1.5

    def parse(self, response):
        data = json.loads(response.body)
        for item in data.get('quotes', []):
            yield {
                'text': item.get('text'),
                'author': item.get('author', {}).get('name'),
                'tags': item.get('tags'),
            }
        if data['has_next']:
            next_page = data['page'] + 1
            yield scrapy.Request(self.quotes_base_url % next_page)

解决方案 7:

从外部 URL 生成的数据是 API 以 POST 方法调用 HTML 响应。

import scrapy
from scrapy.crawler import CrawlerProcess

class TestSpider(scrapy.Spider):
    name = 'test'  
    def start_requests(self):
        url = 'https://howlongtobeat.com/search_results?page=1'
        payload = "queryString=&t=games&sorthead=popular&sortd=0&plat=&length_type=main&length_min=&length_max=&v=&f=&g=&detail=&randomize=0"
        headers = {
            "content-type":"application/x-www-form-urlencoded",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"
        }

        yield scrapy.Request(url,method='POST', body=payload,headers=headers,callback=self.parse)

    def parse(self, response):
        cards = response.css('div[class="search_list_details"]')

        for card in cards: 
            game_name = card.css('a[class=text_white]::attr(title)').get()
            yield {
                "game_name":game_name
            }
           

if __name__ == "__main__":
    process =CrawlerProcess()
    process.crawl(TestSpider)
    process.start()

解决方案 8:

我认为 2022 年应该提及一些更现代的替代方案,并且我想列出该问题中更受欢迎的答案中讨论的方法的一些优缺点。

  1. 最佳答案和其他几个答案讨论了使用浏览器dev tools或数据包捕获软件来尝试识别响应中的模式url,并尝试重新构建它们以用作scrapy.Request

* 优点:在我看来,这仍然是最好的选择,并且当它可用时,它比传统方法(即使用`xpath`和`css`选择器从 HTML 中提取内容)更快,而且通常更简单。
* 缺点:不幸的是,这仅在一小部分动态网站上可用,并且网站通常都有安全措施使使用此策略变得困难。
  1. 使用Selenium Webdriver前面答案中多次提到的另一种方法。

* 优点:易于实现,并集成到 scrapy 工作流中。此外,还有大量示例,如果您使用第三方扩展,则几乎不需要配置,例如`scrapy-selenium`
* 缺点:速度很慢!scrapy 的主要功能之一是异步工作流,因此可以在几秒钟内轻松抓取数十甚至数百个页面。使用 selenium 可以大大减少这一时间。

有两种新方法绝对值得考虑,scrapy-splashscrapy-playwright

scrapy-splash

  • 一个 scrapy 插件,将由scrapy 开发人员创建和维护的 javascript 渲染服务splash集成到 scrapy 工作流中。可以使用 从 pypi 安装该插件pip3 install scrapy-splash,而 splash 需要在自己的进程中运行,最容易从 docker 容器运行。

scrapy-剧作家

  • Playwright 是一种浏览器自动化工具,类似于selenium,但没有使用 selenium 所带来的速度严重下降。Playwright 可以毫无问题地融入异步 scrapy 工作流,发送请求的速度与单独使用 scrapy 一样快。它也比 selenium 更容易安装和集成。该scrapy-playwright插件也由 scrapy 的开发人员维护,通过 pypi 安装后,就像在终端中pip3 install scrapy-playwright运行一样简单。playwright install

每个插件的 github 页面https://github.com/scrapy-plugins/scrapy-playwrighthttps://github.com/scrapy-plugins/scrapy-splash上可以找到更多详细信息和许多示例。

ps 根据我的经验,这两个项目在 Linux 环境中运行得更好。对于 Windows 用户,我建议将其与Windows Subsystem for Linux (wsl)一起使用。

解决方案 9:

是的,Scrapy 可以抓取动态网站,通过 JavaScript 呈现的网站。

有两种方法可以抓取此类网站。

  1. 你可以使用它splash来渲染 Javascript 代码,然后解析渲染后的 HTML。你可以在这里找到文档和项目Scrapy splash、git

  2. 如前所述,通过监控network calls,是的,您可以找到获取数据的 API 调用,并在您的 scrapy spider 中模拟该调用可能会帮助您获取所需的数据。

解决方案 10:

我使用 Selenium 和 Firefox Web 驱动程序处理 ajax 请求。如果您需要将爬虫程序作为守护进程,那么速度就没那么快,但比任何手动解决方案都要好得多。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1068  
  对于许多电脑用户来说,Microsoft Office 2010曾经是办公软件中的经典之作。尽管现在已推出了更新的版本,但许多用户仍然习惯使用Office 2010。然而,随着时间的推移,一些用户可能会遇到一个问题:电脑上的Office 2010究竟放在哪里?这个问题看似简单,但实际上涉及多个层面的内容,包括安装路径、...
Office   2  
  在使用HP笔记本电脑时,许多用户可能会遇到Microsoft Office 2010的激活问题。尤其是在需要重新安装系统或更换设备时,找到正确的激活密钥变得尤为重要。激活密钥不仅是合法使用软件的必要条件,还确保了软件功能的完整性和安全性。然而,很多人并不知道如何找到或恢复这个密钥,尤其是在购买电脑多年后,相关的信息可能...
Office   2  
  Office 2010作为微软推出的一款经典办公软件,至今仍被许多用户广泛使用。尽管微软已经推出了更新的版本,但Office 2010凭借其稳定的性能和丰富的功能,依然在办公领域占据一席之地。然而,正版Office 2010的价格并不低,对于许多个人用户或小型企业来说,购买正版软件可能会带来一定的经济压力。因此,许多用...
Office   2  
  在使用WPS Office进行文档编辑时,自动保存功能是一个非常重要的工具。它不仅可以帮助用户避免因意外断电、系统崩溃或误操作导致的数据丢失,还能在多人协作编辑时确保文档的实时更新。然而,许多用户对WPS自动保存功能的设置并不熟悉,甚至可能从未主动配置过。本文将详细讲解如何设置WPS的自动保存功能,并探讨其在实际使用中...
磁盘空间   2  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用