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

Jos*_*eph 137 javascript python ajax screen-scraping scrapy

我最近一直在学习Python,并且正在努力构建一个web-scraper.它根本没什么特别的; 它的唯一目的是从博彩网站获取数据并将这些数据放入Excel.

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

现在我对动态网页内容的体验很低,所以这件事让我无法理解.

我认为Java或Javascript是一个关键,这经常会弹出.

刮刀只是一种赔率比较引擎.有些网站有API,但我需要这些API.我正在使用Python 2.7的scrapy库

如果这个问题太开放,我真的很抱歉.简而言之,我的问题是:如何使用scrapy来抓取这些动态数据,以便我可以使用它?这样我就可以实时抓取这个赔率数据?

小智 94

这是一个使用scrapy和ajax请求的简单示例.让我们看看网站http://www.rubin-kazan.ru/guestbook.html所有消息都加载了ajax请求.我的目标是获取具有所有属性(作者,日期,...)的此消息.

在此输入图像描述

当我分析页面的源代码时,我无法看到所有这些消息,因为网页使用了ajax技术.但我可以使用Mozila Firefox的Firebug(或其他浏览器中的类比仪器)来分析在网页上生成消息的Http请求. 在此输入图像描述

为此,我不重新加载所有页面,只重新加载包含消息的页面部分.为此,我点击底部的任意数量的页面在此输入图像描述我观察负责消息体的HTTP请求 在此输入图像描述

完成后我分析了请求的标题(我必须引用这个url,我将从var部分的源页面中提取,请参阅下面的代码). 在此输入图像描述

和请求的表单数据内容(Http方法是"Post")

在此输入图像描述

和响应的内容,这是一个Json文件,

在此输入图像描述

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

从现在开始,我必须在scrapy中实现所有这些知识.让我们为此目的定义蜘蛛.

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
Run Code Online (Sandbox Code Playgroud)

在解析函数中,我有第一个请求的响应.在RubiGuessItem中,我有json文件包含所有信息.

  • 你好.你能解释一下'url_list_gb_messages'是什么吗?我无法理解.谢谢. (6认同)
  • 这个肯定更好. (4认同)

Ski*_*Ski 73

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

在此输入图像描述

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

提示:每次加载页面时都会清除日志,在图片的底部,黑点按钮将保留日志.

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

Firefox有类似的扩展名,它叫做firebug.有些人认为萤火虫更强大,但我喜欢webkit的简单性.

  • 如果它甚至没有"scrapy"这个词,它怎么能成为一个被接受的答案? (122认同)

A T*_*A T 40

很多时候,当我们遇到问题时,我们会遇到使用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
    
    Run Code Online (Sandbox Code Playgroud)

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


roc*_*m4l 32

另一种解决方案是实现下载处理程序或下载处理程序中间件.以下是使用selenium和无头phantomjs webdriver的中间件示例:

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'))
Run Code Online (Sandbox Code Playgroud)

我想能够告诉不同的蜘蛛使用哪个中间件,所以我实现了这个包装器:

DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}
Run Code Online (Sandbox Code Playgroud)

settings.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") 
Run Code Online (Sandbox Code Playgroud)

对于包装工作,所有蜘蛛必须至少具备:

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
Run Code Online (Sandbox Code Playgroud)

包含一个中间件:

middleware = set([])
Run Code Online (Sandbox Code Playgroud)

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

  • 它比我在SO上看到的任何其他解决方案效率更高,因为使用下载器中间件使得它只对页面提出了一个请求..如果它太可怕了,为什么不提出更好的解决方案并分享而不是专横地提出单方面的主张."与scrapy无关"你吸烟了吗?除了实现一些疯狂的复杂,​​强大和自定义的解决方案,这是我见过大多数人使用的方法.唯一不同的是大多数实施蜘蛛中的硒部分导致多个请求... (2认同)

Iva*_*aer 10

我使用的是自定义下载器中间件,但对它不是很满意,因为我没有设法让缓存工作.

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

有一个工作示例这里.它看起来像这样:

# 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()
Run Code Online (Sandbox Code Playgroud)

假设您的刮刀被称为"刮刀".如果您将上述代码放在名为handlers.py的文件中"scraper"文件夹的根目录下,那么您可以添加到settings.py:

DOWNLOAD_HANDLERS = {
    'http': 'scraper.handlers.PhantomJSDownloadHandler',
    'https': 'scraper.handlers.PhantomJSDownloadHandler',
}
Run Code Online (Sandbox Code Playgroud)

而且,JS解析了DOM,带有scrapy缓存,重试等.