使用 Scrapy Splash 和 Lua 在鼠标点击时动态加载内容

Posted

技术标签:

【中文标题】使用 Scrapy Splash 和 Lua 在鼠标点击时动态加载内容【英文标题】:Get content loaded dynamically on mouseclick using Scrapy Splash and Lua 【发布时间】:2021-03-30 11:03:03 【问题描述】:

我有一个带有 Lua 脚本的 Scrapy Splash 刮板。 Lua 脚本目前仅在页面上启动滚动以在搜索页面上加载更多结果。 从搜索页面我导航到我抓取的详细信息页面。 但是,在详细信息页面上,照片轮播尚未出现在 DOM 中,它会在用户单击 #showphotos 元素时动态加载。

单击该元素后,将加载以下照片轮播 html

<div id="slider">
    <div class="slider-inner">
        <div class="item active">
            <img src="https://www.example.com/images/1.jpg">
        </div>
        <div class="item">
            <img src="https://www.example.com/images/2.jpg">
        </div>
    </div>
</div>

我已经检查了here 和here。

所以我尝试编写一些脚本:

click_script = """
        function main(splash, args)

            btn = splash:select_all('#showphotos')[0]
            btn:mouse_click()
            assert(splash:wait(0.5))
              return 
                num = #splash:select_all('#slider div.slider-inner'),
                html = splash:html()
              
        end
        """

由于我是 Splash 和 Lua 的新手,我不知道在哪里添加此代码或从哪里调用它。

我创建了一个测试详细信息页面here。

我当前的代码:

myscraper.py

import json
import re

import scrapy
import time
from scrapy_splash import SplashRequest
from scrapy.selector import Selector
from scrapy.http import HtmlResponse
from myresults.items import MyResultItem


class Spider(scrapy.Spider):
    name = 'myscraper'
    allowed_domains = ['example.com']
    start_urls = ['https://www.example.com/results']

    def start_requests(self):
        # lua script for scroll to bottom while all objects appeared
        lua_script = """
        function main(splash, args)
          local object_count = 0
          local url = splash.args.url
          splash:go(url)
          splash:wait(0.5)
          local get_object_count = splash:jsfunc([[
            function ()
              var objects = document.getElementsByClassName("object-adres");
              return objects.length;
            
            ]])
          temp_object_count = get_object_count()
          local retry = 3
          while object_count ~= temp_object_count do
            splash:evaljs('window.scrollTo(0, document.body.scrollHeight);')
            splash:wait(0.5)
            object_count = temp_object_count
            temp_object_count = get_object_count()
            
          end
          return splash:html()
        end
        """

        # yield first splash request with lua script and parse it from parse def
        yield SplashRequest(
            self.start_urls[0], self.parse,
            endpoint='execute',
            args='lua_source': lua_script,
        )

    def parse(self, response):
        # get all properties from first page which was generated with lua script
        # get all adreslink from a tag
        object_links = response.css('a.adreslink::attr(href)').getall()
        for link in object_links:
            # send request with each link and parse it from parse_object def
            yield scrapy.Request(link, self.parse_object)

    def parse_object(self, response):
        # create new MyResultItem which will saved to json file
        item = MyResultItem()

        item['url'] = response.url # get url        
        


        yield item

items.py

import scrapy

class RentalItem(scrapy.Item):
    id = scrapy.Field()
    photos = scrapy.Field()
    url = scrapy.Field()

    pass

【问题讨论】:

【参考方案1】:

Lua 脚本像 Python 脚本一样运行。在Spider -&gt; start_requests -&gt; lua_script 中,您已经有一个 Lua 脚本。您要选择第一个#showphotos 元素并单击它;此外,您还想在结果中添加更多数据。

因此,在执行已经存在的 Lua 代码之后,我们想告诉 Splash 选择第一个 #showphotos 元素:

btn = splash:select_all('#showphotos')[1]

请注意索引 1,而不是 0,因为 splash:select_all 数组从 1 开始。

之后,点击它:

btn:mouse_click()

最后,在结果中添加更多数据:

return 
    num = splash:select_all('#slider div.slider-inner')[1].node.outerHTML,
    html = splash:html()

再次,请注意索引 1,而不是 0,因为 splash:select_all 数组从 1 开始。另外,我添加了 .node.outerHTML,因为 splash:select_all() 返回一个 Lua 对象,并且没有默认的方式将其序列化为 JSON (ref)

最后,你应该得到这样的结果:

function main(splash, args)
  local object_count = 0
  local url = splash.args.url
  splash:go(url)
  splash:wait(0.5)

  local get_object_count = splash:jsfunc([[
    function ()
      var objects = document.getElementsByClassName("object-adres");
      return objects.length;
    
  ]])
  temp_object_count = get_object_count()
  local retry = 3
  while object_count ~= temp_object_count do
    splash:evaljs('window.scrollTo(0, document.body.scrollHeight);')
    splash:wait(0.5)
    object_count = temp_object_count
    temp_object_count = get_object_count()
  end

  btn = splash:select_all('#showphotos')[1]
  btn:mouse_click()
  assert(splash:wait(0.5))
  
  return 
    num = splash:select_all('#slider div.slider-inner')[1].node.outerHTML,
    html = splash:html()
  
end

【讨论】:

太棒了,谢谢!我会试试的。我在您的解决方案中还不明白的是 Lua 脚本的第一部分与搜索概述页面相关,例如/allcompanies 向下滚动以显示所有结果。 Lua 脚本的第二部分以及您建议的在#showphotos 元素上单击鼠标的代码仅与具有照片轮播的详细信息页面相关,例如:company/1443/apple,company/8233/msft,company/1413/google。如果我按照现在的建议将所有内容包装在一个函数 function main(splash, args) 中,我会不必要地多次执行脚本吗? ps。我还更新了我的测试示例页面以显示我的意思

以上是关于使用 Scrapy Splash 和 Lua 在鼠标点击时动态加载内容的主要内容,如果未能解决你的问题,请参考以下文章

scrapy, splash, lua, 按钮点击

在scrapy_splash中加载本地Cookies

基于python的Splash基本使用和负载均衡配置

scrapy飞溅渲染js页面的问题

爬虫进阶-JS自动渲染Scrapy_splash组件的使用

Scrapy中的splash的安装应用