左手用R右手Python系列——动态网页抓取与selenium驱动浏览器

浏览: 1809

关于基础的网络数据抓取相关内容,本公众号已经做过很多次分享,特别是R语言的爬虫框架(RCurl+XML/httr+rvest[xml2+selectr])已经形成了较为丰富的教程系统。

但是所有这些都是基于静态页面的(抓包与API访问的除外),很多动态网页不提供API访问,这样就只能寄希望于selenium这种基于浏览器驱动技术来完成。

好在R语言中已经有了selenium接口包——RSelenium包,这为我们爬取动态网页提供了可能。我在今年年初写过一个实习僧网站的爬虫,那个是使用R语言中另一个基于selenium驱动的接口包——Rwebdriver来完成的。

实习僧招聘网爬虫数据可视化

当时技术不太成熟,思路也比较幼稚,我使用了导航器硬生生的遍历了500页内容,虽然最后也爬完了所有数据,但是耗时较长(将近40分钟),效率比较低。(感兴趣的小伙伴儿可以参考下上面那篇,不过实习僧的官网近期有较大改版,现在爬取难度肯定要比当初难多了!那个代码可能无法使用了)

最近抽时间学习了下RSelenium包的相关内容,这里感谢陈堰平老师在R语言上海大会现场所做《用RSelenium打造灵活强大的网络爬虫》的演讲,虽然未达现场,但是有幸看完视频版,其中的几个细节解决了我近段时间的一些困惑,这里表示感谢。

陈堰平老师主讲:《用RSelenium打造灵活强大的网络爬虫》
http://www.xueqing.tv/course/88
一个老外关于RSelenium的入门视频(youtobe请自行翻墙):
https://www.youtube.com/watch?v=ic65SWRWrKA&feature=youtu.be

当前R语言中能做到解析动态网页的有以下几个包(欢迎补充):

  • RSelenium(推荐)

  • Rwebdriver(不很成熟)

  • seleniumpipes(结合RSelenium更高效)

  • rdom(高级封装,灵活性不够)

  • Rcrawler(支持多进程)

  • webshot(专门用于动态网页截图)

本节以下内容正式分享今日案例,目标是拉勾网(不要问为什么,因为之前我还没有爬过拉钩)!

在介绍案例之前,请确保系统具备以下条件:

本地有selenium服务器并添加系统路径;
本地有plantomjs浏览器并添加系统路径;
安装了RSelenium包。

因为涉及到自动化点击操作,Chrome浏览器倒腾一下午硬是在点击环节出故障,找到了原因,因为拉勾网页面很长,而下一页按钮不在默认视窗范围内,使用了js脚本控制滑动条失败,原因不明,看到有人用firefox浏览器测试成功,我还没有试过,这里改用plantomjs无头浏览器(无需考虑元素是否被窗口遮挡的问题。)

R语言版:

#!!!这两句是在cmd后者PowerShell中运行的!
#RSelenium服务未关闭之前,请务必保持该窗口状态!
###启动selenium服务:
cd D:\
java -jar selenium-server-standalone-3.3.1.jar
##selenium服务器也可以直接在R语言中启动(无弹出窗口)
system("java -jar \"D:/selenium-server-standalone-2.53.1.jar\"",wait = FALSE,invisible = FALSE)
#加载包
library("RSelenium")
library("magrittr")
library("xml2")

启动服务

