一. Splash splash是一个可以动态渲染js的工具. 有助于我们完成复杂的js内容加载工作. 你可以理解为另一个没有界面的selenium.
1.1 splash安装 splash的安装过程十分复杂. 复杂到官方都不推荐你去手动安装它.
官方建议. 用docker去安装splash. 所以. 你需要先去安装docker. 但是docker这玩意在windows上支持非常不好. 各种各样的问题. 外加上后期我们要把爬虫部署到linux. 那干脆. 我们就安装一个linux. 在linux上搞docker是非常easy的.
有能力, 不怕苦的同学可以在windows上搞一个docker试试. 我这里就不带你们找坑踩了. 直接上Linux.
1.1.1安装VM
1.1.2 安装Linux
安装好的linux后,我们需要学会使用linux的一个工具. 叫yum, 我们需要用它来帮我们完成各种软件的安装. 十分的方便. 我们先用ifconfig来做一个测试.
1 2 3 yum search ifconfig // 搜索出ifconfig的包 yum install net-tools.x86_64 // 安装该软件, 安装过程中会出现很多个询问. 直接y即可
发现了吧, 在linux这个破黑窗口里. 属实难受+憋屈. 所以, 我们这里选择用ssh远程连接linux.
mac版本: 打开终端. 输入
就可以顺利的链接到你的linux服务器. 接下来. 我们可以使用各种命令来操纵linux了.
Windows:
1.1.3 安装docker 安装docker就一条例命令就好了
1 [root@sylar-centos-2 ~]# yum install docker
配置docker的源.
1 2 3 4 5 6 [root@sylar-centos-2 ~]# vi /etc/docker/daemon.json # 写入一下内容, 注意.先按'i' , 更换为输入模式. 然后再填写内容 { "registry-mirrors": ["https://registry.docker-cn.com/"] } # 保存: 先按esc. 退出输入模式, 然后输入":wq" 表示写入, 退出. 就完事儿了
1 [root@sylar-centos-2 ~]# systemctl start docker # 启动docker
1 [root@sylar-centos-2 ~]# docker ps # 查看docker运行状态
如需关闭或者重新启动docker:
1 2 systemctl stop docker # 停止docker服务 systemctl restart docker # 重启docker服务
Vm -> cenos -> ssh -> docker -> splash
1.1.4 安装splash
拉取splash镜像
1 docker pull scrapinghub/splash
splash比较大. 大概2个G左右. 有点儿耐心等会儿就好了
运行splash
1 docker run -p 8050:8050 scrapinghub/splash
打开浏览器访问splash
http://192.168.31.82:8050/
1.2 splash简单使用 我们可以在文本框内输入百度的网址. 然后点击render. 可以看到splash会对我们的网页进行动态的加载. 并返回截图. 运行状况. 以及页面代码(经过js渲染后的)
快速解释一下, script中的脚本. 这里面用的是lua的脚本语法. 所以看起来会有些难受.
1 2 3 4 5 6 7 8 9 function main (splash, args) assert (splash:go(args.url)) assert (splash:wait(0.5 )) return { html = splash:html(), png = splash:png(), har = splash:har(), } end
有必要说明一下. 在lua中, .表示的是属性(变量), :表示的是方法(函数)的调用.
常见操作符都一样. 剩下的. 我们到案例里看.
1.3 splash的http-api接口 splash提供了对外的http-api接口. 我们可以像访问一个普通url一样访问splash. 并由splash帮助我们渲染好页面内容.
1 http://192.168 .31 .82 :8050 /render.html?url=http://www.baidu.com
虽然看不出任何差别. 但是你心里要清楚一个事情. 此时拿到的直接是经过js渲染后的html
我们换个url你就知道了
1 http://192.168 .31 .82 :8050 /render.html?url=https://www.endata.com.cn/BoxOffice/BO/Year/index.html&wait=5
endata这个网站. 它的数据是后期经过ajax请求二次加载进来的. 我们通过splash可以等待它后期加载完再拿html.
综上, splash的工作机制:
整个一个代理服务器的逻辑. ~~~~
二. python中使用splash 2.1 splash在python中如何使用 既然splash提供了http-api接口. 那我们就可以像请求普通网站一样去请求到splash. 在python中, 我们最熟悉的能发送http请求的东西就是requests了.
接下来.我们就用requests来完成splash的对接.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requests""" # splash提供的api接口 渲染html的接口 http://192.168.31.184:8050/render.html?url=你的url&wait=等待时间&time_out=超时时间 截图的接口 http://192.168.31.184:8050/render.png 参数和render.html基本一致, 可选width, height 加载过程接口 http://192.168.31.184:8050/render.har 参数和render.html基本一致 json接口 http://192.168.31.184:8050/render.json 参数和render.html基本一致 执行lua脚本的接口 http://192.168.31.184:8050/execute?lua_source=你要执行的lua脚本 """ url = "http://192.168.31.184:8050/render.html?url=https://www.baidu.com&wait=5" resp = requests.get(url) print (resp.text)
2.2 我们以网易新闻首页要闻为例. 先搞定脚本部分.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function main (splash, args) assert (splash:go(args.url)) assert (splash:wait(1 )) get_display_style = splash:jsfunc([[ function(){ return document.getElementsByClassName('load_more_btn')[0].style.display; } ]] ) while (true ) do splash:runjs("document.getElementsByClassName('load_more_btn')[0].scrollIntoView(true)" ) splash:wait(1 ) splash:select (".load_more_btn" ).click() display_style = get_display_style() if (display_style== 'none' ) then break end end assert (splash:wait(2 )) return { html = splash:html(), png = splash:png(), har = splash:har(), } end
到了python里面就可以使用这个脚本了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 lua = """ function main(splash, args) assert(splash:go(args.url)) assert(splash:wait(0.5)) -- 加载一段js, 后面作为lua函数进行调用. -- 在这个脚本中, 主要返回了"加载更多"按钮的状态 get_display_style = splash:jsfunc([[ function(){ return document.getElementsByClassName('load_more_btn')[0].style.display; } ]]) -- lua中的循环语句. 和python的while功能一样. while (true) do -- 语法规定. 相当于开始 -- 直接运行js代码, 滚动到'加载更多'按钮 splash:runjs("document.getElementsByClassName('load_more_btn')[0].scrollIntoView(true)") -- 等待 splash:wait(1) -- 找到该按钮. 点击它 splash:select(".load_more_btn").click() -- 调用上方预制的js脚本, 获取'正在加载按钮'的状态 display_style = get_display_style() -- 如果不显示了. 也就结束了 if(display_style== 'none') then break -- 同python中的break. 打断循环 end end assert(splash:wait(2)) -- 不在乎多等2秒 return { html = splash:html(), -- 拿到页面源代码 cookies = splash:get_cookies() -- 拿到cookies } end """ url = "http://192.168.31.82:8050/execute" from lxml import etreeresp = requests.get(url, params={ "url" :"https://news.163.com" , "lua_source" : lua }) result = resp.json() tree = etree.HTML(result.get('html' )) divs = tree.xpath("//ul[@class='newsdata_list fixed_bar_padding noloading']/li[1]/div[2]/div" ) for div in divs: a = div.xpath("./div/div/h3/a" ) if not a: continue a = a[0 ] href = a.xpath('./@href' ) title = a.xpath('./text()' ) print (title, href) print (result.get("cookies" ))
##三. scrapy_splash
安装scrapy_splash模块
1 pip install scrapy_splash
创建一个普通的scrapy项目, 然后把scrapy_splash配置到settings文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36' ROBOTSTXT_OBEY = False SPLASH_URL = 'http://192.168.31.82:8050' DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware' : 723 , 'scrapy_splash.SplashMiddleware' : 725 , 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware' : 810 , } DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
最后. 整理修改一下spider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import scrapyfrom scrapy_splash.request import SplashRequestlua_source = """ function main(splash, args) -- 主函数 assert(splash:go("https://news.163.com/")) -- 访问url assert(splash:wait(2)) -- 等待 -- 预存一个js函数 get_btn_display = splash:jsfunc([[ function(){ return document.getElementsByClassName('load_more_btn')[0].style.display; } ]]) -- lua的while循环 while(true) do -- 直接执行一个js脚本 -- 向下拉动滚动条. splash:runjs("document.getElementsByClassName('load_more_btn')[0].scrollIntoView(true)") assert(splash:wait(1)) -- 选择 "加载更多" btn = splash:select(".load_more_btn") -- 点它 btn:click() -- 判断是否可见 调用上方预制的js函数 ss = get_btn_display() -- 如果是none. 就没有数据了(网易自己设计的) if (ss == 'none') then break end end return { html = splash:html(), cookies = splash:get_cookies() } end """ class WangyiSpider (scrapy.Spider): name = 'wangyi' allowed_domains = ['163.com' ] start_urls = ['http://news.163.com/' ] def start_requests (self ): yield SplashRequest( url=self.start_urls[0 ], callback=self.parse, endpoint="execute" , args={"lua_source" : lua_source, } ) def parse (self, resp, **kwargs ): divs = resp.xpath("//ul[@class='newsdata_list fixed_bar_padding noloading']/li[1]/div[2]/div" ) for div in divs: a = div.xpath("./div/div/h3/a" ) if not a: continue href = a.xpath('./@href' ).extract_first() title = a.xpath('./text()' ).extract_first() print (href) yield scrapy.Request( url=href, callback=self.details ) break def details (self, resp ): with open ("data.txt" , mode='w' ) as f: f.write("____" .join(resp.xpath("//div[@class='post_body']//p/text()" ).extract()))