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

csga3l58  于 2022-11-09  发布在  其他
关注(0)|答案(9)|浏览(185)

我最近在学习Python,并且正在着手构建一个网页抓取器。它的唯一用途是从博彩网站获取数据并将这些数据放入Excel。
大部分的问题都是可以解决的,我有一个很好的小混乱。然而,我在一个问题上遇到了巨大的障碍。如果一个网站加载了一个马的表格,并列出了当前的投注价格,这些信息并不在任何源文件中。线索是,这些数据有时是实时的,这些数字显然是从某个远程服务器上更新的。我PC上的HTML只是有一个洞,他们的服务器正在推送我需要的所有有趣的数据。
现在我对动态网页内容的经验很低,所以这件事是我有麻烦得到我的头周围。
我认为Java或Javascript是一个关键,这经常弹出。
scraper只是一个几率比较引擎。有些站点有API,但我需要它来支持那些没有的站点。我使用的是Python 2.7的scrapy库
如果这个问题过于开放,我很抱歉。简而言之,我的问题是:如何使用Scrapy来抓取这些动态数据,以便我可以使用它?以便我可以实时抓取这些投注赔率数据?

ckocjqey

ckocjqey1#

下面是一个简单的scrapy示例,其中包含一个 AJAX 请求。
所有的消息都是通过 AJAX 请求加载的,我的目标是获取这些消息及其所有属性(作者、日期......):

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

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

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

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


指令集
和表单数据内容的请求(HTTP方法是“Post”):

响应的内容是一个JSON文件:


指令集
它提供了我要的所有信息。
从现在起,我必须把这一切知识都落实在舍雷里.让我们为这个目的定义蜘蛛:

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文件。

lg40wkob

lg40wkob2#

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

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

  • 提示:日志会在每次加载页面时被清除,在图片底部,黑色圆点按钮将保留日志。*

在分析请求和响应之后,您可以从您的网络爬虫模拟这些请求并提取有价值的数据。在许多情况下,获取数据比解析HTML更容易,因为这些数据不包含表示逻辑,并且被格式化为可由javascript代码访问。
Firefox也有类似的扩展名,它被称为firebug。有些人会认为Firebug甚至更强大,但我喜欢Webkit的简单性。

xxhby3vn

xxhby3vn3#

很多时候,当抓取时,我们会遇到一些问题,页面上呈现的内容是用Javascript生成的,因此Scrapy无法抓取这些内容(例如 AJAX 请求,jQuery疯狂)。
然而,如果你使用Scrapy沿着Web测试框架Selenium,那么我们就可以抓取普通Web浏览器中显示的任何内容。
需要注意的事项:

  • 您必须安装Python版本的SeleniumRC才能使其工作,你必须正确设置Selenium。这只是一个模板爬虫。你可以得到更疯狂和更先进的东西,但我只是想显示基本的想法。作为代码现在的立场,你将做两个请求任何给定的网址。一个请求是由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/

jxct1oxe

jxct1oxe4#

另一个解决方案是实现下载处理程序或下载处理程序中间件。(有关下载中间件的更多信息,请参见scrapy docs)以下是一个使用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()类添加到settings.py内的变量DOWNLOADER_MIDDLEWARE
DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}
  • 3)* 在your_spider.py中集成HTMLResponse。解码响应主体将获得您想要的输出。
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")

可选附加组件:

我希望能够告诉不同的spider使用哪个中间件,所以我实现了这个 Package 器:

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

要使 Package 器工作,所有spider必须至少具有:

middleware = set([])

为了包括中间件:

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

优点:

以这种方式而不是在蜘蛛中实现它的主要优点是,你最终只需要发出一个请求。下载处理程序处理请求,然后将响应传递给蜘蛛,然后蜘蛛在其parse_page函数中发出一个全新的请求--这是对同一内容的两个请求。

zlhcx6iw

zlhcx6iw5#

我 使用 了 一 个 自 定义 的 下载 中间 件 , 但 不是 很 满意 , 因为 我 没有 设法 使 缓存 与 它 一起 工作 。
一 种 更 好 的 方法 是 实现 自 定义 下载 处理 程序 。
有 一 个 工作 示例 here 。 它 看 起来 像 这样 :


# 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()

中 的 每 一 个
Suppose your scraper is called "scraper". If you put the mentioned code inside a file called handlers.py on the root of the "scraper" folder, then you could add to your settings.py:

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

格式
瞧 , JS 解析 的 DOM , 带有 零碎 的 缓存 、 重试 等 。

qlfbtfca

qlfbtfca6#

如何使用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)
4smxwvx5

4smxwvx57#

从外部url(即API)生成的数据调用HTML响应作为POST方法。

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()
zwghvu4y

zwghvu4y8#

是,Scrapy可以抓取动态网站,即通过javaScript呈现的网站。
有两种方法来破坏这些网站。
第一、
你可以使用splash来呈现Javascript代码,然后解析呈现的HTML。你可以在这里找到文档和项目Scrapy splash, git
第二、
正如每个人所说,通过监视network calls,是的,你可以找到获取数据的api调用,并在你的scrapy spider中模拟该调用,这可能会帮助你获得所需的数据。

6ss1mwsb

6ss1mwsb9#

我使用Selenium和Firefox web驱动程序来处理 AJAX 请求。如果你需要爬行器作为守护进程,它不是那么快,但比任何手动解决方案都要好。我写了一个简短的教程here供参考

相关问题