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

关于基础的网络数据抓取相关内容,本公众号已经做过很多次分享,特别是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)
}

运行抓取函数

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

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)

运行抓取程序

url = "https://www.lagou.com/zhaopin"
mydata = getlaogou(driver,url)

原文发布于微信公众号 - 数据小魔方(datamofang)

原文发表时间:2017-12-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编舟记

一名Clojurian的Emacs配置

我是一名热衷于函数式编程的Clojurian(Clojure粉),网络ID是lambeta(λβ),读作/‘læmeitə/,个人的博客网站是https://l...

64320
来自专栏微信公众号:Java团长

不可多得的后端架构师技术图谱!内附参考资料!

由于知识点众多,特整理在GitHub上,微信外链限制,无法在文本中直接加上超链接,有需要的欢迎Start/Fork,地址如下:

17220
来自专栏晨星先生的自留地

看zwell怎么带我解决方程式poc的bug

21520
来自专栏tkokof 的技术,小趣及杂念

小话游戏脚本(一)

( 题记:近来在网上学习到一个新的观点(应该是来自刘未鹏的BLOG :) ):书写是为了更好的学习,这与之前脑子里传道授业解惑的观点颇为迥异,品一品又颇以为然...

11310
来自专栏逍遥剑客的游戏开发

被FMOD的内存管理坑了一把

37620
来自专栏后端技术探索

后端前端恩仇录

其实应该更多的是互相的磨合与学习,希望身边的人可以有自己的经验分享,与理解,互相进步才是大家需要的,作为一个 "年老" (我也是90后) 的开发者,我觉得一代胜...

10130
来自专栏闻道于事

商城项目回顾整理(一)前台页面布局

登录页面: 1 <%@ page language="java" contentType="text/html; charset=utf-8" 2 ...

47130
来自专栏Android 开发者

开发者也是用户 - 第二部分:改善 UI 和 API 可用性的五条指导原则

20530
来自专栏圣杰的专栏

DDD理论学习系列(6)-- 实体

1.引言 实体对应的英语单词为Entity。提到实体,你可能立马就想到了代码中定义的实体类。在使用一些ORM框架时,比如Entity Framework,实体作...

30280
来自专栏进击的程序猿

通过Eloquent实现Repository模式

Eloquent采用了ActiveRecord的模式,这也让Eloquent招致了好多批评,让我们去看现在Eloquent/Model.php文件, 该文件已经...

10230

扫码关注云+社区

领取腾讯云代金券