动态网页抓取(腾讯云轻量服务器Lighthouse(Lighthouse)中员工的信息和薪酬(示例页面))
优采云 发布时间: 2021-11-24 06:05动态网页抓取(腾讯云轻量服务器Lighthouse(Lighthouse)中员工的信息和薪酬(示例页面))
0x00 背景概览
穿越【科技干货007】Scrapy Crawler Preliminary]教程,大家应该对如何编写爬虫有了一定的了解。但是对于更复杂的网站设计,比如使用JavaScript动态渲染的网站页面,入门级爬虫不适合。
本文针对JavaScript动态渲染页面,使用selenium+scrapy爬取levels.fyi(示例页面)中微软员工的信息和工资,目的是描述如何爬取JavaScript页面并在腾讯云轻量级服务器上部署程序在灯塔。
0x01 服务器准备
轻量级应用服务器(Lighthouse)是一款易于使用和管理的云服务器,适合承载轻量级业务负载,可以帮助中小企业和开发者快速搭建网站、博客、电子商务、云中论坛等各类应用及开发测试环境,提供应用部署、配置、管理*敏*感*词*一站式服务。
这里我们先选LAMP镜,选套餐再付款,你就拥有了你的灯塔镜!(购买门户 ->)
0x02 页面分析
在levels.fyi进入开发者模式,可以看到要爬取的元素其实是一个iframe,数据是由脚本脚本生成的:
我们需要获取tbody下的每个tr,选择我们需要的数据
我们直接使用Request获取tbody,会发现这个元素下没有数据:
t_body = response.css("table#compTable tbody").extract()
print(t_body)
下面,我们讲解如何成功获取javaScript生成的tbody数据
0x03 Selenium 获取
Selenium 是一个运行在浏览器中的 Web 自动化工具,它使用脚本来模拟用户在浏览器上的操作。在这种情况下,它本质上使用 Selenium 在获取数据之前等待 javascript 加载。Selenium 的安装和配置非常简单,脚本编写也非常容易。缺点是与其他爬取方式相比,Selenium 的爬取速度相对较慢。
Selenium 安装:pip install selenium
浏览器驱动下载:使用Selenium,需要下载浏览器驱动。建议下载Chrome版本。下载完成后,mac可以直接放在/usr/local/bin下。Windows 需要在脚本中配置路径或环境变量
创建一个scrapy项目,新建一个MicrosoftSpider,并进行简单的配置。方法同【技术干货007 | Scrapy Crawler 初步]。
获取驱动对象:
driver = webdriver.Chrome()
使用 WebDriverWait 并等待页面加载。在这里,我们将超时时间设置为 5 秒:
wait = WebDriverWait(self.driver, 5)
wait.until(
lambda driver: driver.find_element_by_xpath('//*[@id="compTable"]/tbody/tr[1]')) # 等待第一行内容加载完成
等待结束后,尝试获取tbody中的第一行数据
tr1 = self.driver.find_element_by_xpath('//*[@id="compTable"]/tbody/tr[1]').text # 每一行信息
print(tr1)
我们已经成功获取到第一行数据了!在上面的代码中,我们使用了 find_element_by_xpath 函数。这个函数是一个在Selenium中获取元素的函数,它返回WebElement的类型。可以通过text获取元素的文本
接下来,我们使用相同的方法获取'下一页'按钮并单击该按钮:
wait = WebDriverWait(self.driver, 1)
wait.until(lambda driver: driver.find_element_by_css_selector('li.page-item.page-next')) # 等待内容加载完成
next_page = self.driver.find_element_by_css_selector('li.page-item.page-next a')
next_page.click() # 模拟点击下一页
next_page 也是 WebElement 类型。可以看到,WebElement除了文本等基本属性外,还有点击等动作。其实这也是WebElement最常用的方法。
其余方法可以在 WebElement API 文档中找到。
现在已获得所有关键要素!下一步就是爬取每一行的元素,循环点击!
0x04 爬行之路总是充满坎坷
Selenium的教程其实就到这里了,但是如果有同学尝试爬取网站的生活,就会发现各种神奇的bug。
这些bug不是程序问题,而是因为网站的种类繁多。这些网站设计师脑子里可能有哪吒,这让你很难理解他在想什么。这里也分享一下爬取样本时的一些趣事网站。
JavaScript 嵌套
如下图所示,当你点击 iframe 的一行时,会出现一个新的 iframe,数据也是由 JavaScript 生成的。获取新的iframe数据并不难,wait+find即可。难点在于,当每一行被点击时,如何将新出现的 iframe 与它所属的 iframe 关联起来?毕竟像下图,每一个新的iframe的class都是“detail-view”。
一开始我试了两张图,然后加入,但是很难保证加入后的结果是正确的。后来我发现了新iframe的特点:当再次点击该行数据时,新iframe就会被关闭。这样就有个比较棘手的方法:循环爬取数据的时候,每次生成一个新的iFrame,爬取完数据后,再次调用click关闭iframe。这样就可以保证每次基于'detail-view'获取元素时只有一个iframe。
这种方案看似没有技术含量,反而增加了爬行的总耗时:增加了一个点击动作的耗时。但正如我开头所说:在写爬虫的时候,我总是“不择手段”。当你能快速想到解决爬虫问题的方法,并且易于实现时,你就可以大胆地使用它,即使它不是最优解。毕竟,当你在思考更好的解决方案时,使用“哑巴”方法爬取的数据可能已经在手。
抓取中断
如果你尝试爬取例子网站,你会发现爬虫已经爬取了1000多个item就会被中断,并且会提示:元素'page-link'不能点击,也就是,无法点击。“下一页”按钮。
一开始,我以为数据页缺少'下一页'按钮的href。毕竟,类似的按钮缺少href 并且链接突然变成文本是很常见的。然而,当我找到这个页面上的数据时,我发现事实并非如此。这个页面的数据看起来很正常,'Next Page'按钮也有href,可以正常点击。但是我反复爬了很多次,爬到页面数据的时候爬虫会中断,提示不能点击元素'page-link'。这个问题困扰了我很久,直到我发现:
这是一个可以联系网站客服人员的按钮。在第125页,他神奇地出现在'下一页'按钮上方,挡住了'下一页'按钮,导致模拟器无法点击到'下一页'按钮。这个问题也让人有些哭笑不得。
如何解决?方法其实很简单,增加模拟器窗口。因为“聊天按钮”的位置是根据当前窗口大小,即相对位置,和“下一页”按钮不同。这样就可以保证两个按钮是分开的:
可以正常抓取数据。
之所以写这两个例子,是因为这两个解决方案其实是“奇怪的逻辑”:莫名的点击,莫名的放大窗口。如果只看代码,是不会理解这两行代码的意思的。但是对于这个网站,这两行是必须的。就像文章开头提到的“不择手段”,在解决问题的时候,换个角度思考可能比从正面解决更容易。
0x05 引用
示例代码github地址