#给plantomjs浏览器伪装UserAgent
eCap <- list(phantomjs.page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0")
###伪装浏览器UserAgent,为什么即使使用plantomjs这种浏览器也需要伪装UA呢,
###因为plantomjs是专门用于web端页面测试的,通常都是在自己的web项目中测试web端功能,直接拿去抓别人的网站,默认的UA就是plantomjs;
###这是公然的挑衅!

###连接plantomjs服务
remDr <- remoteDriver(browserName = "phantomjs", extraCapabilities = eCap)

构建自动化抓取函数:

#自动化抓取函数:
myresult<-function(remDr,url){
   ###初始化一个数据框,用作后期收据收集之用!
   myresult<-data.frame()
   ###调用后台浏览器(因为是plantomjs这种无头浏览器(headless),所以你看不到弹出窗口)
   remDr$open()
   ###打开导航页面(也就是直达要抓取的目标网址)
   remDr$navigate(url)
   ###初始化一个计时器(用于输出并查看任务进度)
   i = 0
   while(TRUE){
       #计时器开始计数:
       i = i+1
       #范回当前页面DOM
       pagecontent<-remDr$getPageSource()[[1]]
       #以下三个字段共用一部分祖先节点,所以临时建立了一个根节点(节省冗余代码)
       con_list_item       <- pagecontent %>% read_html() %>% xml_find_all('//ul[@class="item_con_list"]/li')
       #职位名称
       position.name       <- con_list_item %>% xml_attr("data-positionname")
       #公司名称
       position.company    <- con_list_item %>% xml_attr("data-company")
       #职位薪资
       position.salary     <- con_list_item %>% xml_attr("data-salary")
       #职位详情链接
       position.link       <- pagecontent %>% read_html() %>% xml_find_all('//div[@class="p_top"]/a') %>% xml_attr("href")
       #职位经验要求
       position.exprience  <- pagecontent %>% read_html() %>% xml_find_all('//div[@class="p_bot"]/div[@class="li_b_l"]') %>% xml_text(trim=TRUE)
       #职位所述行业
       position.industry   <- pagecontent %>% read_html() %>% xml_find_all('//div[@class="industry"]') %>% xml_text(trim=TRUE) %>% gsub("[[:space:]\\u00a0]+|\\n", "",.)
       #职位福利
       position.bonus      <- pagecontent %>% read_html() %>% xml_find_all('//div[@class="list_item_bot"]/div[@class="li_b_l"]') %>% xml_text(trim=TRUE) %>% gsub("[[:space:]\\u00a0]+|\\n", "/",.)
       #职位工作环境
       position.environment<- pagecontent %>% read_html() %>% xml_find_all('//div[@class="li_b_r"]') %>% xml_text(trim=TRUE)
       #收集数据
       mydata<- data.frame(position.name,position.company,position.salary,position.link,position.exprience,position.industry,position.bonus,position.environment,stringsAsFactors = FALSE)
       #将本次收集的数据写入之前创建的数据框
       myresult<-rbind(myresult,mydata)
       #系统休眠0.5~1.5秒
       Sys.sleep(runif(1,0.5,1.5))
       #判断页面是否到尾部
       if ( pagecontent %>% read_html() %>% xml_find_all('//div[@class="page-number"]/span[1]') %>% xml_text() !="30"){
           #如果页面未到尾部,则点击下一页
           remDr$findElement('xpath','//div[@class="pager_container"]/a[last()]')$clickElement()
           #但因当前任务进度
           cat(sprintf("第【%d】页抓取成功",i),sep = "\n")
       } else {
           #如果页面到尾部则跳出while循环
           break
       }
   }
   #跳出循环后关闭remDr服务窗口
   remDr$close()
   #但因全局任务状态(也即任务结束)
   cat("all work is done!!!",sep = "\n")
   #返回最终数据
   return(myresult)
}

运行抓取函数

image.png

url <- "https://www.lagou.com/zhaopin"
myresult <- myresult(remDr,url)
#预览
DT::datatable(myresult)

image.png

Python:

import os,random,time
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities
import DesiredCapabilities
from lxml import etree

启动服务

dcap = dict(DesiredCapabilities.PHANTOMJS)
#这里也是伪装一下UA:
dcap["phantomjs.page.settings.userAgent"] = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:25.0) Gecko/20100101 Firefox/25.0")
#启动服务(python里面的selenium内置有selenium服务器,需要本地启动)
driver = webdriver.PhantomJS(desired_capabilities=dcap)

构建抓取函数

def getlaogou(driver,url):
   #初始化一个长度为0的空字典!以备之后收集数据
   myresult = {
             "position_name":[],
             "position_company":[],
             "position_salary":[],
             "position_link":[],
             "position_exprience":[],
             "position_industry":[],
             "position_environment":[]
             };
   #导航到目标网址
   driver.get(url)
   #计时器初始化
   i =0
   while True:
       #计时器累计计时:
       i+=1
       #获取当前页面DOM
       pagecontent = driver.page_source
       #解析HTML文档
       result = etree.HTML(pagecontent)
       #使用字典内单个list的extend方法累计收集数据
       myresult["position_name"].extend(result.xpath('//ul[@class="item_con_list"]/li/@data-positionname'))
       myresult["position_company"].extend(result.xpath('//ul[@class="item_con_list"]/li/@data-company'))
       myresult["position_salary"].extend(result.xpath('//ul[@class="item_con_list"]/li/@data-salary'))
       myresult["position_link"].extend(result.xpath('//div[@class="p_top"]/a/@href'))
       myresult["position_exprience"].extend([ text.xpath('string(.)').strip() for text in  result.xpath('//div[@class="p_bot"]/div[@class="li_b_l"]')])
       myresult["position_industry"].extend([ text.strip() for text in  result.xpath('//div[@class="industry"]/text()')])
       myresult["position_environment"].extend(result.xpath('//div[@class="li_b_r"]/text()'))
       #单次循环任务休眠
       time.sleep(random.choice(range(3)))
       #判断页面是否到尾部
       if result.xpath('//div[@class="page-number"]/span[1]/text()')[0] != '30':
           #如果未到达页面尾部,则点击下一页:
           driver.find_element_by_xpath('//div[@class="pager_container"]/a[last()]').click()
           #同时打印当前任务 状态!
           print("第【{}】页抓取成功!".format(i))
       else:
           #如果所有页面到达尾部,则跳出循环!
           break
   #打印全局任务状态

   print("everything is OK")
   #退出并关闭selenium服务!
   driver.quit()
   #返回数据
   return pd.DataFrame(myresult)

运行抓取程序

image.png

url = "https://www.lagou.com/zhaopin"
mydata = getlaogou(driver,url)
推荐 0
本文由 杜雨 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册